Dependencias

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

Las dependencias directas de un destino son aquellos otros destinos 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 destinos de los que depende mediante una ruta de acceso 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 incluye a continuación.

Dependencias reales y declaradas

Un X de destino en realidad depende del destino Y si Y debe estar presente, compilado y actualizado para que X se compile de forma correcta. Compiladas podría significar generadas, procesadas, compiladas, vinculadas, archivadas, comprimidas, ejecutadas o cualquiera de los otros tipos de tareas que ocurren de manera rutinaria durante una compilación.

Un X de destino tiene una dependencia declarada en el objetivo Y si hay un perímetro 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 conectados directamente x --> y en A también debe conectarse de forma directa en D. Se puede decir que D es una sobreaproximación de A.

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

Si no cumples con este principio, se genera un comportamiento indefinido: es posible que la compilación falle, pero, lo que es peor, es posible que dependa de algunas operaciones previas o de dependencias transitivas declaradas que tenga el destino. Bazel busca dependencias faltantes y, además, informa errores, pero no es posible que esta verificación se complete en todos los casos.

No necesitas (ni debes) intentar enumerar todo lo que se importó de forma indirecta, incluso si A es necesario para 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 asegurarse de 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 conduce a un error común. En ocasiones, el código de un archivo puede usar código proporcionado por una dependencia indirecta, una arista transitiva pero no directa en el gráfico de dependencias 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 ejemplo de cronograma:

1. Las dependencias declaradas coinciden con las dependencias reales

Al principio, todo funciona. El código del paquete a usa 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 de forma transitiva 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();
}
      
Se declaró el gráfico de dependencias con flechas que conectan a, b y c
Gráfico de dependencia declarada
Gráfico de dependencia real que coincide con el gráfico de dependencia declarado con flechas que conectan a, b y c
Gráfico de dependencia real

Las dependencias declaradas superan a las dependencias reales. Todo está bien.

2. Agrega una dependencia no declarada

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

a / a.in  
        import b;
        import c;
        b.foo();
        c.garply();
      
 
Se declaró el gráfico de dependencias con flechas que conectan a, b y c
Gráfico de dependencia declarada
Gráfico de dependencia real con flechas que conectan a, b y c. Una flecha ahora 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 se aproximan a las dependencias reales. Esto puede funcionar bien, ya que los cierres transitivos de los dos gráficos son iguales, pero enmascara un problema: a tiene una dependencia real, pero no declarada, de c.

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

El riesgo se revela cuando alguien refactoriza b para que ya no dependa de c, lo que interrumpe a de forma involuntaria.

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

El gráfico de dependencia declarado ahora es una subaproximación de las dependencias reales, incluso cuando se cierra de forma transitiva. Es probable que la compilación falle.

Es posible que se haya solucionado el problema asegurándote de que la dependencia real de a a c presentada en el paso 2 se haya declarado 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. Estas se explican a continuación. Para obtener más información, consulta Atributos comunes a todas las reglas.

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

Dependencias de srcs

Archivos que consumen directamente las reglas que generan archivos de origen

Dependencias de deps

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 compilación del 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 cuando ejecutas 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 donde solo están disponibles los archivos enumerados 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 del código fuente de la prueba y la ruta relativa del lugar de trabajo, por ejemplo, ${TEST_SRCDIR}/workspace/path/to/data/file.

Cómo usar etiquetas para hacer referencia a directorios

Al revisar nuestros archivos BUILD, quizás notes que algunas etiquetas data hacen referencia a los 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 se recomienda: data = ["testdata/"]

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

Pero intenta no hacer esto. Para garantizar recompilaciones incrementales correctas (y una nueva ejecución de pruebas) después de un cambio, el sistema de compilación debe conocer el conjunto completo de archivos que son entradas de la compilación (o de prueba). Cuando especificas un directorio, el sistema de compilación vuelve a compilar solo cuando cambia el directorio (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 en el sistema de compilación, debes enumerar el conjunto de archivos que contienen, ya sea de forma explícita o con la función glob(). (usa ** para forzar glob() a que 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 etiquetas, la enumeración explícita de archivos o el uso de la función glob() produce un error de etiquetas no válidas. En este caso, debes usar etiquetas de directorio, pero ten en cuenta el riesgo asociado de recompilaciones incorrectas que 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/..

Toda regla externa, como una prueba, que necesite usar varios archivos debe declarar explícitamente su dependencia de todos. 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