Bzlmod 迁移工具

为了简化从 WORKSPACE 迁移到 Bzlmod 的通常很复杂的过程,强烈建议使用迁移脚本。此辅助工具可自动执行迁移外部依赖项管理系统所涉及的许多步骤。

注意:如果您想试用 AI 驱动的 Bzlmod 迁移,请参阅 Bzlmod 迁移代理设置

核心功能

该脚本的主要功能包括:

  • 收集依赖项信息:分析项目的 WORKSPACE 文件,以识别指定 build 目标使用的外部代码库,并使用 Bazel 的 experimental_repository_resolved_file 标志生成包含此信息的已解析依赖项文件。
  • 识别直接依赖项:使用 bazel query 确定指定目标的哪些代码库是直接依赖项。
  • 迁移到 Bzlmod:将相关的 WORKSPACE 依赖项转换为其 Bzlmod 等效项。此流程分为两步:
    1. 将所有已确定的直接依赖项引入 MODULE.bazel 文件。
    2. 在启用 Bzlmod 的情况下构建指定目标,然后以迭代方式识别并修复可识别的错误。由于第一步中可能缺少某些依赖项,因此需要执行此步骤。
  • 生成迁移报告:创建记录迁移过程的 migration_info.md 文件。此报告包含直接依赖项的列表、生成的 Bzlmod 声明,以及可能需要执行的任何手动步骤,以完成迁移。

迁移工具支持:

  • Bazel 中央注册表中提供的依赖项
  • 用户定义的自定义代码库规则
  • 软件包管理系统依赖项
    • Maven
    • Go
    • Python

重要说明

  • 迁移工具是一种尽力而为的实用程序。请务必仔细检查其建议是否正确。
  • 使用 Bazel 7 的迁移工具(不支持 Bazel 8)。

如何使用迁移工具

准备工作:

  • 升级到最新的 Bazel 7 版本,该版本可为 WORKSPACE 和 Bzlmod 提供强大的支持。
  • 验证以下命令是否针对项目的主要 build 目标成功运行:

    bazel build --nobuild --enable_workspace --noenable_bzlmod <targets>
    

用于运行脚本的命令

满足前提条件后,运行以下命令以使用迁移工具:

# Clone the Bazel Central Registry repository
git clone https://github.com/bazelbuild/bazel-central-registry.git
cd bazel-central-registry

# Build the migration tool
bazel build //tools:migrate_to_bzlmod

# Create a convenient alias for the tool
alias migrate2bzlmod=$(realpath ./bazel-bin/tools/migrate_to_bzlmod)

# Navigate to your project's root directory and run the tool
cd 
migrate2bzlmod -t 

此脚本生成的文件

  • MODULE.bazel - Bzlmod 的中央清单文件,用于声明项目的元数据及其对其他 Bazel 模块的直接依赖关系。
  • migration_info.md - 一个文件,其中提供了有关如何执行迁移工具的分步说明,旨在帮助手动完成迁移流程(如有必要)。
  • resolved_deps.py - 包含项目的外部依赖项的完整列表,通过分析项目的 WORKSPACE 文件生成,可在过渡期间作为参考。
  • query_direct_deps - 包含有关所用目标的相关迁移信息,通过在项目的 WORKSPACE 文件中调用带有 --output=build 的 Bazel 来获取。此文件主要由迁移脚本使用。
  • extension_for_XXX - 包含模块扩展定义的文件。对于不是标准 Bazel 模块但可以使用 Bzlmod 的模块扩展程序管理的依赖项,迁移工具会生成这些文件。

标志

此迁移脚本中可用的标志如下:

  • --t/--target:要迁移的目标。此标志可重复使用,并且目标会累积。
  • --i/--initial:删除 MODULE.bazelresolved_deps.pymigration_info.md 文件并从头开始 - 检测直接依赖项,在 MODULE.bazel 中引入这些依赖项,然后重新运行已解析依赖项的生成。

迁移后清理

  • 删除了 migration_info.mdresolved_deps.pyquery_direct_deps
  • 清理 MODULE.bazel 文件中用于迁移的注释,例如 # -- bazel_dep definitions -- #

迁移示例

