外部依赖项概览

报告问题 查看源代码

Bazel 支持外部依赖项,以及构建中使用的并非来自工作区的源文件(文本和二进制文件)。例如,它们可能是托管在 GitHub 代码库中的规则集、Maven 工件或本地机器上的当前工作区之外的目录。

从 Bazel 6.0 开始,您可以通过以下两种方式使用 Bazel 管理外部依赖项:以代码库为中心的传统 WORKSPACE 系统和以模块的较新 MODULE.bazel 系统(代号为 Bzlmod,启用时带有 --enable_bzlmod 标志)。这两个系统可以一起使用,但 Bzlmod 将在未来的 Bazel 迁移版本中替换 WORKSPACE 系统。请参阅Bzlmod.

本文档先解释了有关 Bazel 中外部依赖项管理的概念,然后再按顺序更详细地介绍这两个系统。

概念

仓库

一个目录树,其根目录带有边界标记文件,其中包含可在 Bazel 构建中使用的源文件。通常简称为“repo”。

代码库边界标记文件可以是 MODULE.bazel(表示此代码库代表 Bazel 模块)、REPO.bazel(请参阅下文);在旧版环境中,也可以是 WORKSPACEWORKSPACE.bazel。任何代码库边界标记文件都将表示代码库的边界;一个目录中可以共存多个此类文件。

主代码库

运行当前 Bazel 命令的代码库。

主代码库的根目录也称为工作区根目录

工作区

所有 Bazel 命令共享的环境都在同一个主代码库中运行。它包含主代码库以及所有已定义的外部代码库的集合。

请注意,过去,“代码库”和“工作区”的概念被混淆;“工作区”一词通常用于指代主代码库,有时甚至用作“代码库”的同义词。

规范化代码库名称

代码库可寻址的规范名称。在工作区环境中,每个代码库都有一个规范名称。代码库中的规范名称为 canonical_name 的目标可通过标签 @@canonical_name//pac/kage:target(请注意双 @)进行寻址。

主代码库始终将空字符串作为规范名称。

表观的代码库名称

代码库的名称,可用于特定其他代码库的上下文。这可视为代码库的“昵称”:具有规范名称 michael 的代码库在代码库 alice 的上下文中可能具有表观名称 mike,但在代码库 bob 的上下文中可能具有表观名称 mickey。在这种情况下,michael 内的目标可以在 alice 的上下文中通过标签 @mike//pac/kage:target 进行寻址(请注意,只有一个 @)。

相反,这可以理解为“代码库映射”:每个代码库都维护着从“明显的代码库名称”到“规范代码库名称”的映射。

代码库规则

用于指示 Bazel 如何具体化代码库的代码库定义的架构。例如,可以“从特定网址下载 zip 归档文件并解压缩”,或者“获取某个 Maven 工件并使其作为 java_import 目标可用”,或者只是“对本地目录进行符号链接”。每个代码库都是通过调用具有适当数量的参数的代码库规则进行定义

如需详细了解如何编写自己的代码库规则,请参阅代码库规则

到目前为止,最常用的代码库规则是 http_archive,用于从网址下载归档文件并将其解压缩;以及 local_repository,用于对已作为 Bazel 代码库的本地目录进行符号链接。

提取代码库

通过运行关联的 Repo 规则,在本地磁盘上提供代码库的操作。在提取之前,工作区中定义的代码库在本地磁盘上不可用。

通常,Bazel 仅在需要从代码库中获取某些内容,且尚未提取代码库时才会提取代码库。如果之前已提取过代码库,则 Bazel 只会在其定义发生变化时重新提取该代码库。

fetch 命令可用于为代码库、目标或执行任何构建所需的所有代码库启动预提取操作。此功能支持使用 --nofetch 选项进行离线构建。

--fetch 选项用于管理网络访问权限。其默认值为 true。 不过,如果设置为 false (--nofetch),该命令将利用相应依赖项的任何缓存版本;如果不存在任何缓存版本,该命令将导致失败。

如需详细了解如何控制提取,请参阅提取选项

目录布局

提取后,您可以在输出库中的 external 子目录中的规范名称下找到代码库。

您可以运行以下命令,查看规范名称为 canonical_name 的代码库的内容:

ls $(bazel info output_base)/external/ canonical_name 

REPO.bazel 文件

REPO.bazel 文件用于标记构成代码库的目录树的最顶层边界。它无需包含任何内容即可充当代码库边界文件;不过,它还可用于为代码库中的所有构建目标指定一些通用属性。

REPO.bazel 文件的语法与 BUILD 文件类似,不同之处在于前者不支持 load 语句,而只提供单个函数 repo()repo() 采用与 BUILD 文件中的 package() 函数相同的参数;package() 则用于指定软件包内所有 build 目标的通用属性,而 repo() 则与之类似地为代码库内的所有构建目标指定通用属性。

例如,您可以使用以下 REPO.bazel 文件为代码库中的所有目标指定通用许可:

repo(
    default_package_metadata = ["//:my_license"],
)

使用 Bzlmod 管理外部依赖项

Bzlmod 是新的外部依赖项子系统,不直接使用 Repo 定义。而是从模块构建依赖关系图,在依赖关系图之上运行扩展程序,并相应地定义代码库。

Bazel 模块是一种可以有多个版本的 Bazel 项目,每个版本都会发布相关模块所依赖的其他模块的元数据。模块的代码库根目录中必须有 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")

模块只能列出其直接依赖项,Bzlmod 会在 Bazel 注册表(默认为 Bazel Central Registry)中查找这些依赖项。注册表提供了依赖项的 MODULE.bazel 文件,以便 Bazel 能够在执行版本解析之前发现整个传递依赖项图。

版本解析(为每个模块选择一个版本)后,Bazel 会再次查询注册表,以了解如何为每个模块定义代码库(大多数情况下使用 http_archive)。

模块还可以指定称为标记的自定义数据片段,在模块解析后,模块扩展会使用这些数据块,以定义额外的仓库。这些扩展程序具有与 Repo 规则类似的功能,能够执行文件 I/O 和发送网络请求等操作。此外,借助这些 API,Bazel 可以与其他软件包管理系统进行交互,同时遵循基于 Bazel 模块构建的依赖关系图。

使用 WORKSPACE 定义代码库

过去,您可以通过在 WORKSPACE(或 WORKSPACE.bazel)文件中定义代码库来管理外部依赖项。此文件的语法与 BUILD 文件类似,使用 Repo 规则而不是构建规则。

以下代码段展示了如何在 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 文件中调用此宏。
    • 这也有自己的问题:宏无法对其他 .bzl 文件执行 load 操作,因此这些项目必须在此“deps”宏中定义其传递依赖项,或者让用户调用多个分层“deps”宏来解决此问题。
    • Bazel 会依序评估 WORKSPACE 文件。此外,依赖项是使用带有网址的 http_archive 指定的,不包含任何版本信息。这意味着,对于菱形依赖项(A 依赖于 BCBC 都依赖于不同版本的 D),无法可靠地执行版本解析。

由于 WORKSPACE 的缺点,Bzlmod 将在未来的 Bazel 版本中取代旧版 WORKSPACE 系统。请参阅 Bzlmod 迁移指南,了解如何迁移到 Bzlmod。