由于使用 API 的缺点 工作区中,Bzlmod 将 在未来 Bazel 版本中取代旧版 WORKSPACE 系统。本指南 将项目迁移到 Bzlmod,并删除 WORKSPACE 以提取外部 依赖项
WORKSPACE 与 Bzlmod
Bazel 的 WORKSPACE 和 Bzlmod 提供类似的功能,但语法有所不同。这个 部分介绍了如何从特定的 WORKSPACE 功能迁移到 Bzlmod。
定义 Bazel 工作区的根
WORKSPACE 文件标记了 Bazel 项目的源根,
在 Bazel 6.3 及更高版本中,已替换为 MODULE.bazel。使用 Bazel 版本
6.3 之前的版本,则应该仍然存在一个 WORKSPACE
或 WORKSPACE.bazel
文件,
您的工作区根目录中,例如:
工作区
# 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
函数 为您的工作区指定代码库名称。这样,目标对象 工作区中的//foo:bar
,将作为@<workspace name>//foo:bar
引用。如果未指定,则使用 工作区为__main__
。## WORKSPACE workspace(name = "com_foo_bar")
Bzlmod
建议使用 不带
@<repo name>
的//foo:bar
语法。但是,如果您确实需要旧语法 时,您可以使用由module
函数作为代码库 名称。如果模块名称与所需的代码库名称不同,您可以 可以使用repo_name
module
函数,用于替换 代码库名称## MODULE.bazel module( name = "bar", repo_name = "com_foo_bar", )
将外部依赖项作为 Bazel 模块提取
如果您的依赖项是 Bazel 项目,那么您应该能够将其用作 Bazel 模块(如果它也采用 Bzlmod)。
工作区
在 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 注册表或您的自定义 Bazel 注册表,您只需使用它
bazel_dep
指令。## MODULE.bazel bazel_dep(name = "bazel_skylib", version = "1.4.2") bazel_dep(name = "rules_java", version = "6.1.1")
Bzlmod 使用 MVS 算法。因此, 系统会自动选择所需的
platform
版本。
将依赖项作为 Bazel 模块替换
作为根模块,您可以替换不同位置中的 Bazel 模块依赖项 方法。
如需了解详情,请参阅替换部分 信息。
您可以在 样本 存储库
使用模块扩展提取外部依赖项
如果您的依赖项不是 Bazel 项目,或者尚不在任何 Bazel 中
你可以使用以下代码
use_repo_rule
或模块
扩展程序。
工作区
使用
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,您可以从
@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 获取所需版本。这是我们遇到的最大问题之一 工作区中的提示,因为它并不是真正提供一种合理的方式来 来解析依赖项
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 构建规则需要检测主机上可用的工具链时 它们使用仓库规则来检查宿主机,并生成 工具链信息作为外部代码库
工作区
给定以下用于检测 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,您可以通过以下方式注册工具链。
您可以在
.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_platforms
API 仅在 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,这通过两条原生代码库规则来实现:
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
, 我们一直在努力对所有原生存储库规则进行星型化( #18285 了解进度)。 然后,您可以在模块中调用相应的 starlarklocal_repository
。实现自定义版本的local_repository
条仓库规则。
绑定目标
WORKSPACE 中的 bind
规则已被弃用,
在 Bzlmod 中不受支持。引入此属性的目的是在
特殊的 //external
软件包。依赖于此 API 的所有用户都将迁移。
举例来说,如果你的
## 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-lib
。
迁移
本部分提供有关 Bzlmod 迁移的实用信息和指南 过程。
了解 WORKSPACE 中的依赖项
迁移的第一步是了解您拥有哪些依赖项。它
可能很难弄清楚
WORKSPACE 文件,因为传递依赖项通常使用 *_deps
加载
宏。
使用工作区解析文件检查外部依赖项
幸运的是
--experimental_repository_resolved_file
可以有所帮助。此标记本质上会生成一个“锁文件”从所有外部提取的
依赖项您可以在此博客中找到更多详细信息。
帖子。
它可通过以下两种方式使用:
获取构建特定目标所需的外部依赖项信息。
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 同步可能会中断 因为某些仓库规则可能只能在受支持的平台上正确运行 平台。
运行此命令后,您应该能够获得外部
resolved.bzl
文件中的依赖项。
使用 bazel query
检查外部依赖项
您可能还知道,可通过以下命令使用 bazel query
检查代码库规则:
bazel query --output=build //external:<repo name>
虽然 Bazel 查询更加方便快捷,但其中 外部依赖项版本, 因此请务必谨慎使用!查询和检查外部 与 Bzlmod 关联的依赖项新的 子命令。
内置默认依赖项
如果您检查 --experimental_repository_resolved_file
生成的文件,
您会找到许多未在工作区中定义的依赖项。
这是因为 Bazel 实际上会向用户的工作区添加前缀和后缀
来注入一些默认依赖项,而这些依赖项通常是
原生规则(例如 @bazel_tools
、@platforms
和 @remote_java_tools
)。包含
Bzlmod,这些依赖项通过内置模块引入
bazel_tools
,这是所有其他
Bazel 模块。
使用混合模式逐步迁移
Bzlmod 和 WORKSPACE 可以协同工作,这允许迁移依赖项 从 WORKSPACE 文件复制到 Bzlmod,是一个逐步的过程。
WORKSPACE.bzlmod
在迁移过程中,Bazel 用户可能需要在不同 build 之间切换 未启用 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 代码库 | |
---|---|---|---|---|
主代码库 | Visible | 如果根模块是直接依赖项 | 如果根模块是托管模块扩展程序的模块的直接依赖项 | Visible |
Bazel 模块代码库 | 直接依赖项 | 直接依赖项 | 托管模块扩展的模块的直接依赖项 | 根模块的直接依赖项 |
模块扩展代码库 | 直接依赖项 | 直接依赖项 | 托管模块扩展程序的模块的直接依赖项 + 同一模块扩展程序生成的所有代码库 | 根模块的直接依赖项 |
WORKSPACE 代码库 | 所有可见项 | 不显示 | 不显示 | 所有可见项 |
迁移过程
典型的 Bzlmod 迁移过程可能如下所示:
- 了解您在 WORKSPACE 中的依赖项。
- 在项目根目录中添加一个空的 MODULE.bazel 文件。
- 添加一个空的 WORKSPACE.bzlmod 文件以替换 WORKSPACE 文件的内容。
- 在启用 Bzlmod 的情况下构建目标,并检查哪个代码库 缺失。
- 检查已解析依赖项中缺失代码库的定义 文件。
- 通过模块将缺失的依赖项作为 Bazel 模块引入 或将其保留在 WORKSPACE.bzlmod 中以供稍后迁移。
- 返回到 4 并重复,直到所有依赖项都可用。
迁移工具
您可以使用交互式 Bzlmod 迁移帮助程序脚本 可帮助您开始使用。
该脚本会执行以下操作:
- 生成并解析 WORKSPACE 解析的文件。
- 以人类可读的方式输出已解析文件中的仓库信息。
- 运行 bazel 构建命令,检测识别出的错误消息,并建议 迁移方式
- 检查 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 不保证在以下日期生成的源代码归档的校验和: 需求。简言之,采用
https://github.com/<org>/<repo>/releases/download/...
被视为稳定版 ,而https://github.com/<org>/<repo>/archive/...
则不是。查看 GitHub 归档校验和 服务中断 获取更多背景信息。确保源代码树遵循原始代码库的布局。
如果您的代码库非常大,而您希望创建一个发行版 删除不必要的来源,缩减文件大小, 确保剥离的源代码树是原始源代码树的子集。这个 使最终用户能更轻松地将模块替换成非版本 版本由
archive_override
提供 和git_override
。在子目录中添加一个测试模块,用于测试 API。
测试模块是具有自己的 WORKSPACE 和 MODULE.bazel 的 Bazel 项目 文件位于源归档的子目录中,该子目录依赖于 要发布的实际模块其中应包含示例或 集成测试,这些测试涵盖最常用 API。查看 测试模块,了解如何进行设置。
准备好来源归档网址后,请按照 BCR 贡献 指南,通过 GitHub 将模块提交到 BCR 拉取请求。
强烈建议将发布到 BCR GitHub 应用,适用于 以便自动执行将模块提交到 BCR 的过程。
最佳做法
本部分介绍了一些最佳做法,您可以遵循这些做法, 管理外部依赖项
将目标拆分为不同的软件包,以避免提取不必要的依赖项。
检查 #12835,其中 dev 测试的依赖项被不必要地提取出来进行构建 目标对象。这并不是特定于 Bzlmod 遵循这种做法可以更轻松地正确指定开发依赖项。
指定开发依赖项
您可以将以下对象的 dev_dependency
属性设置为 true:
bazel_dep
和
use_extension
指令,
不会传播到依赖项目。作为根模块,您可以使用
--ignore_dev_dependency
标志,用于验证您的目标
仍然在没有开发依赖项的情况下构建。
社群迁移进度
您可以检查 Bazel 中央注册表,找到 进行检查或者,您也可以加入 GitHub 讨论 顶或发布阻止迁移的依赖项。
报告问题
请查看 Bazel GitHub 问题列表,了解已知的 Bzlmod 问题。欢迎随时提出新问题或提出功能请求,以帮助我们解决问题 迁移!