使用外部依赖项

报告问题 查看源代码 每夜 build · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Bazel 可以依赖于其他项目中的目标。来自这些其他项目的依赖项称为外部依赖项

工作区目录中的 WORKSPACE 文件(或 WORKSPACE.bazel 文件)会告知 Bazel 如何获取其他项目的源代码。这些其他项目可以包含一个或多个具有各自目标的 BUILD 文件。主项目中的 BUILD 文件可以通过使用 WORKSPACE 文件中的名称来依赖于这些外部目标。

例如,假设系统中有两个项目:

/
  home/
    user/
      project1/
        WORKSPACE
        BUILD
        srcs/
          ...
      project2/
        WORKSPACE
        BUILD
        my-libs/

如果 project1 想要依赖于 /home/user/project2/BUILD 中定义的目标 :foo,则可以指定在 /home/user/project2 中可以找到名为 project2 的代码库。然后,/home/user/project1/BUILD 中的目标可以依赖于 @project2//:foo

借助 WORKSPACE 文件,用户可以依赖于文件系统的其他部分或从互联网下载的目标。它使用的语法与 BUILD 文件相同,但允许使用一组不同的规则,称为“仓库规则”(有时也称为“工作区规则”)。Bazel 附带一些内置代码库规则和一组嵌入式 Starlark 代码库规则。用户还可以编写自定义代码库规则来实现更复杂的行为。

支持的外部依赖项类型

您可以使用一些基本类型的外部依赖项:

依赖于其他 Bazel 项目

如果您想使用第二个 Bazel 项目中的目标,可以使用 local_repositorygit_repositoryhttp_archive 分别从本地文件系统中创建符号链接、引用 Git 代码库或下载目标。

例如,假设您正在处理项目 my-project/,并且想要依赖于同事项目 coworkers-project/ 中的目标。这两个项目都使用 Bazel,因此您可以将同事的项目添加为外部依赖项,然后使用同事通过您自己的 build 文件定义的任何目标。您需要将以下代码添加到 my_project/WORKSPACE

local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
)

如果您的同事有一个目标 //foo:bar,您的项目可以将其称为 @coworkers_project//foo:bar。外部项目名称必须是有效的工作区名称

依赖于非 Bazel 项目

借助前缀为 new_ 的规则(例如 new_local_repository),您可以从不使用 Bazel 的项目创建目标。

例如,假设您正在处理项目 my-project/,并且您希望依赖于同事的项目 coworkers-project/。同事的项目使用 make 进行构建,但您希望依赖于它生成的某个 .so 文件。为此,请将以下代码添加到 my_project/WORKSPACE

new_local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
    build_file = "coworker.BUILD",
)

build_file 指定要叠加在现有项目的 BUILD 文件,例如:

cc_library(
    name = "some-lib",
    srcs = glob(["**"]),
    visibility = ["//visibility:public"],
)

然后,您可以从项目的 BUILD 文件依赖于 @coworkers_project//:some-lib

依赖于外部软件包

Maven 工件和代码库

使用规则集 rules_jvm_external 从 Maven 代码库下载工件,并使其作为 Java 依赖项提供。

提取依赖项

默认情况下,系统会在 bazel build 期间根据需要提取外部依赖项。如果您想预加载特定一组目标所需的依赖项,请使用 bazel fetch。如需无条件提取所有外部依赖项,请使用 bazel sync。由于提取的代码库存储在输出基础目录中,因此提取操作会针对每个工作区进行。

阴影依赖项

建议您尽可能在项目中使用单个版本政策。对于您要针对其进行编译并最终纳入最终二进制文件的依赖项,这是必需的。但是,如果情况并非如此,则可以阴影化依赖项。请考虑以下场景:

myproject/WORKSPACE

workspace(name = "myproject")

local_repository(
    name = "A",
    path = "../A",
)
local_repository(
    name = "B",
    path = "../B",
)

A/WORKSPACE

workspace(name = "A")

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "testrunner",
    urls = ["https://github.com/testrunner/v1.zip"],
    sha256 = "...",
)

B/WORKSPACE

workspace(name = "B")

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "testrunner",
    urls = ["https://github.com/testrunner/v2.zip"],
    sha256 = "..."
)

依赖项 AB 都依赖于 testrunner,但它们依赖于不同版本的 testrunner。这些测试运行程序没有理由不能在 myproject 中和平共存,但由于它们的名称相同,因此会相互冲突。如需声明这两个依赖项,请更新 myproject/WORKSPACE:

workspace(name = "myproject")

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "testrunner-v1",
    urls = ["https://github.com/testrunner/v1.zip"],
    sha256 = "..."
)
http_archive(
    name = "testrunner-v2",
    urls = ["https://github.com/testrunner/v2.zip"],
    sha256 = "..."
)
local_repository(
    name = "A",
    path = "../A",
    repo_mapping = {"@testrunner" : "@testrunner-v1"}
)
local_repository(
    name = "B",
    path = "../B",
    repo_mapping = {"@testrunner" : "@testrunner-v2"}
)

此机制也可用于连接菱形。例如,如果 AB 具有相同的依赖项,但使用不同的名称调用它,则可以在 myproject/WORKSPACE 中联接这些依赖项。

从命令行替换代码库

如需通过命令行使用本地代码库替换声明的代码库,请使用 --override_repository 标志。使用此标志可更改外部代码库的内容,而无需更改源代码。

