Bzlmod 迁移指南

报告问题 查看来源 每晚 · 7.2。 · 7.1敬上 · 7.0 · 6.5 · 6.4

由于使用 API 的缺点 工作区中,Bzlmod 将 在未来 Bazel 版本中取代旧版 WORKSPACE 系统。本指南 将项目迁移到 Bzlmod,并删除 WORKSPACE 以提取外部 依赖项

WORKSPACE 与 Bzlmod

Bazel 的 WORKSPACE 和 Bzlmod 提供类似的功能,但语法有所不同。这个 部分介绍了如何从特定的 WORKSPACE 功能迁移到 Bzlmod。

定义 Bazel 工作区的根

WORKSPACE 文件标记了 Bazel 项目的源根, 在 Bazel 6.3 及更高版本中,已替换为 MODULE.bazel。使用 Bazel 版本 6.3 之前的版本,则应该仍然存在一个 WORKSPACEWORKSPACE.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_archivegit_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_skylibrules_java 依赖于 platformplatform 的确切版本) 具体取决于宏的顺序。

  • 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.0foo 中的模块扩展可以正确 解决此冲突,并自动为数据选择版本 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,您可以通过以下方式注册工具链。

    1. 您可以在 .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()
      
    2. 或者直接在 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_toolchainsregister_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 文件紧随其后 工具链选择期间的优先顺序(从高到低):

  1. 在根模块的 MODULE.bazel 文件。
  2. WORKSPACE 中注册的工具链和执行平台,或 WORKSPACE.bzlmod 文件。
  3. 工具链和执行平台。 根模块的(传递)依赖项。
  4. 不使用 WORKSPACE.bzlmod 时:在 WORKSPACE 中注册的工具链 后缀

引入本地代码库

如果需要将依赖项作为本地代码库, 本地版本的依赖项以进行调试,或者您想要将 目录作为外部代码库。

  • 工作区

    对于 WORKSPACE,这通过两条原生代码库规则来实现: local_repositorynew_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 了解进度)。 然后,您可以在模块中调用相应的 starlark local_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 可以有所帮助。此标记本质上会生成一个“锁文件”从所有外部提取的 依赖项您可以在此博客中找到更多详细信息。 帖子

它可通过以下两种方式使用:

  1. 获取构建特定目标所需的外部依赖项信息。

    bazel clean --expunge
    bazel build --nobuild --experimental_repository_resolved_file=resolved.bzl //foo:bar
    
  2. 提取 WORKSPACE 文件中定义的所有外部依赖项的信息。

    bazel clean --expunge
    bazel sync --experimental_repository_resolved_file=resolved.bzl
    

    您可以使用 bazel sync 命令提取 WORKSPACE 文件,其中包括:

    • bind 用法
    • register_toolchainsregister_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 迁移过程可能如下所示:

  1. 了解您在 WORKSPACE 中的依赖项。
  2. 在项目根目录中添加一个空的 MODULE.bazel 文件。
  3. 添加一个空的 WORKSPACE.bzlmod 文件以替换 WORKSPACE 文件的内容。
  4. 在启用 Bzlmod 的情况下构建目标,并检查哪个代码库 缺失。
  5. 检查已解析依赖项中缺失代码库的定义 文件。
  6. 通过模块将缺失的依赖项作为 Bazel 模块引入 或将其保留在 WORKSPACE.bzlmod 中以供稍后迁移。
  7. 返回到 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_overridegit_override

  • 在子目录中添加一个测试模块,用于测试 API。

    测试模块是具有自己的 WORKSPACE 和 MODULE.bazel 的 Bazel 项目 文件位于源归档的子目录中,该子目录依赖于 要发布的实际模块其中应包含示例或 集成测试,这些测试涵盖最常用 API。查看 测试模块,了解如何进行设置。

准备好来源归档网址后,请按照 BCR 贡献 指南,通过 GitHub 将模块提交到 BCR 拉取请求。

强烈建议发布到 BCR GitHub 应用,适用于 以便自动执行将模块提交到 BCR 的过程。

最佳做法

本部分介绍了一些最佳做法,您可以遵循这些做法, 管理外部依赖项

将目标拆分为不同的软件包,以避免提取不必要的依赖项。

检查 #12835,其中 dev 测试的依赖项被不必要地提取出来进行构建 目标对象。这其实并不属于 Bzlmod, 遵循这种做法可以更轻松地正确指定开发依赖项。

指定开发依赖项

您可以将以下对象的 dev_dependency 属性设置为 true: bazel_depuse_extension 指令, 不会传播到依赖项目。作为根模块,您可以使用 --ignore_dev_dependency 标志,用于验证您的目标 仍然在没有开发依赖项的情况下构建。

社群迁移进度

您可以查看 Bazel 中央注册表,找到 进行检查或者,您也可以加入 GitHub 讨论 顶或发布阻止迁移的依赖项。

报告问题

请查看 Bazel GitHub 问题列表,了解已知的 Bzlmod 问题。欢迎随时提出新问题或提出功能请求,以帮助我们解决问题 迁移!