Bzlmod 遷移指南

回報問題 查看原始碼 Nightly · 8.0 . 7.4 . 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

    您可以透過以下方式,使用 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 套件中為目標指定別名。所有依附此 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 中的依附元件

遷移的第一步是瞭解您有哪些依附元件。由於遞移依附元件通常會透過 *_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 專屬的做法,但遵循這些做法可讓您更輕鬆地正確指定開發依附元件。

指定開發依附元件

您可以將 dev_dependency 屬性設為 bazel_depuse_extension 指令的 true,這樣就不會將指令傳播至相關專案。您可以使用根模組的 --ignore_dev_dependency 標記,確認目標是否仍可在沒有開發依附元件的情況下建構。

社群遷移進度

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

回報問題

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