由于 WORKSPACE 存在 缺点,Bzlmod 将 取代旧版 WORKSPACE 系统。WORKSPACE 文件将在 Bazel 8(2024 年末)中默认停用,并在 Bazel 9(2025 年末)中移除。本指南可帮助您将项目迁移到 Bzlmod,并放弃使用 WORKSPACE 来 提取外部依赖项。
WORKSPACE 与 Bzlmod
Bazel 的 WORKSPACE 和 Bzlmod 提供类似的功能,但语法不同。本部分介绍了如何从特定的 WORKSPACE 功能迁移到 Bzlmod。
定义 Bazel 工作区的根目录
WORKSPACE 文件标记 Bazel 项目的源代码根目录,此职责
在 Bazel 6.3 及更高版本中由 MODULE.bazel 取代。对于 6.3 之前的 Bazel 版本,您的工作区根目录中仍应有一个 WORKSPACE 或 WORKSPACE.bazel 文件,可能带有如下注释:
WORKSPACE
# This file marks the root of the Bazel workspace. # See MODULE.bazel for external dependencies setup.
在 bazelrc 中启用 Bzlmod
借助 .bazelrc,您可以设置每次运行 Bazel 时应用的标志。如需启用
Bzlmod,请使用 --enable_bzlmod 标志,并将其应用于 common 命令,以便
它应用于每个命令:
.bazelrc
# Enable Bzlmod for every Bazel command common --enable_bzlmod
为工作区指定代码库名称
WORKSPACE
workspace函数用于 为工作区指定代码库名称。这样,工作区中的目标//foo:bar就可以引用为@<workspace name>//foo:bar。如果未指定,工作区的默认代码库名称为__main__。## WORKSPACE workspace(name = "com_foo_bar")Bzlmod
建议使用
//foo:bar语法引用同一工作区中的目标,而无需使用@<repo name>。但是,如果您确实需要旧语法 ,则可以使用module函数指定的模块名称作为代码库 名称。如果模块名称与所需的代码库名称不同,您 可以使用repo_name属性替换module函数的代码库名称。## MODULE.bazel module( name = "bar", repo_name = "com_foo_bar", )
将外部依赖项提取为 Bazel 模块
如果您的依赖项是 Bazel 项目,并且它也采用 Bzlmod,那么您应该能够将其作为一 个 Bazel 模块依赖项。
WORKSPACE
使用 WORKSPACE 时,通常使用
http_archive或git_repository代码库规则 下载 Bazel 项目的源代码。## WORKSPACE load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( name = "bazel_skylib", urls = ["https://github.com/bazelbuild/bazel-skylib/releases/download/1.4.2/bazel-skylib-1.4.2.tar.gz"], sha256 = "66ffd9315665bfaafc96b52278f57c7e2dd09f5ede279ea6d39b2be471e7e3aa", ) load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") bazel_skylib_workspace() http_archive( name = "rules_java", urls = ["https://github.com/bazelbuild/rules_java/releases/download/6.1.1/rules_java-6.1.1.tar.gz"], sha256 = "76402a50ae6859d50bd7aed8c1b8ef09dae5c1035bb3ca7d276f7f3ce659818a", ) load("@rules_java//java:repositories.bzl", "rules_java_dependencies", "rules_java_toolchains") rules_java_dependencies() rules_java_toolchains()如您所见,用户需要从依赖项的宏加载传递 依赖项是一种常见模式。假设
bazel_skylib和rules_java都依赖于platform,platform依赖项的确切版本由宏的顺序决定。Bzlmod
使用 Bzlmod 时,只要您的依赖项在 Bazel Central Registry 或自定义 Bazel registry 中可用,您就可以使用
bazel_dep指令轻松地依赖它。## MODULE.bazel bazel_dep(name = "bazel_skylib", version = "1.4.2") bazel_dep(name = "rules_java", version = "6.1.1")Bzlmod 使用 MVS 算法以传递方式解析 Bazel 模块依赖项。因此,系统会自动选择
platform的最大所需版本。
替换 Bazel 模块的依赖项
作为根模块,您可以通过不同的 方式替换 Bazel 模块依赖项。
如需了解详情,请参阅替换部分。
您可以在 示例 代码库中找到一些使用示例。
使用模块扩展程序提取外部依赖项
如果您的依赖项不是 Bazel 项目,或者尚未在任何 Bazel
注册表中提供,您可以使用
use_repo_rule 或 模块
扩展程序引入它。
WORKSPACE
使用
http_file代码库规则下载文件。## WORKSPACE load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") http_file( name = "data_file", url = "http://example.com/file", sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", )Bzlmod
使用 Bzlmod 时,您可以在 MODULE.bazel 文件中使用
use_repo_rule指令直接实例化代码库:## MODULE.bazel http_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") http_file( name = "data_file", url = "http://example.com/file", sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", )在底层,这是使用模块扩展程序实现的。如果您需要 执行比简单调用代码库规则更复杂的逻辑,也可以 自行实现模块扩展程序。您需要将定义 移到
.bzl文件中,这样您还可以在迁移期间在 WORKSPACE 和 Bzlmod 之间共享定义。## repositories.bzl load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") def my_data_dependency(): http_file( name = "data_file", url = "http://example.com/file", sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", )实现模块扩展程序以加载依赖项宏。您可以在宏的同一
.bzl文件中定义 它,但为了与 旧版 Bazel 保持兼容,最好在单独的.bzl文件中定义它。## extensions.bzl load("//:repositories.bzl", "my_data_dependency") def _non_module_dependencies_impl(_ctx): my_data_dependency() non_module_dependencies = module_extension( implementation = _non_module_dependencies_impl, )如需使代码库对根项目可见,您应在 MODULE.bazel 文件中声明 模块扩展程序和代码库的用法。
## MODULE.bazel non_module_dependencies = use_extension("//:extensions.bzl", "non_module_dependencies") use_repo(non_module_dependencies, "data_file")
使用模块扩展程序解决外部依赖项冲突
项目可以提供一个宏,该宏根据 调用者的输入引入外部代码库。但是,如果依赖项图中存在多个调用者,并且它们导致冲突,该怎么办?
假设项目 foo 提供以下宏,该宏将 version 作为
实参。
## repositories.bzl in foo {:#repositories.bzl-foo}
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
def data_deps(version = "1.0"):
http_file(
name = "data_file",
url = "http://example.com/file-%s" % version,
# Omitting the "sha256" attribute for simplicity
)
WORKSPACE
使用 WORKSPACE 时,您可以从
@foo加载宏,并指定所需的数据依赖项的版本 。假设您有另一个依赖项@bar, 它也依赖于@foo,但需要不同版本的数据 依赖项。## WORKSPACE # Introduce @foo and @bar. ... load("@foo//:repositories.bzl", "data_deps") data_deps(version = "2.0") load("@bar//:repositories.bzl", "bar_deps") bar_deps() # -> which calls data_deps(version = "3.0")在这种情况下,最终用户必须仔细调整 WORKSPACE 中宏的顺序,才能获得所需的版本。这是 WORKSPACE 的最大痛点之一,因为它实际上没有提供解决依赖项的合理方法。
Bzlmod
使用 Bzlmod 时,项目
foo的作者可以使用模块扩展程序来解决 冲突。例如,假设在所有 Bazel 模块中始终选择数据依赖项的 最大所需版本是有意义的。## extensions.bzl in foo load("//:repositories.bzl", "data_deps") data = tag_class(attrs={"version": attr.string()}) def _data_deps_extension_impl(module_ctx): # Select the maximal required version in the dependency graph. version = "1.0" for mod in module_ctx.modules: for data in mod.tags.data: version = max(version, data.version) data_deps(version) data_deps_extension = module_extension( implementation = _data_deps_extension_impl, tag_classes = {"data": data}, )## MODULE.bazel in bar bazel_dep(name = "foo", version = "1.0") foo_data_deps = use_extension("@foo//:extensions.bzl", "data_deps_extension") foo_data_deps.data(version = "3.0") use_repo(foo_data_deps, "data_file")## MODULE.bazel in root module bazel_dep(name = "foo", version = "1.0") bazel_dep(name = "bar", version = "1.0") foo_data_deps = use_extension("@foo//:extensions.bzl", "data_deps_extension") foo_data_deps.data(version = "2.0") use_repo(foo_data_deps, "data_file")在这种情况下,根模块需要数据版本
2.0,而其 依赖项bar需要3.0。foo中的模块扩展程序可以正确 解决此冲突,并自动为数据 依赖项选择版本3.0。
集成第三方软件包管理器
在上一部分中,由于模块扩展程序提供了一种从依赖项图中收集 信息、执行自定义逻辑以解析 依赖项和调用代码库规则以引入外部代码库的方法,因此,这 为规则作者提供了一种很好的方式来增强集成 特定语言的软件包管理器的规则集。
如需详细了解如何使用模块扩展程序,请参阅模块扩展程序页面 。
以下是已采用 Bzlmod 从不同软件包管理器提取依赖项 的规则集列表:
检测主机上的工具链
当 Bazel 构建规则需要检测主机上可用的工具链时,它们会使用代码库规则检查主机,并将工具链信息生成为外部代码库。
WORKSPACE
假设有以下代码库规则来检测 shell 工具链。
## local_config_sh.bzl def _sh_config_rule_impl(repository_ctx): sh_path = get_sh_path_from_env("SH_BIN_PATH") if not sh_path: sh_path = detect_sh_from_path() if not sh_path: sh_path = "/shell/binary/not/found" repository_ctx.file("BUILD", """ load("@bazel_tools//tools/sh:sh_toolchain.bzl", "sh_toolchain") sh_toolchain( name = "local_sh", path = "{sh_path}", visibility = ["//visibility:public"], ) toolchain( name = "local_sh_toolchain", toolchain = ":local_sh", toolchain_type = "@bazel_tools//tools/sh:toolchain_type", ) """.format(sh_path = sh_path)) sh_config_rule = repository_rule( environ = ["SH_BIN_PATH"], local = True, implementation = _sh_config_rule_impl, )您可以在 WORKSPACE 中加载代码库规则。
## WORKSPACE load("//:local_config_sh.bzl", "sh_config_rule") sh_config_rule(name = "local_config_sh")Bzlmod
使用 Bzlmod 时,您可以使用模块扩展程序引入相同的代码库, 这与上一部分中引入
@data_file代码库类似。## local_config_sh_extension.bzl load("//:local_config_sh.bzl", "sh_config_rule") sh_config_extension = module_extension( implementation = lambda ctx: sh_config_rule(name = "local_config_sh"), )然后在 MODULE.bazel 文件中使用扩展程序。
## MODULE.bazel sh_config_ext = use_extension("//:local_config_sh_extension.bzl", "sh_config_extension") use_repo(sh_config_ext, "local_config_sh")
注册工具链和执行平台
在上一部分之后,在引入托管工具链
信息的代码库(例如 local_config_sh)后,您可能需要注册
工具链。
WORKSPACE
使用 WORKSPACE 时,您可以通过以下方式注册工具链。
您可以在
.bzl文件中注册工具链,并在 WORKSPACE 文件中加载宏。## local_config_sh.bzl def sh_configure(): sh_config_rule(name = "local_config_sh") native.register_toolchains("@local_config_sh//:local_sh_toolchain")## WORKSPACE load("//:local_config_sh.bzl", "sh_configure") sh_configure()或者直接在 WORKSPACE 文件中注册工具链。
## WORKSPACE load("//:local_config_sh.bzl", "sh_config_rule") sh_config_rule(name = "local_config_sh") register_toolchains("@local_config_sh//:local_sh_toolchain")
Bzlmod
使用 Bzlmod 时,
register_toolchains和register_execution_platformsAPI 仅在 MODULE.bazel 文件中可用。您无法在模块扩展程序中调用native.register_toolchains。## MODULE.bazel sh_config_ext = use_extension("//:local_config_sh_extension.bzl", "sh_config_extension") use_repo(sh_config_ext, "local_config_sh") register_toolchains("@local_config_sh//:local_sh_toolchain")
在 WORKSPACE、
WORKSPACE.bzlmod 和每个 Bazel 模块的 MODULE.bazel 文件中注册的工具链和执行平台在工具链选择期间遵循此
优先级顺序(从高到低):
- 在根模块的工具链和执行平台
MODULE.bazel文件中注册。 - 在
WORKSPACE或WORKSPACE.bzlmod文件中注册的工具链和执行平台。 - 由根模块的 (传递)依赖项的模块注册的工具链和执行平台。
- 未使用
WORKSPACE.bzlmod时:在WORKSPACE后缀中注册的工具链。
引入本地代码库
当您需要依赖项的 本地版本进行调试,或者想要将工作区中的 目录作为外部代码库纳入时,可能需要将依赖项作为本地代码库引入。
WORKSPACE
使用 WORKSPACE 时,可以通过两个原生代码库规则
local_repository和new_local_repository实现此目的。## WORKSPACE local_repository( name = "rules_java", path = "/Users/bazel_user/workspace/rules_java", )Bzlmod
使用 Bzlmod 时,您可以使用
local_path_override将模块替换为本地路径。## MODULE.bazel bazel_dep(name = "rules_java") local_path_override( module_name = "rules_java", path = "/Users/bazel_user/workspace/rules_java", )也可以使用模块扩展程序引入本地代码库。 但是,您无法在模块扩展程序中调用
native.local_repository, 我们正在努力将所有原生代码库规则 Starlark 化(如需了解进度,请查看 #18285)。 然后,您可以在模块 扩展程序中调用相应的 Starlarklocal_repository。如果这对您来说是一个阻碍性问题,那么实现local_repository代码库规则的自定义版本也很简单。
绑定目标
WORKSPACE 中的 bind 规则已废弃,
不支持该规则。引入该规则是为了在
特殊的 //external 软件包中为目标提供别名。所有依赖于此规则的用户都应迁移。
例如,如果您有
## WORKSPACE
bind(
name = "openssl",
actual = "@my-ssl//src:openssl-lib",
)
这样,其他目标就可以依赖于 //external:openssl。您可以通过以下方式迁移:
将所有
//external:openssl用法替换为@my-ssl//src:openssl-lib。或使用
alias构建规则在软件包(例如
//third_party)中定义以下目标## third_party/BUILD alias( name = "openssl", actual = "@my-ssl//src:openssl-lib", )将所有
//external:openssl用法替换为//third_party:openssl。
提取与同步
提取和同步命令用于在本地下载外部代码库并使其保持最新状态
。有时,在提取构建所需的所有代码库后,还可以使用 --nofetch
标志离线构建。
WORKSPACE
同步会对所有代码库或特定 配置的代码库集执行强制提取,而提取仅用于提取特定 目标。
Bzlmod
同步命令不再适用,但提取提供了 各种选项。 您可以提取目标、代码库、一组配置的代码库或依赖项解析和模块扩展程序中涉及的所有 代码库。 提取结果会被缓存,如需强制提取,您必须在提取过程中添加
--force选项。
迁移
本部分为您的 Bzlmod 迁移 过程提供了实用信息和指南。
了解 WORKSPACE 中的依赖项
迁移的第一步是了解您有哪些依赖项。由于传递依赖项通常使用 *_deps
宏加载,因此可能很难确定
WORKSPACE 文件中引入的确切依赖项。
使用工作区解析的文件检查外部依赖项
幸运的是,标志
--experimental_repository_resolved_file
可以提供帮助。此标志实际上会在您上次 Bazel 命令中生成所有提取的外部
依赖项的“锁定文件”。如需了解详情,请参阅这篇博文。
它可以通过两种方式使用:
提取构建特定目标所需的外部依赖项的信息。
bazel clean --expunge bazel build --nobuild --experimental_repository_resolved_file=resolved.bzl //foo:bar提取 WORKSPACE 文件中定义的所有外部依赖项的信息。
bazel clean --expunge bazel sync --experimental_repository_resolved_file=resolved.bzl借助
bazel sync命令,您可以提取 WORKSPACE 文件中定义的所有依赖项,包括:bind用法register_toolchains和register_execution_platforms用法
但是,如果您的项目是跨平台的,则 bazel sync 可能会在某些 平台上中断,因为某些代码库规则可能仅在受支持的 平台上正确运行。
运行该命令后,您应该会在 resolved.bzl 文件中获得外部
依赖项的信息。
使用 bazel query 检查外部依赖项
您可能还知道 bazel query 可用于检查代码库规则
bazel query --output=build //external:<repo name>
虽然 bazel query 更方便且速度更快,但它可能会谎报 外部依赖项版本, 因此请谨慎使用!使用 Bzlmod 查询和检查外部 依赖项将通过新的 子命令实现。
内置默认依赖项
如果您检查 --experimental_repository_resolved_file 生成的文件,
您会发现许多未在 WORKSPACE 中定义的依赖项。
这是因为 Bazel 实际上会向用户的 WORKSPACE
文件内容添加前缀和后缀,以注入一些默认依赖项,这些依赖项通常是
原生规则(例如 @bazel_tools、@platforms 和 @remote_java_tools)所必需的。使用
Bzlmod 时,这些依赖项通过内置模块
bazel_tools 引入,该模块是每个其他
Bazel 模块的默认依赖项。
用于逐步迁移的混合模式
Bzlmod 和 WORKSPACE 可以并行运行,这样就可以逐步将依赖项 从 WORKSPACE 文件迁移到 Bzlmod。
WORKSPACE.bzlmod
在迁移期间,Bazel 用户可能需要在启用和 未启用 Bzlmod 的构建之间切换。为了使该 过程更顺畅,我们实现了 WORKSPACE.bzlmod 支持。
WORKSPACE.bzlmod 的语法与 WORKSPACE 完全相同。启用 Bzlmod 后, 如果工作区根目录中也存在 WORKSPACE.bzlmod 文件:
WORKSPACE.bzlmod生效,WORKSPACE的内容将被忽略。- 不会向 WORKSPACE.bzlmod 文件添加任何 前缀或后缀。
使用 WORKSPACE.bzlmod 文件可以简化迁移,因为:
- 停用 Bzlmod 后,您将回退到从 原始 WORKSPACE 文件提取依赖项。
- 启用 Bzlmod 后,您可以更好地跟踪使用 WORKSPACE.bzlmod 迁移的剩余依赖项。
代码库可见性
Bzlmod 能够控制从给定 代码库中可以看到哪些其他代码库,如需了解详情,请查看 代码库名称和严格 依赖项。
下面总结了在考虑 WORKSPACE 的情况下,不同类型的 代码库的代码库可见性。
| 来自主代码库 | 来自 Bazel 模块代码库 | 来自模块扩展程序代码库 | 来自 WORKSPACE 代码库 | |
|---|---|---|---|---|
| 主代码库 | 可见 | 如果根模块是直接依赖项 | 如果根模块是托管模块扩展程序的模块的直接依赖项 | 可见 |
| Bazel 模块代码库 | 直接依赖项 | 直接依赖项 | 托管模块扩展程序的模块的直接依赖项 | 根模块的直接依赖项 |
| 模块扩展程序代码库 | 直接依赖项 | 直接依赖项 | 托管模块扩展程序的模块的直接依赖项 + 由同一模块扩展程序生成的所有代码库 | 根模块的直接依赖项 |
| WORKSPACE 代码库 | 全部可见 | 不可见 | 不可见 | 全部可见 |
迁移过程
典型的 Bzlmod 迁移过程可能如下所示:
- 了解 WORKSPACE 中的依赖项。
- 在项目根目录中添加一个空的 MODULE.bazel 文件。
- 添加一个空的 WORKSPACE.bzlmod 文件以替换 WORKSPACE 文件内容。
- 启用 Bzlmod 并构建目标,然后检查缺少哪些代码库 。
- 在解析的依赖项 文件中检查缺失的代码库的定义。
- 将缺失的依赖项作为 Bazel 模块引入,通过模块 扩展程序引入,或将其保留在 WORKSPACE.bzlmod 中以供日后迁移。
- 返回第 4 步并重复,直到所有依赖项都可用。
迁移工具
有一个交互式 Bzlmod 迁移 帮助脚本, 可帮助您入门。
该脚本执行以下操作:
- 生成并解析 WORKSPACE 解析的文件。
- 以易于理解的方式输出解析的文件中的代码库信息。
- 运行 bazel build 命令,检测识别的错误消息,并推荐一种 迁移方式。
- 检查依赖项是否已在 BCR 中可用。
- 向 MODULE.bazel 文件添加依赖项。
- 通过模块扩展程序添加依赖项。
- 向 WORKSPACE.bzlmod 文件添加依赖项。
如需使用该脚本,请确保您已安装最新的 Bazel 版本,并运行以下 命令:
git clone https://github.com/bazelbuild/bazel-central-registry.git
cd <your workspace root>
<BCR repo root>/tools/migrate_to_bzlmod.py -t <your build targets>
发布 Bazel 模块
如果您的 Bazel 项目是其他项目的依赖项,您可以发布您的 项目在 Bazel Central Registry。
如需在 BCR 中签入您的项目,您需要该项目的源代码归档网址。创建源代码归档时,请注意以下几点:
确保归档指向特定版本。
BCR 只能接受版本化的源代码归档,因为 Bzlmod 需要 在依赖项解析期间进行版本比较。
确保归档网址稳定。
Bazel 通过哈希值验证归档的内容,因此您应 确保下载文件的校验和永不更改。如果网址是 来自 GitHub,请在发布页面中创建并上传发布归档。 GitHub 不会保证按需生成的源代码归档的校验和。简而言之,
https://github.com/<org>/<repo>/releases/download/...形式的网址被认为是稳定的 而https://github.com/<org>/<repo>/archive/...则不是。如需了解更多背景信息,请参阅 GitHub 归档校验和 中断 。确保源代码树遵循原始代码库的布局。
如果您的代码库非常大,并且您想通过剥离不必要的源代码来创建大小缩小的分发 归档,请确保剥离的源代码树是原始源代码树的子集。这样,最终用户就可以更轻松地通过
archive_override和git_override将模块替换为非发布 版本。在子目录中添加一个测试模块,用于测试最常用的 API。
测试模块是一个 Bazel 项目,它有自己的 WORKSPACE 和 MODULE.bazel 文件,位于源代码归档的子目录中,该子目录依赖于要发布的 实际模块。它应包含涵盖最常用 API 的示例或一些 集成测试。如需了解如何设置 测试模块,请参阅测试模块。
准备好源代码归档网址后,请按照 BCR 贡献 指南,通过 GitHub 拉取请求将模块提交到 BCR。
强烈建议 为您的 代码库设置“发布到 BCR”GitHub 应用,以自动执行将模块提交到 BCR 的过程。
最佳实践
本部分介绍了一些最佳实践,您应遵循这些实践,以便更好地 管理外部依赖项。
将目标拆分为不同的软件包,以避免提取不必要的依赖项。
查看 #12835,其中测试的开发 依赖项被强制提取,而构建 目标不需要这些依赖项。这实际上并非 Bzlmod 特有的,但 遵循这些实践可以更轻松地正确指定开发依赖项。
指定开发依赖项
您可以为
bazel_dep 和
use_extension 指令将 dev_dependency 属性设置为 true,这样
它们就不会传播到依赖项目。作为根模块,您可以使用
--ignore_dev_dependency 标志来验证您的目标
是否仍然在没有开发依赖项和替换的情况下构建。
社区迁移进度
您可以查看 Bazel Central Registry,了解您的依赖项是否已可用。否则,欢迎加入此 GitHub 讨论,为阻止您迁移的依赖项 投赞成票或发布这些依赖项。
报告问题
请查看 Bazel GitHub 问题列表,了解已知的 Bzlmod 问题。欢迎提交新问题或功能请求,以帮助您顺利完成 迁移!