常见问题解答

本页回答了有关 Bazel 中外部依赖项的一些常见问题。

MODULE.bazel

如何为 Bazel 模块设置版本?

如果未仔细管理,在源归档 MODULE.bazel中使用module指令设置version可能会带来一些缺点和意外的副作用:

  • 重复:发布模块的新版本通常需要同时 递增 MODULE.bazel 中的版本并为版本添加标记,这两个 步骤是分开的,可能会失去同步。虽然自动化可以 降低这种风险,但最好是完全避免这种情况,这样更简单也更安全。

  • 不一致:用户使用 非注册表替换项替换具有特定提交的模块时,会看到不正确的版本。例如,如果源归档中的 MODULE.bazel设置了version = "0.3.0",但 自该版本发布以来又进行了其他提交,则使用 其中一个提交进行替换的用户仍会看到0.3.0。实际上,版本 应反映其领先于该版本,例如 0.3.1-rc1

  • 非注册表替换项问题:当用户使用非注册表替换项替换模块时,使用占位符值可能会导致问题。 例如, 0.0.0 不会作为最高版本进行排序,而这通常是用户在使用非注册表替换项时希望看到的 行为。

因此,最好避免在源归档中设置版本 MODULE.bazel。相反,应在注册表 (例如 Bazel Central Registry)中存储的 MODULE.bazel 中设置版本,这是 Bazel 在外部依赖项解析期间模块版本的实际真实来源 (请参阅 Bazel 注册表)。

这通常是自动化的,例如,rules-template 示例规则 代码库使用 bazel-contrib/publish-to-bcr publish.yaml GitHub 操作 将 版本发布到 BCR。该操作会为源归档 MODULE.bazel 生成一个包含发布版本的补丁。此补丁存储在 注册表中,并在 Bazel 的外部 依赖项解析期间提取模块时应用。

这样,注册表中版本的版本将正确设置为 已发布版本,因此,bazel_depsingle_version_overridemultiple_version_override 将按预期工作,同时避免在使用非注册表替换项时可能出现的问题,因为源 归档中的版本将是默认值 (''),该值始终会得到正确 处理(毕竟它是默认版本值),并且在排序时会按 预期运行(空字符串被视为最高版本)。

何时应递增兼容性级别?

Bazel 模块的 compatibility_level 应在引入向后 不兼容(“重大”)更改的同一提交中递增。

但是,如果 Bazel 检测到已解析的依赖项 图中存在具有不同兼容性级别同一 模块的版本,则可能会抛出错误。例如,当两个模块依赖于具有不同兼容性级别的 第三个模块的版本时,可能会发生这种情况。

因此,过于频繁地递增 compatibility_level 可能会造成严重中断 不建议这样做。为避免这种情况,只有当重大更改影响大多数用例且不容易迁移和/或解决时,才应compatibility_level 递增__。

为什么 MODULE.bazel 不支持 load

在依赖项解析期间,所有引用的外部 依赖项的 MODULE.bazel 文件都会从注册表中提取。在此阶段,依赖项的源归档尚未提取;因此,如果 MODULE.bazel 文件 loads 另一个文件,Bazel 无法在不提取整个源归档的情况下实际提取该文件。请注意,MODULE.bazel 文件本身是 特殊的,因为它直接托管在注册表中。

人们通常对在 MODULE.bazel 中请求 load 的一些用例感兴趣, 这些用例可以在没有 load 的情况下解决:

  • 确保 MODULE.bazel 中列出的版本与存储在其他位置(例如在 .bzl 文件中)的 build 元数据一致:这可以通过在从 BUILD 文件加载的 .bzl 文件中使用 native.module_version方法 来实现。
  • 将非常大的 MODULE.bazel 文件拆分为可管理的部分, 尤其对于单体代码库:根模块可以使用 include 指令将其 MODULE.bazel 文件拆分为多个段。出于与不允许在 MODULE.bazel 文件中使用 load相同的原因,include不能在非根模块中使用。
  • 旧 WORKSPACE 系统的用户可能还记得声明代码库,然后 立即 loading 从该代码库以执行复杂的逻辑。此 功能已替换为模块扩展程序

