Aspectos

Informar um problema Ver a fonte Nightly · 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 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:

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

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 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: 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 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.

Referências