Dependencias

Informa un problema Ver código fuente

Un objetivo A depende de un objetivo B si B lo necesita A al momento de la compilación o la ejecución. La relación depende de induce un grafo acíclico dirigido (DAG) sobre los objetivos, y se llama gráfico de dependencias.

Las dependencias directas de un objetivo son otras orientaciones a las que puede acceder una ruta de longitud 1 en el gráfico de dependencia. Las dependencias transitivas de un objetivo son aquellas en las que depende de una ruta de cualquier longitud a través del grafo.

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

Dependencias reales y declaradas

Un objeto X de destino realmente depende del destino Y si Y debe estar presente, compilarse y actualizarse para que X se compile de forma correcta. Compilado puede significar que se haya generado, procesado, compilado, vinculado, archivado, comprimido o ejecutado, o cualquier otro tipo de tareas que se realizan de forma habitual durante la compilación.

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

Para realizar compilaciones correctas, el grafo de dependencias reales A debe ser un subgrafo del gráfico de dependencias declaradas D. Es decir, cada par de nodos x --> y conectados directamente en A también debe conectarse directamente 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 al sistema de compilación y nada más.

Si no se observa este principio, se produce un comportamiento indefinido: la compilación puede fallar, pero, peor aún, la compilación puede depender de algunas operaciones anteriores o de dependencias transitivas declaradas que el destino tiene. Bazel verifica las dependencias faltantes y los errores del informe, pero no es posible que esta verificación se complete en todos los casos.

No es necesario (y no debes) intentar enumerar todos los elementos importados de forma indirecta, incluso si es necesario antes del A durante el tiempo de ejecución.

Durante una compilación de X de destino, la herramienta de compilación inspecciona el cierre transitivo de las dependencias de X para garantizar que los cambios en esos destinos se reflejen en el resultado final y se vuelvan a compilar los intermediarios 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 que proporciona una dependencia indirecta: un borde 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 manera 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 del paquete b utiliza código del 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 dependencia declarado con flechas que conectan a, b y c
Declaración del gráfico de dependencias
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 se aproximan a las dependencias reales. Todo está bien.

2. Cómo agregar 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 olvida declararla en el archivo de compilación a/BUILD.

a / a.in  
        import b;
        import c;
        b.foo();
        c.garply();
      
 
Gráfico de dependencia declarado con flechas que conectan a, b y c
Declaración del gráfico de dependencias
Gráfico de dependencia real con flechas que conectan a, b y c. Una flecha ahora también conecta A a C. No coincide con el gráfico de dependencias declarado.
Gráfico de dependencia real

Las dependencias declaradas ya no superan las dependencias reales. Es posible que se compile 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. Diferencia entre gráficos de dependencia declarados y reales

El peligro se revela cuando alguien refactoriza b para que ya no dependa de c, ya que inadvertidamente rompe a sin causar ningún error propio.

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

Ahora, el gráfico de dependencia declarado es una aproximación de las dependencias reales, incluso cuando está cerrada de forma transitiva; es probable que la compilación falle.

El problema podría haberse evitado si te aseguraste de que la dependencia real de a a c que se introdujo en el paso 2 se declarara 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 información, consulta Atributos comunes a todas las reglas.

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

srcs dependencias

Archivos consumidos directamente por la regla o las reglas que generan archivos de origen

deps dependencias

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

data dependencias

Es posible que un destino de compilación necesite algunos archivos de datos para ejecutarse de forma correcta. 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 puede 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 en el que solo están disponibles los archivos enumerados como data. Por lo tanto, si un objeto binario/biblioteca/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 mediante la ruta de acceso relativa path/to/data/file. En las pruebas, puedes hacer referencia a estos archivos mediante la unión de las rutas de acceso del directorio del código fuente de la prueba y de 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

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

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

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

No se recomienda el data = ["testdata/"]

Esto parece conveniente para las pruebas, en particular porque permite que se usen todos los archivos de datos del directorio.

Pero intenta no hacerlo. Para garantizar recompilaciones incrementales correctas (y volver a ejecutar pruebas) después de un cambio, el sistema de compilación debe tener en cuenta el conjunto completo de archivos ingresados en la compilación (o prueba). Cuando especificas un directorio, el sistema de compilación realiza una recompilación solo cuando cambia el directorio en sí (debido a la adición o eliminación de archivos), pero no puede detectar modificaciones en archivos individuales, ya que esos cambios no afectan al directorio que los contiene. En lugar de especificar directorios como entradas para el sistema de compilación, debes enumerar el conjunto de archivos contenidos en ellos, ya sea de forma explícita o mediante la función glob(). (Usa ** para forzar la glob() a fin de que sea recursiva).

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

Desafortunadamente, 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 etiqueta, la enumeración explícita de archivos o el uso de la función glob() producen 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 describieron 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 a todas ellas. 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