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 las reglas de repo. 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, respeten 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 de repo. No se invocan directamente. En cambio, 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 dependencias.
Uso de extensiones
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 incluirla en el alcance. Considera el siguiente ejemplo: un fragmento de un archivo
MODULE.bazel para usar la extensión "maven" definida en el
rules_jvm_external
módulo:
bazel_dep(name = "rules_jvm_external", version = "4.5")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
Esto vincula el valor que muestra 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. Por ejemplo, para especificar algunas
maven.install y maven.artifact etiquetas, 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 en el 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
"maven" extensión de módulo promete generar un repositorio llamado maven. Con la
declaración anterior, la extensión resuelve correctamente las etiquetas como
@maven//:org_junit_junit para que apunten al repositorio generado por la "maven"
extensión.
Definición de extensión
Puedes definir extensiones de módulos de manera similar a las reglas de repo,
con la module_extension
función. Sin embargo, mientras que las reglas de repo tienen varios atributos, las extensiones de módulos
tienen tag_classes, cada una de las 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.
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 a todas las etiquetas pertinentes.
Luego, la función de implementación llama a las reglas de repo 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ódulos se identifican por el nombre y el .bzl archivo que aparece
en la llamada a use_extension. En el siguiente ejemplo, la extensión maven
se identifica por 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, se le asignará una nueva identidad
Si se usan ambas versiones de la extensión 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 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 generados por extensiones tienen nombres canónicos con el formato module_repo_canonical_name+extension_name+repo_name. 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 de repositorio"; dos
extensiones distintas pueden definir un repositorio con el mismo nombre sin correr el riesgo de que se produzcan
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 de repo.
Si se tienen 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
MODULE.bazelarchivo a través debazel_depyuse_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 de repo como
sus nombres aparentes).
- Esto podría generar un conflicto. Si el repositorio de módulos puede ver un repositorio con
el nombre aparente
fooy la extensión genera un repositorio con el nombre especificadofoo, entonces, para todos los repositorios generados por esa extensión,foose refiere al primero.
- Esto podría generar un conflicto. Si el repositorio de módulos puede ver un repositorio con
el nombre aparente
- Del mismo modo, en la función de implementación de una extensión de módulo, los repositorios creados
por la extensión pueden hacer referencia entre sí por sus nombres aparentes en
los atributos, independientemente del orden en que se creen.
- En caso de conflicto con un repositorio visible para el módulo, las etiquetas
que se pasan a los atributos de la regla de repositorio se pueden incluir en una llamada a
Labelpara garantizar que hagan referencia a el repositorio visible para el módulo en lugar del repositorio generado por la extensión con el mismo nombre.
- En caso de conflicto con un repositorio visible para el módulo, las etiquetas
que se pasan a los atributos de la regla de repositorio se pueden incluir en una llamada a
Cómo anular e insertar repositorios de extensiones de módulos
El módulo raíz puede usar
override_repo y
inject_repo para anular o insertar
repositorios de extensiones de módulos.
Ejemplo: Reemplaza rules_java's java_tools por una copia vendida
# MODULE.bazel
local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository")
local_repository(
name = "my_java_tools",
path = "vendor/java_tools",
)
bazel_dep(name = "rules_java", version = "7.11.1")
java_toolchains = use_extension("@rules_java//java:extension.bzl", "toolchains")
override_repo(java_toolchains, remote_java_tools = "my_java_tools")
Ejemplo: Aplica un parche a una dependencia de Go para que dependa de @zlib en lugar de zlib del sistema
# MODULE.bazel
bazel_dep(name = "gazelle", version = "0.38.0")
bazel_dep(name = "zlib", version = "1.3.1.bcr.3")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
go_deps.module_override(
patches = [
"//patches:my_module_zlib.patch",
],
path = "example.com/my_module",
)
use_repo(go_deps, ...)
inject_repo(go_deps, "zlib")
# patches/my_module_zlib.patch
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,6 +1,6 @@
go_binary(
name = "my_module",
importpath = "example.com/my_module",
srcs = ["my_module.go"],
- copts = ["-lz"],
+ cdeps = ["@zlib"],
)
Prácticas recomendadas
En esta sección, se describen las prácticas recomendadas para escribir extensiones de modo que sean fáciles de usar y mantener, y se adapten bien a los cambios con el tiempo.
Coloca cada extensión en un archivo separado
Cuando las extensiones están en archivos diferentes, una extensión puede cargar repositorios generados por otra. Incluso si no usas esta funcionalidad, es mejor colocarlas en archivos separados en caso de que la 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 la reproducibilidad y usa hechos
Si tu extensión siempre define los mismos repositorios con las mismas entradas
(etiquetas de extensión, archivos que lee, etc.) y, en particular, no depende de
ninguna descarga que no esté protegida por
una suma de verificación, considera mostrar
extension_metadata con
reproducible = True. Esto permite que Bazel omita esta extensión cuando escribe en
el archivo de bloqueo MODULE.bazel, lo que ayuda a mantener el archivo de bloqueo pequeño y reduce
la posibilidad de conflictos de combinación. Ten en cuenta que Bazel aún almacena en caché los resultados de
extensiones reproducibles de una manera que persiste en los reinicios del servidor, por lo que incluso
una extensión de larga duración se puede marcar como reproducible sin una penalización de rendimiento.
Si tu extensión depende de datos efectivamente inmutables obtenidos fuera de
la compilación, por lo general, de la red, pero no tienes una suma de verificación
disponible para proteger la descarga, considera usar el facts parámetro de
extension_metadata para
registrar de forma persistente esos datos y, así, permitir que tu extensión se vuelva
reproducible. facts se espera que sea un diccionario con claves de cadena y
valores de Starlark arbitrarios similares a JSON que siempre persisten en el archivo de bloqueo y
están disponibles para evaluaciones futuras de la extensión a través del
facts campo de module_ctx.
facts no se invalidan incluso cuando cambia el código de la extensión de módulo,
por lo que debes prepararte para controlar el caso en el que cambia la estructura de facts.
Bazel también supone que dos diccionarios facts diferentes producidos por dos evaluaciones diferentes
de la misma extensión se pueden combinar de forma superficial (es decir, como si se usara
el operador | en dos diccionarios). Esto se aplica parcialmente porque module_ctx.facts
no admite la enumeración de sus entradas, solo las búsquedas por clave.
Un ejemplo de uso de facts sería registrar una asignación de números de versión de
algún SDK a un objeto que contenga la URL de descarga y la suma de verificación de esa
versión. La primera vez que se evalúa la extensión, puede recuperar esta asignación
de la red, pero en evaluaciones posteriores puede usar la asignación de facts
para evitar las solicitudes de red.
Especifica la dependencia del 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 alguno de ellos.
Dado que este tipo de dependencia del host dificulta el mantenimiento de la entrada del archivo de bloqueo para esta extensión, considera marcar la extensión como reproducible si es posible.
Solo el módulo raíz debe afectar directamente los nombres de los repositorios
Recuerda que, cuando una extensión crea repositorios, se crean dentro de
l 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 extensión de módulo que tiene un name
argumento que se pasa como el valor name de una regla de 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 A y 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 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.