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 integrados ao Bazel podem usar aspectos para coletar informações sobre o projeto.
- As ferramentas de geração de código podem aproveitar aspectos para executar suas entradas
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 do protobuf para um idioma específico.
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. 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 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 a seguir:
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
criar artefatos, como arquivos .jar
, e transmitir informações, como locais.
e nomes desses artefatos, até as dependências reversas desses destinos em
provedores de serviços.
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, seu poder vem a maneira como o gráfico de dependências é criado para elas. Um aspecto tem uma implementação e uma lista de todos os atributos propagados. Considere um aspecto A que se propaga pelos 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 todas as metas 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 resulta em um "gráfico sombra" de o gráfico de dependência original dos destinos mostrado na figura a seguir:
Figura 2. Crie um gráfico com aspectos.
As únicas arestas sombreadas são as arestas ao longo dos atributos no
conjunto de propagação. Portanto, a aresta 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 os arquivos de origem de uma
e todas as dependências dela com um atributo deps
. 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 examinar cada uma delas.
Definição de 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 com
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 vai se propagar pelo atributo deps
das
regras em que ele é aplicado.
Outro argumento comum para attr_aspects
é ['*']
, que propagaria a
a 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 actions e receba dois argumentos:
target
: o destino a que 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 usando
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.
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
. Considerando que o aspecto acima foi definido em um arquivo chamado print.bzl
isso:
bazel build //MyExample:example --aspects print.bzl%print_aspect
aplicaria o print_aspect
ao example
de destino e a todos os
regras de destino que são acessíveis 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, potencialmente filtrando-os por extensão. Ele mostra como usar um provedor para retornar valores, 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. Atributos de aspecto público
definem os 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 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 particulares 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 regras, uma função de implementação de aspectos 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
. É uma prática recomendada 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. É um erro se um destino e um aspecto aplicado a ele
fornecem um provedor do mesmo tipo, com exceções
OutputGroupInfo
(que é mesclado, desde que o
regra e aspecto especificam diferentes grupos de saída) e
InstrumentedFilesInfo
(retirado do aspecto). Isso significa que as implementações de aspectos podem
nunca retorna 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 contar.
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 que são os
resultados da aplicação do aspecto aos "deps" do destino original em que
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.
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 da regra demonstra como definir um parâmetro (extension
)
e definir um valor padrão (*
). Ter um valor padrão que
não era "cc
", "h
" ou "*
" é um erro devido à
restrições 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
ao aspecto
por meio da 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
.