Aspectos

Informar um problema Mostrar fonte Por noite · 7,4 do Google. 7,3 · 7.2 · 7,1 · 7,0 · 6,5

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 de 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 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 de 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 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 abaixo:

Criar gráfico

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 de a regra correspondente (neste caso, "java_library") para cada 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 propagados. 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 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 gera um "gráfico de sombra" do gráfico de dependência original de destinos mostrado na figura a seguir:

Criar gráfico com o Aspect

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 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 funções 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: objeto ctx 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.

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 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, 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. 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 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 omitida.

Os aspectos também podem ter atributos particulares dos tipos label ou label_list. Os atributos de marca própria podem ser usados para especificar dependências de 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 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. 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 aspectos podem nunca retorna 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 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 das dependências do destino para acumular o número total de arquivos transitivos.

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 na implementação da regra, ele é considerado opcional.

Quando o destino file_count é criado, nosso aspecto é avaliado para em si e todos os destinos acessíveis recursivamente por deps.

Referências