Nesta página, explicamos os conceitos básicos e benefícios de usar os aspectos e fornecemos exemplos simples e avançados.
Os aspectos permitem aumentar os gráficos de dependência do build com mais informações e ações. Alguns cenários típicos em que os aspectos podem ser úteis:
- Os ambientes de desenvolvimento integrado que integram o Bazel podem usar aspectos para coletar informações sobre o projeto.
- As ferramentas de geração de código podem aproveitar aspectos a serem executados nas entradas de maneira
independente de destino. Por exemplo, os arquivos
BUILD
podem especificar uma hierarquia de definições da biblioteca protobuf, e as regras específicas da linguagem podem usar aspectos para anexar ações que geram código de suporte ao protobuf para uma determinada linguagem.
Noções básicas de aspecto
Os arquivos BUILD
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 esses arquivos etc. O Bazel usa
essas informações para executar um build, ou seja, descobre o conjunto de ações
necessárias para produzir os artefatos (como compilador ou vinculador em execução) e
executa essas ações. Para fazer isso, o Bazel cria um gráfico
de dependência entre os destinos e acessa esse gráfico para coletar essas ações.
Considere o seguinte arquivo 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'], ...)
O 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 da
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 reversas desses destinos em
provedores.
Os aspectos são semelhantes às regras, porque têm uma função de implementação que gera ações e retorna provedores. No entanto, o poder vem da forma como o gráfico de dependências é criado para eles. Um aspecto tem uma implementação e uma lista de todos os atributos que ele propaga. Considere um aspecto A que se propaga junto com os atributos chamados "deps". Esse aspecto pode ser aplicado a um X de destino, produzindo um nó de aplicativo 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" do gráfico de dependência original dos destinos, mostrado na figura a seguir:
Figura 2. Criar um gráfico com aspectos.
As únicas bordas sombreadas são as ao longo dos atributos no
conjunto de propagação. Portanto, a borda runtime_deps
não é oculta neste
exemplo. Uma função de implementação de aspecto é invocada em todos os nós no
gráfico de sombra da mesma forma que as implementações de regras são invocadas nos nós
do gráfico original.
Exemplo simples
Este exemplo demonstra como imprimir de maneira recursiva os arquivos de origem de uma
regra e todas as dependências que têm um atributo deps
. Ela mostra
uma implementação de aspecto, uma definição de aspecto e como invocá-lo
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 individualmente.
Definição do aspecto
print_aspect = aspect(
implementation = _print_aspect_impl,
attr_aspects = ['deps'],
)
As definições de aspectos são semelhantes às de regras e são definidas usando
a função aspect
.
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 que 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 aspectos
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 aspectos são semelhantes às de implementação de regras. Eles retornam provedores, podem gerar ações e usam dois argumentos:
target
: o destino ao qual o aspecto está sendo aplicado.ctx
: o objetoctx
que 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
. Ele pode examinar os provedores
fornecidos pelo destino a que é aplicado (usando o argumento target
).
Os aspectos são obrigatórios para retornar uma lista de provedores. Nesse exemplo, o aspecto não fornece nada, então 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
argumento
--aspects
. Supondo que o aspecto acima tenha sido definido em um arquivo chamado print.bzl
,
o seguinte:
bazel build //MyExample:example --aspects print.bzl%print_aspect
aplicaria o print_aspect
ao example
de destino e a todas as
regras de destino acessíveis recursivamente pelo atributo deps
.
A sinalização --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 para uma implementação de aspecto e como invocar um aspecto de uma regra.
Arquivo 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 = '*'),
},
)
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 do 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
são do tipo string
e são chamados de parâmetros. Os parâmetros precisam ter um atributo values
especificado. Este exemplo tem um parâmetro chamado extension
que pode ter "*
", "h
" ou "cc
" como valor.
Os valores dos parâmetros do aspecto são extraídos do atributo da string com o mesmo
nome da regra que solicita o aspecto (consulte a definição de file_count_rule
).
Os aspectos com parâmetros não podem ser usados na linha de comando porque não há
sintaxe para definir os parâmetros.
Os aspectos também podem ter atributos particulares dos tipos label
ou
label_list
. 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 abaixo
demonstra como transmitir uma ferramenta para um aspecto:
...
attrs = {
'_protoc' : attr.label(
default = Label('//tools:protoc'),
executable = True,
cfg = "exec"
)
}
...
Implementação de aspectos
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 um struct de provedores que pode ser acessado pelas dependências dele.
Neste exemplo, o FileCountInfo
é definido como um provedor que tem um
campo count
. É uma prática recomendada definir explicitamente os campos de um
provedor usando o atributo fields
.
O conjunto de provedores de um aplicativo 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 propagados por uma implementação de regra
são criados e congelados antes da aplicação dos aspectos. Eles não podem ser modificados
a partir de um aspecto. Será um erro se um destino e um aspecto aplicado a eles
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 é tirado do aspecto). Isso significa que as implementações de aspecto nunca
podem retornar DefaultInfo
.
Os parâmetros e atributos particulares são transmitidos nos atributos da
ctx
. Este exemplo faz referência ao parâmetro extension
e determina
quais arquivos contar.
Para provedores de retorno, os valores dos atributos com que
o aspecto é propagado (da lista de 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 aos "deps" 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 a ele um valor padrão (*
). Ter um valor padrão que
não fosse "cc
", "h
" ou "*
" seria um erro devido às
restrições impostas ao parâmetro na definição do aspecto.
Como invocar um aspecto com 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 parâmetro extension
para o aspecto
usando a regra. Como o parâmetro extension
tem um valor padrão na implementação da regra, extension
é considerado um parâmetro opcional.
Quando o destino file_count
é criado, nosso aspecto é avaliado por
si mesmo, e todos os destinos podem ser acessados recursivamente por deps
.