如需查看迁移脚本的实际效果,请考虑以下场景:在 WORKSPACE 文件中声明了 Python、Maven 和 Go 依赖项。

点击此处查看 WORKSPACE 文件

workspace(name="example")

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load(":my_custom_macro.bzl", "my_custom_macro")

http_archive(
    name = "rules_cc",
    sha256 = "b8b918a85f9144c01f6cfe0f45e4f2838c7413961a8ff23bc0c6cdf8bb07a3b6",
    strip_prefix = "rules_cc-0.1.5",
    urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.1.5/rules_cc-0.1.5.tar.gz"],
)

# Module dependency
# -------------------
http_archive(
    name = "rules_shell",
    sha256 = "3e114424a5c7e4fd43e0133cc6ecdfe54e45ae8affa14fadd839f29901424043",
    strip_prefix = "rules_shell-0.4.0",
    url = "https://github.com/bazelbuild/rules_shell/releases/download/v0.4.0/rules_shell-v0.4.0.tar.gz",
)

# Repo rule
# -------------------
http_archive(
    name = "com_github_cockroachdb_cockroach",
    sha256 = "6c3568ef244ce6b874694eeeecb83ed4f5d5dff6cf037c952ecde76828a6c502",
    strip_prefix = "cockroach-22.1.6",
    url = "https://github.com/cockroachdb/cockroach/archive/v22.1.6.tar.gz",
)

# Module extension
# -------------------
# Macro which invokes repository_rule
my_custom_macro(
    name = "my_custom_repo",
)

# Go dependencies
# -------------------
http_archive(
    name = "io_bazel_rules_go",
    integrity = "sha256-M6zErg9wUC20uJPJ/B3Xqb+ZjCPn/yxFF3QdQEmpdvg=",
    urls = [
        "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.48.0/rules_go-v0.48.0.zip",
        "https://github.com/bazelbuild/rules_go/releases/download/v0.48.0/rules_go-v0.48.0.zip",
    ],
)

http_archive(
    name = "bazel_gazelle",
    integrity = "sha256-12v3pg/YsFBEQJDfooN6Tq+YKeEWVhjuNdzspcvfWNU=",
    urls = [
        "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.37.0/bazel-gazelle-v0.37.0.tar.gz",
        "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.37.0/bazel-gazelle-v0.37.0.tar.gz",
    ],
)

load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")

go_rules_dependencies()
go_register_toolchains(version = "1.23.1")
gazelle_dependencies()

go_repository(
    name = "org_golang_x_net",
    importpath = "golang.org/x/net",
    sum = "h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=",
    version = "v0.0.0-20190311183353-d8887717615a",
    build_file_proto_mode = "disable",
    build_naming_convention = "import",
)

# Python dependencies
# -------------------
http_archive(
    name = "rules_python",
    integrity = "sha256-qDdnnxOC8mlowe5vg5x9r5B5qlMSgGmh8oFd7KpjcwQ=",
    strip_prefix = "rules_python-1.4.0",
    url = "https://github.com/bazelbuild/rules_python/releases/download/1.4.0/rules_python-1.4.0.tar.gz",
)

load("@rules_python//python:repositories.bzl", "py_repositories")
py_repositories()

load("@rules_python//python:pip.bzl", "pip_parse")
pip_parse(
   name = "my_python_deps",
   requirements_lock = "@example//:requirements_lock.txt",
)

load("@my_python_deps//:requirements.bzl", "install_deps")
install_deps()

load("@rules_python//python:repositories.bzl", "python_register_toolchains")
python_register_toolchains(
    name = "python_3_11",
    python_version = "3.11",
)

# Maven dependencies
# __________________

RULES_JVM_EXTERNAL_TAG = "4.5"
RULES_JVM_EXTERNAL_SHA = "b17d7388feb9bfa7f2fa09031b32707df529f26c91ab9e5d909eb1676badd9a6"

http_archive(
    name = "rules_jvm_external",
    strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
    sha256 = RULES_JVM_EXTERNAL_SHA,
    url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
)

load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")
rules_jvm_external_deps()
load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup")
rules_jvm_external_setup()

