Bzlmod 遷移指南

7.3 · 7.2 · 7.1 · 7.0 · 6.5

由於 WORKSPACE 的缺點,Bzlmod 會在未來的 Bazel 版本中取代舊版 WORKSPACE 系統。本指南可協助您將專案遷移至 Bzlmod,並放棄 WORKSPACE,以便擷取外部依附元件。

WORKSPACE 與 Bzlmod

Bazel 的 WORKSPACE 與 Bzlmod 提供了語法不同的類似功能。本節說明如何從特定 WORKSPACE 功能遷移至 Bzlmod。

定義 Bazel 工作區的根目錄

WORKSPACE 檔案會標示 Bazel 專案的原始根目錄,在 Bazel 6.3 以上版本中,這個責任已由 MODULE.bazel 取代。在 6.3 之前的 Bazel 版本中,工作區根目錄應仍會有 WORKSPACEWORKSPACE.bazel 檔案,可能會附帶以下註解:

  • WORKSPACE

    # This file marks the root of the Bazel workspace.
    # See MODULE.bazel for external dependencies setup.
    

指定工作區的存放區名稱

  • WORKSPACE

    workspace 函式可用來指定工作區的存放區名稱。這樣一來,工作區中的目標 //foo:bar 就能以 @<workspace name>//foo:bar 的形式參照。如果未指定,工作區的預設存放區名稱為 __main__

    ## WORKSPACE
    workspace(name = "com_foo_bar")
    
  • Bzlmod

    建議您在同一個工作區中使用 //foo:bar 語法來參照目標,不要使用 @<repo name>。不過,如果您需要舊語法,可以使用 module 函式指定的模組名稱做為存放區名稱。如果模組名稱與所需存放區名稱不同,您可以使用 module 函式的 repo_name 屬性覆寫存放區名稱。

    ## MODULE.bazel
    module(
        name = "bar",
        repo_name = "com_foo_bar",
    )
    

擷取外部依附元件做為 Bazel 模組

如果依附元件是 Bazel 專案,您應該可以在採用 Bzlmod 時,將其視為 Bazel 模組來依附。

  • WORKSPACE

    使用 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 都依附 platoformplatform 依附元件的確切版本會由巨集的順序決定。

  • Bzlmod

    使用 Bzlmod 時,只要您的依附元件可在 Bazel 中央註冊中心或自訂的 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 演算法,以轉接方式解析 Bazel 模組依附元件。因此,系統會自動選取 platform 的最高必要版本。

將依附元件覆寫為 Bazel 模組

您可以透過不同的方式,在根模組中覆寫 Bazel 模組依附元件。

詳情請參閱「覆寫」一節。

您可以在範例存放區中找到一些使用範例。

使用模組擴充功能擷取外部依附元件

如果依附元件不是 Bazel 專案,或尚未在任何 Bazel 登錄中提供,您可以使用模組擴充功能引入依附元件。

  • 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 時,您必須將定義移至 .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.0foo 中的模組擴充功能可正確解決這項衝突,並自動選取資料依附元件的 3.0 版本。

整合第三方套件管理工具

接續上一節的內容,由於模組擴充功能提供了從依附元件圖表收集資訊、執行自訂邏輯來解析依附元件,以及呼叫存放區規則來引入外部存放區的方法,因此規則作者可以透過這個方法,強化整合特定語言套件管理員的規則集。

請參閱「模組擴充功能」頁面,進一步瞭解如何使用模組擴充功能。

以下列出已採用 Bzlmod 來從不同套件管理員擷取依附元件的規則集清單:

如需整合偽裝套件管理工具的簡易範例,請前往範例存放區。

偵測主機電腦上的工具鏈

當 Bazel 建構規則需要偵測主機上可用的工具鍊時,會使用存放區規則檢查主機,並產生工具鍊資訊做為外部存放區。

  • WORKSPACE

    以下列存放區規則偵測殼層工具鍊。

    ## 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 時,這項作業是由兩個原生存放區規則 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,我們目前正致力於將所有原生存放區規則轉換為 Starlark (請查看 #18285 瞭解進度)。接著,您可以在模組擴充功能中呼叫相對應的 Starlark local_repository。如果這會造成阻礙,則實作 local_repository 存放區規則的自訂版本也很容易。

