Bzlmod es el nombre interno del nuevo sistema de dependencias externas. que se introdujo en Bazel 5.0. Se introdujo para abordar varios puntos débiles del un sistema antiguo que no se podía arreglar de forma incremental; consulta la Sección Planteamiento del problema del documento de diseño original para obtener más información.
En Bazel 5.0, Bzlmod no está activado de forma predeterminada. la bandera
Debes especificar --experimental_enable_bzlmod
para que se complete lo siguiente
efecto. Tal como lo sugiere el nombre de la marca, esta función es actualmente experimental.
Las APIs y los comportamientos pueden cambiar hasta el lanzamiento oficial de la función.
Para migrar tu proyecto a Bzlmod, sigue la guía de migración de Bzlmod. También puedes encontrar ejemplos de usos de Bzlmod en el repositorio de ejemplos.
Módulos de Bazel
El antiguo sistema de dependencia externo basado en WORKSPACE
se centra en
repositories (o repos), creados mediante reglas de repositorio (o reglas de repositorio)
Si bien los repositorios siguen siendo un concepto importante en el sistema nuevo, los módulos son
las unidades centrales de dependencia.
En esencia, un módulo es un proyecto de Bazel que puede tener varias versiones, cada una que publica metadatos sobre otros módulos de los que depende. Este es análogo a los conceptos conocidos en otros sistemas de administración de dependencias: un artifact, un paquete de npm, un cajón de Cargo, un módulo de Go, etcétera.
Un módulo simplemente especifica sus dependencias con los pares name
y version
,
en lugar de URLs específicas en WORKSPACE
. Luego, se buscan las dependencias
un registro de Bazel de forma predeterminada,
Registro Central de Bazel. En tu lugar de trabajo, cada uno
y luego se convierte en un repositorio.
MODULE.bazel
Cada versión de cada módulo tiene un archivo MODULE.bazel
que declara su
las dependencias y otros metadatos. A continuación, te mostramos un ejemplo básico:
module(
name = "my-module",
version = "1.0",
)
bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")
El archivo MODULE.bazel
debe estar ubicado en la raíz del directorio del espacio de trabajo
(junto al archivo WORKSPACE
). A diferencia del archivo WORKSPACE
, no necesitas
para especificar tus dependencias transitivas; en cambio, solo debes especificar
dependencias directas, y los archivos MODULE.bazel
de tus dependencias se
se procesan para descubrir
dependencias transitivas automáticamente.
El archivo MODULE.bazel
es similar a los archivos BUILD
, ya que no admite ninguno
una forma de flujo de control; además prohíbe las sentencias load
. Las directivas
Características de los archivos MODULE.bazel
:
module
, para especificar los metadatos sobre el módulo actual, incluidos el nombre, la versión, etc.bazel_dep
, para especificar las dependencias en otros módulos de Bazel;- anulaciones, que solo las puede usar el módulo raíz (es decir, no un que se usa como dependencia) para personalizar el comportamiento de un una dependencia directa o transitiva determinada:
- Directivas relacionadas con las extensiones de módulos:
Formato de la versión
Bazel tiene un ecosistema diverso y los proyectos usan varios esquemas de control de versiones. El
el más popular por mucho es SemVer, pero hay
también destaca proyectos con diferentes esquemas,
Abseil, cuyo
se basan en fechas, por ejemplo, 20210324.2
).
Por este motivo, Bzlmod adopta una versión más relajada de la especificación de SemVer. El Estas son algunas de las diferencias:
- SemVer prescribe que la "liberación" parte de la versión debe constar de 3
segmentos:
MAJOR.MINOR.PATCH
. En Bazel, este requisito se flexibiliza, por lo que que se permite cualquier cantidad de segmentos. - En SemVer, cada uno de los segmentos del “lanzamiento” debe contener solo dígitos. En Bazel, esto se flexibiliza para permitir letras también, y la comparación la semántica coinciden con los “identificadores” en la sección “Versión preliminar” de una parte del proyecto.
- Además, la semántica de los aumentos mayores, secundarios y de versiones de parche son sin aplicar de manera forzosa. (Sin embargo, consulta el nivel de compatibilidad para detalles sobre cómo denotamos la retrocompatibilidad).
Cualquier versión válida de SemVer es una versión válida del módulo de Bazel. Además, dos
Las versiones a
y b
de SemVer comparan a < b
si son iguales
en comparación con las versiones de módulos de Bazel.
Resolución de la versión
El problema de la dependencia de diamante es un elemento básico de la dependencia con control de versiones de una organización. Supongamos que tienes el siguiente gráfico de dependencias:
A 1.0
/ \
B 1.0 C 1.1
| |
D 1.0 D 1.1
¿Qué versión de D se debería usar? Para resolver esta pregunta, Bzlmod usa la Selección de versión mínima (MVS) en el sistema de módulos de Go. MVS asume que todos los datos de un módulo son retrocompatibles y, por lo tanto, simplemente elige la más alta especificada por cualquier dependiente (D 1.1 en nuestro ejemplo). Se llama “mínimo” porque D 1.1 es la versión mínima que podría satisfacer nuestros requisitos. incluso si existe D 1.2 o posterior, no los seleccionamos. Esto tiene el beneficio adicional que la selección de versión sea de alta fidelidad y reproducible.
La resolución de la versión se realiza de forma local en tu máquina, no en el registro.
Nivel de compatibilidad
Ten en cuenta que la suposición de MVS sobre la retrocompatibilidad es factible porque simplemente trata las versiones incompatibles con versiones anteriores de un módulo como un módulo independiente. En términos de SemVer, eso significa que A 1.x y A 2.x se consideran módulos distintos. y pueden coexistir en el gráfico de dependencia resuelto. Esto, a su vez, posible por el hecho de que la versión principal está codificada en la ruta del paquete en Así que no haya conflictos en el tiempo de compilación ni de vinculación.
En Bazel, no tenemos esas garantías. Por eso necesitamos una forma de denotar
versión" para detectar versiones incompatibles con versiones anteriores. Este número
se denomina nivel de compatibilidad y se especifica en la versión de cada módulo
su directiva module()
. Con esta información a mano, podemos arrojar un error
cuando detectamos versiones del mismo módulo con diferentes
niveles existentes en el gráfico de dependencias resuelto.
Nombres de repositorios
En Bazel, cada dependencia externa tiene un nombre de repositorio. A veces, el mismo
por medio de diferentes nombres de repositorio (por ejemplo, tanto
@io_bazel_skylib
y @bazel_skylib
significan
Bazel skylib), o el mismo
el nombre del repositorio podría usarse
para diferentes dependencias en proyectos distintos.
En Bzlmod, los repositorios se pueden generar con módulos de Bazel y extensiones del módulo. Para resolver conflictos de nombres de repositorios, Implementamos la asignación de repositorios un mecanismo de control en el nuevo sistema. Estos son dos conceptos importantes:
Nombre del repositorio canónico: Es el nombre del repositorio único a nivel global para cada uno. en un repositorio de confianza. Este será el nombre del directorio donde reside el repositorio.
Está construida de la siguiente manera (Advertencia: El formato de nombre canónico es no es una API en la que deberías confiar, está sujeta a cambios en cualquier momento):- Para los repositorios del módulo de Bazel:
module_name~version
(Ejemplo.@bazel_skylib~1.0.3
) - Para los repositorios de extensiones de módulo:
module_name~version~extension_name~repo_name
(Ejemplo:@rules_cc~0.0.1~cc_configure~local_config_cc
)
- Para los repositorios del módulo de Bazel:
Nombre del repositorio aparente: Es el nombre del repositorio que se usará en
BUILD
y.bzl
archivos en un repositorio. La misma dependencia puede tener distintos comportamientos en diferentes repositorios.
Se determina de la siguiente manera:
Cada repositorio tiene un diccionario de asignación de repositorios de sus dependencias directas,
que es un mapa desde el nombre aparente del repositorio hasta el nombre del repositorio canónico.
Usamos la asignación del repositorio para resolver el nombre del repositorio cuando construimos un
etiqueta. Ten en cuenta que no se generan conflictos con los nombres de repositorios canónicos.
Los usos de nombres de repositorio aparentes se pueden descubrir analizando MODULE.bazel
.
Así, los conflictos se pueden detectar y resolver fácilmente sin afectarse
otras dependencias.
Dependencias estrictas
El nuevo formato de especificación de dependencias nos permite realizar verificaciones más estrictas. En particular, ahora establecemos que un módulo solo puede usar repositorios creados a partir de su las dependencias directas. Esto ayuda a evitar fallas accidentales y difíciles de depurar cuando cambia algo en el gráfico de dependencia transitivo.
Las dependencias estrictas se implementan según asignación de repositorios. Básicamente, el la asignación de repositorios para cada repositorio contiene todas sus dependencias directas, cualquier otro repositorio no es visible. Las dependencias visibles para cada repositorio determina de la siguiente manera:
- Un repositorio de módulos de Bazel puede ver todos los repositorios ingresados en el archivo
MODULE.bazel
. a través debazel_dep
yuse_repo
- Un repositorio de extensión de módulo puede ver todas las dependencias visibles del módulo que proporciona la extensión, además de todos los demás repositorios generados por el mismo módulo. .
Registros
Para descubrir dependencias, Bzlmod solicita su información a Bazel registros. Un registro de Bazel es simplemente una base de datos de módulos de Bazel. El único una forma de registros admitida es un registro de índices, que es un directorio local o un servidor HTTP estático según un formato específico. En la en el futuro, planeamos agregar compatibilidad con los registros de un solo módulo, que son Repositorios de Git que contienen la fuente y el historial de un proyecto
Registro de índices
Un registro de índice es un directorio local o un servidor HTTP estático que contiene
información sobre una lista de módulos, como su página principal, los encargados de mantenimiento, los
MODULE.bazel
de cada versión y cómo recuperar la fuente de cada una
versión. En particular, no necesita entregar los archivos de origen en sí.
Un registro de índices debe seguir el siguiente formato:
/bazel_registry.json
: Es un archivo JSON que contiene metadatos para el registro, como el siguiente:mirrors
, que especifica la lista de duplicaciones que se usarán para los archivos de origen.module_base_path
, especificando la ruta base para los módulos con Escribelocal_repository
en el archivosource.json
.
/modules
: Es un directorio que contiene un subdirectorio para cada módulo de este. registro./modules/$MODULE
: Es un directorio que contiene un subdirectorio para cada versión. de este módulo, así como del siguiente archivo:metadata.json
: Es un archivo JSON que contiene información sobre el módulo. con los siguientes campos:homepage
: La URL de la página principal del proyecto.maintainers
: Una lista de objetos JSON, cada uno de los cuales corresponde a lo siguiente: la información de un encargado de mantenimiento del módulo en el registro Ten en cuenta que no es necesariamente lo mismo que los autores de la en un proyecto final.versions
: Es una lista de todas las versiones de este módulo que se encuentran en este registro.yanked_versions
: Es una lista de versiones ya realizadas de este módulo. Esta actualmente es una no-op, pero en el futuro, las versiones ya creadas se omitió o generará un error.
/modules/$MODULE/$VERSION
: Un directorio que contiene los siguientes archivos:MODULE.bazel
: Es el archivoMODULE.bazel
de la versión de este módulo.source.json
: Es un archivo JSON que contiene información para recuperar el de esta versión del módulo.- El tipo predeterminado es "archivar" con los siguientes campos:
url
: Es la URL del archivo de origen.integrity
: El Integridad de los subrecursos de comprobación del archivo.strip_prefix
: Es un prefijo de directorio que se quitará cuando se extraiga el elemento. archivo fuente.patches
: Una lista de cadenas, cada una de las cuales nombra un archivo de parche al se aplica al archivo extraído. Los archivos de parche se encuentran en el directorio/modules/$MODULE/$VERSION/patches
.patch_strip
: Igual que el argumento--strip
del parche de Unix.
- Se puede cambiar el tipo para usar una ruta local con estos campos:
type
:local_path
path
: Es la ruta local al repositorio, que se calcula de la siguiente manera:- Si la ruta es una ruta de acceso absoluta, se usará tal como está.
- Si la ruta de acceso es relativa y
module_base_path
es una ruta absoluta, la ruta se resuelve a<module_base_path>/<path>
- Si ruta y
module_base_path
son rutas de acceso relativas, la ruta es se resolvió a<registry_path>/<module_base_path>/<path>
. El registro debe estar alojado de forma local, y--registry=file://<registry_path>
debe usarlo. De lo contrario, Bazel mostrará un error.
- El tipo predeterminado es "archivar" con los siguientes campos:
patches/
: Es un directorio opcional que contiene archivos de parche; solo se usa cuandosource.json
tiene "file". el tipo de letra.
Registro central de Bazel
Bazel Central Registry (BCR) es un registro de índices ubicado en
bcr.bazel.build. Su contenido
están respaldados por el repositorio de GitHub
bazelbuild/bazel-central-registry
La comunidad de Bazel mantiene el BCR. los colaboradores pueden enviar las solicitudes de extracción. Consulta Políticas y procedimientos del Registro Central de Bazel.
Además de seguir el formato de un registro de índice normal, la BCR requiere
un archivo presubmit.yml
para cada versión del módulo
(/modules/$MODULE/$VERSION/presubmit.yml
) Este archivo especifica algunos elementos
crear y probar objetivos que se pueden usar para comprobar la validez de este
y la usan las canalizaciones de CI de BCR para garantizar la interoperabilidad
entre los módulos en el BCR.
Selecciona registros
Se puede usar la marca repetible de Bazel --registry
para especificar la lista de
registros a los que solicitar módulos, de modo que puedas configurar tu proyecto para recuperar
las dependencias de un registro
interno o de terceros. Los registros anteriores toman
prioridad. Para mayor comodidad, puedes incluir una lista de marcas --registry
en el
.bazelrc
archivo de tu proyecto.
Extensiones de módulo
Las extensiones de módulo te permiten 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
las dependencias y, por último, la creación de repositorios llamando a las reglas de repositorio. Son similares
en función a las macros WORKSPACE
actuales, pero son más adecuadas en el mundo de
módulos y dependencias transitivas.
Las extensiones de módulo se definen en archivos .bzl
, al igual que las reglas del repositorio o
Macros de WORKSPACE
. No se invocan de forma directa. sino que cada módulo
especificar fragmentos de datos llamados etiquetas para que lean las extensiones. Luego, después del módulo
se completa la resolución de la versión, se ejecutan las extensiones del módulo. Cada extensión se ejecuta
una vez después de la resolución del módulo (aún antes de que se produzca una compilación) y
lee todas las etiquetas que le pertenecen en todo el gráfico de la dependencia.
[ A 1.1 ]
[ * maven.dep(X 2.1) ]
[ * maven.pom(...) ]
/ \
bazel_dep / \ bazel_dep
/ \
[ B 1.2 ] [ C 1.0 ]
[ * maven.dep(X 1.2) ] [ * maven.dep(X 2.1) ]
[ * maven.dep(Y 1.3) ] [ * cargo.dep(P 1.1) ]
\ /
bazel_dep \ / bazel_dep
\ /
[ D 1.4 ]
[ * maven.dep(Z 1.4) ]
[ * cargo.dep(Q 1.1) ]
En el gráfico de dependencias de ejemplo anterior, A 1.1
y B 1.2
, etc., son módulos de Bazel.
puedes considerar a cada uno como un archivo MODULE.bazel
. Cada módulo puede especificar algunos
etiquetas para extensiones de módulo; aquí se especifican algunas para la extensión "maven",
y otras están especificadas para “cargo”. Cuando se finaliza este gráfico de dependencia (para
Por ejemplo, tal vez B 1.2
realmente tenga un bazel_dep
en D 1.3
, pero se actualizó a
D 1.4
debido a C
), la extensión "maven" se ejecuta y lee todas las
Etiquetas maven.*
, que usan su información para decidir qué repositorios crear
Lo mismo ocurre con el "cargo" .
Uso de extensiones
Las extensiones se alojan en módulos de Bazel. Usar una extensión en
tu módulo, primero debes agregar un bazel_dep
en ese módulo y, luego, llamar
el use_extension
integrado
para ponerlo dentro del alcance. Considera el siguiente ejemplo, un fragmento de
un archivo MODULE.bazel
para usar un “maven” hipotético de Compute Engine, como se define en el
Módulo rules_jvm_external
:
bazel_dep(name = "rules_jvm_external", version = "1.0")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
Después de definir el alcance de la extensión, puedes usar la sintaxis de puntos para
especificar etiquetas para ella. Ten en cuenta que las etiquetas deben seguir el esquema definido por el
clases de etiquetas correspondientes (consulta la definición de extensión)
a continuación). A continuación, se muestra un ejemplo en el que se especifican algunas etiquetas maven.dep
y maven.pom
.
maven.dep(coord="org.junit:junit:3.0")
maven.dep(coord="com.google.guava:guava:1.2")
maven.pom(pom_xml="//:pom.xml")
Si la extensión genera repositorios que quieres usar en tu módulo, usa el
Directiva use_repo
para declarar
de ellos. Esto se hace para cumplir con la condición de dependencias estrictas y evitar el nombre del repositorio local
conflicto.
use_repo(
maven,
"org_junit_junit",
guava="com_google_guava_guava",
)
Los repositorios que genera una extensión forman parte de su API, así que, a partir de las etiquetas
especificado, debes saber que el término "maven" la extensión generará un
llamado “org_junit_junit” y otro llamado “com_google_guava_guava”. Con
use_repo
. De manera opcional, puedes cambiarles el nombre dentro del alcance de tu módulo, por ejemplo:
“guayaba” aquí.
Definición de la extensión
Las extensiones de módulo se definen de manera similar a las reglas de repo, con las
función module_extension
.
Ambos tienen una función de implementación. pero, aunque las reglas de repo tienen
atributos, las extensiones de módulo tienen varios
tag_class
, cada uno de los cuales tiene un
la cantidad de atributos. Las clases de etiquetas definen esquemas para las etiquetas que esta usa
. Continuando con nuestro ejemplo del hipotético “maven” extensión anterior:
# @rules_jvm_external//:extensions.bzl
maven_dep = tag_class(attrs = {"coord": attr.string()})
maven_pom = tag_class(attrs = {"pom_xml": attr.label()})
maven = module_extension(
implementation=_maven_impl,
tag_classes={"dep": maven_dep, "pom": maven_pom},
)
Estas declaraciones dejan claro que las etiquetas maven.dep
y maven.pom
se pueden
especificado con el esquema de atributos definido anteriormente.
La función de implementación es similar a una macro WORKSPACE
, excepto que
obtiene un objeto module_ctx
, que otorga
acceso al gráfico de la dependencia y a todas las etiquetas pertinentes. La implementación
debería llamar a las reglas de repo para generar repositorios:
# @rules_jvm_external//:extensions.bzl
load("//:repo_rules.bzl", "maven_single_jar")
def _maven_impl(ctx):
coords = []
for mod in ctx.modules:
coords += [dep.coord for dep in mod.tags.dep]
output = ctx.execute(["coursier", "resolve", coords]) # hypothetical call
repo_attrs = process_coursier(output)
[maven_single_jar(**attrs) for attrs in repo_attrs]
En el ejemplo anterior, revisamos todos los módulos en el gráfico de dependencias.
(ctx.modules
), cada uno de los cuales es un
Un objeto bazel_module
cuyo campo tags
expone todas las etiquetas maven.*
en el módulo. Luego, invocamos la utilidad de la CLI
Solicita comunicarte con Maven y realizar la resolución. Por último, usamos la resolución
para crear varios repositorios usando el método maven_single_jar
repo.