本页回答了有关 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_dep、single_version_override 和
multiple_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 范围吗?
不可以。其他一些软件包管理器(如 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 目标模式)被视为将主代码库 作为上下文代码库。
其他
如何准备和运行离线 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_proxy 和 HTTPS_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 才能提取的代码库的方法。