Módulos de Bazel

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 familiares en otros sistemas de administración de dependencias, como un artefacto de Maven, un paquete de npm, un módulo de Go o un crate de Cargo.

Un módulo debe tener un archivo MODULE.bazel en la raíz de su repositorio. Este archivo es el manifiesto del módulo, que declara su nombre, versión, lista de dependencias directas y otra información. Para ver un ejemplo básico, haz lo siguiente:

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 MODULE.bazel archivos.

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 de 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 repositorio y vuelve a consultar el registro para aprender a definir cada uno de los repositorios.

Formato de versión

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

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

  • SemVer prescribe que la parte "release" de la versión debe constar de 3 segmentos: MAJOR.MINOR.PATCH. En Bazel, este requisito se relaja para que se permita cualquier cantidad de segmentos.
  • En SemVer, cada uno de los segmentos de la parte "release" debe ser 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 "prerelease".
  • Además, no se aplica la semántica de los aumentos de versión principal, secundaria y de parche no se aplica.

Cualquier versión válida de SemVer es una versión válida del módulo de Bazel. Además, dos versiones de SemVer a y b comparan a < b si y solo si se cumple lo mismo cuando se comparan como versiones del módulo 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 con versiones. 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. MVS supone que todas las versiones nuevas de un módulo son compatibles con versiones anteriores y, por lo tanto, elige la versión 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á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 versión que es de alta fidelidad y reproducible.

Versiones retiradas

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

Anula

Especifica anulaciones en el archivo MODULE.bazel para modificar el comportamiento de la resolución del módulo de Bazel. Solo las anulaciones del módulo raíz surten efecto. 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. Aunque solo las anulaciones del módulo raíz surten efecto, pueden ser para dependencias transitivas de las que el módulo raíz no depende directamente.

Anulación de una sola versión

El single_version_override tiene 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 registry atributo, puedes forzar que esta dependencia provenga de un registro específico, en lugar de seguir el proceso normal de selección.
  • Con los atributos patch*, puedes especificar un conjunto de parches para aplicar a l módulo descargado.

Todos estos atributos son opcionales y se pueden combinar entre sí.

Anulación de varias versiones

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

Si quedan varias versiones del mismo módulo en el gráfico de dependencias, Bazel elegirá la versión permitida más cercana para cada dependiente.

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:

  • 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 sigan siendo las mismas.
  • 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 registry atributo, de manera similar a las anulaciones de una sola versión.

Anulaciones que no son de registro

Las anulaciones que no son de registro quitan por completo un módulo de la resolución de versiones. Bazel no solicita estos MODULE.bazel archivos de un registro, sino de el repositorio en sí.

Bazel admite las siguientes anulaciones que no son de registro:

Define repositorios que no representan módulos de Bazel

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

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

En segundo plano, esto se implementa con el mismo mecanismo que las extensiones de módulo, lo que te permite definir repositorios con más flexibilidad.

Nombres de repositorios y dependencias estrictas

El nombre aparente de un repositorio que respalda un módulo para sus dependientes directos se establece de forma predeterminada en el nombre del módulo, a menos que el repo_name atributo de la bazel_dep directiva 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 repositorio 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 de nombre canónico no es una API de la que debas depender y está sujeto a cambios en cualquier momento. En lugar de codificar 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 que proporciona el nombre aparente del repositorio, p.ej., Label("@bazel_skylib").repo_name.
  • Cuando busques runfiles, usa $(rlocationpath ...) o una de las bibliotecas de runfiles 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ódulo también pueden introducir repositorios adicionales en el alcance visible de un módulo.