Aspectos

Informar un problema Ver fuente Por la noche · 7.4 de Google Cloud. 7.3 · 7.2 · 7.1 · 7.0 · 6.5

En esta página, se explican los conceptos básicos y los beneficios de utilizar aspectos y brinda una solución simple y avanzada ejemplos.

Los aspectos permiten aumentar los gráficos de dependencias de compilación con información y acciones adicionales. Estas son algunas situaciones típicas en las que los aspectos pueden ser útiles:

  • Los IDEs que integran Bazel pueden usar aspectos para recopilar información sobre el proyecto.
  • Las herramientas de generación de código pueden aprovechar aspectos para ejecutar en sus entradas de manera independiente del objetivo. A modo de ejemplo, los archivos BUILD pueden especificar una jerarquía de definiciones de bibliotecas de protobuf, y las reglas específicas del idioma pueden usar aspectos para adjuntar acciones que generan código de compatibilidad con protobuf para un idioma en particular.

Conceptos básicos de los aspectos

Los archivos BUILD proporcionan una descripción del código fuente de un proyecto: ¿de qué fuente? como parte del proyecto, a partir de qué artefactos (destinos) se deben compilar esos archivos, cuáles son las dependencias entre ellos, etc. Bazel usa esta información para realizar una compilación, es decir, descifra el conjunto de acciones necesarios para producir los artefactos (como la ejecución del compilador o vinculador) y ejecuta esas acciones. Bazel logra esto construyendo una dependencia gráfico entre los objetivos y visitar este gráfico para recopilar esas acciones.

Considera el siguiente archivo 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'], ...)

Este archivo BUILD define un gráfico de dependencias que se muestra en la siguiente imagen:

Gráfico de compilación

Figura 1: Gráfico de dependencias del archivo BUILD.

Bazel analiza este grafo de dependencias llamando a una función de implementación de la regla correspondiente (en este caso, "java_library") para cada objetivo del ejemplo anterior. Las funciones de implementación de reglas generan acciones que compilan artefactos, como archivos .jar, y pasan información, como ubicaciones y nombres de esos artefactos, a las dependencias inversas de esos destinos en los proveedores.

Los aspectos son similares a las reglas en que tienen una función de implementación que genera acciones y muestra proveedores. Sin embargo, su poder proviene de la forma en que se compila el gráfico de dependencias para ellos. Un aspecto tiene una implementación y una lista de todos los atributos que se propagan. Considera un aspecto A que se propaga a lo largo de atributos llamados “deps”. Este aspecto se puede aplicar a un objetivo X, lo que genera un nodo de aplicación de aspectos A(X). Durante su aplicación, el aspecto A se aplica de forma recursiva a todos los destinos a los que X hace referencia en su atributo "deps" (todos los atributos de la lista de propagación de A).

Por lo tanto, un solo acto de aplicar el aspecto A a un objetivo X genera un "gráfico de sombra" del gráfico de dependencias original de los objetivos que se muestra en la siguiente figura:

Cómo compilar un gráfico con aspecto

Figura 2: Compilar un gráfico con aspectos

Los únicos bordes que tienen sombras son los bordes a lo largo de los atributos en el conjunto de propagación, por lo que el borde runtime_deps no tiene sombras en este ejemplo. Luego, se invoca una función de implementación de aspectos en todos los nodos del gráfico de sombras, de manera similar a como se invocan las implementaciones de reglas en los nodos del gráfico original.

Ejemplo simple

Este ejemplo demuestra cómo imprimir de manera recursiva los archivos fuente para un y todas sus dependencias que tienen un atributo deps. Muestra una implementación de aspecto, una definición de aspecto y cómo invocar este aspecto desde la línea de comandos de 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'],
)

Desglosemos el ejemplo en sus partes y examinemos cada una de forma individual.

Definición de aspecto

print_aspect = aspect(
    implementation = _print_aspect_impl,
    attr_aspects = ['deps'],
)

Las definiciones de aspecto son similares a las definiciones de reglas y se definen con La función aspect

Al igual que una regla, un aspecto tiene una función de implementación que, en este caso, es _print_aspect_impl.

attr_aspects es una lista de atributos de reglas a través de los cuales se propaga el aspecto. En este caso, el aspecto se propagará a lo largo del atributo deps de las reglas a las que se aplica.

Otro argumento común para attr_aspects es ['*'], que propagaría el en todos los atributos de una regla.

Implementación de Aspect

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 []

Las funciones de implementación de Aspecto son similares a las de la implementación de reglas funciones. Muestran providers, pueden generar actions y toma dos argumentos:

  • target: Es el objetivo al que se aplica el aspecto.
  • ctx: Es un objeto ctx que se puede usar para acceder a los atributos. y generar resultados y acciones.

La función de implementación puede acceder a los atributos de la regla de destino a través de ctx.rule.attr. Puede examinar a los proveedores que son que proporciona el destino al que se aplica (a través del argumento target).

Los aspectos son obligatorios para mostrar una lista de proveedores. En este ejemplo, el aspecto no proporciona nada, por lo que muestra una lista vacía.

Cómo invocar el aspecto con la línea de comandos

La forma más sencilla de aplicar un aspecto es desde la línea de comandos usando el --aspects argumento. Suponiendo que los aspectos anteriores se hayan definido en un archivo llamado print.bzl esto:

