Un A
de destino depende de un B
de destino si A
necesita B
en la compilación o
tiempo de ejecución. La relación depende de induce una
Grafo acíclico dirigido
(DAG) sobre los objetivos y se denomina gráfico de dependencia.
Las dependencias directas de un objetivo son aquellos otros objetivos a los que puede llegar una ruta de longitud 1 en el gráfico de dependencia. Las dependencias transitivas de un destino se los objetivos de los que depende mediante una ruta de cualquier longitud a través del gráfico.
De hecho, en el contexto de compilaciones, hay dos gráficos de dependencias: el grafo de las dependencias reales y el gráfico de las dependencias declaradas. La mayoría de las tiempo, los dos gráficos son tan similares que no hace falta hacer esta distinción, pero es útil para la siguiente discusión.
Dependencias reales y declaradas
Un X
de destino en realidad depende del Y
de destino si debe estar presente Y
.
compilada y actualizada para que X
se compile correctamente. Compilada podría
significa generar, procesar, compilar, vincular, archivar, comprimir, ejecutar o
cualquiera de los otros tipos de tareas que ocurren habitualmente durante una compilación.
Un X
de destino tiene una dependencia declarada en el Y
de destino si hay una dependencia
borde de X
a Y
en el paquete de X
.
Para compilaciones correctas, el gráfico de dependencias reales A debe ser un subgrafo de
el gráfico de dependencias declaradas D. Es decir, cada par de
los nodos conectados directamente x --> y
en A también deben conectarse directamente
D Se puede decir que D es una sobreaproximación de A.
Los escritores de archivos BUILD
deben declarar de forma explícita todos los
dependencias para cada regla del sistema de compilación y nada más.
No seguir este principio causa un comportamiento indefinido: la compilación puede fallar, pero lo que es peor, la compilación puede depender de algunas operaciones previas o de declaradas que tiene el objetivo. Bazel comprueba si faltan las dependencias e informar errores, pero no es posible que esta verificación completar en todos los casos.
No debe (ni debe) intentar enumerar
todo lo que se importa indirectamente
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 toda la información transitiva
cierre de las dependencias de X
para garantizar que se realicen cambios en esas metas
se refleja en el resultado final y vuelve a compilar intermedios según sea necesario.
La naturaleza transitiva de las dependencias conduce a un error común. A veces,
código en un archivo puede usar el código proporcionado por una dependencia indirecta, una
transitiva pero no perimetral directa en el gráfico de la dependencia declarada. Indirecta
no aparecen en el archivo BUILD
. Debido a que la regla no
dependen directamente del proveedor, no hay forma de hacer 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
usa el código del paquete c
y, por lo tanto, a
de forma transitiva.
depende 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(); } |
|
|
Las dependencias declaradas superan la estimación de las dependencias reales. Todo está bien.
2. Agrega una dependencia no declarada
Se genera un peligro latente cuando alguien agrega un 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(); |
|
|
|
Las dependencias declaradas ya no sobreestiman las dependencias reales.
Esto puede crearse 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 los gráficos de dependencias declarados y reales
El peligro se revela cuando alguien refactoriza b
para que ya no dependa de
c
, dejando atrás a
por error de forma involuntaria.
es culpa de ellos mismos.
b/BUILD |
|
---|---|
rule( name = "b", srcs = "b.in", deps = "//d:d", ) |
|
b / b.in |
|
import d; function foo() { d.baz(); } |
|
|
|
El gráfico de la dependencia declarada ahora es una subaproximación del valor real las dependencias, incluso cuando se cierran transitivamente; es probable que falle la compilación.
El problema podría haberse evitado garantizando 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 distintos tipos de
dependencias genéricas: srcs
, deps
y data
. Estos se explican a continuación. Para
más detalles, consulta
Atributos comunes a todas las reglas.
Muchas reglas también tienen atributos adicionales para tipos de reglas específicos
dependencias, por ejemplo, compiler
o resources
. Estos se detallan en el
Build Encyclopedia
srcs
de dependencias
Archivos consumidos directamente por la regla o reglas que generan archivos de origen.
deps
de dependencias
Una regla que apunta a módulos compilados por separado y que proporciona archivos de encabezado. símbolos, bibliotecas, datos, etc.
data
de dependencias
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, un la prueba de unidades podría comparar el resultado de una función con el contenido de un archivo. Cuando compila la prueba de unidades. No necesitas el archivo, pero sí lo necesitas al 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 donde solo los archivos indicados como
Hay data
disponibles. Por lo tanto, si un objeto binario, biblioteca o prueba necesita algunos archivos para ejecutarse,
especificarlos (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 uniendo las rutas de acceso del código fuente
y la ruta de acceso relativa del espacio de trabajo, por ejemplo,
${TEST_SRCDIR}/workspace/path/to/data/file
Usa etiquetas para hacer referencia a directorios
Al revisar nuestros archivos BUILD
, quizás notes que algunas etiquetas data
se refieren a los directorios. Estas etiquetas terminan con /.
o /
, como estos ejemplos,
que no deberías usar:
No recomendado:
data = ["//data/regression:unittest/."]
No recomendado:
data = ["testdata/."]
No recomendado:
data = ["testdata/"]
Esto parece conveniente, en especial para pruebas, ya que permite que una prueba usar todos los archivos de datos del directorio.
Pero no lo hagas. Para garantizar que las recompilaciones incrementales correctas (y
o volver a ejecutar pruebas) después de un cambio, el sistema de compilación debe tener en cuenta el
conjunto completo de archivos que son entradas a la compilación (o prueba). Cuando especificas
un directorio, el sistema de compilación vuelve a compilar solo cuando el directorio en sí
cambios (debido a la adición o eliminación de archivos), pero no podrá detectar
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 contienen, ya sea explícitamente o con el
función glob()
. (Usa **
para forzar la
glob()
para que sean recursivos).
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
cumplan con la sintaxis de las etiquetas
a continuación, una enumeración explícita de archivos o el uso de
La función glob()
produce etiquetas no válidas
. Debes usar etiquetas de directorio en este caso, pero ten cuidado con
el riesgo asociado de reconstrucciones incorrectas
descrita anteriormente.
Si debes usar etiquetas de directorio, recuerda que no puedes consultar el
paquete superior con una ruta de acceso ../
relativa; sino una ruta de acceso absoluta como
//data/regression:unittest/.
Toda regla externa, como una prueba, que necesite usar varios archivos
declarar explícitamente su dependencia en todos ellos. Puedes usar filegroup()
para lo siguiente:
Agrupa 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 de COMPILACIÓN | Visibilidad |