Bazel 模块是指可以有多个版本的 Bazel 项目,每个 用于发布其所依赖的其他模块的元数据。这是 类似于其他依赖管理系统中熟悉的概念, Maven 工件、npm 软件包、Go 模块或 Cargo crate。
模块的 Repo 根目录中必须有一个 MODULE.bazel
文件(位于
WORKSPACE
文件)。这个文件是模块的清单,声明了其名称
版本、直接依赖项列表等信息。对于基本的
示例:
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 拥有多元化的生态系统,项目使用各种版本控制方案。通过
是迄今为止最受欢迎的软件版本 SemVer,但
也有一些采用不同架构的优秀项目
Abseil,其
版本基于日期,例如 20210324.2
)。
因此,Bzlmod 采用了更为宽松的 SemVer 规范。通过 差异包括:
- SemVer 规定,“发布”部分版本必须包含 3
细分受众群:
MAJOR.MINOR.PATCH
。在 Bazel 中,这项要求放宽了 允许任意数量的细分受众群 - 在 SemVer 中,“版本”中的每个细分部分只能包含数字。 在 Bazel 中,我们放宽了限制,允许使用字母, 语义匹配“identifiers”在“预发布”中部分。
- 此外,主要、次要和补丁版本递增的语义 未强制执行。不过,请参阅兼容性级别 详细了解我们如何表示向后兼容性
任何有效的 SemVer 版本都是有效的 Bazel 模块版本。此外,还有两个
当且仅当相同内容成立时,SemVer 版本 a
和 b
才会比较 a < b
将它们作为 Bazel 模块版本进行比较
版本选择
考虑菱形依赖项问题,这是版本化依赖项的主要问题 管理空间假设您有以下依赖关系图:
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 可以创建
版本选择流程高保真度且可重现。
被取消的版本
如果应避免某些版本,注册表可以声明为“被抢劫”
(例如,针对安全漏洞)。在选择
拉取版本的模块。要修复此错误,请升级到
非拉取版本,或使用
--allow_yanked_versions
标志,以明确允许被拉取的版本。
兼容性级别
在 Go 中,MVS 关于向后兼容性的假设之所以可行,是因为它
作为独立模块提供的向后不兼容版本。对于
SemVer,这意味着 A 1.x
和 A 2.x
被视为不同的模块,
共存于已解析的依赖关系图中。这反过来又使得
在 Go 的软件包路径中对 Major 版本进行编码,
编译时冲突或链接时冲突。
但是,Bazel 无法提供此类保证,因此它需要“主要版本”
编号,以检测向后不兼容的版本。此号码称为
兼容性级别,由每个模块版本在其
module()
指令。掌握了这些信息后,Bazel 可能会在以下情况下抛出错误:
会检测到具有不同兼容性级别的同一模块的版本
已解析的依赖关系图中存在。
覆盖对象
在 MODULE.bazel
文件中指定替换项以更改 Bazel 的行为
模块分辨率。只有根模块的替换项才会生效(如果模块是
用作依赖项,其替换项会被忽略。
每个替换项都是为特定模块名称指定的,这会影响其所有 依赖关系图中的各个版本虽然只有根模块的替换项 它们可以针对根模块没有的传递依赖项 直接依赖的技术。
单个版本替换
single_version_override
有多种用途:
- 借助
version
属性,您可以将依赖项固定到特定 所有版本的依赖项,无论在 依赖关系图 - 借助
registry
属性,您可以强制此依赖项来自 特定注册表,而不是遵循常规的注册表 选择流程。 - 借助
patch*
属性,您可以指定一组要应用于的补丁。 下载的模块中。
这些属性均为可选属性,可以相互混合和匹配。
多版本替换
multiple_version_override
可指定允许同一模块的多个版本
已解析的依赖关系图。
您可以为模块指定一个明确的允许版本列表,该列表必须 在解析之前都存在于依赖关系图中 - 必须存在 一些传递依赖项,具体取决于每个允许使用的版本。更新后 在 Bazel 升级的过程中, 将模块的其他版本更新为相同的允许更高的最接近版本 兼容性级别。如果没有相同兼容性且允许更高的版本 则 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.5
和2.0
的多版本替换会导致错误,因为1.7
没有相同兼容性级别的更高版本可升级到。 - 允许
1.9
和2.0
的多版本替换会导致错误,因为 在解析之前,依赖关系图中不存在1.9
。
此外,用户还可以使用 registry
覆盖注册表。
属性,这类似于单一版本替换。
非注册表覆盖
非注册表替换会从版本解析中完全移除模块。Bazel
不会从注册表中请求这些MODULE.bazel
文件,而是从
代码库本身
Bazel 支持以下非注册表替换项:
定义不代表 Bazel 模块的代码库
借助 bazel_dep
,您可以定义代表其他 Bazel 模块的代码库。
有时需要定义的代码库不代表 Bazel
module;例如包含要作为数据读取的纯 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
。
* 查找 runfile 时,请使用
$(rlocationpath ...)
或某个运行文件库中的
@bazel_tools//tools/{bash,cpp,java}/runfiles
;或者,对于规则集 rules_foo
,
在 @rules_foo//foo/runfiles
中。
* 从 IDE 或语言等外部工具与 Bazel 交互时
请使用 bazel mod dump_repo_mapping
命令从 Cloud Storage 中获取
将显而易见名称转换为规范名称。
模块扩展还可以引入其他代码库 放入模块的可见作用域内