使用外部依赖项

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 项目中的目标,可以使用 use 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 异常和构建失败。 在这些情况下,您可以使用 Bazel 的行为替换以优先使用 IPv6 通过使用 java.net.preferIPv6Addresses=true 系统属性。 具体而言:

  • 使用 --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),而项目 (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

离线构建

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

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

Bazel 本身使用此技术从 分发 工件离线启动。它通过收集所有必需的外部 依赖项 在内部 distdir_tar来实现此目的。

但是,Bazel 允许在代码库规则中执行任意命令,而无需知道它们是否会调用网络。因此,Bazel 没有强制构建完全离线的选项。因此,测试构建是否在离线状态下正常运行需要外部阻止网络,就像 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()。如需详细了解其问题和替代方案,请参阅“考虑移除 绑定”。