繫結目標

WORKSPACE 中的 bind 規則已淘汰,且 Bzlmod 不支援該規則。這項功能的推出目的,是為了在特殊 //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-lib

遷移

本節提供 Bzlmod 遷移程序的實用資訊和指南。

瞭解 WORKSPACE 中的依附元件

遷移的第一步是瞭解您有哪些依附元件。由於遞移依附元件通常會透過 *_deps 巨集載入,因此很難找出 WORKSPACE 檔案中導入的確切依附元件。

使用工作區解析的檔案檢查外部依附元件

幸好,--experimental_repository_resolved_file 標記可以提供協助。此標記基本上會產生一個「鎖定檔案」,當中包含最後一個 Bazel 指令擷取的所有外部依附元件。詳情請參閱這篇網誌文章

使用方法有兩種:

  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 產生的檔案,就會發現 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 模組 repos 直接依附元件 直接依附元件 代管模組擴充功能的模組直接依附元件 根模組的直接依附元件
模組擴充功能存放區 直接依附元件 直接依附元件 代管模組擴充功能的模組的直接依附元件 + 由相同模組擴充功能產生的所有存放區 根模組的直接依附元件
Workspace Repos 所有可見項目 隱藏 隱藏 所有可見

遷移程序

典型的 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,請在發布頁面中建立並上傳發布檔案。GitHub 不會保證按需產生的來源封存檔總和。簡而言之,以 https://github.com/<org>/<repo>/releases/download/... 為格式的網址視為穩定,而 https://github.com/<org>/<repo>/archive/... 則不視為穩定。如需更多資訊,請參閱「GitHub 封存總和檢查碼服務中斷」。

  • 請務必確保原始碼樹狀結構遵循原始存放區的版面配置。

    如果您的存放區非常大,且您想透過移除不必要的來源來建立較小的發布封存檔,請確認移除的來源樹狀結構是原始來源樹狀結構的子集。如此一來,使用者就能更容易透過 archive_overridegit_override 將模組覆寫為非發布版本。

  • 在可測試您最常用的 API 的子目錄中加入測試模組。

    測試模組是 Bazel 專案,其中包含自己的 WORKSPACE 和 MODULE.bazel 檔案,位於來源封存檔的子目錄中,且取決於要發布的實際模組。應包含涵蓋最常見 API 的範例或一些整合測試。如要瞭解如何設定,請參閱測試模組

準備好來源封存網址後,請按照 BCR 貢獻指南,使用 GitHub 提取要求將模組提交至 BCR。

強烈建議為存放區設定「Publish to BCR」GitHub 應用程式,以便自動將模組提交至 BCR。

最佳做法

本節將說明幾項最佳做法,協助您更妥善地管理外部依附元件。

將目標分割成不同套件,避免擷取不必要的依附元件。

請檢查 #12835,其中會強制擷取測試的開發依附元件,用於建構不需要這些依附元件的目標。這並非 Bzlmod 專屬的做法,但遵循這些做法可讓您更輕鬆地正確指定開發依附元件。

指定開發依附元件

您可以將 bazel_depuse_extension 指令的 dev_dependency 屬性設為 true,這樣這兩個指令就不會套用至相依專案。根模組可以使用 --ignore_dev_dependency 標記,驗證目標是否仍在建構,且沒有開發依附元件。

社群遷移進度

您可以查看 Bazel 中央註冊中心,瞭解是否已提供所需的依附元件。如有其他問題,歡迎加入這個 GitHub 討論串,為阻礙遷移作業的依附元件按讚,或將這些依附元件貼出。

回報問題

如要查看已知的 Bzlmod 問題,請參閱 Bazel GitHub 問題清單。歡迎您隨時提出新問題或提出功能要求,以便解除封鎖遷移作業!