Módulos de Bazel

Informar un problema Ver fuente Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

Un módulo de Bazel es un proyecto de Bazel que puede tener varias versiones, cada una de las cuales publica metadatos sobre otros módulos de los que depende. Esto es análogo 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 una crate de Cargo.

Un módulo debe tener un archivo MODULE.bazel en la raíz del repo (junto al archivo WORKSPACE). Este archivo es el manifiesto del módulo, en el que se declara su nombre, versión, lista de dependencias directas y otra información. Por ejemplo, para 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")

Consulta la lista completa de directivas disponibles en los archivos MODULE.bazel.

Para realizar la resolución de módulos, Bazel comienza leyendo 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 todo el gráfico de dependencias.

De forma predeterminada, Bazel selecciona una versión de cada módulo para usar. Bazel representa cada módulo con un repo y vuelve a consultar el registro para saber cómo definir cada uno de los repos.

Formato de versión

Bazel tiene un ecosistema diverso, y los proyectos usan varios esquemas de versiones. La más popular es SemVer, pero también hay proyectos destacados que usan otros esquemas, como Abseil, cuyas versiones se basan en la fecha (por ejemplo, 20210324.2).

Por este motivo, Bzlmod adopta una versión más flexible de la especificación de SemVer. Las diferencias incluyen lo siguiente:

  • SemVer indica que la parte de "versión" debe constar de 3 segmentos: MAJOR.MINOR.PATCH. En Bazel, este requisito se relaja para permitir cualquier cantidad de segmentos.
  • En SemVer, cada uno de los segmentos de la parte de "versión" debe contener solo dígitos. En Bazel, esto se relaja para permitir también letras, y la semántica de comparación coincide con los "identificadores" en la parte de "versión preliminar".
  • Además, no se aplican las semánticas de los aumentos de versiones principales, secundarias y de parches. Sin embargo, consulta nivel de compatibilidad para obtener detalles sobre cómo denotamos la retrocompatibilidad.

Cualquier versión de SemVer válida es una versión de módulo de Bazel válida. Además, dos versiones de SemVer a y b se comparan como a < b si y solo si se cumple la misma condición cuando se comparan como versiones de módulos de Bazel.

Selección de versión

Considera el problema de dependencia de diamante, un elemento básico en el espacio de administración de dependencias versionadas. 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 debe usar? Para resolver esta pregunta, Bzlmod usa el algoritmo de selección de versión mínima (MVS) que se introdujo en el sistema de módulos de Go. El MVS supone que todas las versiones nuevas de un módulo son compatibles con versiones anteriores, por lo que elige la versión más alta especificada por cualquier elemento dependiente (D 1.1 en nuestro ejemplo). Se llama "mínima" porque D 1.1 es la versión más antigua que podría satisfacer nuestros requisitos. Incluso si existe D 1.2 o una versión más reciente, no las seleccionamos. El uso de MVS crea un proceso de selección de versiones que es de alta fidelidad y reproducible.

Versiones retiradas

El registro puede declarar ciertas versiones como yanked si se deben evitar (por ejemplo, por vulnerabilidades de seguridad). Bazel arroja un error cuando se selecciona una versión anulada de un módulo. Para corregir este error, actualiza a una versión más reciente que no se haya retirado o usa la marca --allow_yanked_versions para permitir explícitamente la versión retirada.

Nivel de compatibilidad

En Go, la suposición de MVS sobre la retrocompatibilidad funciona porque 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 grafo de dependencias resuelto. A su vez, esto es posible porque la versión principal se codifica en la ruta del paquete en Go, por lo que no hay conflictos en el tiempo de compilación ni en el tiempo de 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 cada versión del módulo lo especifica 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 de módulos de Bazel. Solo tienen efecto las anulaciones del módulo raíz. Si un módulo se usa 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 dependencias. Si bien solo tienen efecto las anulaciones del módulo raíz, estas pueden ser para dependencias transitivas de las que el módulo raíz no depende directamente.

Anulación de una sola versión

El objeto single_version_override cumple varios propósitos:

  • Con el atributo version, puedes fijar una dependencia a una versión específica, independientemente de las versiones de la dependencia que 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 entre sí.

Anulación de varias versiones

Se puede especificar un multiple_version_override para permitir que varias versiones del mismo módulo coexistan en el gráfico de dependencias resuelto.

Puedes especificar una lista explícita de versiones permitidas para el módulo, las cuales deben estar presentes en el gráfico de dependencias antes de la resolución. Debe existir alguna dependencia transitiva que dependa 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 más alta más cercana con el mismo nivel de compatibilidad. Si no existe una versión permitida más alta en el mismo nivel de compatibilidad, Bazel arroja 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, se produce lo siguiente:

  • Una anulación de varias versiones que permite 1.3, 1.7 y 2.0 hace que 1.1 se actualice a 1.3, 1.5 se actualice a 1.7 y otras versiones permanezcan iguales.
  • Una anulación de varias versiones que permite 1.5 y 2.0 genera un error, ya que 1.7 no tiene una versión superior en el mismo nivel de compatibilidad a la que se pueda actualizar.
  • Una anulación de varias versiones que permite 1.9 y 2.0 genera un error, ya que 1.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 una sola versión.

Anulaciones fuera del registro

Los reemplazos que no son de registro quitan por completo un módulo de la resolución de versiones. Bazel no solicita estos archivos MODULE.bazel desde un registro, sino desde el propio repo.

Bazel admite los siguientes reemplazos que no son de registro:

Define repositorios que no representan módulos de Bazel

Con bazel_dep, puedes definir repositorios que representen otros módulos de Bazel. A veces, es necesario definir un repo que no represente un módulo de Bazel, por ejemplo, uno que contenga un archivo JSON simple que se leerá como datos.

En este caso, puedes usar la directiva use_repo_rule para definir directamente un repo invocando una regla de repo. Este repo solo será visible para el módulo en el que se define.

Internamente, esto se implementa con el mismo mecanismo que las extensiones de módulos, lo que te permite definir repositorios con más flexibilidad.

Nombres de repositorios y dependencias estrictas

El nombre visible de un repo que respalda un módulo para sus dependencias directas se establece de forma predeterminada en el nombre del 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 interrupciones accidentales debido a cambios en las dependencias transitivas.

El nombre canónico de un repo que respalda un módulo es module_name~version (por ejemplo, bazel_skylib~1.0.3) o module_name~ (por ejemplo, bazel_features~), según si hay varias versiones del módulo en todo el gráfico de dependencias (consulta multiple_version_override). Ten en cuenta que el formato del nombre canónico no es una API de la que debas depender y está sujeto a cambios en cualquier momento. En lugar de codificar de forma rígida el nombre canónico, usa una forma compatible para obtenerlo directamente de Bazel: * En los archivos BUILD y .bzl, usa Label.repo_name en una instancia de Label construida a partir de una cadena de etiqueta proporcionada por el nombre aparente del repo, p.ej., Label("@bazel_skylib").repo_name. * Cuando busques archivos ejecutables, usa $(rlocationpath ...) o una de las bibliotecas de archivos ejecutables en @bazel_tools//tools/{bash,cpp,java}/runfiles o, para un conjunto de reglas rules_foo, en @rules_foo//foo/runfiles. * Cuando interactúes con Bazel desde una herramienta externa, como un IDE o un servidor de lenguaje, usa el comando bazel mod dump_repo_mapping para obtener la asignación de nombres aparentes a nombres canónicos para un conjunto determinado de repositorios.

Las extensiones de módulos también pueden introducir repositorios adicionales en el alcance visible de un módulo.