本页面解答了有关 Bazel 中外部依赖项的一些常见问题。
MODULE.bazel
为什么 MODULE.bazel 不支持 load
?
在依赖项解析期间,系统会从注册表中提取所有引用的外部依赖项的 MODULE.bazel 文件。在此阶段,尚未提取依赖项的源代码归档文件;因此,如果 MODULE.bazel 文件 load
了另一个文件,那么 Bazel 就无法在不提取整个源代码归档文件的情况下实际提取该文件。请注意,MODULE.bazel 文件本身是特殊的,因为它直接托管在注册表上。
在 MODULE.bazel 中请求 load
的用户通常会关注以下几个用例,而这些用例无需 load
即可解决:
- 确保 MODULE.bazel 中列出的版本与存储在其他位置(例如 .bzl 文件中)的 build 元数据一致:您可以在从 BUILD 文件加载的 .bzl 文件中使用
native.module_version
方法来实现这一点。 - 将非常大的 MODULE.bazel 文件拆分为易于管理的部分,尤其是对于单 repo:根模块可以使用
include
指令将其 MODULE.bazel 文件拆分为多个部分。出于同样的原因,我们不允许在 MODULE.bazel 文件中使用load
,因此include
也不能在非根模块中使用。 - 旧版 WORKSPACE 系统的用户可能还记得,他们需要声明一个代码库,然后立即从该代码库中
load
以执行复杂的逻辑。此功能已被模块扩展取代。
我可以为 bazel_dep
指定 SemVer 范围吗?
不可以。npm 和 Cargo 等一些其他软件包管理器支持版本范围(隐式或显式),这通常需要使用约束条件求解器(这会使输出结果更难以被用户预测),并且在没有锁定文件的情况下,版本解析不可重现。
与 Go 不同,Bazel 使用最小版本选择,这样一来,输出就很容易预测,并且可保证可重现性。这与 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
指令。例如,gazelle
中的 go_deps
扩展的典型用法可能如下所示:
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-world 代码库(主代码库除外)只会看到 Bzlmod-world 代码库。
- 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
)。
如需确保在构建期间不会发生提取,请使用 --nofetch
。更确切地说,这会导致任何尝试运行非本地代码库规则的操作都失败。
如果您想提取代码库并对其进行修改以便在本地进行测试,请考虑使用 bazel vendor
命令。
如何使用 HTTP 代理?
Bazel 会遵循其他程序(例如 curl)通常接受的 http_proxy
和 HTTPS_PROXY
环境变量。
如何让 Bazel 在双栈 IPv4/IPv6 设置中优先使用 IPv6?
在仅使用 IPv6 的机器上,Bazel 可以不做任何更改地下载依赖项。不过,在双栈 IPv4/IPv6 机器上,Bazel 遵循与 Java 相同的惯例,优先使用 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 构建目标(例如集成测试)时,请使用
--jvmopt=-Djava.net.preferIPv6Addresses=true
工具标志。例如,在.bazelrc
文件中添加以下内容:build --jvmopt=-Djava.net.preferIPv6Addresses
如果您使用
rules_jvm_external
进行依赖项版本解析,还应将-Djava.net.preferIPv6Addresses=true
添加到COURSIER_OPTS
环境变量,以为 Coursier 提供 JVM 选项。
可以使用远程执行功能远程运行代码库规则吗?
不会;至少目前不会。使用远程执行服务来加快构建速度的用户可能会注意到,代码库规则仍在本地运行。例如,http_archive
会先下载到本地计算机(使用任何本地下载缓存,如果适用),然后解压缩,然后每个源文件都会作为输入文件上传到远程执行服务。因此,人们自然会问,为什么远程执行服务不直接下载并解压缩该归档文件,以免进行无用的往返传输。
部分原因在于,代码库规则(和模块扩展)类似于由 Bazel 本身运行的“脚本”。远程执行器甚至不一定安装了 Bazel。
另一个原因是,Bazel 通常需要下载并解压缩的归档文件中的 BUILD 文件来执行加载和分析,这些操作在本地执行。
我们提出了一些初步想法来解决此问题,即将代码库规则重新构想为构建规则,这样自然就可以远程运行这些规则,但反过来又会引发新的架构问题(例如,query
命令可能需要运行操作,这会使其设计变得复杂)。
如需详细了解此主题的先前讨论,请参阅一种支持需要使用 Bazel 提取的代码库的方法。