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 suas entradas
independente de 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.
Conceitos básicos do aspecto
Os arquivos BUILD
fornecem uma descrição do código-fonte de um projeto: qual código-fonte
arquivos que fazem parte do projeto, quais artefatos (destinos) precisam ser criados
esses arquivos, quais são as dependências entre eles etc. O Bazel usa
essas informações para executar um build, ou seja, descobrir o conjunto de ações
necessárias para produzir os artefatos (como a execução do compilador ou vinculador) e
executa essas ações. O Bazel faz isso construindo 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 a seguir:
Figura 1. Gráfico de dependências do arquivo BUILD
.
O Bazel analisa esse gráfico de dependência chamando uma função de implementação de
a rule correspondente (neste caso, "java_library") para cada
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 é propagada ao longo de atributos denominados "deps". Este 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 resulta em um "gráfico sombra" de o gráfico de dependência original dos destinos mostrado na figura abaixo:
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, fazendo com 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 os arquivos de origem de uma
e todas as dependências dela com 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 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 será propagado pelo atributo deps
da
em que ela é aplicada.
Outro argumento comum para attr_aspects
é ['*']
, que propagaria a
aspecto 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 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 é pela linha de comando, usando o
--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 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. 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
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
--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 necessárias para ações geradas por aspectos. Não há
um atributo privado definido neste exemplo, mas o snippet de código a seguir
demonstra como passar 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 de os aspectos serem aplicados e não podem ser modificados
importante. É 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 que retornam, os valores dos atributos nos quais
o aspecto é propagado (da lista attr_aspects
) são substituídos por
os resultados da aplicação do aspecto a elas. Por exemplo, se target
X tem Y e Z nas suas 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
.
via 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 para
em si e todos os destinos acessíveis recursivamente por deps
.