使用外部依赖项

报告问题 查看源代码

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 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

离线构建

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

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

Bazel 本身使用此技术从分发工件进行离线引导。为此,它会在内部 distdir_tar收集所有需要的外部依赖项

但是,bazel 允许在代码库规则中执行任意命令,而无需知道它们是否向网络调用。因此,bazel 无法选择将构建强制设为完全离线。因此,要测试 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()。请参阅“考虑移除绑定”,查看关于其问题和替代方案的长期讨论。