本頁面將說明使用巨集的基本概念,並介紹常見的用途、偵錯和慣例。
巨集是從 BUILD
檔案呼叫的函式,可將規則例項化。巨集主要用於封裝現有規則和其他巨集的程式碼,並重複使用這些程式碼。
巨集有兩種類型:本頁所述的符號巨集和舊版巨集。建議您盡可能使用符號巨集,讓程式碼更清楚。
符號巨集提供類型引數 (字串轉換為標籤,相對於巨集呼叫的位置),以及限制和指定建立目標的顯示設定。這些類別的設計可接受延遲評估 (將於日後的 Bazel 版本中新增)。根據預設,Bazel 8 會提供符號巨集。本文件提到 macros
時,是指符號巨集。
用量
在 .bzl
檔案中定義巨集時,請呼叫 macro()
函式,並傳入兩個必要參數:attrs
和 implementation
。
屬性
attrs
會接受屬性名稱至屬性類型的字典,代表巨集的引數。系統會隱含地將兩個常見的屬性 (name
和 visibility
) 新增至所有巨集,但不會將這些屬性納入傳遞至 attrs
的字典。
# macro/macro.bzl
my_macro = macro(
attrs = {
"deps": attr.label_list(mandatory = True, doc = "The dependencies passed to the inner cc_binary and cc_test targets"),
"create_test": attr.bool(default = False, configurable = False, doc = "If true, creates a test target"),
},
implementation = _my_macro_impl,
)
屬性類型宣告可接受 參數 mandatory
、default
和 doc
。大部分屬性類型也接受 configurable
參數,這項參數會決定屬性是否接受 select
。如果屬性為 configurable
,則會將非 select
值解析為無法設定的 select
,"foo"
會變成 select({"//conditions:default": "foo"})
。詳情請參閱「選取」一節。
屬性繼承
重要事項:屬性繼承是透過 --experimental_enable_macro_inherit_attrs
標記啟用的實驗功能。在預設情況下啟用這項功能前,本節所述的部分行為可能會有所變動。
巨集通常用於包裝規則 (或其他巨集),而巨集作者通常會使用 **kwargs
,將包裝符號的大部分屬性原封不動地轉送至巨集的主要目標 (或主要內部巨集)。
為了支援這類模式,巨集可以將規則或巨集符號傳遞至 macro()
的 inherit_attrs
引數,藉此繼承規則或其他巨集的屬性。(您也可以使用特殊字串 "common"
取代規則或巨集符號,以便繼承為所有 Starlark 建構規則定義的通用屬性)。只有公開屬性會繼承,而且巨集專屬 attrs
字典中的屬性會覆寫同名繼承屬性。您也可以使用 None
做為 attrs
字典中的值,移除繼承的屬性:
# macro/macro.bzl
my_macro = macro(
inherit_attrs = native.cc_library,
attrs = {
# override native.cc_library's `local_defines` attribute
local_defines = attr.string_list(default = ["FOO"]),
# do not inherit native.cc_library's `defines` attribute
defines = None,
},
...
)
無論原始屬性定義的預設值為何,非必要的繼承屬性預設值一律會覆寫為 None
。如果您需要檢查或修改繼承的非必要屬性 (例如,如果您想在繼承的 tags
屬性中新增標記),請務必在巨集的實作函式中處理 None
情況:
# macro/macro.bzl
_my_macro_implementation(name, visibility, tags, **kwargs):
# Append a tag; tags attr is an inherited non-mandatory attribute, and
# therefore is None unless explicitly set by the caller of our macro.
my_tags = (tags or []) + ["another_tag"]
native.cc_library(
...
tags = my_tags,
**kwargs,
)
...
實作
implementation
會接受含有巨集邏輯的函式。實作函式通常會透過呼叫一或多個規則來建立目標,且通常為私有 (名稱開頭為底線)。傳統上,這些函式會與巨集同名,但前置字串為 _
,後置字串為 _impl
。
與規則實作函式不同,規則實作函式會採用單一引數 (ctx
),其中包含屬性參照,而巨集實作函式會為每個引數接受參數。
# macro/macro.bzl
def _my_macro_impl(name, visibility, deps, create_test):
cc_library(
name = name + "_cc_lib",
deps = deps,
)
if create_test:
cc_test(
name = name + "_test",
srcs = ["my_test.cc"],
deps = deps,
)
如果巨集繼承屬性,其實作函式「必須」具有 **kwargs
剩餘關鍵字參數,可將其轉送至叫用繼承規則或子巨集的呼叫。(這有助於確保如果您繼承的規則或巨集新增了新屬性,巨集不會因此中斷)。
聲明
您可以透過在 BUILD
檔案中載入及呼叫巨集定義,宣告巨集。
# pkg/BUILD
my_macro(
name = "macro_instance",
deps = ["src.cc"] + select(
{
"//config_setting:special": ["special_source.cc"],
"//conditions:default": [],
},
),
create_tests = True,
)
這會建立 //pkg:macro_instance_cc_lib
和 //pkg:macro_instance_test
目標。
就像規則呼叫一樣,如果巨集呼叫中的屬性值設為 None
,系統會將該屬性視為由巨集呼叫端省略。舉例來說,以下兩個巨集呼叫的作用相同:
# pkg/BUILD
my_macro(name = "abc", srcs = ["src.cc"], deps = None)
my_macro(name = "abc", srcs = ["src.cc"])
這項功能通常不適用於 BUILD
檔案,但在以程式輔助方式在另一個巨集中包裝巨集時,這項功能就很實用。
詳細資料
建立目標的命名慣例
由符號巨集建立的任何目標或子巨集名稱,必須與巨集的 name
參數相符,或是前置 name
,後接 _
(建議)、.
或 -
。舉例來說,my_macro(name = "foo")
可能只會建立名為 foo
的檔案或目標,或是前置字串為 foo_
、foo-
或 foo.
的檔案或目標,例如 foo_bar
。
您可以宣告違反巨集命名慣例的目標或檔案,但無法建構,也無法用作依附元件。
與巨集例項位於相同套件中的非巨集檔案和目標,名稱應不與潛在的巨集目標名稱衝突,但系統不會強制執行這項排他性規定。我們正在實作延遲評估功能,以改善符號巨集的效能,因為在違反命名架構的套件中,符號巨集會受到影響。
限制
符號巨集與舊版巨集相比,有額外的限制。
符號巨集
- 必須使用
name
引數和visibility
引數 - 必須具備
implementation
函式 - 可能不會傳回值
- 可能不會變更其引數
- 除非是特殊的
finalizer
巨集,否則不得呼叫native.existing_rules()
- 可能無法呼叫
native.package()
- 可能不會呼叫
glob()
- 可能不會呼叫
native.environment_group()
- 必須建立名稱符合命名架構的目標
- 無法參照未宣告或未以引數傳入的輸入檔案 (詳情請參閱「可見度和巨集」)。
可見度和巨集
如要深入瞭解 Bazel 中的瀏覽權限,請參閱「瀏覽權限」。
指定可視度
根據預設,由符號巨集建立的目標只會顯示在包含定義巨集的 .bzl 檔案所在的套件中。具體來說,除非巨集的 .bzl 檔案與呼叫端位於相同套件中,否則呼叫端無法看到這些巨集。
如要讓目標對象對符號巨集的呼叫端可見,請將 visibility = visibility
傳遞至規則或內部巨集。您也可以為目標設定更廣泛 (甚至是公開) 的顯示範圍,讓目標在其他套件中顯示。
套件的預設瀏覽權限 (如 package()
中所宣告) 預設會傳遞至最外層巨集的 visibility
參數,但由巨集決定是否將該 visibility
傳遞至其例項化的目標。
依附元件可見度
巨集實作中所參照的目標必須對該巨集的定義可見。您可以透過下列任一方式提供可見度:
- 如果目標是透過標籤、標籤清單,或是標籤鍵或標籤值的字典屬性明確傳遞給巨集,巨集就會看到這些目標:
# pkg/BUILD
my_macro(... deps = ["//other_package:my_tool"] )
- ... 或做為屬性預設值:
# my_macro:macro.bzl
my_macro = macro(
attrs = {"deps" : attr.label_list(default = ["//other_package:my_tool"])},
...
)
- 如果目標宣告為可供包含定義巨集的 .bzl 檔案的套件可見,巨集也會看得到這些目標:
# other_package/BUILD
# Any macro defined in a .bzl file in //my_macro package can use this tool.
cc_binary(
name = "my_tool",
visibility = "//my_macro:\\__pkg__",
)
選取
如果屬性為 configurable
(預設),且其值並非 None
,則巨集實作函式會將屬性值視為包裝在簡單的 select
中。這樣一來,巨集作者就能更輕鬆地找出未預期屬性值可能為 select
的錯誤。
舉例來說,請參考以下巨集:
my_macro = macro(
attrs = {"deps": attr.label_list()}, # configurable unless specified otherwise
implementation = _my_macro_impl,
)
如果 my_macro
是使用 deps = ["//a"]
叫用,則會導致 _my_macro_impl
以其 deps
參數設為 select({"//conditions:default":
["//a"]})
的狀態下叫用。如果這會導致實作函式失敗 (例如,因為程式碼嘗試將索引標示為 deps[0]
中的值,而 select
不允許這類操作),巨集作者可以做出選擇:重新撰寫巨集,只使用與 select
相容的作業,或是將屬性標示為不可設定 (attr.label_list(configurable = False)
)。後者可確保使用者無法傳入 select
值。
規則目標會反向執行這項轉換作業,並將簡單的 select
儲存為無條件值;在上述範例中,如果 _my_macro_impl
宣告規則目標 my_rule(..., deps = deps)
,則該規則目標的 deps
會儲存為 ["//a"]
。這可確保 select
包裝不會導致在由巨集例項化的所有目標中儲存瑣碎的 select
值。
如果可設定屬性的值為 None
,則不會包裝在 select
中。這可確保 my_attr == None
等測試仍能正常運作,且當屬性轉送至具有計算預設值的規則時,規則會正常運作 (也就是說,就好像完全沒有傳入屬性一樣)。屬性不一定會採用 None
值,但 attr.label()
類型和任何繼承的非必要屬性都可能會採用。
終結器
規則完成器是特殊的符號巨集,無論其在 BUILD 檔案中的字彙位置為何,都會在載入套件的最後階段評估,也就是在定義所有非完成器目標後。與一般符號式巨集不同,終結器可以呼叫 native.existing_rules()
,其行為與舊版巨集略有不同:只會傳回一組非終結器規則目標。終結器可能會針對該集合的狀態提出斷言,或定義新的目標。
如要宣告終結器,請使用 finalizer = True
呼叫 macro()
:
def _my_finalizer_impl(name, visibility, tags_filter):
for r in native.existing_rules().values():
for tag in r.get("tags", []):
if tag in tags_filter:
my_test(
name = name + "_" + r["name"] + "_finalizer_test",
deps = [r["name"]],
data = r["srcs"],
...
)
continue
my_finalizer = macro(
attrs = {"tags_filter": attr.string_list(configurable = False)},
implementation = _impl,
finalizer = True,
)
懶惰
重要事項:我們正在實作延遲巨集展開和評估功能。這項功能尚未推出。
目前,系統會在載入 BUILD 檔案後立即評估所有巨集,這可能會對套件中目標的效能造成負面影響,因為這些套件也包含耗用大量資源的無關巨集。日後,只有在建構作業需要非完成器符號巨集時,系統才會評估這些巨集。前置字串命名結構定義可協助 Bazel 判斷要針對要求的目標展開哪個巨集。
遷移作業疑難排解
以下列舉一些常見的遷移問題和解決方法。
- 舊版巨集呼叫
glob()
將 glob()
呼叫移至 BUILD 檔案 (或從 BUILD 檔案呼叫的舊版巨集),然後使用標籤清單屬性將 glob()
值傳遞至符號巨集:
# BUILD file
my_macro(
...,
deps = glob(...),
)
- 舊版巨集的參數不是有效的 starlark
attr
類型。
盡可能將邏輯納入巢狀符號式巨集,但請將頂層巨集設為舊版巨集。
- 舊版巨集會呼叫規則,建立違反命名結構定義的目標
沒關係,只要不要依賴「違規」目標即可。系統會悄悄略過命名檢查。