Dependencias

Un A de destino depende de un B de destino si A necesita B en el momento de la compilación o ejecución. La relación depende de induce un grafo acíclico dirigido (DAG) sobre los destinos y se denomina gráfico de dependencias.

Las dependencias directas de un objetivo son aquellos otros objetivos a los que se puede acceder mediante una ruta de longitud 1 en el gráfico de dependencias. Las dependencias transitivas de un destino son aquellos de los que depende mediante una ruta de cualquier longitud a través del gráfico.

De hecho, en el contexto de las compilaciones, hay dos gráficos de dependencias, el de dependencias reales y el de dependencias declaradas. La mayoría de las veces, los dos gráficos son tan similares que no es necesario hacer esta distinción, pero es útil para el análisis que se describe a continuación.

Dependencias reales y declaradas

Un X objetivo en realidad depende de un Y objetivo si Y debe estar presente, compilado y actualizado para que X se compile correctamente. Compilada puede significar generada, procesada, compilada, vinculada, archivada, comprimida, ejecutada o cualquiera de los otros tipos de tareas que se realizan de forma rutinaria durante una compilación.

Un X objetivo tiene una dependencia declarada en el Y objetivo si hay un borde de dependencia de X a Y en el paquete de X.

Para compilaciones correctas, el gráfico de dependencias reales A debe ser un subgrafo del gráfico de dependencias declaradas D. Es decir, cada par de nodos x --> y directamente conectados en A también debe estar directamente conectado en D. Se puede decir que D es una sobreaproximación de A.

Los escritores de archivos BUILD deben declarar de manera explícita todas las dependencias directas reales para cada regla al sistema de compilación, y no más.

Si no se observa este principio, se produce un comportamiento no definido: es posible que la compilación falle, pero, lo que es peor, es posible que dependa de algunas operaciones anteriores o de dependencias declaradas transitivas que tenga el destino. Bazel verifica que no falten dependencias y que informen errores, pero no es posible que esta verificación esté completa en todos los casos.

No necesitas (y no debes) intentar enumerar todo lo que se importa de manera indirecta, incluso si A lo necesita en el momento de la ejecución.

Durante una compilación de X de destino, la herramienta de compilación inspecciona todo el cierre transitivo de las dependencias de X para garantizar que cualquier cambio en esos destinos se refleje en el resultado final y vuelve a compilar los intermedios según sea necesario.

La naturaleza transitiva de las dependencias genera un error común. A veces, el código en un archivo puede usar código proporcionado por una dependencia indirecta: un perímetro transitivo pero no directo en el gráfico de dependencia declarado. Las dependencias indirectas no aparecen en el archivo BUILD. Debido a que la regla no depende directamente del proveedor, no hay forma de realizar un seguimiento de los cambios, como se muestra en el siguiente cronograma de ejemplo:

1. Las dependencias declaradas coinciden con las dependencias reales.

Al principio, todo funciona. El código del paquete a usa el código del paquete b. El código en el paquete b usa código en el paquete c y, por lo tanto, a depende transitivamente de c.

a/BUILD b/BUILD
rule(
    name = "a",
    srcs = "a.in",
    deps = "//b:b",
)
      
rule(
    name = "b",
    srcs = "b.in",
    deps = "//c:c",
)
      
a / a.in b / b.in
import b;
b.foo();
    
import c;
function foo() {
  c.bar();
}
      
Gráfico de la dependencia declarada con flechas que conectan a, b y c
Gráfico de dependencias declarado
Gráfico de la dependencia real que coincide con el gráfico de la dependencia declarado con flechas que conectan a, b y c
Gráfico de dependencias real

Las dependencias declaradas superan la estimación de las dependencias reales. Todo está bien.

2. Agrega una dependencia no declarada

Se presenta un riesgo latente cuando alguien agrega código a a que crea una dependencia real directa en c, pero olvida declararlo en el archivo de compilación a/BUILD.

a / a.in  
        import b;
        import c;
        b.foo();
        c.garply();
      
 
Gráfico de dependencias declarado con flechas que conectan a, b y c
Gráfico de dependencias declarado
Gráfico de dependencias real con flechas que conectan a, b y c. Ahora, una flecha también conecta A con C. Esto no coincide con el gráfico de dependencia declarado.
Gráfico de dependencia real

Las dependencias declaradas ya no sobreaproximan las dependencias reales. Es posible que se compile correctamente, ya que los cierres transitivos de los dos gráficos son iguales, pero enmascara un problema: a tiene una dependencia real, pero no declarada, en c.

3. Divergencia entre los gráficos de dependencias declarados y reales

El peligro se revela cuando alguien refactoriza b para que ya no dependa de c, lo que rompe a por error sin que sea su culpa.

  b/BUILD
 
rule(
    name = "b",
    srcs = "b.in",
    deps = "//d:d",
)
      
  b / b.in
 
      import d;
      function foo() {
        d.baz();
      }
      
Gráfico de dependencias declarado con flechas que conectan a y b.
                  b ya no se conecta con c, lo que interrumpe la conexión de a a c
