使用外部依赖项

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(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 代码库替换为本地目录,以便在其中更轻松地进行更改。
  • Selling。如果处于无法进行网络调用的环境中,请将基于网络的代码库规则替换为指向本地目录。

使用代理

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 文件大小,但会限制一个库包含 C(版本 1.0)和另一个库包含 C(版本 2.0)的可能性。

缓存外部依赖项

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

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

布局

外部依赖项都将下载到输出基准external 子目录下的目录中。对于本地代码库,系统会在此处创建符号链接,而不是创建新目录。您可以运行以下命令来查看 external 目录:

ls $(bazel info output_base)/external

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

离线 build

有时需要或必须离线运行构建。对于简单的用例(例如飞机出行),使用 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 的构建的非 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()。请参阅“考虑移除绑定”,详细了解其问题和替代方案。