Archivo de bloqueo de Bazel

Informar un problema Ver código fuente Nocturno · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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. Esto se logra almacenando 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 que interrumpen el funcionamiento de 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 espacio de trabajo con el nombre MODULE.bazel.lock. Se crea o 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 destacar que solo incluye 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 nuevo estado. Esto garantiza que el archivo de bloqueo se centre en el conjunto específico de dependencias necesarias 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 presente en el archivo de bloqueo para omitir las descargas de archivos de registro conocidos y evitar volver a evaluar las 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 retiradas, 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á y mostrará un error. Este modo nunca cambia el archivo de bloqueo ni realiza solicitudes de red durante la resolución. Las extensiones de módulos que se marcaron como reproducible aún pueden realizar 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 diversas maneras:

  • Compilaciones reproducibles: Al capturar las versiones o dependencias específicas de las bibliotecas de software, el archivo de bloqueo garantiza que las compilaciones sean reproducibles en diferentes entornos y a lo largo del tiempo. Los desarrolladores pueden confiar en resultados coherentes y predecibles cuando crean sus proyectos.

  • Resoluciones 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 las bibliotecas externas. Al bloquear las dependencias en versiones específicas, se reduce el riesgo de introducir errores.

    se reduce debido a actualizaciones incompatibles o no probadas.

Archivo de bloqueo oculto

Bazel también mantiene otro archivo de bloqueo en "$(bazel info output_base)"/MODULE.bazel.lock. El formato y el contenido de este archivo de bloqueo no se especifican de forma explícita. Solo se usa como una optimización del rendimiento. Si bien se puede borrar junto con la base de salida a través de bazel clean --expunge, cualquier necesidad de hacerlo es un error en Bazel o en una extensión de módulo.

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 la compilación del proyecto en el estado actual. El archivo de bloqueo consta de dos partes principales:

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

A continuación, se muestra un ejemplo que demuestra la estructura del archivo de bloqueo, junto con explicaciones de 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 hashes 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 determinístico cuando se proporcionan las mismas entradas y todas las entradas remotas se generan con hash, 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 sí lo contenía un registro con menor precedencia (consulta la entrada "no encontrado" en el ejemplo). Esta información inherentemente mutable se puede actualizar a través de bazel mod deps --lockfile_mode=refresh.

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

Versiones anuladas seleccionadas

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

Este campo es necesario, ya que, en comparación con los archivos de módulos, la información de la versión eliminada 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 usaron en la invocación actual o en invocaciones anteriores, y excluye las extensiones que ya no se utilizan. En otras palabras, si ya no se usa una extensión en el gráfico de dependencias, se quita del mapa de 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 el nombre del SO, la arquitectura o ambos, y cada una corresponde al resultado de evaluar la extensión en esas especificaciones.

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

  1. bzlTransitiveDigest es el resumen de la implementación de la extensión y los archivos .bzl que se cargan de forma transitiva.
  2. El 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 registran otras entradas de la extensión, como el contenido de los archivos o directorios que lee, o las variables de entorno que usa.
  4. El generatedRepoSpecs codifica los repositorios creados por la extensión con la entrada actual.
  5. El campo moduleExtensionMetadata opcional contiene metadatos proporcionados por la extensión, como si ciertos repositorios que creó se deben importar a través de use_repo por el módulo raíz. Esta información alimenta el comando bazel mod tidy.

Las extensiones de módulos pueden inhabilitar su inclusión en el archivo de bloqueo si configuran los metadatos de devolución con reproducible = True. Al hacerlo, 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, considera las siguientes prácticas recomendadas:

  • Actualiza el archivo de bloqueo con regularidad para reflejar 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 garantizar 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. Dado que Bazel en sí 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. Usar 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, podrás utilizar de manera eficaz la función de archivo de bloqueo en Bazel, lo que generará flujos de trabajo de desarrollo de software más eficientes, confiables y colaborativos.

Conflictos de combinación

El formato del archivo de bloqueo está diseñado para minimizar los conflictos de combinación, pero estos pueden ocurrir.

Resolución automática

Bazel proporciona un controlador de fusió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 posterior).
  2. Ejecuta 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 conservando todas las entradas de ambos lados del conflicto.

Otros tipos de conflictos de combinación no se deben resolver de forma manual. En su lugar, siga estos pasos:

  1. Restablece el estado anterior del archivo de bloqueo con 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.