本頁面說明 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
使用建構設定別名
您可以為建構設定目標路徑設定別名,方便在指令列上閱讀。別名功能與原生旗標類似,同樣使用雙破折號選項語法。
在 .bazelrc 中加入 --flag_alias=ALIAS_NAME=TARGET_PATH,即可設定別名。舉例來說,如要將別名設為 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"
}
)
使用者定義的轉換
設定轉換會在建構圖表中,將一個已設定的目標對應轉到另一個目標。
定義
轉換會定義規則之間的設定變更。舉例來說,如果要求是「為與父項不同的 CPU 編譯依附元件」,系統就會透過轉換作業處理。
正式來說,轉換是將一個輸入設定對應到一或多個輸出設定的函式。大多數轉換都是 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 是在 transition() 的 inputs 參數中宣告的所有設定的字典 {String:Object}。
attr 是規則的屬性和值的字典,轉換會附加至該規則。當附加為傳出邊緣轉換時,這些屬性的值全都會在 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//pkg:i_0和//pkg:i_1( \([1..n]\)的 \(i\) )
假設您實作了 --//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"
...
對於 \(\{0,1\}\)中所有 \(b_i\) ,//pkg:dep 會產生 \(2^n\) 設定目標:config.owner= (\(b_0b_1...b_n\))。
這會導致建構圖表比目標圖表大上許多,進而影響記憶體和效能。
待解決:新增策略,用於評估及緩解這些問題。
延伸閱讀
如要進一步瞭解如何修改建構設定,請參閱:
- Starlark 建構設定
- 完整的端對端範例集