En esta página, se explican los conceptos básicos y los beneficios de usar aspectos y se proporcionan ejemplos simples y avanzados ejemplos.
Los aspectos permiten aumentar los gráficos de dependencias de compilación con información adicional y acciones. Estas son algunas situaciones típicas en las que los aspectos pueden ser útiles:
- Los IDE que integran Bazel pueden usar aspectos para recopilar información sobre el proyecto.
- Las herramientas de generación de código pueden aprovechar los aspectos para ejecutarlos en sus entradas de manera
independiente del destino. Como ejemplo, los archivos
BUILDpueden especificar una jerarquía de protobuf definiciones, y las reglas específicas del lenguaje pueden usar aspectos para adjuntar acciones que generen código de compatibilidad de protobuf para un lenguaje en particular.
Conceptos básicos de los aspectos
BUILD archivos proporcionan una descripción del código fuente de un proyecto: qué archivos
fuente forman parte del proyecto, qué artefactos (destinos) se deben compilar a partir de
esos archivos, cuáles son las dependencias entre esos archivos, etcétera. Bazel usa
esta información para realizar una compilación, es decir, determina el conjunto de acciones
necesarias para producir los artefactos (como ejecutar el compilador o el vinculador) y
ejecuta esas acciones. Bazel logra esto mediante la construcción de un gráfico
de dependencias entre los destinos y la visita a este gráfico para recopilar esas acciones.
Considera el siguiente BUILD archivo:
java_library(name = 'W', ...)
java_library(name = 'Y', deps = [':W'], ...)
java_library(name = 'Z', deps = [':W'], ...)
java_library(name = 'Q', ...)
java_library(name = 'T', deps = [':Q'], ...)
java_library(name = 'X', deps = [':Y',':Z'], runtime_deps = [':T'], ...)
Este archivo BUILD define un gráfico de dependencias que se muestra en la siguiente figura:

Figura 1: Gráfico de dependencias del archivo BUILD
Bazel analiza este gráfico de dependencias llamando a una función de implementación de
la regla correspondiente (en este caso, "java_library") para cada
destino en el ejemplo anterior. Las funciones de implementación de reglas generan acciones que
compilan artefactos, como archivos .jar, y pasan información, como ubicaciones
y nombres de esos artefactos, a las dependencias inversas de esos destinos en
los proveedores.
Los aspectos son similares a las reglas, ya que tienen una función de implementación que genera acciones y muestra proveedores. Sin embargo, su potencia proviene de la forma en que se compila el gráfico de dependencias para ellos. Un aspecto tiene una implementación y una lista de todos los atributos que propaga. Considera un aspecto A que se propaga a lo largo de los atributos denominados "deps". Este aspecto se puede aplicar a un destino X, lo que genera un nodo de aplicación de aspecto A(X). Durante su aplicación, el aspecto A se aplica de forma recursiva a todos los destinos a los que X hace referencia en su "deps" atributo (todos los atributos de la lista de propagación de A).
Por lo tanto, un solo acto de aplicar el aspecto A a un destino X genera un "gráfico de sombra" de el gráfico de dependencias original de los destinos que se muestra en la siguiente figura:

