模組擴充功能可讓使用者透過以下方式擴充模組系統:讀取依附元件圖表中模組的輸入資料、執行必要的邏輯來解決依附元件,最後再透過呼叫存放區規則建立存放區。這些擴充功能的功能類似於 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
指令,將擴充功能產生的存放區移至目前模組的範圍內。
use_repo(maven, "maven")
擴充功能產生的 repos 是其 API 的一部分。在這個範例中,「maven」模組擴充功能會產生名為 maven
的存放區。有了上述宣告,擴充功能就能正確解析 @maven//:org_junit_junit
等標籤,指向由「maven」擴充功能產生的 repo。
擴充功能定義
您可以使用 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.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
格式的標準名稱。請注意,正規名稱格式並非您應依附的 API,因為格式隨時可能會變更。
這項命名政策表示每個擴充功能都有自己的「存放區命名空間」;兩個不同的擴充功能可以各自定義同名的存放區,而不會發生任何衝突。這也表示 repository_ctx.name
會回報原始碼存放區的正規名稱,而這與原始碼存放區規則呼叫中指定的名稱「不一樣」。
將模組擴充功能產生的存放區納入考量,有幾個存放區瀏覽權限規則:
- Bazel 模組存放區可透過
bazel_dep
和use_repo
查看其MODULE.bazel
檔案內導入的所有存放區。 - 模組擴充功能產生的存放區,可查看代管擴充功能模組的所有存放區,以及由同一模組擴充功能產生的所有其他存放區 (使用存放區規則呼叫中指定的名稱做為其顯示名稱)。
- 這可能會導致衝突。如果模組存放區可以看到名稱為
foo
的存放區,而擴充功能會產生名稱為foo
的存放區,那麼對於該擴充功能產生的所有存放區,foo
會參照前者。
- 這可能會導致衝突。如果模組存放區可以看到名稱為
- 同樣地,在模組擴充功能的實作函式中,擴充功能建立的 repos 可以透過屬性中的明顯名稱彼此參照,不受建立順序影響。
- 如果與模組可見的存放區發生衝突,則可將傳遞至存放區規則屬性的標籤包裝在對
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,並對使用者造成不相容的變更。
指定可重現性
如果您的擴充功能在相同輸入內容 (擴充功能標記、讀取的檔案等) 下,一律定義相同的存放區,且特別不依賴任何未受總和檢查碼保護的下載內容,請考慮使用 reproducible = True
傳回 extension_metadata
。這樣一來,Bazel 在寫入鎖定檔案時,就能略過這個擴充功能。
指定作業系統和架構
如果擴充功能需要依賴作業系統或其架構類型,請務必使用 os_dependent
和 arch_dependent
布林值屬性,在擴充功能定義中指出這項資訊。這樣一來,如果其中任何一個項目有變更,Bazel 就會知道需要重新評估。
由於這種對主機的依附性會使這類擴充功能的鎖定檔案項目更難維護,因此請盡可能將擴充功能標示為可重現。
只有根模組應直接影響存放區名稱
請注意,當擴充功能建立存放區時,這些存放區會在擴充功能的命名空間中建立。也就是說,如果不同模組使用相同的擴充功能,並且建立同一個名稱的存放區,就可能發生衝突。這通常會導致模組擴充功能的 tag_class
具有 name
引數,而該引數會做為存放區規則的 name
值傳遞。
舉例來說,假設根模組 A
依附於 B
模組。兩個模組都依附於模組 mylang
。如果 A
和 B
都呼叫 mylang.toolchain(name="foo")
,兩者都會嘗試在 mylang
模組中建立名為 foo
的存放區,並發生錯誤。
為避免這種情況,請移除直接設定存放區名稱的功能,或是只允許根模組執行此操作。允許根模組執行此操作是安全的,因為沒有任何東西會依附於此模組,因此不必擔心其他模組會建立相衝突的名稱。