Un módulo de Bazel es un proyecto de Bazel que puede tener varias versiones, y cada una publica metadatos sobre otros módulos de los que depende. Esto es similar a conceptos conocidos en otros sistemas de administración de dependencias, como un artefacto de Maven, un paquete de npm, un módulo de Go o un contenedor de Cargo.
Un módulo debe tener un archivo MODULE.bazel
en la raíz del repositorio (junto al archivo WORKSPACE
). Este archivo es el manifiesto del módulo y declara su nombre, versión, lista de dependencias directas y otra información. A continuación, se muestra 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")
Para resolver el módulo, Bazel comienza por leer el archivo MODULE.bazel
del módulo raíz y, luego, solicita repetidamente el archivo MODULE.bazel
de cualquier dependencia desde un registro de Bazel hasta que descubre el gráfico de dependencias completo.
Luego, de forma predeterminada, Bazel selecciona una versión de cada módulo para usar. Bazel representa cada módulo con un repositorio y vuelve a consultar el registro para aprender a definir cada uno de los repositorios.
Formato de la versión
Bazel tiene un ecosistema diverso, y los proyectos usan varios esquemas de control de versiones. La más popular es, por mucho, SemVer, pero también hay proyectos importantes que usan diferentes esquemas, como Abseil, cuyas versiones están basadas en fechas, como 20210324.2
.
Por este motivo, Bzlmod adopta una versión más relajada de la especificación de SemVer. Las diferencias incluyen las siguientes:
- SemVer establece que la parte de “actualización” de la versión debe tener 3 segmentos:
MAJOR.MINOR.PATCH
. En Bazel, este requisito se flexibiliza para que se permita cualquier cantidad de segmentos. - En SemVer, cada uno de los segmentos de la parte “release” debe contener solo dígitos. En Bazel, esto se flexibiliza para permitir letras también, y la semántica de comparación coincide con los "identificadores" de la parte de "prelanzamiento".
- Además, no se aplica la semántica de los aumentos de versión mayor, menor y de parche. Sin embargo, consulta el nivel de compatibilidad para obtener 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
versiones a
y b
de SemVer comparan a < b
si y solo si se mantiene igual cuando
se comparan con las versiones del módulo de Bazel.
Seleccionar versión
Considera el problema de la dependencia de Diamante, un elemento básico en el espacio de administración de dependencias con control de versiones. Supongamos que tienes el gráfico de dependencia:
A 1.0
/ \
B 1.0 C 1.1
| |
D 1.0 D 1.1
¿Qué versión de D
debería usar? Para resolver esta pregunta, Bzlmod usa el algoritmo de Selección de versión mínima (MVS) que se introdujo en el sistema del módulo de Go. MVS supone que todas las versiones nuevas de un módulo son retrocompatibles, por lo que elige la versión más alta especificada por cualquier dependiente (D 1.1
en nuestro ejemplo). Se denomina "mínima" porque D 1.1
es la primera versión que podría satisfacer nuestros requisitos; no las seleccionamos, incluso si existe D 1.2
o una versión más reciente. El uso de MVS crea un proceso de selección de versión que es de alta fidelidad y reproducible.
Versiones yankadas
El registro puede declarar ciertas versiones como anuladas si deben evitarse (por ejemplo, para vulnerabilidades de seguridad). Bazel muestra un error cuando se selecciona una versión transferida de un módulo. Para corregir este error, actualiza a una versión más reciente y no recuperada o usa la marca --allow_yanked_versions
para permitir explícitamente la versión.
Nivel de compatibilidad
En Go, la suposición de MVS sobre la retrocompatibilidad funciona porque trata las versiones incompatibles 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 dependencias resuelto. A su vez, esto es posible mediante la codificación de la versión principal en la ruta del paquete en Go, de modo que no haya conflictos entre el tiempo de compilación y la vinculación.
Sin embargo, Bazel no puede proporcionar esas garantías, por lo que necesita el número de "versión principal"
para detectar versiones incompatibles con versiones anteriores. Este número se denomina nivel de compatibilidad y se especifica por cada versión del módulo en su directiva module()
. Con esta información, Bazel puede arrojar un error cuando detecta que existen versiones del mismo módulo con diferentes niveles de compatibilidad en el gráfico de dependencias resuelto.
Anula
Especifica anulaciones en el archivo MODULE.bazel
para alterar el comportamiento de la resolución del módulo
de Bazel. Solo se aplican las anulaciones del módulo raíz; si se usa un módulo como dependencia, se ignoran sus anulaciones.
Cada anulación se especifica para un nombre de módulo determinado, lo que afecta a todas sus versiones en el gráfico de dependencia. Aunque solo se aplican las anulaciones del módulo raíz, pueden ser para dependencias transitivas de las que el módulo raíz no depende directamente.
Anulación de una sola versión
single_version_override
cumple varios propósitos:
- Con el atributo
version
, puedes fijar una dependencia a una versión específica, sin importar qué versiones de la dependencia se soliciten en el gráfico de dependencias. - Con el atributo
registry
, puedes forzar que esta dependencia provenga de un registro específico, en lugar de seguir el proceso normal de selección de registros. - Con los atributos
patch*
, puedes especificar un conjunto de parches para aplicar al módulo descargado.
Todos estos atributos son opcionales y se pueden combinar y combinar.
Anulación de varias versiones
Se puede especificar un multiple_version_override
para permitir que coexistan varias versiones del mismo módulo en el gráfico de dependencias resuelto.
Puedes especificar una lista explícita de las versiones permitidas del módulo, que deben estar presentes en el gráfico de dependencias antes de la resolución. Debe existir alguna dependencia transitiva dependiendo de cada versión permitida. Después de la resolución, solo permanecen las versiones permitidas del módulo, mientras que Bazel actualiza otras versiones del módulo a la versión permitida superior más cercana con el mismo nivel de compatibilidad. Si no existe una versión permitida más alta con el mismo nivel de compatibilidad, Bazel muestra un error.
Por ejemplo, si las versiones 1.1
, 1.3
, 1.5
, 1.7
y 2.0
existen en el gráfico de dependencias antes de la resolución y la versión principal es el nivel de compatibilidad, haz lo siguiente:
- Una anulación de varias versiones que permite
1.3
,1.7
y2.0
hace que1.1
se actualice a1.3
,1.5
se actualice a1.7
y que otras versiones permanezcan iguales. - Una anulación de varias versiones que permite
1.5
y2.0
genera un error, ya que1.7
no tiene una versión posterior con el mismo nivel de compatibilidad para actualizar. - Una anulación de varias versiones que permite
1.9
y2.0
genera un error, ya que1.9
no está presente en el gráfico de dependencias antes de la resolución.
Además, los usuarios también pueden anular el registro con el atributo registry
, de manera similar a las anulaciones de versión única.
Anulaciones sin registro
Las anulaciones que no son de registro quitan por completo un módulo de la resolución de la versión. Bazel
no solicita estos archivos MODULE.bazel
de un registro, sino del
repositorio.
Bazel admite las siguientes anulaciones que no son de registro:
Nombres de repositorios y dependencias estrictas
El nombre canónico de un repositorio que respalda un módulo es module_name~version
(por ejemplo, bazel_skylib~1.0.3
). Para los módulos con una anulación que no sea de registro, reemplaza la parte version
por la string override
. Ten en cuenta que el formato del nombre canónico no es una API en la que debas depender y está sujeto a cambios en cualquier momento.
El nombre aparente de un repositorio que respalda un módulo ante sus dependientes directas se establece de forma predeterminada con su nombre de módulo, a menos que el atributo repo_name
de la directiva bazel_dep
indique lo contrario. Ten en cuenta que esto significa que un módulo solo puede encontrar sus dependencias directas. Esto ayuda a evitar fallas accidentales debido a cambios en las dependencias transitivas.
Las extensiones del módulo también pueden agregar repositorios adicionales en el alcance visible de un módulo.