Aspectos

Relatar um problema Conferir código-fonte Por noite · 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 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:

Criar gráfico

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:

Criar gráfico com o Aspect

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: 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 é 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 que são 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 junto com os 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.

Referências