本页回答了一些关于 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 中设置版本。相反,应在注册表中存储的 MODULE.bazel(例如 Bazel Central Registry)中设置版本,这是 Bazel 在外部依赖项解析期间模块版本的实际真实来源(请参阅 Bazel
注册表)。
这通常是自动化的,例如 rules-template 示例规则
代码库使用 bazel-contrib/publish-to-bcr publish.yaml GitHub 操作 将
版本发布到 BCR。该操作会为源归档 生成包含发布版本的补丁
archive MODULE.bazel。此补丁存储在注册表中,并在 Bazel 的外部依赖项解析期间提取模块时应用。
这样,注册表中版本的版本将正确设置为
已发布版本,因此,bazel_dep、single_version_override 和
multiple_version_override 将按预期工作,同时避免在使用非注册表替换项时可能出现的问题,因为源
归档中的版本将是默认值 (''),该值始终会得到正确
处理(毕竟它是默认版本值),并且在排序时会按
预期运行(空字符串被视为最高版本)。
什么是兼容性级别?
您应停止使用 compatibility_level。
提高 compatibility_level 会导致最终用户难以解决的版本冲突。因此,从 Bazel 8.6.0 和 9.1.0 开始,两者
compatibility_level 和 max_compatibility_level 都是空操作。
引入重大破坏性变更的模块维护人员应确保构建失败提供清晰的错误消息和可操作的迁移路径。
旧版文档:
Bazel 模块的 compatibility_level
应在引入向后
不兼容(“破坏性”)变更的 同一提交 中递增。
但是,如果 Bazel 检测到已解析的依赖项图中存在 同一模块 的 不同兼容性级别 的版本,则可能会抛出错误。例如,当两个模块依赖于具有不同兼容性级别的第三个模块的版本时,可能会发生这种情况。
因此,过于频繁地递增 compatibility_level 可能会造成严重中断,不建议这样做。为避免这种情况,只有当破坏性变更影响大多数用例且不容易迁移和/或解决时,才应递增 compatibility_level。
为什么 MODULE.bazel 不支持 load?
在依赖项解析期间,所有引用的外部依赖项的 MODULE.bazel 文件都会从注册表中提取。在此阶段,依赖项的源归档尚未提取;因此,如果 MODULE.bazel 文件 load 了另一个文件,Bazel 无法在不提取整个源归档的情况下实际提取该文件。请注意,MODULE.bazel 文件本身是特殊的,因为它直接托管在注册表中。
通常,请求在 MODULE.bazel 中使用 load 的人对以下几个用例感兴趣,这些用例可以在不使用 load 的情况下解决:
- 确保 MODULE.bazel 中列出的版本与存储在其他位置(例如 .bzl 文件中)的构建
元数据一致:这可以通过在从 BUILD 文件加载的 .bzl 文件中使用
native.module_version方法 来实现。 - 将非常大的 MODULE.bazel 文件拆分为可管理的部分,
特别是对于单体代码库:根模块可以使用
include指令将其 MODULE.bazel 文件拆分为多个段。出于与不允许在 MODULE.bazel 文件中使用load相同的原因,include不能在非根模块中使用。 - 旧版 WORKSPACE 系统的用户可能还记得声明代码库,然后立即从该代码库
load以执行复杂的逻辑。此 功能已替换为模块扩展程序。
我可以为 bazel_dep 指定 SemVer 范围吗?
不可以。其他一些软件包管理系统(如 npm 和 Cargo )支持版本范围(隐式或显式),这通常需要 约束求解器(使用户更难预测输出),并且在没有锁定文件的情况下, 版本解析是不可重现的。
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。
然后,根据上下文仓库是什么,可以使用迁移指南中的"仓库可见性"表来找出明显名称实际解析为哪个仓库。
- 如果上下文代码库是主代码库 (
@@):- 如果
bar是根模块的 MODULE.bazel 文件(通过bazel_dep、use_repo、module、use_repo_rule中的任何一个)引入的明显代码库名称,则@bar解析为该 MODULE.bazel 文件声明的内容。 - 否则,如果
bar是在 WORKSPACE 中定义的代码库(这意味着其 规范名称为@@bar),则@bar解析为@@bar。 - 否则,
@bar解析为类似@@[unknown repo 'bar' requested from @@]的内容,这最终会导致 错误。
- 如果
- 如果上下文代码库是 Bzlmod 世界代码库(也就是说,它对应于非根 Bazel 模块,或由模块扩展程序生成),则它只会看到其他 Bzlmod 世界代码库,而不会看到 WORKSPACE 世界代码库。
- 值得注意的是,这包括在根模块中类似
non_module_deps的模块扩展程序中引入的任何代码库,或根模块中的use_repo_rule实例化。
- 值得注意的是,这包括在根模块中类似
- 如果在 WORKSPACE 中定义了上下文代码库:
- 首先,检查上下文代码库定义是否具有神奇的
repo_mapping属性。如果有,请先查看映射(因此,对于使用repo_mapping = {"@bar": "@baz"}定义的 代码库,我们将在下面查看@baz)。 - 如果
bar是根模块的 MODULE.bazel 文件引入的明显代码库名称,则@bar解析为该 MODULE.bazel 文件 声明的内容。(这与主代码库情况下的第 1 项相同。) - 否则,
@bar解析为@@bar。这很可能会指向在 WORKSPACE 中定义的代码库bar;如果未定义此类代码库,Bazel 将抛出错误。
- 首先,检查上下文代码库定义是否具有神奇的
如需更简洁的版本,请参阅以下内容:
- Bzlmod 世界代码库(不包括主代码库)只会看到 Bzlmod 世界代码库。
- WORKSPACE 世界代码库(包括主代码库)将首先看到 Bzlmod 世界中的根模块定义的内容,然后回退到查看 WORKSPACE 世界代码库。
值得注意的是,Bazel 命令行中的标签(包括 Starlark 标志、标签类型标志值和 build/test 目标模式)被视为将主代码库作为上下文代码库。
其他
如何准备和运行离线构建?
使用 bazel fetch 命令预提取代码库。您可以使用 --repo 标志
(如 bazel fetch --repo @foo)仅提取仓库 @foo(在
主仓库的上下文中解析,请参阅上面的问题),或使用目标
模式(如 bazel fetch @foo//:bar)提取
@foo//:bar 的所有传递依赖项(这等效于 bazel build --nobuild @foo//:bar)。
为确保在构建期间不发生提取,请使用 --nofetch。更准确地说,这会使任何运行非本地代码库规则的尝试失败。
如果您想提取代码库 并 对其进行修改以在本地进行测试,请考虑使用
bazel vendor 命令。
如何使用 HTTP 代理?
Bazel 遵循其他程序(如
curl)通常
接受的 http_proxy 和 HTTPS_PROXY 环境变量。
如何在双栈 IPv4/IPv6 设置中让 Bazel 优先使用 IPv6?
在仅支持 IPv6 的机器上,Bazel 可以下载依赖项,而无需进行任何更改。但是,在双栈 IPv4/IPv6 机器上,Bazel 遵循与 Java 相同的惯例,如果启用,则优先使用 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 选项。
代码库规则是否可以使用远程执行在远程运行?
不可以;或者至少目前还不行。使用远程执行服务来加快 build 速度的用户可能会注意到,仓库规则仍会在本地运行。例如,http_archive 将首先下载到本地机器上(如果适用,则使用任何本地下载缓存),然后提取,最后每个源文件都将作为输入文件上传到远程执行服务。很自然会问,为什么远程执行服务不直接下载并提取该归档,从而节省无用的往返行程。
部分原因是代码库规则(和模块扩展程序)类似于由 Bazel 本身运行的“脚本”。远程执行器甚至不一定安装了 Bazel。
另一个原因是,Bazel 通常需要下载和提取的归档中的 BUILD 文件来执行加载和分析,而这些操作是在本地执行的。
目前有一些初步的想法,可以通过将代码库规则重新构想为构建规则来解决此问题,这样自然可以远程运行这些规则,但反过来也会引发新的架构问题(例如,query 命令可能需要运行操作,从而使其设计复杂化)。
如需详细了解此主题之前的讨论,请参阅支持需要 Bazel 才能提取的代码库的方法。