Las extensiones de módulo permiten a los usuarios extender el sistema de módulos leyendo datos de entrada de módulos en el gráfico de dependencias, realizando la lógica necesaria para resolver dependencias y, por último, creando repositorios con llamadas a reglas de repositorio. Estas extensiones tienen capacidades similares a las reglas de 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 compilado a partir de los módulos de Bazel.
Puedes definir extensiones de módulo en archivos .bzl
, al igual que las reglas del repositorio. No se invocan directamente. En su lugar, cada módulo especifica fragmentos de datos llamados etiquetas para que lean las extensiones. Bazel ejecuta la resolución del módulo antes de evaluar las extensiones. La extensión lee todas las etiquetas que le pertenecen en todo el gráfico de dependencia.
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 ponerla 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 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 especificadas en la
definición de la extensión. Para ver un ejemplo en el que se especifican algunas etiquetas maven.install
y maven.artifact
, haz lo siguiente:
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 incluir los repositorios
generados por 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 correctamente etiquetas como @maven//:org_junit_junit
para que apunten al repositorio que generó la extensión "maven".
Definición de la extensión
Puedes definir extensiones de módulo de manera similar a las reglas del repositorio con la función module_extension
. Sin embargo, aunque las reglas de repositorio tienen varios atributos, las extensiones de módulo 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},
)
En estas declaraciones, se muestra que las etiquetas maven.install
y maven.artifact
se pueden especificar con el esquema de atributo especificado.
La función de implementación de las extensiones de módulo es similar a la de las reglas de repositorio, con la excepción de 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 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 con 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 brinda una identidad nueva
y, si se usan ambas versiones de la extensión en el gráfico del módulo transitivo,
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 solo usen la extensión del
módulo desde un único archivo .bzl
.
Nombres y visibilidad de repositorios
Los repositorios generados por 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 en la que debas confiar, 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 arriesgarse
a ningún conflicto. También significa que repository_ctx.name
informa el nombre canónico del repositorio, que no es el mismo que el que se especificó en la llamada a la regla del repositorio.
Si se tienen en cuenta los repositorios generados por extensiones de módulo, existen varias reglas de visibilidad del repositorio:
- Un repositorio de módulos de Bazel puede ver todos los repositorios ingresados 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 del módulo (con los nombres especificados en las llamadas de 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
,foo
hace referencia al primero para todos los repositorios generados por esa extensión.
- 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, se puedan mantener y se adapten bien a los cambios con el tiempo.
Colocar cada extensión en un archivo separado
Cuando las extensiones se encuentran en archivos diferentes, una extensión puede cargar los repositorios generados por otra extensión. Incluso si no usas esta funcionalidad, es mejor colocarlos en archivos separados en caso de que necesites más adelante. Esto se debe a que la identificación de la extensión se basa en su archivo, por lo que mover la extensión a otro archivo luego cambia tu API pública y es un cambio incompatible con versiones anteriores para los usuarios.
Especifica el sistema operativo y la arquitectura
Si tu extensión depende del 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 reevaluación si hay cambios en cualquiera de ellas.
Solo el módulo raíz debería afectar directamente los nombres del repositorio
Recuerda que, cuando una extensión crea repositorios, estos se crean dentro
del espacio de nombres de la extensión. Esto significa que pueden producirse colisiones si diferentes
módulos usan la misma extensión y terminan creando un repositorio con el mismo
nombre. A menudo, esto se manifiesta como el tag_class
de una extensión de módulo con 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 configurar el nombre del repositorio directamente o permite que solo lo haga el módulo raíz. Se puede permitir esta capacidad al módulo raíz, ya que nada dependerá de ella, por lo que no tiene que preocuparse de que otro módulo cree un nombre en conflicto.