Archivo de bloqueo de Bazel

Denuncia un problema Ver fuente Nightly · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

La función de archivo de bloqueo en Bazel permite registrar versiones o dependencias específicas de bibliotecas o paquetes de software que requiere un proyecto. Para ello, almacena el resultado de la resolución del módulo y la evaluación de la extensión. El archivo de bloqueo promueve compilaciones reproducibles, lo que garantiza entornos de desarrollo coherentes. Además, mejora la eficiencia de la compilación, ya que permite que Bazel omita las partes del proceso de resolución que no se ven afectadas por los cambios en las dependencias del proyecto. Además, el archivo de bloqueo mejora la estabilidad, ya que evita actualizaciones inesperadas o cambios en las bibliotecas externas, lo que reduce el riesgo de introducir errores.

Generación de archivos de bloqueo

El archivo de bloqueo se genera en la raíz del lugar de trabajo con el nombre MODULE.bazel.lock. Se crea o se actualiza durante el proceso de compilación, específicamente después de la resolución del módulo y la evaluación de la extensión. Es importante que solo incluya las dependencias que se incluyen en la invocación actual de la compilación.

Cuando se producen cambios en el proyecto que afectan sus dependencias, el archivo de bloqueo se actualiza automáticamente para reflejar el estado nuevo. Esto garantiza que el archivo de bloqueo permanezca enfocado en el conjunto específico de dependencias requeridas para la compilación actual, lo que proporciona una representación precisa de las dependencias resueltas del proyecto.

Uso de Lockfile

El archivo de bloqueo se puede controlar con la marca --lockfile_mode para personalizar el comportamiento de Bazel cuando el estado del proyecto difiere del archivo de bloqueo. Los modos disponibles son los siguientes:

  • update (predeterminado): Usa la información que se encuentra en el archivo de bloqueo para omitir las descargas de archivos de registro conocidos y evitar volver a evaluar extensiones cuyos resultados aún están actualizados. Si falta información, se agregará al archivo de bloqueo. En este modo, Bazel también evita actualizar la información mutable, como las versiones quitadas, para las dependencias que no cambiaron.
  • refresh: Al igual que update, pero la información mutable siempre se actualiza cuando se cambia a este modo y aproximadamente cada hora mientras se está en este modo.
  • error: Es similar a update, pero si falta información o está desactualizada, Bazel fallará con un error. Este modo nunca cambia el archivo de bloqueo ni realiza solicitudes de red durante la resolución. Es posible que las extensiones de módulo que se marcaron como reproducible aún realicen solicitudes de red, pero se espera que siempre produzcan el mismo resultado.
  • off: El archivo de bloqueo no se verifica ni se actualiza.

Beneficios del archivo de bloqueo

El archivo de bloqueo ofrece varios beneficios y se puede utilizar de varias maneras:

  • Compilaciones reproducibles. Cuando captura las versiones o dependencias específicas de las bibliotecas de software, el archivo de bloqueo se asegura de que las compilaciones se puedan reproducir en diferentes entornos y con el tiempo. Los desarrolladores pueden confiar en resultados coherentes y predecibles cuando compilan sus proyectos.

  • Soluciones incrementales rápidas. El archivo de bloqueo permite que Bazel evite descargar archivos de registro que ya se usaron en una compilación anterior. Esto mejora significativamente la eficiencia de la compilación, especialmente en situaciones en las que la resolución puede llevar mucho tiempo.

  • Estabilidad y reducción de riesgos. El archivo de bloqueo ayuda a mantener la estabilidad, ya que evita actualizaciones inesperadas o cambios rotundos en bibliotecas externas. Cuando se bloquean las dependencias en versiones específicas, se reduce el riesgo de introducir errores debido a actualizaciones incompatibles o no probadas.

Contenido del archivo de bloqueo

El archivo de bloqueo contiene toda la información necesaria para determinar si el estado del proyecto cambió. También incluye el resultado de compilar el proyecto en el estado actual. El archivo de bloqueo consta de dos partes principales:

  1. Son los valores hash de todos los archivos remotos que son entradas para la resolución de módulos.
  2. Para cada extensión de módulo, el archivo de bloqueo incluye entradas que lo afectan, representadas por bzlTransitiveDigest, usagesDigest y otros campos, así como el resultado de ejecutar esa extensión, denominado generatedRepoSpecs.

