Bazel 支持外部依赖项,即 build 中使用的并非来自工作区的源文件(包括文本文件和二进制文件)。例如,它们可以是托管在 GitHub 代码库中的规则集、Maven 制品,也可以是本地机器上当前工作区之外的目录。
本文档先简要介绍该系统,然后再详细探讨一些概念。
系统概览
Bazel 的外部依赖项系统基于 Bazel 模块(每个模块都是一个已纳入版本控制的 Bazel 项目)和代码库(或代码库,即包含源文件的目录树)运行。
Bazel 从根模块(即您正在处理的项目)开始。与所有模块一样,它需要在目录根目录中有一个 MODULE.bazel
文件,用于声明其基本元数据和直接依赖项。以下是一个基本示例:
module(name = "my-module", version = "1.0")
bazel_dep(name = "rules_cc", version = "0.1.1")
bazel_dep(name = "platforms", version = "0.0.11")
然后,Bazel 会在 Bazel 注册表(默认情况下为 Bazel 中央注册表)中查找所有传递依赖项模块。注册表提供依赖项的 MODULE.bazel
文件,这使得 Bazel 能够在执行版本解析之前发现整个传递依赖关系图。
在版本解析(为每个模块选择一个版本)之后,Bazel 会再次查询注册表,以了解如何为每个模块定义代码库,即如何获取每个依赖模块的源代码。大多数情况下,这些只是从互联网下载并提取的归档文件。
模块还可以指定称为标记的自定义数据块,这些数据块在模块解析后由模块扩展程序使用,以定义其他代码库。这些扩展程序可以执行文件 I/O 和发送网络请求等操作。除其他事项外,它们还允许 Bazel 与其他软件包管理系统进行交互,同时遵守由 Bazel 模块构建的依赖关系图。
这三种代码库(即您正在处理的源代码树所在的主代码库、表示传递依赖项模块的代码库,以及由模块扩展程序创建的代码库)共同构成了工作区。外部代码库(非主代码库)会按需提取,例如当 BUILD 文件中的标签(如 @repo//pkg:target
)引用它们时。
优势
Bazel 的外部依赖项系统具有广泛的优势。
自动处理依赖项
- 确定性版本解析:Bazel 采用确定性 MVS 版本解析算法,可最大限度地减少冲突并解决菱形依赖项问题。
- 简化了依赖项管理:
MODULE.bazel
仅声明直接依赖项,而传递依赖项会自动解析,从而更清晰地了解项目的依赖项。 - 严格的依赖项可见性:只有直接依赖项可见,确保正确性和可预测性。
生态系统集成
- Bazel 中央注册表:一个集中式存储库,用于发现和管理作为 Bazel 模块的常见依赖项。
- 采用非 Bazel 项目:当非 Bazel 项目(通常是 C++ 库)被改编为 Bazel 项目并在 BCR 中提供时,它会简化整个社区的集成,并消除自定义 BUILD 文件的重复工作和冲突。
- 与特定于语言的软件包管理器进行统一集成:规则集可简化与非 Bazel 依赖项的外部软件包管理器的集成,包括:
- 适用于 Maven 的 rules_jvm_external,
- rules_python(适用于 PyPi)、
- 适用于 Go 模块的 bazel-gazelle,
- 适用于 Cargo 的 rules_rust。
高级功能
- 模块扩展功能:借助
use_repo_rule
和模块扩展功能,您可以灵活使用自定义代码库规则和解析逻辑来引入任何非 Bazel 依赖项。 bazel mod
命令:此子命令提供了强大的外部依赖项检查方法。您确切知道外部依赖项的定义方式和来源。- 供应商模式:预提取您需要的确切外部依赖项,以方便离线构建。
- 锁定文件:锁定文件可提高 build 可重现性并加快依赖项解析速度。
- (即将推出)BCR 出处证明:通过确保依赖项的出处经过验证来加强供应链安全性。
概念
本部分更详细地介绍了与外部依赖项相关的概念。
模块
一个可以有多个版本的 Bazel 项目,每个版本都可以依赖于其他模块。
在本地 Bazel 工作区中,模块由代码库表示。
如需了解详情,请参阅 Bazel 模块。
代码库
根目录中包含边界标记文件的目录树,其中包含可在 Bazel build 中使用的源文件。通常简称为 repo。
代码库边界标记文件可以是 MODULE.bazel
(表示相应代码库代表一个 Bazel 模块)、REPO.bazel
(请参阅下文),或者在旧版环境中,可以是 WORKSPACE
或 WORKSPACE.bazel
。任何代码库边界标记文件都将表示代码库的边界;多个此类文件可以共存于一个目录中。
主代码库
当前 Bazel 命令正在运行的代码库。
主代码库的根目录也称为工作区根目录。
工作区
在同一主代码库中运行的所有 Bazel 命令共享的环境。它包含主代码库和所有已定义的外部代码库。
请注意,从历史上看,“代码库”和“工作区”的概念一直混为一谈;“工作区”一词通常用于指代主代码库,有时甚至用作“代码库”的同义词。
规范代码库名称
代码库始终可寻址的名称。在工作区的上下文中,每个代码库都有一个规范名称。如果代码库中某个目标的规范名称为 canonical_name
,则可以使用标签 @@canonical_name//package:target
(请注意双 @
)来寻址该目标。
主代码库的规范名称始终为空字符串。
表面上的代码库名称
在特定其他代码库的上下文中,代码库的可寻址名称。您可以将此视为代码库的“昵称”:具有规范名称 michael
的代码库在代码库 alice
的上下文中可能具有显式名称 mike
,但在代码库 bob
的上下文中可能具有显式名称 mickey
。在这种情况下,在 alice
的上下文中,michael
内的目标可以通过标签 @mike//package:target
来寻址(请注意单个 @
)。
反之,这可以理解为代码库映射:每个代码库都维护着从“表面代码库名称”到“规范代码库名称”的映射。
代码库规则
用于存储库定义的架构,用于告知 Bazel 如何实现存储库。例如,它可以是“从特定网址下载 zip 归档并将其解压缩”,也可以是“提取特定 Maven 制品并将其作为 java_import
目标提供”,或者只是“为本地目录创建符号链接”。每个代码库都通过调用具有适当数量实参的代码库规则来定义。
如需详细了解如何编写自己的代码库规则,请参阅代码库规则。
目前最常见的代码库规则是 http_archive
(用于从网址下载归档并提取归档)和 local_repository
(用于对已是 Bazel 代码库的本地目录进行符号链接)。
提取代码库
通过运行与代码库关联的代码库规则,使代码库在本地磁盘上可用的操作。工作区中定义的代码库在被提取之前无法在本地磁盘上使用。
通常,Bazel 仅在需要从代码库中获取某些内容且尚未获取该代码库时才会提取代码库。如果之前已提取过相应代码库,则仅当其定义发生变化时,Bazel 才会重新提取。
fetch
命令可用于启动对代码库、目标或所有必要代码库的预提取,以执行任何 build。此功能支持使用 --nofetch
选项进行离线 build。
--fetch
选项用于管理网络访问权限。其默认值为 true。
不过,如果设置为 false (--nofetch
),该命令将使用依赖项的任何缓存版本,如果不存在任何缓存版本,该命令将失败。
如需详细了解如何控制提取,请参阅提取选项。
目录布局
提取后,您可以在输出库的子目录 external
中找到该代码库,其名称为标准名称。
您可以运行以下命令来查看具有规范名称 canonical_name
的代码库的内容:
ls $(bazel info output_base)/external/ canonical_name
REPO.bazel 文件
REPO.bazel
文件用于标记构成代码库的目录树的最顶层边界。它无需包含任何内容即可作为代码库边界文件;不过,它也可用于为代码库内的所有 build 目标指定一些通用属性。
REPO.bazel
文件的语法与 BUILD
文件类似,但不支持 load
语句。repo()
函数采用与 BUILD
文件中的 package()
函数相同的实参;package()
用于指定软件包内所有 build 目标的通用属性,而 repo()
则以类似方式指定代码库内所有 build 目标的通用属性。
例如,您可以通过创建以下 REPO.bazel
文件,为代码库中的所有目标指定通用许可:
repo(
default_package_metadata = ["//:my_license"],
)
旧版 WORKSPACE 系统
在较旧的 Bazel 版本(9.0 之前)中,通过在 WORKSPACE
(或 WORKSPACE.bazel
)文件中定义代码库来引入外部依赖项。此文件的语法与 BUILD
文件类似,但采用的是 repo 规则,而不是 build 规则。
以下代码段展示了如何在 WORKSPACE
文件中使用 http_archive
代码库规则:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "foo",
urls = ["https://example.com/foo.zip"],
sha256 = "c9526390a7cd420fdcec2988b4f3626fe9c5b51e2959f685e8f4d170d1a9bd96",
)
此代码段定义了一个规范名称为 foo
的代码库。在 WORKSPACE
系统中,默认情况下,代码库的规范名称也是其对所有其他代码库的显示名称。
如需查看 WORKSPACE
文件中可用的函数完整列表,请点击相应链接。
WORKSPACE
系统的缺点
在 WORKSPACE
系统推出后的几年里,用户报告了许多痛点,包括:
- Bazel 不会评估任何依赖项的
WORKSPACE
文件,因此除了直接依赖项之外,所有传递依赖项都必须在主代码库的WORKSPACE
文件中定义。 - 为了解决这个问题,项目采用了“deps.bzl”模式,在该模式下,项目定义了一个宏,该宏又定义了多个代码库,并要求用户在其
WORKSPACE
文件中调用此宏。- 但这也有自己的问题:宏无法
load
其他.bzl
文件,因此这些项目必须在此“deps”宏中定义其传递依赖项,或者通过让用户调用多个分层“deps”宏来解决此问题。 - Bazel 会按顺序评估
WORKSPACE
文件。此外,依赖项使用http_archive
和网址指定,不包含任何版本信息。这意味着,在出现菱形依赖项(A
依赖于B
和C
;B
和C
都依赖于不同版本的D
)的情况下,无法可靠地执行版本解析。
- 但这也有自己的问题:宏无法
由于 WORKSPACE 存在缺点,在 Bazel 6 和 9 之间,新的基于模块的系统(代号为“Bzlmod”)逐渐取代了旧版 WORKSPACE 系统。请参阅 Bzlmod 迁移指南,了解如何迁移到 Bzlmod。