本頁面說明 Starlark 設定的優點和基本用法,以及 Bazel 的 API,用於自訂專案建構方式。內容包括如何定義建構設定及提供範例。
這可讓您:
- 為專案定義自訂標記,不再需要使用
--define
- 請編寫轉場效果,以便採用與父項不同的設定 (例如
--compilation_mode=opt
或--cpu=arm
) - 運用更妥善的預設規則 (例如使用特定 SDK 自動建構
//my:android_app
)
而且全都來自 .bzl 檔案 (不需要 Bazel 版本)。如需範例,請參閱 bazelbuild/examples
存放區。
使用者定義的建構設定
建構設定是一部分設定資訊。您可以將設定視為鍵/值對應。設定 --cpu=ppc
和 --copt="-DFoo"
會產生類似 {cpu: ppc, copt: "-DFoo"}
的設定。每個項目都是一個建構設定。
cpu
和 copt
等傳統旗標是原生設定,均已定義鍵,且值是在原生 Bazel Java 程式碼中定義。Bazel 使用者只能透過指令列和其他原生維護的 API 讀取及寫入這些資料。變更原生標記,以及公開標記的 API 需要 bazel 版本。.bzl
檔案中定義了使用者定義的建構設定,因此您不需要透過 Bazel 版本登錄變更。您也可以透過指令列設定這些區域 (如果指定為 flags
,詳情請見下文),您也可以透過使用者定義的轉場效果進行設定。
定義建構設定
build_setting
rule()
參數
建構設定與任何其他規則一樣,會使用 Starlark rule()
函式的 build_setting
屬性區分版本。
# example/buildsettings/build_settings.bzl
string_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
build_setting
屬性會用於指定建構設定類型的函式。類型限制為一組基本 Starlark 類型,例如 bool
和 string
。詳情請參閱 config
模組說明文件。您可以在規則的實作函式中完成更複雜的輸入。下文將詳細說明。
config
模組的函式使用選用的布林參數 flag
,該參數預設為 false。如果 flag
設為 true,使用者也可以在指令列上透過預設值和轉換,在指令列上設定建構設定。使用者無法自行設定部分設定。舉例來說,如果您是規則寫入者,而想在測試規則中啟用這項功能,您就不會不想讓使用者在其他非測試規則中自行開啟該功能。
使用 ctx.build_setting_value
如同所有規則,建構設定規則具有實作函式。您可以透過 ctx.build_setting_value
方法存取建構設定的基本 Starlark 類型值。這個方法僅適用於建構設定規則的 ctx
物件。這些實作方法可以直接轉寄建構設定值或執行其他工作,例如類型檢查或更複雜的結構建立作業。以下說明如何實作 enum
類型的建構設定:
# example/buildsettings/build_settings.bzl
TemperatureProvider = provider(fields = ['type'])
temperatures = ["HOT", "LUKEWARM", "ICED"]
def _impl(ctx):
raw_temperature = ctx.build_setting_value
if raw_temperature not in temperatures:
fail(str(ctx.label) + " build setting allowed to take values {"
+ ", ".join(temperatures) + "} but was set to unallowed value "
+ raw_temperature)
return TemperatureProvider(type = raw_temperature)
temperature = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
定義多組字串標記
字串設定內有額外的 allow_multiple
參數,可讓您在指令列或 bazelrc 中多次設定該旗標。系統仍會透過字串類型的屬性設定這些欄位的預設值:
# example/buildsettings/build_settings.bzl
allow_multiple_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True, allow_multiple = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "allow_multiple_flag")
allow_multiple_flag(
name = "roasts",
build_setting_default = "medium"
)
系統會將該標記的每項設定視為單一值:
$ bazel build //my/target --//example:roasts=blonde \
--//example:roasts=medium,dark
上述屬性會剖析為 {"//example:roasts": ["blonde", "medium,dark"]}
,ctx.build_setting_value
會傳回 ["blonde", "medium,dark"]
清單。
正在執行個體化建構設定
使用 build_setting
參數定義的規則具有隱含的必要 build_setting_default
屬性。此屬性採用的類型與 build_setting
參數所宣告的類型相同。
# example/buildsettings/build_settings.bzl
FlavorProvider = provider(fields = ['type'])
def _impl(ctx):
return FlavorProvider(type = ctx.build_setting_value)
flavor = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "flavor")
flavor(
name = "favorite_flavor",
build_setting_default = "APPLE"
)
預先定義的設定
Skylib 程式庫提供一組預先定義的設定,可供您在不編寫自訂 Starlark 的情況下執行個體化。
舉例來說,如要定義接受一組有限字串值的設定,請使用:
# example/BUILD
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
string_flag(
name = "myflag",
values = ["a", "b", "c"],
build_setting_default = "a",
)
如需完整清單,請參閱常見建構設定規則。
使用建構設定
視建構設定而定
如果目標想讀取設定資訊,可以透過一般屬性依附元件直接依賴建構設定。
# example/rules.bzl
load("//example/buildsettings:build_settings.bzl", "FlavorProvider")
def _rule_impl(ctx):
if ctx.attr.flavor[FlavorProvider].type == "ORANGE":
...
drink_rule = rule(
implementation = _rule_impl,
attrs = {
"flavor": attr.label()
}
)
# example/BUILD
load("//example:rules.bzl", "drink_rule")
load("//example/buildsettings:build_settings.bzl", "flavor")
flavor(
name = "favorite_flavor",
build_setting_default = "APPLE"
)
drink_rule(
name = "my_drink",
flavor = ":favorite_flavor",
)
語言可能會想建立一組標準的建構設定,該語言的所有規則都會依附於該語言。雖然 fragments
的原生概念不再做為 Starlark 設定環境中的硬式編碼物件,但轉譯這個概念的方法之一,是使用常見的隱含屬性組合。舉例來說:
# kotlin/rules.bzl
_KOTLIN_CONFIG = {
"_compiler": attr.label(default = "//kotlin/config:compiler-flag"),
"_mode": attr.label(default = "//kotlin/config:mode-flag"),
...
}
...
kotlin_library = rule(
implementation = _rule_impl,
attrs = dicts.add({
"library-attr": attr.string()
}, _KOTLIN_CONFIG)
)
kotlin_binary = rule(
implementation = _binary_impl,
attrs = dicts.add({
"binary-attr": attr.label()
}, _KOTLIN_CONFIG)
在指令列中使用建構設定
與大多數原生旗標類似,您可以使用指令列設定標示為旗標的建構設定。建構設定的名稱是使用 name=value
語法的完整目標路徑:
$ bazel build //my/target --//example:string_flag=some-value # allowed
$ bazel build //my/target --//example:string_flag some-value # not allowed
支援特殊布林值語法:
$ bazel build //my/target --//example:boolean_flag
$ bazel build //my/target --no//example:boolean_flag
使用建構設定別名
您可以為建構設定目標路徑設定別名,使其更容易閱讀指令列。別名的功能與原生標記類似,並且使用雙破折號選項語法。
將 --flag_alias=ALIAS_NAME=TARGET_PATH
新增至 .bazelrc
來設定別名。舉例來說,如要將別名設為 coffee
:
# .bazelrc
build --flag_alias=coffee=//experimental/user/starlark_configurations/basic_build_setting:coffee-temp
最佳做法:多次設定別名會導致最接近者優先採用。請使用不重複的別名名稱,避免意外剖析結果。
如要使用別名,請輸入別名,以取代建構設定目標路徑。針對上述使用者 .bazelrc
中設定的 coffee
範例:
$ bazel build //my/target --coffee=ICED
而非
$ bazel build //my/target --//experimental/user/starlark_configurations/basic_build_setting:coffee-temp=ICED
最佳做法:雖然可以在指令列上設定別名,但將其保留在 .bazelrc
中可減少指令列的負擔。
標籤類型的建構設定
與其他建構設定不同,無法使用 build_setting
規則參數定義標籤類型的設定。bazel 有兩種內建規則:label_flag
和 label_setting
。這些規則會轉送實際目標的設定提供者。label_flag
和 label_setting
可經由轉場讀取/寫入,而 label_flag
可由使用者設定,就像其他 build_setting
規則一樣。唯一的差別在於
無法自訂定義
標籤類型的設定最終會取代延遲限制預設值的功能。延遲繫結的預設屬性是「標籤」類型屬性,其最終值可受設定影響。在 Starlark 中,這將取代 configuration_field
API。
# example/rules.bzl
MyProvider = provider(fields = ["my_field"])
def _dep_impl(ctx):
return MyProvider(my_field = "yeehaw")
dep_rule = rule(
implementation = _dep_impl
)
def _parent_impl(ctx):
if ctx.attr.my_field_provider[MyProvider].my_field == "cowabunga":
...
parent_rule = rule(
implementation = _parent_impl,
attrs = { "my_field_provider": attr.label() }
)
# example/BUILD
load("//example:rules.bzl", "dep_rule", "parent_rule")
dep_rule(name = "dep")
parent_rule(name = "parent", my_field_provider = ":my_field_provider")
label_flag(
name = "my_field_provider",
build_setting_default = ":dep"
)
建構設定和 select()
使用者可以透過 select()
設定建構設定的屬性。建構設定目標可以傳遞至 config_setting
的 flag_values
屬性。要比對的設定值會以 String
的形式傳遞,然後剖析為建構設定的類型以進行比對。
config_setting(
name = "my_config",
flag_values = {
"//example:favorite_flavor": "MANGO"
}
)
使用者定義的轉換
設定轉換會在建構圖形中將一個設定目標的轉換對應至另一個設定目標。
設定這項功能的規則必須包含特殊屬性:
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist"
)
透過新增轉場效果,您可以輕鬆探索建構圖表的大小。這會為您可以在其中建立此規則目標的套件設定許可清單。上述程式碼區塊中的預設值可將所有項目加入許可清單。不過,如果您要限制哪些使用者能使用規則,可以將該屬性設為指向您的自訂許可清單。如果您需要建議或協助,瞭解轉換作業對建構效能的影響,請聯絡 bazel-discuss@googlegroups.com。
定義
轉換定義了規則之間的設定變更。例如,「編譯我的依附元件的依附元件 (不同於其父項) 」這類要求是由轉場處理。
基本上,轉換是指從輸入設定到一或多個輸出設定的函式。大多數的轉場效果都是 1:1,例如「使用 --cpu=ppc
覆寫輸入設定」。您也可以有 1:2 以上的轉場效果,但也有特殊限制。
在 Starlark 中,轉場效果與規則非常類似,兩者都有定義 transition()
函式和實作函式。
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {"//example:favorite_flavor" : "MINT"}
hot_chocolate_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
transition()
函式會採用實作函式、一組要讀取的建構設定(inputs
),以及要寫入的一組建構設定 (outputs
)。實作函式有兩個參數:settings
和 attr
。settings
是 inputs
參數中宣告 transition()
的字典 {String
:Object
}。
attr
是附加轉換規則的屬性和值字典。以傳出邊緣轉換的形式附加時,這些屬性的值都會設為 post-select() 解析度。附加為「傳入邊緣轉換」時,attr
不會加入任何使用選取器解析值的屬性。如果傳入的邊緣轉換在 --foo
上讀取屬性 bar
,接著同時選取 --foo
來設定 bar
屬性,則傳入邊緣轉換可能會在轉場中讀取錯誤的 bar
值。
實作函式必須傳回要套用的新建構設定值的字典 (在具有多個輸出設定的轉換的情況下,則傳回字典清單)。傳回的字典金鑰組必須確切包含傳遞至轉換函式 outputs
參數的建構設定組合。即使在轉換過程中實際上並未變更建構設定也是如此,其原始值必須在傳回的字典中明確傳遞。
定義 1:2 以上的轉場效果
傳出邊緣轉換可將單一輸入設定對應至兩種以上的輸出設定。這有助於定義包含多架構程式碼的規則。
1:2 以上的轉場效果,是在轉換實作函式中傳回字典清單來定義。
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return [
{"//example:favorite_flavor" : "LATTE"},
{"//example:favorite_flavor" : "MOCHA"},
]
coffee_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
也可以設定自訂鍵,讓規則實作函式用來讀取個別依附元件:
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {
"Apple deps": {"//command_line_option:cpu": "ppc"},
"Linux deps": {"//command_line_option:cpu": "x86"},
}
multi_arch_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
)
附加轉場效果
轉場效果可以在兩個地方附加:傳入邊緣和傳出邊緣。這意味著規則可以轉換自己的設定 (邊緣轉換) 並轉換其依附元件的設定 (傳出邊緣轉換)。
注意:目前無法將 Starlark 轉場效果附加至原生規則。在這種情況下,如需找出解決方法的協助,請聯絡 bazel-discuss@googlegroups.com。
進入邊緣轉場效果
將 transition
物件 (由 transition()
建立) 附加至 rule()
的 cfg
參數,即可啟用傳入邊緣轉場:
# example/rules.bzl
load("example/transitions:transitions.bzl", "hot_chocolate_transition")
drink_rule = rule(
implementation = _impl,
cfg = hot_chocolate_transition,
...
傳入邊緣轉場效果必須為 1:1 轉場。
傳出邊緣轉場效果
將 transition
物件 (由 transition()
建立) 附加至屬性的 cfg
參數,即可啟用邊界轉場效果:
# example/rules.bzl
load("example/transitions:transitions.bzl", "coffee_transition")
drink_rule = rule(
implementation = _impl,
attrs = { "dep": attr.label(cfg = coffee_transition)}
...
連出邊緣轉場效果可以是 1:1 或 1:2+。
如要瞭解如何讀取這些金鑰,請參閱「使用轉場效果存取屬性」。
原生選項的轉換
Starlark 轉場效果也可以透過選項名稱的特殊前置字串,宣告原生建構設定選項上的讀取和寫入作業。
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {"//command_line_option:cpu": "k8"}
cpu_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
不支援的原生選項
Bazel 不支援在 --define
上使用 "//command_line_option:define"
轉換。請改用自訂建構設定。一般來說,我們並不建議改用 --define
的新用法,改用建構設定。
Bazel 不支援在 --config
上進行轉換。這是因為 --config
是展開至其他標記的「展開」旗標。
至關重要的,--config
可能包含不會影響建構設定的標記,例如 --spawn_strategy
。根據設計,Bazel 無法將這類標記繫結至個別目標。換言之,您無法在轉換時以一致的方式套用這些設定。
如要解決這個問題,您可以明確列出轉換中「是」設定的一部分標記。這需要將 --config
的擴展至兩個位置,也就是已知的 UI 瑕疵。
允許多個版本設定的轉換
如果是允許多個值的版本設定,必須使用清單來設定設定的值。
# example/buildsettings/build_settings.bzl
string_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True, allow_multiple = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "string_flag")
string_flag(name = "roasts", build_setting_default = "medium")
# example/transitions/rules.bzl
def _transition_impl(settings, attr):
# Using a value of just "dark" here will throw an error
return {"//example:roasts" : ["dark"]},
coffee_transition = transition(
implementation = _transition_impl,
inputs = [],
outputs = ["//example:roasts"]
)
免人工管理的轉場效果
如果轉場效果傳回 {}
、[]
或 None
,那麼所有設定都保留原始值的好處。比起明確將每項輸出內容設定為自己,這種做法會比較方便。
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (attr)
if settings["//example:already_chosen"] is True:
return {}
return {
"//example:favorite_flavor": "dark chocolate",
"//example:include_marshmallows": "yes",
"//example:desired_temperature": "38C",
}
hot_chocolate_transition = transition(
implementation = _impl,
inputs = ["//example:already_chosen"],
outputs = [
"//example:favorite_flavor",
"//example:include_marshmallows",
"//example:desired_temperature",
]
)
透過轉場效果存取屬性
將轉換附加至向外邊緣時 (無論轉換作業是 1:1 還是 1:2 以上的轉場效果),如果 ctx.attr
尚未強制設為清單,則一律強制設為清單。未指定這個清單中的元素順序。
# example/transitions/rules.bzl
def _transition_impl(settings, attr):
return {"//example:favorite_flavor" : "LATTE"},
coffee_transition = transition(
implementation = _transition_impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
def _rule_impl(ctx):
# Note: List access even though "dep" is not declared as list
transitioned_dep = ctx.attr.dep[0]
# Note: Access doesn't change, other_deps was already a list
for other_dep in ctx.attr.other_deps:
# ...
coffee_rule = rule(
implementation = _rule_impl,
attrs = {
"dep": attr.label(cfg = coffee_transition)
"other_deps": attr.label_list(cfg = coffee_transition)
})
如果轉場效果為 1:2+
且設定了自訂鍵,則可使用 ctx.split_attr
讀取每個鍵的個別依附元件:
# example/transitions/rules.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {
"Apple deps": {"//command_line_option:cpu": "ppc"},
"Linux deps": {"//command_line_option:cpu": "x86"},
}
multi_arch_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
)
def _rule_impl(ctx):
apple_dep = ctx.split_attr.dep["Apple deps"]
linux_dep = ctx.split_attr.dep["Linux deps"]
# ctx.attr has a list of all deps for all keys. Order is not guaranteed.
all_deps = ctx.attr.dep
multi_arch_rule = rule(
implementation = _rule_impl,
attrs = {
"dep": attr.label(cfg = multi_arch_transition)
})
請參閱完整範例。
與平台和工具鍊整合
現今的許多原生旗標 (例如 --cpu
和 --crosstool_top
) 都與工具鍊解決方案有關。日後,我們可能會在目標平台上轉換,取代這些類型標記的明確轉換。
記憶體與效能注意事項
為版本新增轉場效果 (亦即新設定) 需付費:建構圖表較大、較不理解建構圖,以及建構速度較慢。在考慮在建構規則中使用轉換時,建議您考量這些費用。以下舉例說明轉換作業可能會如何使建構圖表呈指數成長。
不良行為:個案研究
圖 1 顯示頂層目標及其依附元件的擴充性圖表。
此圖表顯示頂層目標 //pkg:app
,取決於兩個目標://pkg:1_0
和 //pkg:1_1
。這兩個目標都依附於兩個目標://pkg:2_0
和 //pkg:2_1
。這兩個目標都依附於兩個目標://pkg:3_0
和 //pkg:3_1
。此程序持續到 //pkg:n_0
和 //pkg:n_1
為止,兩者都依附於單一目標 //pkg:dep
。
建構「//pkg:app
」需要 \(2n+2\) 個目標:
//pkg:app
//pkg:dep
- 「 \([1..n]\)」中的 \(i\) 和「
//pkg:i_0
」//pkg:i_1
假設您implement旗標 --//foo:owner=<STRING>
,且套用 //pkg:i_b
depConfig = myConfig + depConfig.owner="$(myConfig.owner)$(b)"
換句話說,//pkg:i_b
會針對所有依附元件,將 b
附加至 --owner
的舊值。
這會產生下列設定的目標:
//pkg:app //foo:owner=""
//pkg:1_0 //foo:owner=""
//pkg:1_1 //foo:owner=""
//pkg:2_0 (via //pkg:1_0) //foo:owner="0"
//pkg:2_0 (via //pkg:1_1) //foo:owner="1"
//pkg:2_1 (via //pkg:1_0) //foo:owner="0"
//pkg:2_1 (via //pkg:1_1) //foo:owner="1"
//pkg:3_0 (via //pkg:1_0 → //pkg:2_0) //foo:owner="00"
//pkg:3_0 (via //pkg:1_0 → //pkg:2_1) //foo:owner="01"
//pkg:3_0 (via //pkg:1_1 → //pkg:2_0) //foo:owner="10"
//pkg:3_0 (via //pkg:1_1 → //pkg:2_1) //foo:owner="11"
...
//pkg:dep
會產生 \(2^n\) 設定的目標:config.owner=
「\(b_0b_1...b_n\)」適用於所有 \(b_i\) \(\{0,1\}\)。
因此,建構圖會以指數比目標圖高,並產生對應的記憶體和效能結果。
任務:新增這些問題的評估與緩解策略。
其他資訊
如要進一步瞭解如何修改建構設定,請參閱:
- Starlark 版本設定
- Bazel 設定藍圖
- 端對端範例的完整組合