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文件,然后反复从Bazel 注册表请求任何依赖项的
MODULE.bazel文件,直到
发现整个依赖关系图为止。
默认情况下,Bazel 随后会选择每个模块的一个版本 来使用。Bazel 使用代码库表示每个模块,并再次查询注册表 ,以了解如何定义每个代码库。
版本格式
Bazel 拥有多样化的生态系统,项目使用各种版本控制方案。到目前为止,最受欢迎的方案是 SemVer,但也有一些知名项目使用不同的方案,例如 Abseil,其版本基于日期,例如 20210324.2)。
因此,Bazel 采用了更宽松的 SemVer 规范版本。 不同之处包括:
- SemVer 规定,版本的“发布”部分必须包含 3
个段:
MAJOR.MINOR.PATCH。在 Bazel 中,此要求放宽,允许使用任意数量的段。 - 在 SemVer 中,“发布”部分中的每个段都必须仅包含数字。 在 Bazel 中,此要求放宽,允许使用字母,并且比较 语义与“预发布”部分中的“标识符”相匹配。
- 此外,系统不会强制执行主要版本、次要版本和补丁版本递增的语义 。
任何有效的 SemVer 版本都是有效的 Bazel 模块版本。此外,当两个
SemVer 版本 a 和 b 作为 Bazel 模块版本进行比较时,当且仅当 a < b 成立时,它们才会进行比较。
最后,如需详细了解模块版本控制,请参阅 MODULE.bazel
常见问题解答。
版本选择
考虑一下菱形依赖项问题,这是版本化依赖项 管理领域中的一个常见问题。假设您有以下依赖关系图:
A 1.0
/ \
B 1.0 C 1.1
| |
D 1.0 D 1.1
应使用哪个版本的 D?如需解决此问题,Bazel 使用
最小版本选择
(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.1、1.3、1.5、1.7 和 2.0:
- 允许使用
1.3、1.7和2.0的多版本替换会导致1.1升级到1.3,1.5升级到1.7,其他 版本保持不变。 - 允许使用
1.9和2.0的多版本替换会导致错误,因为1.9解析前依赖关系图中不存在。
此外,用户还可以使用 registry
属性替换注册表,类似于单版本替换。
非注册表替换
非注册表替换会从版本解析中完全移除模块。Bazel
不会从注册表请求这些 MODULE.bazel 文件,而是从
代码库本身请求。
Bazel 支持以下非注册表替换:
请注意,当使用非注册表替换替换模块时,在源归档 MODULE.bazel 中设置版本值可能会有
缺点。如需详细了解此问题,请
参阅MODULE.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命令获取给定代码库集的显示名称到规范名称的映射。
模块扩展还可以将其他代码库 引入模块的可见范围。