模組擴充功能可讓使用者從依附元件圖表中的模組讀取輸入資料、執行必要邏輯來解析依附元件,最後呼叫 repo 規則來建立 repo,藉此擴充模組系統。這些擴充功能的功能與存放區規則類似,因此可以執行檔案 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.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
指令,將擴充功能產生的存放區帶入目前模組的範圍。
use_repo(maven, "maven")
擴充功能產生的存放區是其 API 的一部分。在本例中,「maven」模組擴充功能保證會產生名為 maven
的存放區。使用上述宣告後,擴充功能會正確解析標籤 (例如 @maven//:org_junit_junit
),指向「maven」擴充功能產生的存放區。
擴充功能定義
您可以使用 module_extension
函式,定義類似於存放區規則的模組擴充功能。不過,雖然存放區規則有多項屬性,但模組擴充功能具有 tag_class
es,每個 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
標記。
模組擴充功能的實作函式與存放區規則的函式類似,但會取得 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
的呼叫中。在下列範例中,擴充功能 maven
是由 .bzl
檔案 @rules_jvm_external//:extension.bzl
和名稱 maven
所識別:
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
從其他 .bzl
檔案重新匯出擴充功能會產生新的 ID,如果擴充功能的兩個版本都用於遞移模組圖,系統會分別評估這兩個版本,且只會看到與該特定 ID 相關聯的標記。
身為擴充功能作者,您應確保使用者只會從單一 .bzl
檔案使用模組擴充功能。
存放區名稱和瀏覽權限
擴充功能產生的存放區會採用標準名稱,格式為 module_repo_canonical_name+extension_name+repo_name
。請注意,正規名稱格式不屬於您應依附的 API,隨時可能變更。
這項命名政策表示每個擴充功能都有自己的「存放區命名空間」;兩個不同的擴充功能可以各自定義名稱相同的存放區,不會有任何衝突風險。這也表示 repository_ctx.name
會回報存放區的正規名稱,並非存放區規則呼叫中指定的名稱。
考量模組擴充功能產生的存放區,有幾項存放區顯示規則:
- Bazel 模組存放區可以透過
bazel_dep
和use_repo
,查看MODULE.bazel
檔案中導入的所有存放區。 - 模組擴充功能產生的存放區可以查看模組 (主機) 可見的所有存放區,以及同一模組擴充功能產生的所有其他存放區 (使用存放區規則呼叫中指定的名稱做為顯示名稱)。
- 這可能會導致衝突。如果模組存放區可看到名稱為
foo
的存放區,且擴充功能會產生名稱為foo
的存放區,則對於該擴充功能產生的所有存放區,foo
會參照前者。
- 這可能會導致衝突。如果模組存放區可看到名稱為
- 同樣地,在模組擴充功能的實作函式中,擴充功能建立的存放區可以在屬性中依其顯而易見的名稱互相參照,無論建立順序為何。
- 如果與模組可見的存放區發生衝突,傳遞至存放區規則屬性的標籤可以包裝在
Label
的呼叫中,確保標籤參照的是模組可見的存放區,而非同名的擴充功能產生的存放區。
- 如果與模組可見的存放區發生衝突,傳遞至存放區規則屬性的標籤可以包裝在
覆寫及注入模組擴充功能存放區
根模組可以使用 override_repo
和 inject_repo
覆寫或注入模組擴充功能 repo。
範例:以供應商提供的副本取代 rules_java
的 java_tools
# MODULE.bazel
local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository")
local_repository(
name = "my_java_tools",
path = "vendor/java_tools",
)
bazel_dep(name = "rules_java", version = "7.11.1")
java_toolchains = use_extension("@rules_java//java:extension.bzl", "toolchains")
override_repo(java_toolchains, remote_java_tools = "my_java_tools")
範例:修補 Go 依附元件,使其依附於 @zlib
,而非系統 zlib
# MODULE.bazel
bazel_dep(name = "gazelle", version = "0.38.0")
bazel_dep(name = "zlib", version = "1.3.1.bcr.3")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
go_deps.module_override(
patches = [
"//patches:my_module_zlib.patch",
],
path = "example.com/my_module",
)
use_repo(go_deps, ...)
inject_repo(go_deps, "zlib")
# patches/my_module_zlib.patch
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,6 +1,6 @@
go_binary(
name = "my_module",
importpath = "example.com/my_module",
srcs = ["my_module.go"],
- copts = ["-lz"],
+ cdeps = ["@zlib"],
)
最佳做法
本節說明撰寫擴充功能的最佳做法,確保擴充功能易於使用、可維護,且能隨著時間推移適應變化。
將每個擴充功能放在不同的檔案中
如果擴充功能位於不同檔案中,一個擴充功能就能載入由另一個擴充功能產生的存放區。即使您未使用這項功能,最好還是將這些檔案分開,以備不時之需。這是因為擴充功能的 ID 是以檔案為依據,因此之後將擴充功能移至其他檔案會變更公開 API,對使用者來說是向後不相容的變更。
指定可複製性
如果擴充功能一律會根據相同輸入內容 (擴充功能標記、讀取的檔案等) 定義相同的存放區,且特別是不依賴任何下載內容 (不受總和檢查碼保護),請考慮傳回 extension_metadata
和 reproducible = True
。這樣 Bazel 在寫入鎖定檔時,就能略過這個擴充功能。
指定作業系統和架構
如果擴充功能依附於作業系統或其架構類型,請務必使用 os_dependent
和 arch_dependent
布林值屬性,在擴充功能定義中指出這點。這樣一來,只要其中一個項目有變更,Bazel 就會知道需要重新評估。
由於這類對主機的依附關係會增加維護這項擴充功能鎖定檔項目的難度,因此請盡可能將擴充功能標示為可重現。
只有根模組應直接影響存放區名稱
請注意,擴充功能建立的存放區會位於擴充功能的命名空間中。也就是說,如果不同模組使用相同的擴充功能,並建立名稱相同的存放區,就可能發生衝突。這通常會顯示為模組擴充功能的 tag_class
具有做為存放區規則 name
值的 name
引數。
舉例來說,假設根模組 A
依附於模組 B
。這兩個模組都依附於模組 mylang
。如果 A
和 B
都呼叫 mylang.toolchain(name="foo")
,兩者都會嘗試在 mylang
模組中建立名為 foo
的存放區,因此會發生錯誤。
為避免發生這種情況,請移除直接設定存放區名稱的功能,或只允許根模組執行這項操作。允許根模組執行這項操作沒問題,因為沒有任何項目會依附於根模組,因此根模組不必擔心其他模組建立衝突的名稱。