外部依赖项概览

报告问题 查看源代码 每夜 build · 8.1 · 8.0 · 7.6 · 7.5 · 7.4

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 文件的冲突。
  • 与特定于语言的软件包管理器的统一集成:Rulesets 简化了与非 Bazel 依赖项的外部软件包管理器的集成,包括:

高级功能

  • 模块扩展:借助 use_repo_rule 和模块扩展功能,您可以灵活使用自定义代码库规则和解析逻辑来引入任何非 Bazel 依赖项。
  • bazel mod 命令:此子命令提供了强大的外部依赖项检查方法。您可以准确了解外部依赖项的定义方式和来源。
  • 供应商模式:预提取您需要的确切外部依赖项,以便进行离线构建。
  • Lockfile:锁文件可提高构建可重复性并加快依赖项解析速度。
  • (即将推出)BCR 来源证明:通过确保依赖项的来源经过验证,加强供应链安全性。

概念

本部分详细介绍了与外部依赖项相关的概念。

模块

一个 Bazel 项目可以有多个版本,每个版本都可以依赖于其他模块。

在本地 Bazel 工作区中,模块由代码库表示。

如需了解详情,请参阅 Bazel 模块

代码库

一个目录树,其根目录包含边界标记文件,其中包含可在 Bazel build 中使用的源文件。通常简称为代码库

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

主代码库

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

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

工作区

在同一主仓库中运行的所有 Bazel 命令共享的环境。它包含主代码库和所有已定义的外部代码库。

请注意,在历史上,“代码库”和“工作区”这两个概念一直被混淆;“工作区”一词通常用于指代主代码库,有时甚至用作“代码库”的同义词。

规范代码库名称

代码库的规范名称。在工作区的上下文中,每个代码库都有一个规范名称。可以使用标签 @@canonical_name//package:target(注意双 @)来指定代码库中规范名称为 canonical_name 的目标。

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

明显的代码库名称

在某个其他代码库的上下文中可用于访问某个代码库的名称。这可以被视为代码库的“别名”:具有规范名称 michael 的代码库在代码库 alice 的上下文中可能具有显式名称 mike,但在代码库 bob 的上下文中可能具有显式名称 mickey。在这种情况下,michael 内的目标可通过 alice 上下文中的标签 @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 文件用于标记构成代码库的目录树的顶层边界。它无需包含任何内容即可用作代码库边界文件;不过,它还可用于为代码库内的所有构建目标指定一些常见属性。

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 文件类似,采用的是代码库规则,而不是构建规则。

以下代码段展示了如何在 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 依赖于 BCBC 都依赖于不同版本的 D),没有可靠的方法来执行版本解析。

由于 WORKSPACE 存在缺点,在 Bazel 6 到 9 之间,基于模块的新系统(代号为“Bzlmod”)逐渐取代了旧版 WORKSPACE 系统。如需了解如何迁移到 Bzlmod,请参阅 Bzlmod 迁移指南