Las extensiones de módulos permiten a los usuarios extender el sistema de módulos leyendo datos de entrada de los módulos en el gráfico de dependencias, realizando la lógica necesaria para resolver las dependencias y, por último, llamando a reglas de repositorios para crear repositorios. Estas extensiones tienen capacidades similares a las reglas de repo, 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, respete el gráfico de dependencias creado a partir de módulos de Bazel.
Puedes definir extensiones de módulo en archivos .bzl
, al igual que las reglas de repo. No se invocan directamente, sino que cada módulo especifica 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 dependencias.
Uso de extensiones
Las extensiones se alojan en los 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 en el 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 que se muestra de use_extension
a una variable, lo que le permite al
usuario usar la sintaxis de punto para especificar etiquetas para la extensión. Las etiquetas deben seguir el esquema definido por las clases de etiquetas correspondientes 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 llevar los repositorios que genera la extensión al 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 correctamente etiquetas como @maven//:org_junit_junit
para apuntar 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 repo con la función module_extension
. Sin embargo, mientras que las reglas de repo tienen una serie de atributos, las extensiones de módulo tienen tag_class
, cada una de las cuales tiene una serie de atributos. Las clases de etiquetas definen los esquemas para las etiquetas que usa esta extensión. Por ejemplo, la extensión "maven" anterior se podría definir 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.
La función de implementación de las extensiones de módulos es similar a la de las reglas de repo, 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 de 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, el archivo .bzl
@rules_jvm_external//:extension.bzl
y el nombre maven
identifican la extensión maven
:
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
Si vuelves a exportar una extensión desde un archivo .bzl
diferente, se le asigna una identidad nueva y, si se usan ambas versiones de la extensión en el gráfico de módulos transitivos, se evaluarán por separado y solo se verán las etiquetas asociadas con esa identidad en particular.
Como autor de la 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 que generan las 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 nombres significa 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 que haya 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 a la regla del repositorio.
Si tenemos en cuenta los repositorios generados por las extensiones de módulos, existen varias reglas de visibilidad de repositorios:
- Un repositorio de módulos de Bazel puede ver todos los repositorios introducidos en su archivo
MODULE.bazel
a través debazel_dep
yuse_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 a la regla del repositorio como sus nombres aparentes).
- Esto podría generar un conflicto. 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 especificadofoo
, para todos los repositorios que genera esa extensión,foo
hace referencia al primero.
- Esto podría generar un conflicto. Si el repositorio del módulo puede ver un repositorio con el nombre aparente
Prácticas recomendadas
En esta sección, se describen las prácticas recomendadas para escribir extensiones de modo que sean fáciles de usar, fáciles de mantener y se adapten bien a los cambios con el tiempo.
Coloca cada extensión en un archivo independiente
Cuando las extensiones están en archivos diferentes, una extensión puede cargar repositorios generados por otra extensión. Incluso si no usas esta funcionalidad, es mejor colocarlos 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 cambiará tu API pública y será un cambio incompatible con versiones anteriores para tus usuarios.