En esta página, se explican los conceptos básicos y los beneficios de utilizar aspectos y brinda una solución simple y avanzada 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 en un proyecto final.
- Las herramientas de generación de código pueden aprovechar aspectos para ejecutar en sus entradas
independiente del objetivo. Por ejemplo, los archivos
BUILD
pueden especificar una jerarquía de la biblioteca de protobuf y las reglas específicas de cada lenguaje pueden usar aspectos para adjuntar acciones que generan código de compatibilidad de protobuf para un lenguaje en particular.
Aspectos básicos de los aspectos
Los archivos BUILD
proporcionan una descripción del código fuente de un proyecto: ¿de qué fuente?
como parte del proyecto, a partir de qué artefactos (destinos) se deben compilar
esos archivos, cuáles son las dependencias entre ellos, etc. Bazel usa
esta información para realizar una compilación, es decir, descifra el conjunto de acciones
necesarios para producir los artefactos (como la ejecución del compilador o vinculador) y
ejecuta esas acciones. Bazel logra esto construyendo una dependencia
gráfico entre los objetivos y visitar este gráfico para recopilar esas acciones.
Considera el siguiente archivo BUILD
:
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 dependencia que se muestra en la siguiente figura:
Figura 1: Gráfico de dependencia de archivos de 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
objetivo en el ejemplo anterior. Las funciones de implementación de reglas generan acciones
compilar artefactos, como archivos .jar
, y pasar información, como ubicaciones
y nombres de esos artefactos, a las dependencias inversas de esos objetivos en
proveedores.
Los aspectos son similares a las reglas, ya que tienen una función de implementación genera acciones y devuelve proveedores. Sin embargo, su poder proviene de la forma en que el gráfico de dependencias está creado para ellos. Un aspecto tiene una implementación y una lista de todos los atributos que se propagan. Considera un aspecto A que se propaga a lo largo de atributos llamados “deps”. Este aspecto se puede aplicar un destino X, lo que produce un nodo de aplicación de aspecto A(X). Durante su aplicación, el aspecto A se aplica de manera recursiva a todos los objetivos a los que X hace referencia en sus “dependencias” (todos los atributos de la lista de propagación de A).
Por lo tanto, un solo acto de aplicar el aspecto A a un objetivo X produce un “gráfico de sombras” de el gráfico de dependencia original de los objetivos que se muestra en la siguiente figura:
Figura 2: Compilar un gráfico con aspectos
Los únicos bordes sombreados son aquellos a lo largo de los atributos en
el conjunto de propagación, por lo que el borde 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 paralelo, de manera similar a como se invocan las implementaciones de reglas en los nodos
del gráfico original.
Ejemplo simple
Este ejemplo demuestra cómo imprimir de manera recursiva los archivos fuente para un
y todas sus dependencias que tienen un atributo deps
. Muestra
una implementación de aspecto, una definición de aspecto y cómo invocar este 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'],
)
Desglosemos el ejemplo en sus partes y examinemos cada una 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 función aspect
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 través de los cuales se propaga el aspecto.
En este caso, el aspecto se propagará a lo largo del atributo deps
del
las reglas a las que se aplica.
Otro argumento común para attr_aspects
es ['*']
, que propagaría el
en todos los atributos de una regla.
Implementación de Aspect
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 de la implementación de reglas funciones. Muestran providers, pueden generar actions y toma dos argumentos:
target
: Es el objetivo al que se aplica el aspecto.ctx
: Es un objetoctx
que 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 objetivo mediante
ctx.rule.attr
Puede examinar a los proveedores que son
que proporciona el destino al que se aplica (a través del argumento target
).
Los aspectos son obligatorios para mostrar una lista de proveedores. En este ejemplo, el aspecto no proporciona nada, por lo que muestra una lista vacía.
Cómo invocar el aspecto con la línea de comandos
La forma más sencilla de aplicar un aspecto es desde la línea de comandos usando el
--aspects
argumento. Suponiendo que los aspectos anteriores se hayan definido en un archivo llamado print.bzl
esto:
bazel build //MyExample:example --aspects print.bzl%print_aspect
aplicaría el print_aspect
al example
objetivo y todos los
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 objetivo que registra los archivos en los destinos y, posiblemente, los filtre por extensión. Muestra cómo usar un proveedor para devolver valores, cómo usar parámetros para pasar un argumento en la implementación de un aspecto y cómo invocar un aspecto a partir de una regla.
Archivo file_count.bzl
:
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. Atributos de aspecto público
definen parámetros y solo pueden ser de los tipos bool
, int
o string
.
Para los aspectos propagados por reglas, los parámetros int
y string
deben tener
Se especificó values
en ellos. Este ejemplo tiene un parámetro llamado extension
que puede tener "*
", "h
" o "cc
" como un valor.
Para los aspectos propagados por regla, 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
--aspects_parameters
marca. Se puede aplicar la restricción values
de los parámetros int
y string
omitido.
Los aspectos también pueden tener atributos privados de los tipos label
o
label_list
Los atributos de etiqueta privada se pueden usar
para especificar dependencias
herramientas o bibliotecas necesarias para acciones generadas por aspectos. No hay
un atributo privado definido en este ejemplo, pero el siguiente fragmento de código
demuestra cómo podrías pasar una herramienta a un aspecto:
...
attrs = {
'_protoc' : attr.label(
default = Label('//tools:protoc'),
executable = True,
cfg = "exec"
)
}
...
Implementación de Aspect
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 aspectos devuelve una estructura de proveedores a los que pueden acceder sus dependencias.
En este ejemplo, FileCountInfo
se define como un proveedor que tiene un
count
. Una práctica recomendada es definir explícitamente los campos de un
con el atributo fields
.
El conjunto de proveedores para una aplicación de aspecto A(X) es la unión de proveedores.
que provienen de la implementación de una regla para el objetivo X y de
implementación del aspecto A. Los proveedores que propaga la implementación de una regla
se crean y se inmovilizan antes de que se apliquen los aspectos y no se pueden modificar desde una
aspecto. Es un error si un objetivo y un aspecto que se le aplica cada uno
proporcionar a un proveedor del mismo tipo, a excepción de
OutputGroupInfo
(que está fusionado, siempre que el
la regla y el aspecto especifican diferentes grupos de salida) y
InstrumentedFilesInfo
(toma del aspecto). Esto significa que las implementaciones de aspectos
nunca se muestra DefaultInfo
.
Los parámetros y los atributos privados se pasan en los atributos de la
ctx
En este ejemplo, se hace referencia al parámetro extension
y determina
qué archivos contar.
En el caso de los proveedores recurrentes, los valores de los atributos con los que
el aspecto se propaga (de la lista attr_aspects
) se reemplazan por
los resultados de una aplicación del aspecto. Por ejemplo, si el objetivo
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
resultados de la aplicación del aspecto a las “deps” del destino original al que
se aplicó el aspecto.
En el ejemplo, el aspecto accede al proveedor de FileCountInfo
desde el
destino para acumular la cantidad total transitiva de archivos.
Invoca el aspecto de 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 del ctx.attr.deps
.
La definición de la regla muestra cómo definir un parámetro (extension
)
y asígnale un valor predeterminado (*
). Ten en cuenta que tener un valor predeterminado que
no fue uno de "cc
", "h
" ni "*
" sería un error debido al
restricciones aplicadas al parámetro en la definición de aspecto.
Cómo invocar un aspecto a través de una regla objetivo
load('//:file_count.bzl', 'file_count_rule')
cc_binary(
name = 'app',
...
)
file_count_rule(
name = 'file_count',
deps = ['app'],
extension = 'h',
)
Esto muestra cómo pasar el parámetro extension
al aspecto
a través de la regla. Como el parámetro extension
tiene un valor predeterminado en el
implementación de reglas, extension
se consideraría un parámetro opcional.
Cuando se crea el destino file_count
, nuestro aspecto se evaluará en función de lo siguiente:
y todos los destinos accesibles de forma recurrente a través de deps
.