Las extensiones de módulos permiten a los usuarios extender el sistema de módulos leyendo datos de entrada de módulos en todo el gráfico de dependencias, realizando la lógica necesaria para resolver dependencias y, finalmente, creando repositorios llamando a reglas de repositorios. Estas extensiones tienen capacidades similares a las reglas del repositorio, lo que les permite realizar 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 creado a partir de los módulos de Bazel.
Puedes definir extensiones de módulos en archivos .bzl
, al igual que las reglas del repositorio. No se invocan directamente, sino que cada módulo especifica fragmentos de datos llamados etiquetas para que las extensiones los lean. Bazel ejecuta la resolución de módulos antes de evaluar cualquier extensión. La extensión lee todas las etiquetas que le pertenecen en todo el gráfico de dependencia.
Uso de la extensión
Las extensiones se alojan en los propios 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 que esté dentro del alcance. 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 de retorno de use_extension
a una variable, lo que permite que el usuario use la sintaxis de puntos para especificar etiquetas para la extensión. Las etiquetas deben seguir el esquema definido por las correspondientes clases de etiquetas especificadas en la definición de la extensión. A continuación, se muestra 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 generados por la extensión al alcance del módulo actual.
use_repo(maven, "maven")
Los repositorios generados por una extensión forman parte de su API. En este ejemplo, la extensión del módulo "maven" promete generar un repo llamado maven
. Con la declaración anterior, la extensión resuelve correctamente las etiquetas, como @maven//:org_junit_junit
, para que apunten al repo generado por la extensión "maven".
Definición de la extensión
Puedes definir extensiones de módulos de manera similar a las reglas de repositorios, con la función module_extension
. Sin embargo, mientras que las reglas del repositorio tienen varios atributos, las extensiones de módulos tienen tag_class
s, 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 con el esquema de atributos especificado.
Las funciones de implementación de las extensiones de módulos son similares a las de las reglas de repositorios, excepto que obtienen un objeto module_ctx
, que otorga acceso a todos los módulos que usan la extensión y a todas las etiquetas pertinentes.
Luego, la función de implementación llama a las reglas del repo para generar repos.
# @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ódulos 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")
Si vuelves a exportar una extensión desde un archivo .bzl
diferente, esta tendrá una nueva identidad. Si ambas versiones de la extensión se usan en el gráfico de módulos transitivo, se evaluarán por separado y solo verán las etiquetas asociadas con esa identidad en particular.
Como autor de una extensión, debes asegurarte de que los usuarios solo usen tu extensión de módulo desde un solo archivo .bzl
.
Nombres y visibilidad de los repositorios
Los repositorios generados por extensiones tienen nombres canónicos con el formato module_repo_canonical_name~extension_name~repo_name
. En el caso de las extensiones alojadas en el módulo raíz, la parte module_repo_canonical_name
se reemplaza por la cadena _main
. Ten en cuenta que el formato de nombre canónico no es una API de la que debas depender, ya que está sujeto a cambios en cualquier momento.
Esta política de nomenclatura significa que cada extensión tiene su propio "espacio de nombres del repo"; dos extensiones distintas pueden definir cada una un repo con el mismo nombre sin riesgo de conflictos. También significa que repository_ctx.name
informa el nombre canónico del repo, que no es el mismo que el nombre especificado en la llamada a la regla del repo.
Teniendo en cuenta los repositorios generados por las extensiones de módulos, existen varias reglas de visibilidad de repositorios:
- Un repo de módulos de Bazel puede ver todos los repos introducidos en su archivo
MODULE.bazel
a través debazel_dep
yuse_repo
. - Un repo generado por una extensión de módulo puede ver todos los repos visibles para el módulo que aloja la extensión, además de todos los demás repos generados por la misma extensión de módulo (con los nombres especificados en las llamadas a la regla del repo como sus nombres aparentes).
- Esto podría generar un conflicto. Si el repo del módulo puede ver un repo con el nombre aparente
foo
y la extensión genera un repo con el nombre especificadofoo
, entonces, para todos los repos generados por esa extensión,foo
hace referencia al primero.
- Esto podría generar un conflicto. Si el repo del módulo puede ver un repo con el nombre aparente
Prácticas recomendadas
En esta sección, se describen las prácticas recomendadas para escribir extensiones que sean fáciles de usar y mantener, y que se adapten bien a los cambios con el tiempo.
Coloca cada extensión en un archivo separado
Cuando las extensiones se encuentran en archivos diferentes, una extensión puede cargar repositorios generados por otra. Incluso si no usas esta función, es mejor que los coloques en archivos separados en caso de que los 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 adelante 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 depende del sistema operativo o de 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 volver a evaluar si hay cambios en cualquiera de ellos.
Solo el módulo raíz debe 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 terminan creando un repositorio con el mismo nombre. Esto suele manifestarse como un tag_class
de la extensión del 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 directamente o permite que solo el módulo raíz lo haga. Está bien permitir que el módulo raíz tenga esta capacidad porque nada dependerá de él, por lo que no tiene que preocuparse por que otro módulo cree un nombre en conflicto.