Figura 2: Gráfico de compilación con aspectos
Las únicas aristas que se sombrean son las que se encuentran a lo largo de los atributos en
el conjunto de propagación, por lo que la arista runtime_deps no se sombrea en este
ejemplo. Luego, se invoca una función de implementación de aspecto en todos los nodos en
el gráfico de sombra de manera similar a cómo se invocan las implementaciones de reglas en los nodos
del gráfico original.
Ejemplo sencillo
En este ejemplo, se muestra cómo imprimir de forma recursiva los archivos fuente de una
regla y todas sus dependencias que tienen un deps atributo. Muestra
una implementación de aspecto, una definición de aspecto y cómo invocar el aspecto
desde la línea de comandos de Bazel.
def _print_aspect_impl(target, ctx):
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the files that make up the sources and
# print their paths.
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
print(f.path)
return []
print_aspect = aspect(
implementation = _print_aspect_impl,
attr_aspects = ['deps'],
)
Dividamos el ejemplo en sus partes y examinemos cada una de ellas de forma individual.
Definición de aspecto
print_aspect = aspect(
implementation = _print_aspect_impl,
attr_aspects = ['deps'],
)
Las definiciones de aspecto son similares a las definiciones de reglas y se definen con
la aspect función.
Al igual que una regla, un aspecto tiene una función de implementación que, en este caso, es
_print_aspect_impl.
attr_aspects es una lista de atributos de reglas a lo largo de los cuales se propaga el aspecto.
En este caso, el aspecto se propagará a lo largo del atributo deps de las
reglas a las que se aplica.
Otro argumento común para attr_aspects es ['*'], que propagaría el
aspecto a todos los atributos de una regla.
Implementación de aspecto
def _print_aspect_impl(target, ctx):
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the files that make up the sources and
# print their paths.
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
print(f.path)
return []
Las funciones de implementación de aspecto son similares a las funciones de implementación de reglas. Muestran proveedores, pueden generar acciones y toman dos argumentos:
target: el destino al que se aplica el aspecto.ctx: Objetoctxque se puede usar para acceder a los atributos y generar resultados y acciones.
La función de implementación puede acceder a los atributos de la regla de destino a través
ctx.rule.attr. Puede examinar los proveedores que
proporciona el destino al que se aplica (a través del argumento target).
Los aspectos deben mostrar una lista de proveedores. En este ejemplo, el aspecto no proporciona nada, por lo que muestra una lista vacía.
Invoca el aspecto con la línea de comandos
La forma más sencilla de aplicar un aspecto es desde la línea de comandos con el
--aspects
argumento. Si se supone que el aspecto anterior se definió en un archivo llamado print.bzl
esto:
bazel build //MyExample:example --aspects print.bzl%print_aspect
aplicaría el print_aspect al destino example y a todas las
reglas de destino a las que se puede acceder de forma recursiva a través del atributo deps.
La marca --aspects toma un argumento, que es una especificación del aspecto
en el formato <extension file label>%<aspect top-level name>.
Ejemplo avanzado
En el siguiente ejemplo, se muestra el uso de un aspecto de una regla de destino que cuenta archivos en destinos, y que puede filtrarlos por extensión. Muestra cómo usar un proveedor para mostrar valores, cómo usar parámetros para pasar un argumento a una implementación de aspecto y cómo invocar un aspecto desde una regla.
file_count.bzl archivo:
FileCountInfo = provider(
fields = {
'count' : 'number of files'
}
)
def _file_count_aspect_impl(target, ctx):
count = 0
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the sources counting files
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
count = count + 1
# Get the counts from our dependencies.
for dep in ctx.rule.attr.deps:
count = count + dep[FileCountInfo].count
return [FileCountInfo(count = count)]
file_count_aspect = aspect(
implementation = _file_count_aspect_impl,
attr_aspects = ['deps'],
attrs = {
'extension' : attr.string(values = ['*', 'h', 'cc']),
}
)
def _file_count_rule_impl(ctx):
for dep in ctx.attr.deps:
print(dep[FileCountInfo].count)
file_count_rule = rule(
implementation = _file_count_rule_impl,
attrs = {
'deps' : attr.label_list(aspects = [file_count_aspect]),
'extension' : attr.string(default = '*'),
},
)
Archivo BUILD.bazel:
load('//:file_count.bzl', 'file_count_rule')
cc_library(
name = 'lib',
srcs = [
'lib.h',
'lib.cc',
],
)
cc_binary(
name = 'app',
srcs = [
'app.h',
'app.cc',
'main.cc',
],
deps = ['lib'],
)
file_count_rule(
name = 'file_count',
deps = ['app'],
extension = 'h',
)
Definición de aspecto
file_count_aspect = aspect(
implementation = _file_count_aspect_impl,
attr_aspects = ['deps'],
attrs = {
'extension' : attr.string(values = ['*', 'h', 'cc']),
}
)
En este ejemplo, se muestra cómo se propaga el aspecto a través del atributo deps.
attrs define un conjunto de atributos para un aspecto. Los atributos de aspecto público
definen parámetros y solo pueden ser de tipos bool, int o string.
Para los aspectos propagados por reglas, los parámetros int y string deben tener
values especificados en ellos. Este ejemplo tiene un parámetro llamado extension
que puede tener '*', 'h' o 'cc' como valor.
Para los aspectos propagados por reglas, los valores de los parámetros se toman de la regla que solicita
el aspecto, con el atributo de la regla que tiene el mismo nombre y tipo
(consulta la definición de file_count_rule).
Para los aspectos de la línea de comandos, los valores de los parámetros se pueden pasar con la
--aspects_parameters
marca. Se puede omitir la restricción values de los parámetros int y string.
Los aspectos también pueden tener atributos privados de tipos label o
label_list. Los atributos de etiqueta privada se pueden usar para especificar dependencias de
herramientas o bibliotecas que son necesarias para las acciones generadas por los aspectos. No hay un atributo privado definido en este ejemplo, pero el siguiente fragmento de código muestra cómo puedes pasar una herramienta a un aspecto:
...
attrs = {
'_protoc' : attr.label(
default = Label('//tools:protoc'),
executable = True,
cfg = "exec"
)
}
...
Implementación de aspecto
FileCountInfo = provider(
fields = {
'count' : 'number of files'
}
)
def _file_count_aspect_impl(target, ctx):
count = 0
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the sources counting files
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
count = count + 1
# Get the counts from our dependencies.
for dep in ctx.rule.attr.deps:
count = count + dep[FileCountInfo].count
return [FileCountInfo(count = count)]
Al igual que una función de implementación de reglas, una función de implementación de aspecto muestra una estructura de proveedores a la que pueden acceder sus dependencias.
En este ejemplo, FileCountInfo se define como un proveedor que tiene un
campo count. Es una práctica recomendada definir explícitamente los campos de un
proveedor con el fields atributo.
El conjunto de proveedores para una aplicación de aspecto A(X) es la unión de los proveedores
que provienen de la implementación de una regla para el destino X y de la
implementación del aspecto A. Los proveedores que propaga una implementación de reglas
se crean y se inmovilizan antes de que se apliquen los aspectos y no se pueden modificar desde un
aspecto. Es un error si un destino y un aspecto que se le aplica cada uno
proporcionan un proveedor con el mismo tipo, con las excepciones de
OutputGroupInfo
(que se combina, siempre que la
regla y el aspecto especifiquen diferentes grupos de salida) y
InstrumentedFilesInfo
(que se toma del aspecto). Esto significa que las implementaciones de aspectos nunca pueden mostrar DefaultInfo.
Los parámetros y los atributos privados se pasan en los atributos del
ctx. En este ejemplo, se hace referencia al parámetro extension y se determina
qué archivos se deben contar.
Para mostrar proveedores, los valores de los atributos a lo largo de los cuales
se propaga el aspecto (de la lista attr_aspects) se reemplazan por
los resultados de una aplicación del aspecto a ellos. Por ejemplo, si el destino
X tiene Y y Z en sus dependencias, ctx.rule.attr.deps para A(X) será [A(Y), A(Z)].
En este ejemplo, ctx.rule.attr.deps son objetos de destino que son los
resultados de aplicar el aspecto a las 'dependencias' del destino original al que
se aplicó el aspecto.
En el ejemplo, el aspecto accede al proveedor FileCountInfo de las dependencias del destino
para acumular la cantidad total transitiva de archivos.
Invoca el aspecto desde una regla
def _file_count_rule_impl(ctx):
for dep in ctx.attr.deps:
print(dep[FileCountInfo].count)
file_count_rule = rule(
implementation = _file_count_rule_impl,
attrs = {
'deps' : attr.label_list(aspects = [file_count_aspect]),
'extension' : attr.string(default = '*'),
},
)
La implementación de reglas muestra cómo acceder a FileCountInfo
a través de ctx.attr.deps.
La definición de reglas muestra cómo definir un parámetro (extension)
y asignarle un valor predeterminado (*). Ten en cuenta que tener un valor predeterminado que
no sea uno de 'cc', 'h' o '*' sería un error debido a las
restricciones impuestas en el parámetro en la definición de aspecto.
Invoca un aspecto a través de una regla de destino
load('//:file_count.bzl', 'file_count_rule')
cc_binary(
name = 'app',
...
)
file_count_rule(
name = 'file_count',
deps = ['app'],
extension = 'h',
)
En este ejemplo, se muestra cómo pasar el extension parámetro al aspecto
a través de la regla. Como el parámetro extension tiene un valor predeterminado en la
implementación de reglas, extension se consideraría un parámetro opcional.
Cuando se compile el destino file_count, nuestro aspecto se evaluará por
sí mismo y todos los destinos a los que se puede acceder de forma recursiva a través de deps.