Esta página explica os conceitos básicos e os benefícios do uso de aspectos e fornece exemplos simples e avançados exemplos.
Os aspectos permitem aumentar os gráficos de dependência de build com mais informações e ações. Confira alguns cenários típicos em que os aspectos podem ser úteis:
- Os IDEs que integram o Bazel podem usar aspectos para coletar informações sobre o projeto.
- As ferramentas de geração de código podem aproveitar os aspectos para executar as entradas de maneira
independente do destino Por exemplo, os arquivos
BUILDpodem especificar uma hierarquia de protobuf definições de biblioteca, e as regras específicas do idioma podem usar aspectos para anexar ações que geram código de suporte do protobuf para um idioma específico.
Noções básicas sobre aspectos
BUILD arquivos fornecem uma descrição do código-fonte de um projeto: quais arquivos de origem fazem parte do projeto, quais artefatos (destinos) precisam ser criados com esses arquivos, quais são as dependências entre eles etc. O Bazel usa essas informações para realizar um build, ou seja, ele descobre o conjunto de ações necessárias para produzir os artefatos (como executar o compilador ou o vinculador) e as executa. O Bazel faz isso construindo um gráfico
de dependência entre os destinos e acessando esse gráfico para coletar essas ações.
Considere o seguinte BUILD arquivo:
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'], ...)
Esse arquivo BUILD define um gráfico de dependência mostrado na figura a seguir:

Figura 1. Gráfico de dependência do arquivo BUILD.
O Bazel analisa esse gráfico de dependência chamando uma função de implementação de
a regra correspondente (neste caso, "java_library") para cada
destino no exemplo acima. As funções de implementação de regras geram ações que
criam artefatos, como arquivos .jar, e transmitem informações, como locais
e nomes desses artefatos, para as dependências inversas desses destinos em
provedores.
Os aspectos são semelhantes às regras, pois têm uma função de implementação que gera ações e retorna provedores. No entanto, o poder deles vem de a maneira como o gráfico de dependência é criado. Um aspecto tem uma implementação e uma lista de todos os atributos que ele propaga. Considere um aspecto A que se propaga ao longo de atributos chamados "deps". Esse aspecto pode ser aplicado a um destino X, gerando um nó de aplicação de aspecto A(X). Durante a aplicação, o aspecto A é aplicado recursivamente a todos os destinos a que X se refere no atributo "deps" (todos os atributos na lista de propagação de A).
Assim, um único ato de aplicar o aspecto A a um destino X gera um "gráfico de sombra" de o gráfico de dependência original dos destinos mostrados na figura a seguir:

