Extensiones de módulos

Informar un problema Ver fuente

Las extensiones de módulo permiten a los usuarios ampliar el sistema de módulos leyendo datos de entrada de módulos en el gráfico de dependencias, realizan la lógica necesaria para resolver dependencias y, por último, crean repositorios llamando a reglas de repositorios. Estas extensiones tienen capacidades similares a las reglas del repositorio, lo que les permite realizar operaciones de E/S de archivos, enviar solicitudes de red, etcétera. Entre otras cosas, permiten que Bazel interactúe con otros sistemas de administración de paquetes y, al mismo tiempo, respetan el gráfico de dependencias compilado a partir de módulos de Bazel.

Puedes definir extensiones de módulo en archivos .bzl, al igual que las reglas de repositorio. No se invocan directamente, sino que cada módulo especifica fragmentos de datos llamados etiquetas que las extensiones deben leer. Bazel ejecuta la resolución de módulos antes de evaluar las extensiones. La extensión lee todas las etiquetas que pertenecen a ella en todo el gráfico de dependencias.

Uso de extensiones

Las extensiones se alojan en módulos de Bazel. Para usar una extensión en un módulo, primero agrega un bazel_dep en el módulo que aloja la extensión y, luego, llama a la función integrada use_extension para incluirla. Considera el siguiente ejemplo: un fragmento de un archivo MODULE.bazel para usar la extensión "maven" definida en el módulo rules_jvm_external:

bazel_dep(name = "rules_jvm_external", version = "4.5")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

Esto vincula el valor que se muestra de use_extension a una variable, lo que permite al usuario usar la sintaxis de puntos para especificar etiquetas para la extensión. Las etiquetas deben seguir el esquema definido por las clases de etiquetas correspondientes que se especifican en la definición de la extensión. Para ver un ejemplo en el que se especifican algunas etiquetas maven.install y maven.artifact:

maven.install(artifacts = ["org.junit:junit:4.13.2"])
maven.artifact(group = "com.google.guava",
               artifact = "guava",
               version = "27.0-jre",
               exclusions = ["com.google.j2objc:j2objc-annotations"])

Usa la directiva use_repo para incorporar los repositorios que genera la extensión dentro del alcance del módulo actual.

use_repo(maven, "maven")

Los repositorios que genera una extensión forman parte de su API. En este ejemplo, la extensión del módulo "maven" promete generar un repositorio llamado maven. Con la declaración anterior, la extensión resuelve de manera correcta etiquetas como @maven//:org_junit_junit para apuntar al repositorio que genera la extensión “maven”.

Definición de extensión

Puedes definir extensiones de módulo de manera similar a las reglas de repositorio, con la función module_extension. Sin embargo, aunque las reglas de repositorio tienen varios atributos, las extensiones de módulos tienen tag_class, cada uno de los cuales tiene varios atributos. Las clases de etiquetas definen esquemas para las etiquetas que usa esta extensión. Por ejemplo, la extensión "maven" anterior podría definirse de la siguiente manera:

# @rules_jvm_external//:extensions.bzl

_install = tag_class(attrs = {"artifacts": attr.string_list(), ...})
_artifact = tag_class(attrs = {"group": attr.string(), "artifact": attr.string(), ...})
maven = module_extension(
  implementation = _maven_impl,
  tag_classes = {"install": _install, "artifact": _artifact},
)

Estas declaraciones muestran que las etiquetas maven.install y maven.artifact se pueden especificar mediante el esquema de atributos especificados.

La función de implementación de las extensiones de módulo es similar a la de las reglas del repositorio, excepto que obtienen un objeto module_ctx, que otorga acceso a todos los módulos que usan la extensión y todas las etiquetas pertinentes. Luego, la función de implementación llama a las reglas del repositorio para generar repositorios.

# @rules_jvm_external//:extensions.bzl

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")  # a repo rule
def _maven_impl(ctx):
  # This is a fake implementation for demonstration purposes only

  # collect artifacts from across the dependency graph
  artifacts = []
  for mod in ctx.modules:
    for install in mod.tags.install:
      artifacts += install.artifacts
    artifacts += [_to_artifact(artifact) for artifact in mod.tags.artifact]

  # call out to the coursier CLI tool to resolve dependencies
  output = ctx.execute(["coursier", "resolve", artifacts])
  repo_attrs = _process_coursier_output(output)

  # call repo rules to generate repos
  for attrs in repo_attrs:
    http_file(**attrs)
  _generate_hub_repo(name = "maven", repo_attrs)