load("@rules_jvm_external//:defs.bzl", "maven_install")
maven_install(
    name = "px_deps",
    artifacts = [
        "org.antlr:antlr4:4.11.1",
    ],
    repositories = [
        "https://repo1.maven.org/maven2",
    ],
)

此外,为了演示模块扩展程序的用法,自定义宏是从 WORKSPACE 调用的,并且在 my_custom_macro.bzl 中定义。

点击此处查看 my_custom_macro.bzl 文件

"""Repo rule and macro used for testing"""

def _test_repo_rule_impl(repository_ctx):
    repository_ctx.file(
        "BUILD",
        content = """
genrule(
    name = "foo",
    outs = ["rule_name.out"],
    cmd = "touch $@",
    visibility = ["//visibility:public"],
)
"""
    )

_test_repo_rule = repository_rule(
    implementation = _test_repo_rule_impl,
)

def my_custom_macro(name):
    _test_repo_rule(name = name)

最终目标是拥有 MODULE.bazel 文件并删除 WORKSPACE 文件,同时不影响用户体验。

第一步是按照如何使用迁移工具中的说明操作,主要是检查 Bazel 版本(必须是 Bazel 7)并为迁移脚本添加别名。

然后,运行 migrate2bzlmod -t=//... 会输出:

  bazel 7.6.1

  Generating ./resolved_deps.py file - It might take a while...

  RESOLVED: rules_java has been introduced as a Bazel module.
  RESOLVED: bazel_gazelle has been introduced as a Bazel module.
  RESOLVED: io_bazel_rules_go has been introduced as a Bazel module.
  RESOLVED: rules_python has been introduced as a Bazel module.
  IMPORTANT: 3.11 is used as a default python version. If you need a different version, please change it manually and then rerun the migration tool.
  RESOLVED: my_python_deps has been introduced as python extension.
  RESOLVED: org_golang_x_net has been introduced as go extension.
  RESOLVED: rules_jvm_external has been introduced as a Bazel module.
  RESOLVED: org.antlr has been introduced as maven extension.
  RESOLVED: rules_shell has been introduced as a Bazel module.

  Congratulations! All external repositories needed for building //... are available with Bzlmod!
  IMPORTANT: Fix potential build time issues by running the following command:
      bazel build --enable_bzlmod --noenable_workspace //...

  IMPORTANT: For details about the migration process, check `migration_info.md` file.

其中包含以下重要信息:

  • 生成 ./resolved_deps.py 文件,其中包含有关使用 WORKSPACE 文件声明和加载的所有外部代码库的信息。
  • RESOLVED 关键字用于描述由该工具解析并添加到 MODULE.bazel 文件中的所有依赖项。
  • IMPORTANT 关键字用于描述值得花时间了解的重要信息。
  • 在此示例中,所有依赖项都已解决,至少带有 --nobuild 标志。
  • 请务必运行完整 build(指定的命令),并手动修复潜在的错误(例如工具链未正确注册)。
  • migration_info.md 文件包含有关迁移的详细信息。如需了解详情,请参阅此部分

转换

本部分展示了如何将代码从 WORKSPACE 文件迁移到 MODULE.bazel

WORKSPACE - Bazel 模块

http_archive(
    name = "rules_shell",
    sha256 = "3e114424a5c7e4fd43e0133cc6ecdfe54e45ae8affa14fadd839f29901424043",
    strip_prefix = "rules_shell-0.4.0",
    url = "https://github.com/bazelbuild/rules_shell/releases/download/v0.4.0/rules_shell-v0.4.0.tar.gz",
)

MODULE.bazel - Bazel 模块

bazel_dep(name = "rules_shell", version = "0.6.1")

WORKSPACE - Go 扩展服务

http_archive(
    name = "io_bazel_rules_go",
    integrity = "sha256-M6zErg9wUC20uJPJ/B3Xqb+ZjCPn/yxFF3QdQEmpdvg=",
    urls = [
        "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.48.0/rules_go-v0.48.0.zip",
        "https://github.com/bazelbuild/rules_go/releases/download/v0.48.0/rules_go-v0.48.0.zip",
    ],
)
http_archive(
    name = "bazel_gazelle",
    integrity = "sha256-12v3pg/YsFBEQJDfooN6Tq+YKeEWVhjuNdzspcvfWNU=",
    urls = [
        "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.37.0/bazel-gazelle-v0.37.0.tar.gz",
        "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.37.0/bazel-gazelle-v0.37.0.tar.gz",
    ],
)