例如,如需将 @foo 替换为本地目录 /path/to/local/foo,请传递 --override_repository=foo=/path/to/local/foo 标志。

部分用例包括:

  • 调试问题。例如,您可以将 http_archive 代码库替换为本地目录,以便更轻松地进行更改。
  • 供应商开发。如果您所在的环境无法进行网络调用,请替换基于网络的代码库规则,以指向本地目录。

使用代理

Bazel 将从 HTTPS_PROXYHTTP_PROXY 环境变量中提取代理地址,并使用这些地址下载 HTTP/HTTPS 文件(如果指定)。

支持 IPv6

在仅支持 IPv6 的机器上,Bazel 无需任何更改即可下载依赖项。不过,在双栈 IPv4/IPv6 机器上,Bazel 遵循与 Java 相同的惯例:如果启用了 IPv4,则首选 IPv4。在某些情况下,例如当 IPv4 网络无法解析/访问外部地址时,可能会导致 Network unreachable 异常和构建失败。在这些情况下,您可以使用 java.net.preferIPv6Addresses=true 系统属性来替换 Bazel 的行为以优先使用 IPv6。具体而言:

  • 使用 --host_jvm_args=-Djava.net.preferIPv6Addresses=true 启动选项,例如在 .bazelrc 文件中添加以下代码行:

    startup --host_jvm_args=-Djava.net.preferIPv6Addresses=true

  • 如果您运行的 Java build 目标也需要连接到互联网(集成测试有时需要这样做),还可以使用 --jvmopt=-Djava.net.preferIPv6Addresses=true 工具标志,例如,在 .bazelrc 文件中添加以下代码行:

    build --jvmopt=-Djava.net.preferIPv6Addresses

  • 如果您使用 rules_jvm_external(例如,用于依赖项版本解析),还应将 -Djava.net.preferIPv6Addresses=true 添加到 COURSIER_OPTS 环境变量,以为 Coursier 提供 JVM 选项

传递依赖项

Bazel 只会读取 WORKSPACE 文件中列出的依赖项。如果您的项目 (A) 依赖于另一个项目 (B),而该项目在其 WORKSPACE 文件中列出了对第三个项目 (C) 的依赖项,则您必须将 BC 都添加到项目的 WORKSPACE 文件中。此要求可能会使 WORKSPACE 文件大小膨胀,但会降低出现以下情况的可能性:一个库在版本 1.0 中包含 C,另一个库在版本 2.0 中包含 C

外部依赖项的缓存

默认情况下,只有在外部依赖项的定义发生变化时,Bazel 才会重新下载这些依赖项。bazel 还会考虑对定义中引用的文件(例如补丁或 BUILD 文件)所做的更改。

如需强制重新下载,请使用 bazel sync

布局

外部依赖项都会下载到输出基础目录中的子目录 external 下的一个目录。如果是本地代码库,系统会在其中创建符号链接,而不是创建新目录。您可以通过运行以下命令查看 external 目录:

ls $(bazel info output_base)/external

请注意,运行 bazel clean 不会实际删除外部目录,如需移除所有外部工件,请使用 bazel clean --expunge

离线 build

有时,以离线方式运行 build 是可取的或必要的。对于简单的用例(例如在飞机上旅行),使用 bazel fetchbazel sync 预提取所需的代码库就足够了;此外,使用选项 --nofetch 可在构建期间停用提取其他代码库的操作。

对于真正的离线构建(需要由不同于 bazel 的实体提供所需文件),Bazel 支持 --distdir 选项。每当代码库规则要求 Bazel 通过 ctx.downloadctx.download_and_extract 提取文件并提供所需文件的哈希总和时,Bazel 会先在该选项指定的目录中查找与所提供第一个网址的文件名匹配的文件,如果哈希匹配,则使用该本地副本。

Bazel 本身使用此方法从分发工件线下启动。为此,它会在内部 distdir_tar收集所有需要的外部依赖项

不过,bazel 允许在代码库规则中执行任意命令,而无需知道这些命令是否会调用网络。因此,Bazel 无法强制执行完全离线的 build。因此,如需测试 build 是否能够在线下正常运行,就需要对网络进行外部屏蔽,就像 bazel 在其引导测试中所做的那样。

最佳做法

代码库规则

代码库规则通常应负责:

  • 检测系统设置并将其写入文件。
  • 在系统的其他位置查找资源。
  • 从网址下载资源。
  • 将 BUILD 文件生成或符号链接到外部代码库目录。

请尽可能避免使用 repository_ctx.execute。例如,在使用具有使用 Make 的 build 的非 Bazel C++ 库时,最好使用 repository_ctx.download(),然后编写用于构建该 build 的 BUILD 文件,而不是运行 ctx.execute(["make"])

首选 http_archive,而不是 git_repositorynew_git_repository。原因如下:

  • Git 代码库规则依赖于系统 git(1),而 HTTP 下载程序内置于 Bazel 中,并且没有系统依赖项。
  • http_archive 支持将 urls 列表用作镜像,而 git_repository 仅支持单个 remote
  • http_archive 适用于代码库缓存,但不适用于 git_repository。如需了解详情,请参阅 #5116

请勿使用 bind()。如需详细了解 bind 的问题和替代方案,请参阅考虑移除 bind