Identidad de la extensión

Las extensiones de módulo se identifican por el nombre y el archivo .bzl que aparece en la llamada a use_extension. En el siguiente ejemplo, la extensión maven se identifica con el archivo .bzl @rules_jvm_external//:extension.bzl y el nombre maven:

maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

Volver a exportar una extensión desde un archivo .bzl diferente le otorga una identidad nueva y, si ambas versiones de la extensión se usan en el gráfico de módulos transitivos, se evaluarán por separado y solo verán las etiquetas asociadas con esa identidad en particular.

Como autor de la extensión, debes asegurarte de que los usuarios usen la extensión de tu módulo desde un único archivo .bzl.

Nombres de repositorios y visibilidad

Los repositorios que generan las extensiones tienen nombres canónicos con el formato module_repo_canonical_name~extension_name~repo_name. Para las extensiones alojadas en el módulo raíz, la parte module_repo_canonical_name se reemplaza por la string _main. Ten en cuenta que el formato del nombre canónico no es una API de la que debas confiar; está sujeto a cambios en cualquier momento.

Esta política de nombres implica que cada extensión tiene su propio “espacio de nombres del repositorio”. Dos extensiones distintas pueden definir un repositorio con el mismo nombre sin correr el riesgo de conflictos. También significa que repository_ctx.name informa el nombre canónico del repositorio, que no es el mismo que el nombre especificado en la llamada de la regla del repositorio.

Si se tienen en cuenta los repositorios generados por extensiones de módulo, hay varias reglas de visibilidad de repositorio:

  • Un repositorio de módulo de Bazel puede ver todos los repositorios presentados en su archivo MODULE.bazel a través de bazel_dep y use_repo.
  • Un repositorio generado por una extensión de módulo puede ver todos los repositorios visibles para el módulo que aloja la extensión, además de todos los demás repositorios generados por la misma extensión de módulo (con los nombres especificados en las llamadas de regla del repositorio como sus nombres aparentes).
    • Esto podría generar conflictos. Si el repositorio del módulo puede ver un repositorio con el nombre aparente foo y la extensión genera un repositorio con el nombre foo especificado, foo hará referencia al primero para todos los repositorios generados por esa extensión.

Prácticas recomendadas

En esta sección, se describen las prácticas recomendadas para escribir extensiones para que sean fáciles de usar, mantener y adaptarse bien a los cambios a lo largo del tiempo.

Coloca cada extensión en un archivo separado

Cuando las extensiones están en archivos diferentes, permite que una extensión cargue los repositorios que generó otra extensión. Incluso si no usas esta funcionalidad, es mejor colocarlos en archivos separados en caso de que lo necesites más adelante. Esto se debe a que la identidad de la extensión se basa en su archivo, por lo que mover la extensión a otro archivo más tarde cambia tu API pública y es un cambio incompatible con versiones anteriores para tus usuarios.

Especifica el sistema operativo y la arquitectura

Si tu extensión se basa en el sistema operativo o su tipo de arquitectura, asegúrate de indicarlo en la definición de la extensión con los atributos booleanos os_dependent y arch_dependent. Esto garantiza que Bazel reconozca la necesidad de una reevaluación si cualquiera de ellas se modifica.

Solo el módulo raíz debería afectar directamente los nombres de los repositorios.

Recuerda que cuando una extensión crea repositorios, estos se crean dentro del espacio de nombres de la extensión. Esto significa que pueden ocurrir colisiones si diferentes módulos usan la misma extensión y crean un repositorio con el mismo nombre. A menudo, se manifiesta como el tag_class de una extensión de módulo que tiene un argumento name que se pasa como el valor name de una regla del repositorio.

Por ejemplo, supongamos que el módulo raíz, A, depende del módulo B. Ambos módulos dependen del módulo mylang. Si tanto A como B llaman a mylang.toolchain(name="foo"), ambos intentarán crear un repositorio llamado foo dentro del módulo mylang, y se producirá un error.

Para evitar esto, quita la capacidad de establecer el nombre del repositorio de forma directa o solo permite que lo haga el módulo raíz. No hay problema en permitir esta función al módulo raíz, ya que nada dependerá de ella y no deberás preocuparte de que otro módulo cree un nombre en conflicto.