Bazel 模块

Bazel 模块 是一个 Bazel 项目,可以有多个版本,每个版本都会发布有关其所依赖的其他模块的元数据。这类似于其他依赖项管理系统中的熟悉概念,例如 Maven 工件、npm 软件包、Go 模块或 Cargo

模块必须在其代码库根目录下有一个 MODULE.bazel 文件。此文件是 模块的清单,用于声明模块的名称、版本、直接依赖项列表以及 其他信息。以下是一个基本示例:

module(name = "my-module", version = "1.0")

bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")

如需查看文件中提供的指令的完整列表,请点击此处 MODULE.bazel

如需执行模块解析,Bazel 首先会读取根模块的 MODULE.bazel文件,然后反复从任何依赖项的 MODULE.bazel文件请求Bazel 注册表,直到 发现整个依赖关系图。

默认情况下,Bazel 随后会选择每个模块的一个版本 来使用。Bazel 使用代码库表示每个模块,并再次查询注册表 ,以了解如何定义每个代码库。

版本格式

Bazel 拥有多样化的生态系统,项目使用各种版本控制方案。到目前为止,最受欢迎的方案是 SemVer,但也有一些知名项目使用不同的方案,例如 Abseil,其版本基于日期,例如 20210324.2)。

因此,Bzlmod 采用了更宽松的 SemVer 规范版本。 不同之处包括:

  • SemVer 规定,版本的“发布”部分必须包含 3 个部分:MAJOR.MINOR.PATCH。在 Bazel 中,此要求有所放宽,允许使用任意数量的部分。
  • 在 SemVer 中,“发布”部分中的每个部分都必须仅包含数字。 在 Bazel 中,此要求有所放宽,允许使用字母,并且比较 语义与“预发布”部分中的“标识符”相匹配。
  • 此外,系统不会强制执行主要版本、次要版本和补丁版本递增的语义 。

任何有效的 SemVer 版本都是有效的 Bazel 模块版本。此外,当两个 SemVer 版本 ab 作为 Bazel 模块版本进行比较时,当且仅当 a < b 成立时,a < b 成立。

版本选择

请考虑菱形依赖项问题,这是版本化依赖项 管理领域中的一个常见问题。假设您有以下依赖关系图:

       A 1.0
      /     \
   B 1.0    C 1.1
     |        |
   D 1.0    D 1.1

应使用哪个版本的 D?如需解决此问题,Bzlmod 使用 最小版本选择 (MVS) 算法(此算法在 Go 模块系统中引入)。MVS 假定模块的所有新 版本都向后兼容,因此会选择任何依赖项指定的最高版本 (在我们的示例中为 D 1.1)。之所以称为“最小”, 是因为 D 1.1 是满足我们要求的最早版本 — 即使存在 D 1.2 或更新版本,我们也不会选择它们。使用 MVS 会创建一个 版本选择流程,该流程高保真可重现

已撤消的版本

如果某些版本应避免使用 (例如,存在安全漏洞),注册表可以将这些版本声明为“已撤消” __。Bazel 在选择模块的 已撤消版本时会抛出错误。如需修复此错误,请升级到较新的 非撤消版本,或使用 --allow_yanked_versions 标志显式允许使用已撤消的版本。

替换

MODULE.bazel 文件中指定替换,以更改 Bazel 模块解析的行为。只有根模块的替换才会生效 - 如果模块用作依赖项,其替换将被忽略。

每个替换都是针对特定模块名称指定的,会影响依赖关系图中的所有 版本。虽然只有根模块的替换才会生 效,但它们可以用于根模块不 直接依赖的传递依赖项。

单版本替换

single_version_override 有多种用途:

  • 借助 version 属性,您可以将依赖项固定到特定 版本,无论依赖关系图中请求的是哪个版本的依赖项。
  • 借助 registry 属性,您可以强制此依赖项来自特定注册表,而不是遵循正常的 注册表 选择 流程。
  • 借助 patch* 属性,您可以指定要应用于 下载的模块的一组补丁。

这些属性都是可选的,可以相互混合搭配使用。

多版本替换

您可以指定 multiple_version_override ,以允许同一模块的多个版本在 解析的依赖关系图中共存。

如果依赖关系图 中仍存在同一模块的多个版本,Bazel 将为每个依赖项选择最接近的较高允许版本。

例如,如果解析前依赖关系图中存在版本 1.11.31.51.72.0

  • 允许使用 1.31.72.0 的多版本替换会导致 1.1 升级到 1.31.5 升级到 1.7,其他 版本保持不变。
  • 允许使用 1.92.0 的多版本替换会导致错误,因为 1.9 解析前依赖关系图中不存在。

此外,与单版本替换类似,用户还可以使用 registry 属性替换注册表。

非注册表替换

非注册表替换会从版本解析中完全移除模块。Bazel 不会从注册表请求这些 MODULE.bazel 文件,而是从 代码库本身请求。

Bazel 支持以下非注册表替换:

定义不代表 Bazel 模块的代码库

借助 bazel_dep,您可以定义代表其他 Bazel 模块的代码库。 有时,您需要定义一个不代表 Bazel 模块的代码库;例如,一个包含要作为数据读取的纯 JSON 文件的代码库。

在这种情况下,您可以使用 use_repo_rule 指令 通过调用代码库规则直接定义代码库。此代码库仅对定义它 的模块可见。

在底层,这是使用与 模块 扩展 相同的机制实现的,这让您可以更 灵活地定义代码库。

代码库名称和严格依赖项

支持模块的代码库对其直接依赖项的显示名称默认为其模块名称,除非 bazel_dep 指令的 repo_name属性另有说明。请注意,这意味着模块只能找到其直接 依赖项。这有助于防止因 传递依赖项发生更改而导致意外中断。

支持模块的代码库的规范名称module_name+version(例如,bazel_skylib+1.0.3)或module_name+(例如,bazel_features+),具体取决于整个依赖关系图中是否存在模块的多个版本(请参阅multiple_version_override)。请注意,规范名称格式 不是您应依赖的 API,随时可能会发生变化。请勿对规范名称进行硬编码, 而是使用受支持的方式直接从 Bazel 获取规范名称:

  • 在 BUILD 和 .bzl 文件中,对从代码库的显示名称给定的标签字符串构造的 Label 实例使用 Label.repo_name,例如 Label("@bazel_skylib").repo_name
  • 查找运行文件时,请使用 $(rlocationpath ...)@bazel_tools//tools/{bash,cpp,java}/runfiles 中的某个运行文件库,或者对于规则集 rules_foo,使用 @rules_foo//foo/runfiles
  • 从外部工具(例如 IDE 或语言 服务器)与 Bazel 交互时,请使用 bazel mod dump_repo_mapping 命令获取给定代码库集的显示名称到规范名称的映射。

模块扩展还可以将其他代码库 引入模块的可见范围。