load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")

go_rules_dependencies()
go_register_toolchains(version = "1.23.1")
gazelle_dependencies()

go_repository(
    name = "org_golang_x_net",
    importpath = "golang.org/x/net",
    sum = "h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=",
    version = "v0.0.0-20190311183353-d8887717615a",
    build_file_proto_mode = "disable",
    build_naming_convention = "import",
)

MODULE.bazel - Go 扩展程序

go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps")
go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")

go_deps.from_file(go_mod = "//:go.mod")
use_repo(go_deps, "org_golang_x_net")
go_sdk.from_file(go_mod = "//:go.mod")

go_deps.gazelle_override(
    path = "golang.org/x/net",
    directives = [
        "gazelle:proto disable",
         "gazelle:go_naming_convention import",
    ],
)

WORKSPACE - Python 扩展程序

http_archive(
    name = "rules_python",
    integrity = "sha256-qDdnnxOC8mlowe5vg5x9r5B5qlMSgGmh8oFd7KpjcwQ=",
    strip_prefix = "rules_python-1.4.0",
    url = "https://github.com/bazelbuild/rules_python/releases/download/1.4.0/rules_python-1.4.0.tar.gz",
)

load("@rules_python//python:repositories.bzl", "py_repositories")
py_repositories()

load("@rules_python//python:pip.bzl", "pip_parse")
pip_parse(
   name = "my_python_deps",
   requirements_lock = "@example//:requirements_lock.txt",
)

load("@my_python_deps//:requirements.bzl", "install_deps")
install_deps()

load("@rules_python//python:repositories.bzl", "python_register_toolchains")
python_register_toolchains(
    name = "python_3_11",
    python_version = "3.11",
)

MODULE.bazel - Python 扩展程序

pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
pip.parse(
    hub_name = "my_python_deps",
    python_version = "3.11",
    requirements_lock = "//:requirements_lock.txt",
)
use_repo(pip, "my_python_deps")

python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.defaults(python_version = "3.11")
python.toolchain(python_version = "3.11")

WORKSPACE - Maven 扩展程序

RULES_JVM_EXTERNAL_TAG = "4.5"
RULES_JVM_EXTERNAL_SHA = "b17d7388feb9bfa7f2fa09031b32707df529f26c91ab9e5d909eb1676badd9a6"

http_archive(
    name = "rules_jvm_external",
    strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
    sha256 = RULES_JVM_EXTERNAL_SHA,
    url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
)

load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")
rules_jvm_external_deps()
load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup")
rules_jvm_external_setup()

load("@rules_jvm_external//:defs.bzl", "maven_install")
maven_install(
    name = "px_deps",
    artifacts = [
        "org.antlr:antlr4:4.11.1",
    ],
    repositories = [
        "https://repo1.maven.org/maven2",
    ],
)

MODULE.bazel - Maven 扩展程序

bazel_dep(name = "rules_jvm_external", version = "6.8")

maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
use_repo(maven, "px_deps")

maven.artifact(
    name = "px_deps",
    group = "org.antlr",
    artifact = "antlr4",
    version = "4.11.1"
)

WORKSPACE - 代码库规则

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "com_github_cockroachdb_cockroach",
    sha256 = "6c3568ef244ce6b874694eeeecb83ed4f5d5dff6cf037c952ecde76828a6c502",
    strip_prefix = "cockroach-22.1.6",
    url = "https://github.com/cockroachdb/cockroach/archive/v22.1.6.tar.gz",
)

MODULE.bazel - 代码库规则

http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
  name = "com_github_cockroachdb_cockroach",
  url = "https://github.com/cockroachdb/cockroach/archive/v22.1.6.tar.gz",
  sha256 = "6c3568ef244ce6b874694eeeecb83ed4f5d5dff6cf037c952ecde76828a6c502",
  strip_prefix = "cockroach-22.1.6",
)

