使用外部依赖项

报告问题 查看源代码 每夜 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(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 构建目标也需要连接到互联网(集成测试有时需要这样),请同时使用 --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 进行构建的非 Bazel C++ 库时,最好使用 repository_ctx.download(),然后编写用于构建该库的 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