Bazel 模块

报告问题 查看来源 每晚 · 7.4。 ,了解所有最新动态。 7.3 · 7.2 · 7.1敬上 · 7.0敬上 · 6.5

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

模块的代码库根目录中必须有一个 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 文件,然后重复从 Bazel 注册库请求任何依赖项的 MODULE.bazel 文件,直到发现整个依赖关系图。

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

版本格式

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

因此,Bzlmod 采用了更宽松的 SemVer 规范版本。差异包括:

  • SemVer 规定,“发布”部分版本必须包含 3 细分受众群:MAJOR.MINOR.PATCH。在 Bazel 中,此要求已放宽,允许使用任意数量的段。
  • 在 SemVer 中,“release”部分中的每个部分都必须是纯数字。在 Bazel 中,此限制已放宽,也允许使用字母,并且比较语义与“预发布”部分中的“标识符”相匹配。
  • 此外,系统不会强制执行主要版本号、次要版本号和补丁版本号递增的语义。不过,请参阅兼容性级别 详细了解我们如何表示向后兼容性

任何有效的 SemVer 版本都是有效的 Bazel 模块版本。此外,还有两个 当且仅当相同内容成立时,SemVer 版本 ab 才会比较 a < b 将它们作为 Bazel 模块版本进行比较

版本选择

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

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

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

已撤消的版本

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

兼容性级别

在 Go 中,MVS 对向后兼容性的假设适用,因为它会将模块的向后不兼容版本视为单独的模块。对于 SemVer,这意味着 A 1.xA 2.x 被视为不同的模块, 共存于已解析的依赖关系图中。这反过来又使得 在 Go 的软件包路径中对 Major 版本进行编码, 编译时冲突或链接时冲突。

但是,Bazel 无法提供此类保证,因此它需要“主要版本” 编号,以检测向后不兼容的版本。此号码称为 兼容性级别,由每个模块版本在其 module() 指令。掌握了这些信息后,Bazel 可能会在以下情况下抛出错误: 会检测到具有不同兼容性级别的同一模块的版本 已解析的依赖关系图中存在。

覆盖对象

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

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

单个版本替换

single_version_override 有多种用途:

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

这些属性均为可选属性,可以相互混合和匹配。

多版本替换

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

您可以为模块指定允许的版本的明确列表,这些版本必须在解析之前都存在于依赖项图中,并且必须存在某些传递依赖项,具体取决于每个允许的版本。更新后 解决方案,则在 Bazel 升级的同时, 将模块的其他版本更新为相同的允许更高的最接近版本 兼容性级别。如果不存在同一兼容性级别的更高允许版本,Bazel 会抛出错误。

例如,如果在解析之前依赖项图中存在版本 1.11.31.51.72.0,并且主要版本是兼容性级别:

  • 允许使用 1.31.72.0 的多版本替换项会导致 1.1 升级到 1.31.5 升级到 1.7,其他版本保持不变。
  • 允许 1.52.0 的多版本替换会导致错误,因为 1.7 没有相同兼容性级别的更高版本可升级到。
  • 允许使用 1.92.0 的多版本替换项会导致错误,因为在解析之前,依赖项图中不存在 1.9

此外,用户还可以使用 registry 覆盖注册表。 属性,这类似于单一版本替换。

非注册表覆盖

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

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

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

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

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

在后台,此功能是使用与模块扩展相同的机制实现的,这样您就可以更灵活地定义代码库。

代码库名称和严格依赖项

为项目提供支持的代码库的表观名称 模块默认为其直接依赖项,除非 bazel_deprepo_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。 * 查找 runfile 时,请使用 $(rlocationpath ...) 或某个运行文件库中的 @bazel_tools//tools/{bash,cpp,java}/runfiles;或者,对于规则集 rules_foo, 位于 @rules_foo//foo/runfiles。 * 从 IDE 或语言等外部工具与 Bazel 交互时 请使用 bazel mod dump_repo_mapping 命令从 Cloud Storage 中获取 将显而易见名称转换为规范名称。

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