bazel build //MyExample:example --aspects print.bzl%print_aspect

aplicaría el print_aspect al example de destino y a todas las reglas de destino a las que se puede acceder de forma recursiva a través del atributo deps.

La marca --aspects toma un argumento, que es una especificación del aspecto en el formato <extension file label>%<aspect top-level name>.

Ejemplo avanzado

En el siguiente ejemplo, se muestra el uso de un aspecto de una regla de destino que cuenta los archivos en los destinos y, posiblemente, los filtra por extensión. En él, se muestra cómo usar un proveedor para mostrar valores, cómo usar parámetros para pasar un argumento a una implementación de aspecto y cómo invocar un aspecto desde una regla.

Archivo 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 = '*'),
    },
)

Archivo 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',
)

Definición de aspecto

file_count_aspect = aspect(
    implementation = _file_count_aspect_impl,
    attr_aspects = ['deps'],
    attrs = {
        'extension' : attr.string(values = ['*', 'h', 'cc']),
    }
)

En este ejemplo, se muestra cómo se propaga el aspecto a través del atributo deps.

attrs define un conjunto de atributos para un aspecto. Atributos de aspecto público definen parámetros y solo pueden ser de tipos bool, int o string. Para los aspectos propagados por reglas, los parámetros int y string deben tener Se especificó values en ellos. Este ejemplo tiene un parámetro llamado extension que puede tener como valor "*", "h" o "cc".

En el caso de los aspectos propagados por reglas, los valores de los parámetros se toman de la regla que solicita el aspecto, con el atributo de la regla que tiene el mismo nombre y tipo. (consulta la definición de file_count_rule).

En el caso de los aspectos de la línea de comandos, los valores de los parámetros se pueden pasar con la marca --aspects_parameters. Se puede omitir la restricción values de los parámetros int y string.

Los aspectos también pueden tener atributos privados de los tipos label o label_list. Los atributos de etiqueta privada se pueden usar para especificar dependencias herramientas o bibliotecas necesarias para acciones generadas por aspectos. No hay un atributo privado definido en este ejemplo, pero el siguiente fragmento de código demuestra cómo podrías pasar una herramienta a un aspecto:

...
    attrs = {
        '_protoc' : attr.label(
            default = Label('//tools:protoc'),
            executable = True,
            cfg = "exec"
        )
    }
...

Implementación 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)]

Al igual que una función de implementación de reglas, una función de implementación de aspectos muestra una estructura de proveedores a los que pueden acceder sus dependencias.

En este ejemplo, FileCountInfo se define como un proveedor que tiene un campo count. Una práctica recomendada es definir de forma explícita los campos de un proveedor con el atributo fields.

El conjunto de proveedores de una aplicación de aspecto A(X) es la unión de los proveedores que provienen de la implementación de una regla para el objetivo X y de la implementación del aspecto A. Los proveedores que propaga una implementación de reglas se crean y se inmovilizan antes de que se apliquen los aspectos y no se pueden modificar desde un aspecto. Es un error si un objetivo y un aspecto que se le aplica cada uno proporcionar a un proveedor del mismo tipo, a excepción de OutputGroupInfo (que está fusionado, siempre que el la regla y el aspecto especifican diferentes grupos de salida) y InstrumentedFilesInfo (toma del aspecto). Esto significa que las implementaciones de aspectos nunca se muestra DefaultInfo.

Los parámetros y los atributos privados se pasan en los atributos de ctx. En este ejemplo, se hace referencia al parámetro extension y se determina qué archivos se deben contar.

En el caso de los proveedores que muestran resultados, los valores de los atributos a lo largo de los cuales se propaga el aspecto (de la lista attr_aspects) se reemplazan por los resultados de una aplicación del aspecto a ellos. Por ejemplo, si el destino X tiene Y y Z en sus dependencias, ctx.rule.attr.deps para A(X) será [A(Y), A(Z)]. En este ejemplo, ctx.rule.attr.deps son objetos de destino que son resultados de la aplicación del aspecto a las “deps” del destino original al que se aplicó el aspecto.

En el ejemplo, el aspecto accede al proveedor de FileCountInfo desde el destino para acumular la cantidad total transitiva de archivos.

Invoca el aspecto de una regla

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 = '*'),
    },
)

La implementación de reglas muestra cómo acceder a FileCountInfo. a través del ctx.attr.deps.

La definición de la regla muestra cómo definir un parámetro (extension) y asígnale un valor predeterminado (*). Ten en cuenta que tener un valor predeterminado que no fue uno de "cc", "h" ni "*" sería un error debido al restricciones aplicadas al parámetro en la definición de aspecto.

Cómo invocar un aspecto a través de una regla de destino

load('//:file_count.bzl', 'file_count_rule')

cc_binary(
    name = 'app',
...
)

file_count_rule(
    name = 'file_count',
    deps = ['app'],
    extension = 'h',
)

Esto demuestra cómo pasar el parámetro extension al aspecto a través de la regla. Como el parámetro extension tiene un valor predeterminado en el implementación de reglas, extension se consideraría un parámetro opcional.

Cuando se crea el destino file_count, nuestro aspecto se evaluará en función de lo siguiente: y todos los destinos accesibles de forma recurrente a través de deps.

Referencias