WORKSPACE - 模块扩展服务

load(":my_custom_macro.bzl", "my_custom_macro")

my_custom_macro(
    name = "my_custom_repo",
)

MODULE.bazel - 模块扩展

extension_for_my_custom_macro = use_extension("//:extension_for_my_custom_macro.bzl", "extension_for_my_custom_macro")
use_repo(extension_for_my_custom_macro, "my_custom_repo")

extension_for_my_custom_macro.bzl

load("//:my_custom_macro.bzl", "my_custom_macro")

def _extension_for_my_custom_macro_impl(ctx):
  my_custom_macro(
    name = "my_custom_repo",
  )

extension_for_my_custom_macro = module_extension(implementation = _extension_for_my_custom_macro_impl)

调试技巧

本部分提供了一些实用命令和信息,可帮助您调试在 Bzlmod 迁移期间可能出现的问题。

实用提示

  • 替换版本 - 升级依赖项版本有时会造成问题。Bzlmod 可能会因 MVS 算法而更改依赖项的版本。为了使用与 WORKSPACE 中相同的版本或类似的版本,请使用 single_version_override 替换该版本。 请注意,此功能有助于调试 WORKSPACE 和 Bzlmod 之间的差异,但不应长期依赖此功能。

    single_version_override(module_name = "{dep_name}", version = "{version}")

  • 使用 bazel mod 命令。

    • 使用 show_repo 命令检查指定代码库的版本。例如:

      bazel mod show_repo @rules_python

    • 使用 show_extension 命令查看模块扩展程序的相关信息。例如:

      bazel mod show_extension @rules_python//python/extensions:pip.bzl%pip

  • 如果您想监控或控制代码库的来源,可以使用供应商模式创建代码库的本地副本。例如:

    bazel vendor --enable_bzlmod --vendor_dir=vendor_src --repo=@protobuf

生成迁移报告

每次运行迁移脚本时,系统都会更新此文件;如果是首次运行,或者使用了 --i 标志,系统会从头开始生成此文件。报告包含以下内容:

  • 用于本地测试的命令。
  • 直接依赖项的列表(至少是项目中直接使用的依赖项)。
  • 对于每个依赖项,都有一个下拉菜单,用于检查代码库在 WORKSPACE 文件中的声明位置,这对于调试特别有用。您可以将其视为:

    > Click here to see where and how the repo was declared in the WORKSPACE
    file
  • 对于每个依赖项,它在 MODULE.bazel 文件中的实现方式。在之前的迁移示例中,该命令如下所示:

    1. Bazel 模块依赖项 - Migration of rules_python

      Found perfect name match in BCR: rules_python
      Found partially name matches in BCR: rules_python_gazelle_plugin
      
      It has been introduced as a Bazel module:
          `bazel_dep(name = "rules_python", version = "1.6.1")`
      • 如果脚本找到 perfect name match,就会自动使用它。如果出现错误,您可以仔细检查名称是否已正确添加。
    2. Python 扩展程序 - Migration of my_python_deps

      pip.parse(
          hub_name = "my_python_deps",
          requirements_lock = "//:requirements_lock.txt",
          python_version = "3.11",
      )
      use_repo(pip, "my_python_deps")
    3. Maven 扩展程序 - Migration of org.antlr (px_deps):

      maven.artifact(
          name = "px_deps",
          group = "org.antlr",
          artifact = "antlr4",
          version = "4.11.1"
      )
    4. Go 扩展程序 - Migration of org_golang_x_net

      go_deps.from_file(go_mod = "//:go.mod")
      go_sdk.from_file(go_mod = "//:go.mod")
      
      go_deps.gazelle_override(
          path = "golang.org/x/net",
          directives = [
              "gazelle:proto disable",
              "gazelle:go_naming_convention import",
          ],
      )
      • 它已在 go.mod 的帮助下作为 Go 模块引入。如果 go.modgo.sum 不可用,则直接将 Go 模块添加到 MODULE.bazel 文件中。
      • gazelle_override 用于添加特定指令。

反馈

如果您想做出贡献,请在 bazel-central-registry 中创建问题或 PR。