Figura 2. Gráfico de build com aspectos.
As únicas bordas sombreadas são as bordas ao longo dos atributos em
o conjunto de propagação. Portanto, a borda runtime_deps não é sombreada neste
exemplo. Uma função de implementação de aspecto é invocada em todos os nós no
gráfico de sombra, de maneira semelhante a como as implementações de regras são invocadas nos nós
do gráfico original.
exemplo simples
Este exemplo demonstra como imprimir recursivamente os arquivos de origem de uma
regra e todas as dependências que têm um deps atributo. Ele mostra
uma implementação de aspecto, uma definição de aspecto e como invocar o aspecto
na linha de comando do 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'],
)
Vamos dividir o exemplo em partes e analisar cada uma delas individualmente.
Definição de aspecto
print_aspect = aspect(
implementation = _print_aspect_impl,
attr_aspects = ['deps'],
)
As definições de aspecto são semelhantes às definições de regras e são definidas usando
a aspect função.
Assim como uma regra, um aspecto tem uma função de implementação, que neste caso é
_print_aspect_impl.
attr_aspects é uma lista de atributos de regra ao longo dos quais o aspecto se propaga.
Nesse caso, o aspecto será propagado ao longo do atributo deps das
regras a que ele é aplicado.
Outro argumento comum para attr_aspects é ['*'], que propagaria o
aspecto para todos os atributos de uma regra.
Implementação 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 []
As funções de implementação de aspecto são semelhantes às funções de implementação de regras. Elas retornam provedores, podem gerar ações e usam dois argumentos:
target: o destino a que o aspecto está sendo aplicado.ctx: objetoctxque pode ser usado para acessar atributos e gerar saídas e ações.
A função de implementação pode acessar os atributos da regra de destino usando
ctx.rule.attr. Ela pode examinar os provedores que são
fornecidos pelo destino a que é aplicado (pelo argumento target).
Os aspectos precisam retornar uma lista de provedores. Neste exemplo, o aspecto não fornece nada, então ele retorna uma lista vazia.
Como invocar o aspecto usando a linha de comando
A maneira mais simples de aplicar um aspecto é na linha de comando usando o
--aspects
argumento. Supondo que o aspecto acima tenha sido definido em um arquivo chamado print.bzl
isto:
bazel build //MyExample:example --aspects print.bzl%print_aspect
aplicaria o print_aspect ao destino example e a todas as
regras de destino que podem ser acessadas recursivamente pelo atributo deps.
A flag --aspects usa um argumento, que é uma especificação do aspecto
no formato <extension file label>%<aspect top-level name>.
Exemplo avançado
O exemplo a seguir demonstra o uso de um aspecto de uma regra de destino que conta arquivos em destinos, possivelmente filtrando-os por extensão. Ele mostra como usar um provedor para retornar valores, como usar parâmetros para transmitir um argumento a uma implementação de aspecto e como invocar um aspecto de uma regra.
file_count.bzl arquivo:
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 = '*'),
},
)
Arquivo 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',
)
Definição de aspecto
file_count_aspect = aspect(
implementation = _file_count_aspect_impl,
attr_aspects = ['deps'],
attrs = {
'extension' : attr.string(values = ['*', 'h', 'cc']),
}
)
Este exemplo mostra como o aspecto se propaga pelo atributo deps.
attrs define um conjunto de atributos para um aspecto. Os atributos de aspecto público
definem parâmetros e só podem ser dos tipos bool, int ou string.
Para aspectos propagados por regras, os parâmetros int e string precisam ter
values especificados. Este exemplo tem um parâmetro chamado extension
que pode ter '*', 'h' ou 'cc' como valor.
Para aspectos propagados por regras, os valores de parâmetro são extraídos da regra que solicita
o aspecto, usando o atributo da regra que tem o mesmo nome e tipo.
(consulte a definição de file_count_rule).
Para aspectos de linha de comando, os valores dos parâmetros podem ser transmitidos usando
--aspects_parameters
flag. A restrição values dos parâmetros int e string pode ser
omitida.
Os aspectos também podem ter atributos particulares dos tipos label ou
label_list. Os atributos de rótulo particular podem ser usados para especificar dependências em
ferramentas ou bibliotecas necessárias para ações geradas por aspectos. Não há
um atributo particular definido neste exemplo, mas o snippet de código a seguir
demonstra como transmitir uma ferramenta para um aspecto:
...
attrs = {
'_protoc' : attr.label(
default = Label('//tools:protoc'),
executable = True,
cfg = "exec"
)
}
...
Implementação 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)]
Assim como uma função de implementação de regra, uma função de implementação de aspecto retorna uma estrutura de provedores que podem ser acessados pelas dependências.
Neste exemplo, o FileCountInfo é definido como um provedor que tem um
campo count. A prática recomendada é definir explicitamente os campos de um
provedor usando o fields atributo.
O conjunto de provedores para uma aplicação de aspecto A(X) é a união de provedores
que vêm da implementação de uma regra para o destino X e da
implementação do aspecto A. Os provedores que uma implementação de regra propaga
são criados e congelados antes que os aspectos sejam aplicados e não possam ser modificados de um
aspecto. É um erro se um destino e um aspecto aplicado a ele cada
fornecerem um provedor com o mesmo tipo, com exceção de
OutputGroupInfo
(que é mesclado, desde que a
regra e o aspecto especifiquem grupos de saída diferentes) e
InstrumentedFilesInfo
(que é extraído do aspecto). Isso significa que as implementações de aspectos podem
nunca retornar DefaultInfo.
Os parâmetros e atributos particulares são transmitidos nos atributos do
ctx. Este exemplo faz referência ao parâmetro extension e determina
quais arquivos contar.
Para retornar provedores, os valores dos atributos ao longo dos quais
o aspecto é propagado (da lista attr_aspects) são substituídos pelos resultados de uma aplicação do aspecto a eles. Por exemplo, se o destino
X tiver Y e Z nas dependências, ctx.rule.attr.deps para A(X) será [A(Y), A(Z)].
Neste exemplo, ctx.rule.attr.deps são objetos de destino que são os
resultados da aplicação do aspecto às 'dependências' do destino original a que
o aspecto foi aplicado.
No exemplo, o aspecto acessa o provedor FileCountInfo das dependências do destino
para acumular o número total transitivo de arquivos.
Como invocar o aspecto de uma regra
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 = '*'),
},
)
A implementação da regra demonstra como acessar o FileCountInfo
pelo ctx.attr.deps.
A definição da regra demonstra como definir um parâmetro (extension)
e atribuir um valor padrão (*). Ter um valor padrão que
não seja um de 'cc', 'h' ou '*' seria um erro devido às
restrições impostas ao parâmetro na definição do aspecto.
Como invocar um aspecto por uma regra de destino
load('//:file_count.bzl', 'file_count_rule')
cc_binary(
name = 'app',
...
)
file_count_rule(
name = 'file_count',
deps = ['app'],
extension = 'h',
)
Isso demonstra como transmitir o extension parâmetro para o aspecto
pela regra. Como o parâmetro extension tem um valor padrão na
implementação da regra, extension seria considerado um parâmetro opcional.
Quando o destino file_count é criado, nosso aspecto é avaliado para
ele mesmo e para todos os destinos acessíveis recursivamente por deps.