模組擴充功能

回報問題 查看原始碼

模組擴充功能可讓使用者讀取依附元件圖表中的模組輸入資料、執行必要的邏輯以解析依附元件,最後透過呼叫存放區規則建立存放區,藉此擴充模組系統。這些擴充功能的功能與存放區規則類似,能夠執行檔案 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 的傳回值繫結至變數,讓使用者可使用點號語法來指定擴充功能的標記。標記必須符合擴充功能定義中相應標記類別定義的結構定義。例如指定部分 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")

擴充功能產生的 Repos 屬於其 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)

擴充功能身分

模組擴充功能透過 use_extension 呼叫中顯示的名稱和 .bzl 檔案識別。在以下範例中,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,對使用者而言不具回溯相容性。

指定作業系統和架構

如果您的擴充功能依賴作業系統或其架構類型,請務必在擴充功能定義中使用 os_dependentarch_dependent 布林屬性表明這一點。這可確保 Bazel 知道如果其中一個項目有變更,就必須確認重新評估。

只有根模組應直接影響存放區名稱

請記住,擴充功能建立存放區時,會在擴充功能的命名空間中建立。這意味著如果不同模組使用相同的擴充功能,且最終建立有相同名稱的存放區,便可能會發生衝突。這通常是做為模組擴充功能的 tag_class,具有以存放區規則 name 值形式傳遞的 name 引數。

舉例來說,假設根模組 A 依附於 B 模組。這兩個模組都依附於 mylang 模組。如果 AB 都呼叫了 mylang.toolchain(name="foo"),則兩者都會嘗試在 mylang 模組中建立名為 foo 的存放區,並發生錯誤。

為避免這種情況發生,請移除直接設定存放區名稱的功能,或是僅允許根模組這麼做。您可以允許根模組執行這項動作,因為不需依賴根模組,這樣就不必考慮其他建立衝突的模組。