Esta página explica os conceitos básicos e os benefícios do uso aspectos e oferece informações simples e avançadas exemplos.
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 os projeto.
- As ferramentas de geração de código podem aproveitar aspectos para executar as entradas de maneira
independente do destino. Por exemplo, os arquivos
BUILD
podem especificar uma hierarquia da biblioteca protobuf definições e regras específicas de uma linguagem podem usar aspectos para anexar ações que geram o código de suporte protobuf para uma linguagem específica.
Noções básicas sobre o 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 (alvos) devem ser criados a partir
desses arquivos, quais são as dependências entre esses arquivos 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
executa essas ações. Para isso, o Bazel cria uma dependência
gráfico entre destinos e acessar 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'], ...)
Esse arquivo BUILD
define um gráfico de dependência mostrado na figura abaixo:
Figura 1. Gráfico de dependência de 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 dessas metas 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 deles vem da maneira como o gráfico de dependência é 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 ao longo de atributos chamados "deps". Esse aspecto pode ser aplicado um alvo X, produzindo um nó de aplicativo de aspecto A(X). Durante a aplicação, o aspecto A é aplicado recursivamente a todos os destinos aos quais X se refere nas "dependências" (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 de destinos mostrado na figura a seguir:
Figura 2. Criar um gráfico com aspectos.
As únicas bordas que estão sombreadas são as bordas ao longo dos atributos na
o conjunto de propagação, de modo que a borda runtime_deps
não seja ocultada
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 dela que têm um atributo deps
. 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 nas partes e analisar cada uma 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 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 em que o aspecto é propagado.
Nesse caso, o aspecto será propagado ao longo do atributo deps
do
em que ela é aplicada.
Outro argumento comum para attr_aspects
é ['*']
, que propaga 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 . Elas retornam provedores, podem gerar ações e usam dois argumentos:
target
: o alvo ao qual o aspecto está sendo aplicado.ctx
: 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 por meio de
ctx.rule.attr
. Ele pode examinar os provedores que estão
fornecidos pelo destino a que é aplicado (por meio do argumento target
).
Os aspectos são necessários para retornar uma lista de provedores. Neste exemplo, o aspecto não fornece nada, então retorna uma lista vazia.
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
assim:
bazel build //MyExample:example --aspects print.bzl%print_aspect
aplicaria o print_aspect
ao example
de destino e a todas as
regras de destino que são acessíveis de forma recursiva 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 e como usar parâmetros para transmitir um argumento em 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 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
especificado neles. Este exemplo tem um parâmetro chamado extension
que podem ter "*
", "h
" ou "cc
" como um valor.
Para aspectos propagados por regras, os valores dos parâmetros são retirados 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 a
flag
--aspects_parameters
. A restrição values
dos parâmetros int
e string
pode ser
omitido.
Os aspectos também podem ter atributos privados dos tipos label
ou
label_list
. Os atributos de rótulo particular podem ser usados para especificar dependências
ferramentas ou bibliotecas que são 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 uma estrutura de provedores que são acessíveis às dependências.
Neste exemplo, o FileCountInfo
é definido como um provedor que tem um
campo count
. É recomendável definir explicitamente os campos de um
provedor usando o atributo fields
.
O conjunto de provedores para um aplicativo de aspecto A(X) é a união de provedores
da implementação de uma regra para a meta 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 podem ser modificados por um
aspecto. Será um erro se um destino e um aspecto aplicado a ele
fornecerem um provedor com o mesmo tipo, com as exceções de
OutputGroupInfo
(que é mesclada, desde que a
regra e o aspecto especifiquem grupos de saída diferentes) e
InstrumentedFilesInfo
(que é tirada do aspecto). Isso significa que as implementações de aspecto podem
nunca retornar DefaultInfo
.
Os parâmetros e atributos particulares são passados nos atributos da
ctx
: Este exemplo faz referência ao parâmetro extension
e determina
quais arquivos serão contados.
Para provedores retornados, os valores dos atributos em que
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
resultados de aplicar o aspecto às dependências do destino original
o aspecto foi aplicado.
No exemplo, o aspecto acessa o provedor FileCountInfo
do
das dependências do destino para acumular o número transitivo total 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
usando o ctx.attr.deps
.
A definição de regra demonstra como definir um parâmetro (extension
)
e atribuir um valor padrão (*
). Ter um valor padrão que
não seja 'cc
', 'h
' ou '*
' seria um erro devido às
restrições aplicadas ao parâmetro na definição do aspecto.
Como invocar um aspecto por meio de 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 no
implementação de regra, extension
será considerado um parâmetro opcional.
Quando o destino file_count
é criado, nosso aspecto é avaliado
por ele mesmo e por todos os destinos acessíveis recursivamente por deps
.