Aspectos

Informar un problema Ver fuente

En esta página, se explican los conceptos básicos y los beneficios de usar aspectos y se proporcionan ejemplos simples y avanzados.

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

  • Los IDE 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 agnóstica del objetivo. Como ejemplo, los archivos BUILD pueden especificar una jerarquía de definiciones de la biblioteca de protobuf, y las reglas específicas de lenguaje pueden usar aspectos a fin de adjuntar acciones que generan código de compatibilidad de protobuf para un idioma en particular.

Aspectos básicos de los aspectos

Los archivos BUILD proporcionan una descripción del código fuente de un proyecto: qué archivos fuente forman parte del proyecto, qué artefactos (destinos) se deben compilar a partir de esos archivos, cuáles son las dependencias entre esos archivos, etc. Bazel usa esta información para realizar una compilación, es decir, determina el conjunto de acciones necesarias para producir los artefactos (como ejecutar el compilador o el vinculador) y ejecuta esas acciones. Para ello, Bazel crea un gráfico de dependencias entre los objetivos y visita 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 dependencia que se muestra en la siguiente figura:

Gráfico de compilación

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

Bazel analiza este gráfico de dependencias llamando a una función de implementación de la regla correspondiente (en este caso, "java_library") para cada destino 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 proveedores.

Los aspectos son similares a las reglas, ya que tienen una función de implementación que genera acciones y muestra proveedores. Sin embargo, su potencia proviene de la forma en que se crea el gráfico de dependencia para ellos. Un aspecto tiene una implementación y una lista de todos los atributos con los que se propaga. Considera un aspecto A que se propaga a lo largo de los atributos llamados "deps". Este aspecto se puede aplicar a un destino X, lo que da como resultado un nodo de aplicación de aspecto A(X). Durante su aplicación, el aspecto A se aplica de manera recurrente a todos los objetivos a los que X hace referencia en su atributo "dependencias" (todos los atributos de la lista de propagación A).

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

Crear gráfico con Aspecto

Figura 2: Crear un gráfico con aspectos

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

Ejemplo simple

En este ejemplo, se muestra cómo imprimir de manera recurrente los archivos de origen de una regla y todas sus dependencias que tienen un atributo deps. Se muestra una implementación de aspecto, una definición de aspecto y cómo invocar el 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 partes y examinemos cada una de manera individual.

Definición del 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 sucede con 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 lo largo 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 aspecto a todos los atributos de una regla.

Implementación de aspecto

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 aspectos son similares a las funciones de implementación de reglas. Estas muestran proveedores, pueden generar acciones y tomar 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 mediante ctx.rule.attr. Puede examinar los proveedores que proporciona el destino al que se aplica (a través del argumento target).

Se requieren aspectos 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 mediante la línea de comandos

La forma más sencilla de aplicar un aspecto es desde la línea de comandos con el argumento --aspects. Si el aspecto anterior se definió en un archivo llamado print.bzl, este:

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

aplicaría print_aspect al example de destino y a todas las reglas de destino a las que se puede acceder de forma recurrente mediante el 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 es posible que los filtre por extensión. 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 del 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. Los atributos de aspecto público definen parámetros y solo pueden ser de los tipos bool, int o string. Para los aspectos que propagan las reglas, los parámetros int y string deben tener values especificado. Este ejemplo tiene un parámetro llamado extension que puede tener “*”, “h” o “cc” como valor.

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

Para los aspectos de la línea de comandos, los valores de los parámetros se pueden pasar con la marca --aspects_parameters. Es posible que se omita la restricción values de los parámetros int y string.

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

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

Implementación de aspecto

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 aspecto 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. La práctica recomendada es definir explícitamente los campos de un proveedor mediante el atributo fields.

El conjunto de proveedores para una aplicación de aspecto A(X) es la unión de proveedores que provienen de la implementación de una regla para el destino X y de la implementación del aspecto A. Los proveedores que propaga una implementación de reglas se crean y congelan antes de que se apliquen los aspectos y no se pueden modificar desde un aspecto. Es un error si cada objetivo y un aspecto que se le aplica proporcionan un proveedor con el mismo tipo, a excepción de OutputGroupInfo (que se combina, siempre que la regla y el aspecto especifiquen diferentes grupos de salida) y InstrumentedFilesInfo (que se toma del aspecto). Esto significa que las implementaciones de aspectos pueden nunca mostrar 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 cuentan.

Para los proveedores que se muestran, los valores de los atributos a lo largo de los cuales se propaga el aspecto (de la lista attr_aspects) se reemplazan con los resultados de una aplicación del aspecto a ellos. Por ejemplo, si el objetivo 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 los resultados de aplicar el aspecto a las "dependencias" del destino original al que se aplicó el aspecto.

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

Cómo invocar el aspecto a partir 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 la regla muestra cómo acceder a FileCountInfo a través de ctx.attr.deps.

La definición de la regla muestra cómo definir un parámetro (extension) y asignarle un valor predeterminado (*). Ten en cuenta que tener un valor predeterminado que no fuera "cc", "h" o "*" sería un error debido a las restricciones impuestas al parámetro en la definición de aspecto.

Invoca un aspecto mediante 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. Dado que el parámetro extension tiene un valor predeterminado en la implementación de la regla, extension se consideraría un parámetro opcional.

Cuando se compile el destino file_count, se evaluará nuestro aspecto y se podrá acceder a todos los destinos de forma recursiva a través de deps.

Referencias