Bazel Lockfile

报告问题 查看源代码

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

生成锁文件

锁定文件会在工作区根目录下生成,名为 MODULE.bazel.lock。它会在构建流程中创建或更新,特别是在模块解析和扩展评估之后。锁定文件会捕获项目的当前状态,包括 MODULE 文件、标志、替换和其他相关信息。重要的是,它仅包含 build 的当前调用中包含的依赖项。

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

锁定文件使用情况

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

  • update(默认):如果项目状态与 lockfile 匹配,则立即从 lockfile 返回解决结果。否则,系统会执行解析并更新 lockfile 以反映当前状态。
  • error:如果项目状态与 lockfile 匹配,则从 lockfile 返回解析结果。否则,Bazel 会抛出错误,指明项目与 lockfile 之间的差异。如果您想要确保项目的依赖项保持不变,并且所有差异都被视为错误,此模式特别有用。
  • off:完全不检查锁定文件。

Lockfile 的优势

Lockfile 具有多种优势,可以通过多种方式加以利用:

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

  • 高效跳过分辨率。如果自上次构建以来项目依赖项没有变化,则 Lockfile 可让 Bazel 跳过解决过程。这可以显著提高构建效率,尤其是在解析可能非常耗时的场景中。

  • 稳定性和风险降低。锁定文件通过防止外部库发生意外更新或破坏性更改,有助于保持稳定性。通过将依赖项锁定到特定版本,可以降低因不兼容或未经测试的更新而引入 bug 的风险。

锁定文件内容

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

  1. 模块分辨率的输入(例如 moduleFileHashflagslocalOverrideHashes)以及分辨率的输出(即 moduleDepGraph)。
  2. 对于每个模块扩展程序,lockfile 都包含对其产生影响的输入(由 transitiveDigest 表示)和运行该扩展程序的输出(称为 generatedRepoSpecs

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

{
  "lockFileVersion": 1,
  "moduleFileHash": "b0f47b98a67ee15f9.......8dff8721c66b721e370",
  "flags": {
    "cmdRegistries": [
      "https://bcr.bazel.build/"
    ],
    "cmdModuleOverrides": {},
    "allowedYankedVersions": [],
    "envVarAllowedYankedVersions": "",
    "ignoreDevDependency": false,
    "directDependenciesMode": "WARNING",
    "compatibilityMode": "ERROR"
  },
  "localOverrideHashes": {
    "bazel_tools": "b5ae1fa37632140aff8.......15c6fe84a1231d6af9"
  },
  "moduleDepGraph": {
    "<root>": {
      "name": "",
      "version": "",
      "executionPlatformsToRegister": [],
      "toolchainsToRegister": [],
      "extensionUsages": [
        {
          "extensionBzlFile": "extension.bzl",
          "extensionName": "lockfile_ext"
        }
      ],
      ...
    }
  },
  "moduleExtensions": {
    "//:extension.bzl%lockfile_ext": {
      "general": {
        "transitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=",
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      }
    },
    "//:extension.bzl%lockfile_ext2": {
      "os:macos": {
        "transitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=",
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      },
      "os:linux": {
        "transitiveDigest": "eWDzxG/aLsyY3Ubrto....+Jp4maQvEPxn0pLK=",
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      }
    }
  }
}

模块文件哈希

moduleFileHash 表示 MODULE.bazel 文件内容的哈希值。如果此文件发生任何更改,则哈希值有所不同。

标志

Flags 对象会存储可能影响分辨率结果的所有标志。

本地替换哈希值

如果根模块包含 local_path_overrides,则此部分会将 MODULE.bazel 文件的哈希值存储在本地代码库中。它允许跟踪此依赖项的更改。

模块依赖关系图

moduleDepGraph 表示使用上述输入的解析过程的结果。它构成了运行项目所需的所有模块的依赖关系图。

模块扩展

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

如果扩展程序与操作系统或架构类型无关,则此部分仅包含一个“常规”条目。否则,它会包含多个条目,并以操作系统和/或架构命名,每个条目对应根据这些细节评估扩展程序的结果。

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

  1. transitiveDigest - 扩展程序实现及其传递 .bzl 文件的摘要。
  2. generatedRepoSpecs:使用当前输入运行该扩展程序的结果。

可能影响扩展程序结果的另一个因素是它们的使用情况。虽然未存储在 Lockfile 中,但在将扩展程序的当前状态与 Lockfile 中的扩展程序状态进行比较时,会考虑使用情况。

最佳做法

为了充分利用锁定文件功能的优势,请考虑以下最佳实践:

  • 定期更新锁定文件,以反映项目依赖项或配置的变化。这样可以确保后续构建基于最新、准确的依赖项集。

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

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

通过遵循这些最佳实践,您可以有效利用 Bazel 中的 lockfile 功能,从而使软件开发工作流变得高效、可靠且更具协作性。