模組擴充功能

回報問題 查看來源

模組擴充功能可讓使用者透過以下方式擴充模組系統:讀取依附元件圖表中模組的輸入資料、執行必要的邏輯來解決依附元件,最後再透過呼叫存放區規則建立存放區。這些擴充功能的功能與存放區規則類似,可執行檔案 I/O、傳送網路要求等。除此之外,這些物件可讓 Bazel 與其他套件管理系統互動,同時遵循透過 Bazel 模組建構的依附元件圖表。

您可以在 .bzl 檔案中定義模組擴充功能,就像存放區規則一樣。不會直接叫用;而是每個模組都會指定要讓擴充功能讀取的資料稱為「標記」。Bazel 會先執行模組解析作業,再評估任何擴充功能。這個擴充功能會讀取整個依附元件圖表中屬於該擴充功能的所有標記。

擴充功能使用情形

擴充功能是由 Bazel 模組代管。如要在模組中使用擴充功能,請先在代管擴充功能的模組中新增 bazel_dep,然後呼叫 use_extension 內建函式來納入該擴充功能的範圍。請參考以下範例:來自 MODULE.bazel 檔案的程式碼片段,使用在 rules_jvm_external 模組中定義的「maven」副檔名:

bazel_dep(name = "rules_jvm_external", version = "4.5")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

這會將 use_extension 的傳回值繫結至變數,讓使用者可使用 dot-syntax 指定擴充功能的標記。標記必須遵循擴充功能定義中指定的對應標記類別定義的結構定義。例如指定部分 maven.installmaven.artifact 標記的範例:

maven.install(artifacts = ["org.junit:junit:4.13.2"])
maven.artifact(group = "com.google.guava",
               artifact = "guava",
               version = "27.0-jre",
               exclusions = ["com.google.j2objc:j2objc-annotations"])

使用 use_repo 指令,將擴充功能產生的存放區移至目前模組的範圍內。

use_repo(maven, "maven")

由擴充功能產生的重新產生的是其 API 的一部分。在這個範例中,「maven」模組擴充功能會承諾產生名為 maven 的存放區。透過上述的宣告,擴充功能會正確解析 @maven//:org_junit_junit 等標籤,指向由「maven」擴充功能產生的存放區。

擴充功能定義

您可以採用類似存放區規則的 module_extension 函式定義模組擴充功能。不過,雖然存放區規則有多個屬性,但模組擴充功能有 tag_class,每個擴充功能都具備多個屬性。標記類別會定義這項擴充功能所用標記的結構定義。例如,上述的「maven」擴充功能可以定義如下:

# @rules_jvm_external//:extensions.bzl

_install = tag_class(attrs = {"artifacts": attr.string_list(), ...})
_artifact = tag_class(attrs = {"group": attr.string(), "artifact": attr.string(), ...})
maven = module_extension(
  implementation = _maven_impl,
  tag_classes = {"install": _install, "artifact": _artifact},
)

這些宣告顯示,可以使用指定的屬性結構定義來指定 maven.installmaven.artifact 標記。

模組擴充功能的實作函式與存放區規則的實作函式類似,差別在於會取得 module_ctx 物件,可使用擴充功能和所有相關標記授予所有模組的存取權。接著,實作函式會呼叫存放區規則來產生存放區。

# @rules_jvm_external//:extensions.bzl

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")  # a repo rule
def _maven_impl(ctx):
  # This is a fake implementation for demonstration purposes only

  # collect artifacts from across the dependency graph
  artifacts = []
  for mod in ctx.modules:
    for install in mod.tags.install:
      artifacts += install.artifacts
    artifacts += [_to_artifact(artifact) for artifact in mod.tags.artifact]

  # call out to the coursier CLI tool to resolve dependencies
  output = ctx.execute(["coursier", "resolve", artifacts])
  repo_attrs = _process_coursier_output(output)

  # call repo rules to generate repos
  for attrs in repo_attrs:
    http_file(**attrs)
  _generate_hub_repo(name = "maven", repo_attrs)

擴充功能身分

模組擴充功能可透過名稱和 .bzl 檔案識別,這些內容會顯示在 use_extension 呼叫中。在以下範例中,擴充功能 maven 可透過 .bzl 檔案 @rules_jvm_external//:extension.bzl 和名稱 maven 識別:

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

從不同的 .bzl 檔案重新匯出擴充功能會取得新的身分,如果兩個版本的擴充功能都用於遞移模組圖表中,系統會分別評估兩者,而且只會看到與該特定身分相關聯的標記。

做為擴充功能作者,請確保使用者只會使用單一 .bzl 檔案中的模組擴充功能。

存放區名稱和瀏覽權限

擴充功能產生的回覆會採用 module_repo_canonical_name~extension_name~repo_name 形式的標準名稱。對於在根模組中代管的擴充功能,module_repo_canonical_name 部分會替換為 _main 字串。請注意,標準名稱格式並非您應依附的 API,且名稱隨時可能變更。

這個命名政策代表每個擴充功能都有自己的「存放區命名空間」;兩個不同的擴充功能可分別定義名稱相同的存放區,而不會產生任何衝突。這也表示 repository_ctx.name 會報告存放區的正規名稱,這與存放區規則呼叫中指定的名稱「不同」

將模組擴充功能產生的存放區納入考量,有幾個存放區瀏覽權限規則:

  • Bazel 模組存放區可透過 bazel_depuse_repo 查看其 MODULE.bazel 檔案內導入的所有存放區。
  • 模組擴充功能產生的存放區,可查看代管擴充功能模組的所有存放區,以及由同一模組擴充功能產生的所有其他存放區 (使用存放區規則呼叫中指定的名稱做為其顯示名稱)。
    • 這可能會造成衝突。如果模組存放區可查看名稱為 foo 的存放區,且擴充功能會產生指定名稱 foo 的存放區,則該擴充功能 foo 產生的所有存放區都會參照前者。

最佳做法

本節說明編寫擴充功能的最佳做法,方便日後使用、維護,並隨時間變化妥善調整。

將各項副檔名放入個別檔案中

當擴充功能位於不同檔案中時,會允許某個擴充功能載入其他擴充功能產生的存放區。即使您並未使用這項功能,最好將這些檔案放在不同檔案,以備不時之需。這是因為擴充功能的識別是以其檔案為基礎,因此將擴充功能移至其他檔案之後,會變更您的公用 API,而且對使用者而言,不具回溯相容性。