Bazel Lockfile

报告问题 查看源代码

通过 Bazel 中的锁定文件功能,您可以记录项目所需的软件库或软件包的特定版本或依赖项。它通过存储模块解析和扩展评估的结果来实现这一点。Lockfile 促进了可重现的 build,确保了一致的开发环境。此外,它还允许 Bazel 跳过不受项目依赖项更改影响的解析过程部分,从而提高构建效率。此外,锁定文件可防止意外更新或破坏外部库中的破坏性更改,从而提高了稳定性,从而降低引入 bug 的风险。

生成锁定文件

系统会在工作区根目录下生成名为 MODULE.bazel.lock 的锁定文件。它在构建流程中创建或更新,特别是在模块解析和扩展评估之后。重要的是,它仅包含 build 的当前调用中包含的依赖项。

当项目中发生会影响其依赖项的更改时,锁定文件会自动更新以反映新状态。这可确保 lockfile 始终专注于当前 build 所需的特定依赖项集,从而准确表示项目的已解析依赖项。

锁定文件使用情况

锁文件可以通过 --lockfile_mode 标志控制,以便在项目状态与锁定文件不同时自定义 Bazel 的行为。可用的模式包括:

  • update(默认):使用锁定文件中显示的信息跳过已知注册表文件的下载,并避免重新评估结果仍为最新结果的扩展程序。如果缺少信息,则会将其添加到锁定文件中。在此模式下,Bazel 还会避免刷新未更改的依赖项的可变信息(例如被拉取的版本)。
  • refresh:与 update 类似,但切换到此模式时始终刷新可变信息,并且在此模式下大约每小时刷新一次。
  • error:与 update 类似,但如果任何信息缺失或过时,Bazel 将失败并报错。此模式从不更改 lockfile 或在解决期间执行网络请求。自行标记为 reproducible 的模块扩展或许仍可执行网络请求,但应始终产生相同的结果。
  • off:系统不会检查或更新锁定文件。

Lockfile 的优势

锁定文件具有多种优势,可通过多种方式加以利用:

  • 可重现的 build。通过捕获软件库的特定版本或依赖项,锁定文件可确保构建在不同环境中和随着时间的推移的可重现性。开发者在构建项目时可以依靠一致且可预测的结果。

  • 快速提高分辨率。通过锁定文件,Bazel 可避免下载已在先前 build 中使用的注册表文件。这样可以显著提高构建效率,尤其是在解析可能非常耗时的情况下。

  • 稳定性和降低风险。锁定文件可防止意外更新或破坏外部库中的破坏性更改,从而帮助保持稳定性。将依赖项锁定在特定版本后,可降低因更新不兼容或未经测试而引入 bug 的风险。

锁定文件内容

锁定文件包含确定项目状态是否已更改所需的所有信息。它还包含在当前状态下构建项目的结果。lockfile 由两个主要部分组成:

  1. 作为模块解析输入的所有远程文件的哈希值。
  2. 对于每个模块扩展程序, lockfile 包含影响它的输入(由 bzlTransitiveDigestusagesDigest 和其他字段表示)以及运行该扩展程序的输出(称为 generatedRepoSpecs

以下示例展示了锁定文件的结构,以及各个部分的说明:

{
  "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",
            ...
          }
        }
      }
    }
  }
}

注册文件哈希

registryFileHashes 部分包含在模块解析期间访问的远程注册表中所有文件的哈希值。由于在给定相同的输入并且所有远程输入都经过哈希处理时,解析算法具有完全确定性,因此这可确保完全可重现解析结果,同时避免 lockfile 中过多的远程信息重复。请注意,当特定注册表不包含特定模块,但优先级较低的注册表包含特定模块时,这也需要记录(请参阅示例中的“未找到”条目)。这种固有可变的信息可以通过 bazel mod deps --lockfile_mode=refresh 进行更新。

在下载之前,Bazel 会使用锁定文件中的哈希来查找代码库缓存中的注册表文件,从而提高后续解析的速度。

所选退出版本

selectedYankedVersions 部分包含根据模块分辨率选择的模块的拉取版本。由于这通常会导致在尝试构建时发生错误,因此仅当通过 --allow_yanked_versionsBZLMOD_ALLOW_YANKED_VERSIONS 明确允许被拉取的版本时,此部分才是非空的。

必须使用此字段,因为与模块文件相比,被提取的版本信息本质上是可变的,因此无法通过哈希引用。您可以通过 bazel mod deps --lockfile_mode=refresh 更新此信息。

模块扩展

moduleExtensions 部分是一个映射,仅包含当前调用中或之前调用的扩展程序,但排除不再使用的扩展程序。换句话说,如果某个扩展不再在整个依赖关系图中使用,则会从 moduleExtensions 映射中移除。

如果扩展程序与操作系统或架构类型无关,则本部分仅包含单个“常规”条目。否则,会包含多个条目(以操作系统和/或架构命名),每个条目对应根据这些具体信息评估扩展程序的结果。

扩展程序映射中的每个条目都对应于一个已使用的扩展程序,并由其包含的文件和名称标识。每个条目的对应值包含与该扩展相关联的相关信息:

  1. bzlTransitiveDigest 是扩展程序实现及其以传递方式加载的 .bzl 文件的摘要。
  2. usagesDigest 是依赖关系图中扩展程序的使用情况摘要,其中包含所有标记。
  3. 跟踪扩展程序的其他输入(例如,它读取的文件或目录内容或它使用的环境变量)的其他未指定字段。
  4. generatedRepoSpecs 会使用当前输入对扩展程序创建的代码库进行编码。
  5. 可选的 moduleExtensionMetadata 字段包含扩展程序提供的元数据,例如根模块是否应通过 use_repo 导入扩展程序创建的特定代码库。此信息为 bazel mod tidy 命令提供支持。

使用 reproducible = True 设置返回元数据,模块扩展可以选择不包含在锁定文件中。这样,他们承诺在给定相同的输入时始终创建相同的代码库。

最佳实践

为了最大限度地发挥 Lockfile 功能的优势,请考虑以下最佳做法:

  • 定期更新锁定文件,以反映项目依赖项或配置中的更改。这可确保后续构建基于最新、最准确的一组依赖项。如需一次锁定所有扩展程序,请运行 bazel mod deps --lockfile_mode=update

  • 在版本控制中添加 Lockfile 以促进协作,并确保所有团队成员都可以访问同一个锁定文件,从而在整个项目中实现一致的开发环境。

  • 使用 bazelisk 运行 Bazel,并在版本控制中添加 .bazelversion 文件,以指定与锁定文件对应的 Bazel 版本。由于 Bazel 本身是构建的依赖项,因此锁定文件特定于 Bazel 版本,甚至在向后兼容的 Bazel 版本之间也会发生变化。使用 bazelisk 可确保所有开发者使用的 Bazel 版本与锁定文件相匹配。

通过遵循这些最佳做法,您可以有效利用 Bazel 中的 lockfile 功能,从而打造更高效、更可靠和协作式软件开发工作流。