Este es un ejemplo que demuestra la estructura del archivo de bloqueo, junto con explicaciones para cada sección:

{
  "lockFileVersion": 10,
  "registryFileHashes": {
    "https://bcr.bazel.build/bazel_registry.json": "8a28e4af...5d5b3497",
    "https://bcr.bazel.build/modules/foo/1.0/MODULE.bazel": "7cd0312e...5c96ace2",
    "https://bcr.bazel.build/modules/foo/2.0/MODULE.bazel": "70390338... 9fc57589",
    "https://bcr.bazel.build/modules/foo/2.0/source.json": "7e3a9adf...170d94ad",
    "https://registry.mycorp.com/modules/foo/1.0/MODULE.bazel": "not found",
    ...
  },
  "selectedYankedVersions": {
    "foo@2.0": "Yanked for demo purposes"
  },
  "moduleExtensions": {
    "//:extension.bzl%lockfile_ext": {
      "general": {
        "bzlTransitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=",
        "usagesDigest": "aLmqbvowmHkkBPve05yyDNGN7oh7QE9kBADr3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      }
    },
    "//:extension.bzl%lockfile_ext2": {
      "os:macos": {
        "bzlTransitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=",
        "usagesDigest": "aLmqbvowmHkkBPve05y....yDNGN7oh7r3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      },
      "os:linux": {
        "bzlTransitiveDigest": "eWDzxG/aLsyY3Ubrto....+Jp4maQvEPxn0pLK=",
        "usagesDigest": "aLmqbvowmHkkBPve05y....yDNGN7oh7r3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      }
    }
  }
}

Hashes de archivos del registro

La sección registryFileHashes contiene los valores hash de todos los archivos de los registros remotos a los que se accedió durante la resolución del módulo. Dado que el algoritmo de resolución es completamente determinista cuando se proporcionan las mismas entradas y todas las entradas remotas se hashean, esto garantiza un resultado de resolución completamente reproducible y, al mismo tiempo, evita la duplicación excesiva de información remota en el archivo de bloqueo. Ten en cuenta que esto también requiere registrar cuando un registro en particular no contenía un módulo determinado, pero un registro con una prioridad más baja sí lo hacía (consulta la entrada "no se encontró" en el ejemplo). Esta información inherentemente mutable se puede actualizar a través de bazel mod deps --lockfile_mode=refresh.

Bazel usa los valores hash del archivo de bloqueo para buscar archivos de registro en la caché del repositorio antes de descargarlos, lo que acelera las resoluciones posteriores.

Versiones quitadas seleccionadas

La sección selectedYankedVersions contiene las versiones quitadas de los módulos que se seleccionaron por resolución de módulos. Como esto suele generar un error cuando se intenta compilar, esta sección solo no está vacía cuando las versiones quitadas se permiten de forma explícita a través de --allow_yanked_versions o BZLMOD_ALLOW_YANKED_VERSIONS.

Este campo es necesario porque, en comparación con los archivos de módulo, la información de la versión quitada es inherentemente mutable y, por lo tanto, no se puede hacer referencia a ella con un hash. Esta información se puede actualizar a través de bazel mod deps --lockfile_mode=refresh.

Extensiones de módulos

La sección moduleExtensions es un mapa que incluye solo las extensiones que se usan en la invocación actual o que se invocó anteriormente, y excluye las extensiones que ya no se usan. En otras palabras, si una extensión ya no se usa en el gráfico de dependencias, se quita del mapa moduleExtensions.

Si una extensión es independiente del sistema operativo o del tipo de arquitectura, esta sección solo incluye una entrada "general". De lo contrario, se incluyen varias entradas, con nombres que hacen referencia al SO, la arquitectura o ambos, y cada una corresponde al resultado de evaluar la extensión en esos detalles.

Cada entrada del mapa de extensiones corresponde a una extensión utilizada y se identifica por su nombre y archivo contenedor. El valor correspondiente de cada entrada contiene la información relevante asociada con esa extensión:

  1. bzlTransitiveDigest es el resumen de la implementación de la extensión y los archivos .bzl que carga de forma transitiva.
  2. usagesDigest es el resumen de los usos de la extensión en el gráfico de dependencias, que incluye todas las etiquetas.
  3. Otros campos no especificados que hacen un seguimiento de otras entradas a la extensión, como el contenido de los archivos o directorios que lee o las variables de entorno que usa.
  4. generatedRepoSpecs codifica los repositorios creados por la extensión con la entrada actual.
  5. El campo moduleExtensionMetadata opcional contiene metadatos que proporciona la extensión, como si el módulo raíz debe importar ciertos repositorios que creó a través de use_repo. Esta información potencia el comando bazel mod tidy.

Las extensiones de módulo pueden inhabilitar su inclusión en el archivo de bloqueo si configuran los metadatos que se muestran con reproducible = True. De esta manera, prometen que siempre crearán los mismos repositorios cuando se les proporcionen las mismas entradas.

Prácticas recomendadas

Para maximizar los beneficios de la función de archivo de bloqueo, ten en cuenta las siguientes prácticas recomendadas:

  • Actualiza el archivo de bloqueo periódicamente para que refleje los cambios en las dependencias o la configuración del proyecto. Esto garantiza que las compilaciones posteriores se basen en el conjunto de dependencias más actualizado y preciso. Para bloquear todas las extensiones a la vez, ejecuta bazel mod deps --lockfile_mode=update.

  • Incluye el archivo de bloqueo en el control de versiones para facilitar la colaboración y asegurarte de que todos los miembros del equipo tengan acceso al mismo archivo de bloqueo, lo que promueve entornos de desarrollo coherentes en todo el proyecto.

  • Usa bazelisk para ejecutar Bazel y, luego, incluye un archivo .bazelversion en el control de versiones que especifique la versión de Bazel correspondiente al archivo de bloqueo. Debido a que Bazel es una dependencia de tu compilación, el archivo de bloqueo es específico de la versión de Bazel y cambiará incluso entre las versiones de Bazel retrocompatibles. El uso de bazelisk garantiza que todos los desarrolladores usen una versión de Bazel que coincida con el archivo de bloqueo.

Si sigues estas prácticas recomendadas, puedes usar de manera eficaz la función de archivo de bloqueo en Bazel, lo que genera flujos de trabajo de desarrollo de software más eficientes, confiables y colaborativos.

Conflictos de combinación

El formato de archivo de bloqueo está diseñado para minimizar los conflictos de combinación, pero es posible que aún se produzcan.

Resolución automática

Bazel proporciona un controlador de combinación de git personalizado para ayudar a resolver estos conflictos automáticamente.

Para configurar el controlador, agrega esta línea a un archivo .gitattributes en la raíz de tu repositorio de git:

# A custom merge driver for the Bazel lockfile.
# https://bazel.build/external/lockfile#automatic-resolution
MODULE.bazel.lock merge=bazel-lockfile-merge

Luego, cada desarrollador que quiera usar el controlador debe registrarlo una vez siguiendo estos pasos:

  1. Instala jq (1.5 o una versión posterior).
  2. Ejecute los siguientes comandos:
jq_script=$(curl https://raw.githubusercontent.com/bazelbuild/bazel/master/scripts/bazel-lockfile-merge.jq)
printf '%s\n' "${jq_script}" | less # to optionally inspect the jq script
git config --global merge.bazel-lockfile-merge.name   "Merge driver for the Bazel lockfile (MODULE.bazel.lock)"
git config --global merge.bazel-lockfile-merge.driver "jq -s '${jq_script}' -- %O %A %B > %A.jq_tmp && mv %A.jq_tmp %A"

Resolución manual

Los conflictos de combinación simples en los campos registryFileHashes y selectedYankedVersions se pueden resolver de forma segura si se conservan todas las entradas de ambos lados del conflicto.

No se deben resolver manualmente otros tipos de conflictos de combinación. En su lugar, siga estos pasos:

  1. Restablece el estado anterior del archivo de bloqueo a través de git reset MODULE.bazel.lock && git checkout MODULE.bazel.lock.
  2. Resuelve cualquier conflicto en el archivo MODULE.bazel.
  3. Ejecuta bazel mod deps para actualizar el archivo de bloqueo.