我可以为 bazel_dep 指定 SemVer 范围吗?

不可以。其他一些软件包管理器(如 npmCargo 支持版本范围(隐式或显式),这通常需要 约束求解器(使用户更难预测输出),并且在没有锁定文件的情况下使 版本解析不可重现。

Bazel 改为使用 Minimal Version Selection(如 Go),这与前者相比,使输出易于预测并保证了 可重现性。这是符合 Bazel 设计目标的权衡。

此外,Bazel 模块版本是 SemVer 的超集,因此在严格的 SemVer 环境中合理的内容并不总是适用于 Bazel 模块版本。

我可以自动获取 bazel_dep 的最新版本吗?

一些用户偶尔会要求能够指定 bazel_dep(name = "foo", version = "latest") 以自动获取依赖项的最新版本。这与 有关 SemVer 范围的问题类似,答案也是 否定的。

此处的建议解决方案是让自动化来处理此问题。例如,Renovate 支持 Bazel 模块。

有时,提出此问题的用户实际上是在寻找一种在本地开发期间快速 迭代的方法。这可以通过使用 local_path_override来实现。

为什么会有这么多 use_repo

MODULE.bazel 文件中的模块扩展程序使用有时会附带一个大的 use_repo 指令。例如, go_deps扩展程序的典型用法gazelle可能如下所示:

go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(
    go_deps,
    "com_github_gogo_protobuf",
    "com_github_golang_mock",
    "com_github_golang_protobuf",
    "org_golang_x_net",
    ...  # potentially dozens of lines...
)

较长的 use_repo 指令可能看起来是多余的,因为这些信息 可以说已在引用的 go.mod 文件中。

Bazel 需要此 use_repo 指令的原因是,它会延迟运行模块 扩展程序。也就是说,只有在观察到模块扩展程序的结果时 ,才会运行该模块扩展程序。由于模块扩展程序的“输出”是代码库定义,这意味着 只有在请求模块扩展程序定义的代码库时,我们才会运行该模块扩展程序(例如,如果构建了目标 @org_golang_x_net//:foo,则在上面的示例 中)。但是,在运行模块扩展程序之前,我们不知道它会定义哪些代码库,直到 我们运行它。这就是 use_repo 指令的用武之地;用户可以 告知 Bazel 他们希望扩展程序生成哪些代码库,然后 Bazel 会 只在使用这些特定代码库时运行该扩展程序。

为了帮助维护此 use_repo 指令,模块扩展程序可以返回 一个 extension_metadata 对象,从其实现函数。用户可以运行 bazel mod tidy 命令来更新这些模块扩展程序的 use_repo 指令。

Bzlmod 迁移

先评估 MODULE.bazel 还是 WORKSPACE?

如果同时设置了 --enable_bzlmod--enable_workspace,那么自然会 想知道哪个系统先被查询。简短的回答是,先评估 MODULE.bazel (Bzlmod)。

详细的回答是,“哪个先评估”不是正确的问题;相反,正确的问题是:在具有 规范名称 @@foo的代码库的上下文中,明显的 代码库名称 @bar解析为哪个?或者, 的代码库映射是什么?@@base

具有明显代码库名称(单个前导 @)的标签可以根据其解析的上下文引用不同的 内容。当您看到标签 @bar//:baz 并想知道它实际指向什么时,您需要先找出 上下文代码库是什么:例如,如果标签位于代码库 @@foo 中的 BUILD 文件中,则上下文代码库为 @@foo

然后,根据上下文代码库是什么,可以使用迁移指南中的"代码库 可见性"表来找出明显名称实际解析为哪个代码库。

  • 如果上下文代码库是主代码库 (@@):
    1. 如果 bar 是根模块的 MODULE.bazel 文件(通过 bazel_depuse_repomoduleuse_repo_rule 中的任何一个)引入的明显代码库名称,则 @bar 解析为该 MODULE.bazel 文件声明的内容。
    2. 否则,如果 bar 是在 WORKSPACE 中定义的代码库(这意味着其 规范名称为 @@bar),则 @bar 解析为 @@bar
    3. 否则,@bar 解析为类似 @@[unknown repo 'bar' requested from @@] 的内容,这最终会导致 错误。
  • 如果上下文代码库是 Bzlmod 世界代码库(也就是说,它对应于非根 Bazel 模块,或由模块扩展程序生成),那么它只会看到其他 Bzlmod 世界代码库,而不会看到 WORKSPACE 世界代码库。
    • 值得注意的是,这包括在根模块的类似 non_module_deps 的 模块扩展程序中引入的任何代码库,或 use_repo_rule 实例化 在根模块中。
  • 如果上下文代码库是在 WORKSPACE 中定义的:
    1. 首先,检查上下文代码库定义是否具有神奇的 repo_mapping属性。如果有,请先查看映射(因此,对于使用 repo_mapping = {"@bar": "@baz"} 定义的 代码库,我们将在下面查看 @baz)。
    2. 如果 bar 是根模块的 MODULE.bazel 文件引入的明显代码库名称,则 @bar 解析为该 MODULE.bazel 文件 声明的内容。(这与主代码库情况下的第 1 项相同。)
    3. 否则,@bar 解析为 @@bar。这很可能会指向在 WORKSPACE 中定义的 代码库 bar;如果未定义此类代码库,Bazel 将抛出错误。

如需查看更简洁的版本,请参阅以下内容:

  • Bzlmod 世界代码库(不包括主代码库)只会看到 Bzlmod 世界 代码库。
  • WORKSPACE 世界代码库(包括主代码库)将首先看到 Bzlmod 世界中的根 模块定义的内容,然后回退到查看 WORKSPACE 世界 代码库。

值得注意的是,Bazel 命令行中的标签(包括 Starlark 标志、标签类型 标志值以及 build/test 目标模式)被视为将主代码库 作为上下文代码库。

其他

如何准备和运行离线 build?

使用 bazel fetch 命令预提取代码库。您可以使用 --repo 标志 (如 bazel fetch --repo @foo)仅提取代码库 @foo(在主代码库的 上下文中解析,请参阅上面的问题),或使用目标 模式(如 bazel fetch @foo//:bar)提取 @foo//:bar 的所有传递依赖项 (这等同于 bazel build --nobuild @foo//:bar)。

如需确保在 build 期间不发生提取,请使用 --nofetch。更准确地说, 这会使任何运行非本地代码库规则的尝试失败。

如果您想提取代码库 对其进行修改以在本地进行测试,请考虑使用 bazel vendor 命令。

如何使用 HTTP 代理?

Bazel 遵循其他程序(如 curl)通常 接受的 http_proxyHTTPS_PROXY 环境变量。

如何在双栈 IPv4/IPv6 设置中让 Bazel 优先使用 IPv6?

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

  • 使用 --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 选项。

代码库规则是否可以使用远程执行在远程运行?

不可以;或者至少目前还不行。使用远程执行服务来加快 build 速度的用户可能会注意到,代码库规则仍在本地运行。例如,一个 http_archive会先下载到本地机器(如果适用,则使用任何本地 下载缓存),然后解压缩,之后每个源文件会 作为输入文件上传到远程执行服务。很自然地会问 为什么远程执行服务不直接下载并解压缩该归档, 从而节省无用的往返行程。

部分原因是,代码库规则(和模块扩展程序)类似于 “脚本”,由 Bazel 本身运行。远程执行器甚至不一定 安装了 Bazel。

另一个原因是,Bazel 通常需要下载并 解压缩归档中的 BUILD 文件来执行加载和分析,而这些操作是在本地 执行的。

目前有一些初步的想法,可以通过将代码库规则重新构想为 build 规则来解决此问题,这样自然就可以在远程运行这些规则,但反过来 也会引发新的架构问题(例如,query 命令可能 需要运行操作,从而使其设计复杂化)。

如需详细了解之前有关此主题的讨论,请参阅支持需要 Bazel 才能提取的代码库的方法