模組擴充功能可讓使用者透過讀取依附元件圖中模組的輸入資料、執行必要邏輯來解析依附元件,並最終透過呼叫 repo 規則來建立 repo,進而擴充模組系統。這些擴充功能的功能類似於 repo 規則,可執行檔案 I/O、傳送網路要求等。除了其他要素,Bazel 也能協助 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.install
和 maven.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
指令,將擴充功能產生的 repos 納入目前模組的範圍。
use_repo(maven, "maven")
擴充功能產生的 repos 是其 API 的一部分。在這個範例中,「maven」模組擴充功能會承諾產生名為 maven
的存放區。有了上述宣告,擴充功能就能正確解析 @maven//:org_junit_junit
等標籤,指向由「maven」擴充功能產生的 repo。
擴充功能定義
您可以使用 module_extension
函式,定義模組擴充功能,類似於設定存放區規則。不過,雖然 repo 規則具有多個屬性,但模組擴充功能則具有 tag_class
,每個 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.install
和 maven.artifact
標記。
模組擴充功能的實作函式與 repo 規則的實作函式類似,唯一的差異在於它們會取得 module_ctx
物件,可授予使用擴充功能的所有模組和所有相關標記的存取權。實作函式會呼叫 repo 規則來產生 repo。
# @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
呼叫中。在以下範例中,系統會透過 .bzl
檔案 @rules_jvm_external//:extension.bzl
和名稱 maven
識別擴充功能 maven
:
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
從其他 .bzl
檔案重新匯出擴充功能,可為其提供新的身分識別資訊。如果在傳遞模組圖中使用兩個版本的擴充功能,系統會分別評估這兩個版本,並只顯示與該特定身分識別資訊相關聯的標記。
身為擴充功能作者,您應確保使用者只會從單一 .bzl
檔案使用模組擴充功能。
存放區名稱和瀏覽權限
擴充功能產生的 repo 具有 module_repo_canonical_name~extension_name~repo_name
格式的標準名稱。如果是代管在根模組中的擴充功能,module_repo_canonical_name
部分會替換為字串 _main
。請注意,標準名稱格式並非您應依附的 API,且名稱隨時可能變更。
這項命名政策表示每個擴充功能都有自己的「存放區命名空間」;兩個不同的擴充功能可以各自定義同名的存放區,而不會發生任何衝突。這也表示 repository_ctx.name
會回報原始碼存放區的正規名稱,而這與原始碼存放區規則呼叫中指定的名稱「不一樣」。
將模組擴充功能產生的存放區納入考量,有幾個存放區瀏覽權限規則:
- Bazel 模組存放區可以透過
bazel_dep
和use_repo
,查看MODULE.bazel
檔案中引入的所有存放區。 - 模組擴充功能產生的存放區可以查看代管擴充功能的模組可見的所有存放區,加上由相同模組擴充功能產生的所有其他存放區 (使用存放區規則呼叫中指定的名稱做為其顯示名稱)。
- 這可能會造成衝突。如果模組存放區可查看名稱為
foo
的存放區,且擴充功能會產生指定名稱foo
的存放區,則該擴充功能foo
產生的所有存放區都會參照前者。
- 這可能會造成衝突。如果模組存放區可查看名稱為
最佳做法
本節說明編寫擴充功能的最佳做法,方便日後使用、維護,並隨時間變化妥善調整。
將每個擴充功能放在個別檔案中
當擴充功能位於不同檔案中時,可讓一個擴充功能載入由另一個擴充功能產生的存放區。即使您不使用這項功能,還是建議將這些資訊放在個別檔案中,以備日後使用。這是因為擴充功能的 ID 會根據其檔案而定,因此日後將擴充功能移至其他檔案會變更公開 API,並對使用者造成不相容的變更。
指定作業系統和架構
如果您的擴充功能必須使用作業系統或架構類型,請務必在擴充功能定義中使用 os_dependent
和 arch_dependent
布林值屬性來表示。這樣一來,如果其中任何一個項目有變更,Bazel 就會知道需要重新評估。
只有根模組應直接影響存放區名稱
請注意,當擴充功能建立存放區時,這些存放區會在擴充功能的命名空間中建立。也就是說,如果不同模組使用相同的擴充功能,並且建立同一個名稱的存放區,就可能發生衝突。這通常會導致模組擴充功能的 tag_class
具有 name
引數,而該引數會做為存放區規則的 name
值傳遞。
舉例來說,假設根模組 A
依附於模組 B
。兩個模組都依附於模組 mylang
。如果 A
和 B
都呼叫 mylang.toolchain(name="foo")
,兩者都會嘗試在 mylang
模組中建立名為 foo
的存放區,並發生錯誤。
為避免這種情況,請移除直接設定存放區名稱的功能,或是只允許根模組執行此操作。允許根模組執行此操作是安全的,因為沒有任何東西會依附於此模組,因此不必擔心其他模組會建立相衝突的名稱。