Gráfico de dependencias declarado
Gráfico de dependencia real que muestra una conexión con b y c, pero b ya no se conecta con c
Gráfico de dependencia real

El gráfico de la dependencia declarada ahora es una aproximación de las dependencias reales, incluso cuando se cierra transitivamente. Es probable que la compilación falle.

El problema se podría haber evitado si te aseguras de que la dependencia real de a a c que se introdujo en el paso 2 se declaró correctamente en el archivo BUILD.

Tipos de dependencias

La mayoría de las reglas de compilación tienen tres atributos para especificar diferentes tipos de dependencias genéricas: srcs, deps y data. Estos se explican a continuación. Para obtener más detalles, consulta Atributos comunes a todas las reglas.

Muchas reglas también tienen atributos adicionales para tipos de dependencias específicas de la regla, por ejemplo, compiler o resources. Estos se detallan en la Enciclopedia de compilación.

srcs de dependencias

Son archivos que consume directamente la regla o las reglas que generan archivos de origen.

deps de dependencias

Regla que apunta a módulos compilados por separado que proporcionan archivos de encabezado, símbolos, bibliotecas, datos, etcétera.

Dependencias de data

Es posible que un destino de compilación necesite algunos archivos de datos para ejecutarse correctamente. Estos archivos de datos no son código fuente: no afectan la forma en que se compila el destino. Por ejemplo, una prueba de unidades podría comparar el resultado de una función con el contenido de un archivo. Cuando compilas la prueba de unidades, no necesitas el archivo, pero lo necesitas para ejecutar la prueba. Lo mismo se aplica a las herramientas que se inician durante la ejecución.

El sistema de compilación ejecuta pruebas en un directorio aislado en el que solo están disponibles los archivos que aparecen como data. Por lo tanto, si un objeto binario, una biblioteca o una prueba necesita algunos archivos para ejecutarse, especifícalos (o una regla de compilación que los contenga) en data. Por ejemplo:

# I need a config file from a directory named env:
java_binary(
    name = "setenv",
    ...
    data = [":env/default_env.txt"],
)

# I need test data from another directory
sh_test(
    name = "regtest",
    srcs = ["regtest.sh"],
    data = [
        "//data:file1.txt",
        "//data:file2.txt",
        ...
    ],
)

Estos archivos están disponibles con la ruta de acceso relativa path/to/data/file. En las pruebas, puedes hacer referencia a estos archivos uniendo las rutas de acceso del directorio fuente de la prueba y la ruta de acceso relativa al espacio de trabajo, por ejemplo, ${TEST_SRCDIR}/workspace/path/to/data/file.

Usa etiquetas para hacer referencia a directorios

Cuando revisas nuestros archivos BUILD, es posible que notes que algunas etiquetas data hacen referencia a directorios. Estas etiquetas terminan con /. o /, como en estos ejemplos, que no debes usar:

No se recomienda: data = ["//data/regression:unittest/."]

No se recomienda: data = ["testdata/."]

No recomendado: data = ["testdata/"]

Esto parece conveniente, en especial para pruebas porque permite que una prueba use todos los archivos de datos del directorio.

Pero intenta no hacerlo. Para garantizar que las recompilaciones incrementales correctas (y la reejecución de pruebas) después de un cambio, el sistema de compilación debe conocer el conjunto completo de archivos que son entradas para la compilación (o prueba). Cuando especificas un directorio, el sistema de compilación realiza una recompilación solo cuando el directorio en sí cambia (debido a la adición o eliminación de archivos), pero no podrá detectar ediciones en archivos individuales, ya que esos cambios no afectan el directorio contenedor. En lugar de especificar directorios como entradas para el sistema de compilación, debes enumerar el conjunto de archivos que contiene, ya sea de forma explícita o con la función glob(). (Usa ** para forzar que glob() sea recursiva).

Recomendado: data = glob(["testdata/**"])

Lamentablemente, hay algunas situaciones en las que se deben usar las etiquetas de directorio. Por ejemplo, si el directorio testdata contiene archivos cuyos nombres no cumplen con la sintaxis de la etiqueta, la enumeración explícita de archivos o el uso de la función glob() genera un error de etiquetas no válidas. Debes usar etiquetas de directorio en este caso, pero ten en cuenta el riesgo asociado de recompilaciones incorrectas, como se describió anteriormente.

Si debes usar etiquetas de directorio, ten en cuenta que no puedes hacer referencia al paquete superior con una ruta de acceso ../ relativa. En su lugar, usa una ruta de acceso absoluta, como //data/regression:unittest/..

Cualquier regla externa, como una prueba, que necesite usar varios archivos debe declarar explícitamente su dependencia de todos ellos. Puedes usar filegroup() para agrupar archivos en el archivo BUILD:

filegroup(
        name = 'my_data',
        srcs = glob(['my_unittest_data/*'])
)

Luego, puedes hacer referencia a la etiqueta my_data como la dependencia de datos en tu prueba.

Archivos BUILD Visibilidad