Módulos de Bazel

Informar un problema Ver fuente

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 los conceptos conocidos de 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. 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 archivos MODULE.bazel.

Para realizar la resolución del módulo, Bazel comienza por leer el archivo MODULE.bazel del módulo raíz y, luego, solicita de manera reiterada el archivo MODULE.bazel de cualquier dependencia desde un registro de Bazel hasta descubrir el gráfico de dependencias completo.

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. El más popular por mucho es SemVer, pero también hay proyectos destacados que usan diferentes esquemas, como Abseil, cuyas versiones 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. Estas son algunas de las diferencias:

  • SemVer prescribe que la parte de "lanzamiento" de la versión debe constar de 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 en la parte “release” debe tener solo dígitos. En Bazel, esto se flexibiliza para permitir también letras, y la semántica de comparación coincide con los “identificadores” de la parte “lanzamiento previo”.
  • Además, no se aplica la semántica de los aumentos principales, secundarios y de versiones 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 solo si se aplica lo mismo 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 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 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 retrocompatibles, por lo que elige la versión más alta especificada por cualquier dependiente (D 1.1 en nuestro ejemplo). Se llama "mínima" porque D 1.1 es la primera versión que podría satisfacer nuestros requisitos. No la seleccionamos incluso si existe D 1.2 o una versión posterior. El uso de MVS crea un proceso de selección de versión que es de alta fidelidad y reproducible.

Versiones ya compartidas

El registro puede declarar que ciertas versiones están retiradas si deben evitarse (por ejemplo, en el caso de las vulnerabilidades de seguridad). Bazel muestra un error cuando se selecciona una versión ya compilada de un módulo. Para corregir este error, actualiza a una versión más reciente que no sea no listada o usa la marca --allow_yanked_versions para permitir explícitamente la versión ya realizada.

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 gráfico de dependencias resuelto. Esto, a su vez, es posible gracias a la codificación de la versión principal en la ruta del paquete en Go, de modo que no haya conflictos de tiempo de compilación ni 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 se especifica mediante la versión de cada 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 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 dependencia. Si bien 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 versión única

El objeto 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 atributo registry, puedes forzar esta dependencia para que 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 dependencia 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 haber algunas dependencias transitivas según cada versión permitida. Después de la resolución, solo quedan las versiones permitidas del módulo, mientras que Bazel actualiza otras versiones del módulo a la versión permitida más cercana que tenga el mismo nivel de compatibilidad. Si no existe una versión posterior permitida con 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 dependencia antes de la resolución y la versión principal es el nivel de compatibilidad:

  • Una anulación de varias versiones que permite 1.3, 1.7 y 2.0 hace que 1.1 se actualice a 1.3, que 1.5 se actualice a 1.7 y que las demás 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 posterior con el mismo nivel de compatibilidad a la que actualizar.
  • Una anulación de varias versiones que permite 1.9 y 2.0 da como resultado un error, ya que 1.9 no está presente en el gráfico de dependencia antes de la resolución.

Además, los usuarios pueden anular el registro con el atributo registry, de manera similar a las anulaciones de versión única.

Anulaciones que no son de registro

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

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 sin registro, reemplaza la parte version con la string override. Ten en cuenta que el formato del nombre canónico no es una API de la que debas confiar y que está sujeto a cambios en cualquier momento.

El nombre aparente de un repositorio que respalda un módulo en sus dependencias directas es el nombre predeterminado, 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.

Las extensiones del módulo también pueden incorporar repositorios adicionales dentro del alcance visible de un módulo.