設定

回報問題 查看來源

本頁說明 Starlark 設定的優點和基本用法,即 Bazel 的 API,可用於自訂專案建構方式。課程內容包括如何定義建構設定並提供範例。

這麼做可:

  • 為專案定義自訂標記,而不再需要使用 --define
  • 寫入轉換以在與父項不同的設定中設定 dep (例如 --compilation_mode=opt--cpu=arm)
  • 為規則建立更理想的預設值 (例如使用指定 SDK 自動建構 //my:android_app)

還可從 .bzl 檔案完全擷取 (不需 Bazel 版本)。請參閱 bazelbuild/examples 存放區中的範例

使用者定義的版本設定

建構設定是一則設定資訊。將設定視為鍵/值對應。設定 --cpu=ppc--copt="-DFoo" 會產生類似 {cpu: ppc, copt: "-DFoo"} 的設定。每個項目都是建構設定。

cpucopt 等傳統標記是原生設定,其鍵已定義,且值是在原生 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 類型,例如 boolstring。詳情請參閱 config 模組說明文件。在規則實作函式中,您可以在規則的實作函式中完成更複雜的輸入。詳情請參閱下方說明。

config 模組函式會採用選用的布林值參數 flag,預設為 false。如果將 flag 設為 true,使用者也可以在指令列上透過預設值和轉換在內部設定建構設定。有些設定不應由使用者調整,舉例來說,假如您身為規則寫入者,想在測試規則內啟用某些偵錯模式,您不希望讓使用者在其他非測試規則中隨意開啟該功能。

使用 ctx.build_setting_value

如同所有規則,建構設定規則也提供實作函式。建構設定的基本 Starlark 類型值可透過 ctx.build_setting_value 方法存取。這個方法僅適用於建構設定規則的 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 參數,可讓該標記在指令列或 bazelrcs 中多次設定。預設值仍會使用字串類型的屬性設定:

# 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

最佳做法:多次設定別名會導致最近期的別名優先。使用不重複的別名名稱可避免非預期的剖析結果。

如要使用這個別名,請輸入該別名,取代建構設定目標路徑。 按照上述 coffee 範例 (位於使用者的 .bazelrc 中設定):

$ bazel build //my/target --coffee=ICED

而非

$ bazel build //my/target --//experimental/user/starlark_configurations/basic_build_setting:coffee-temp=ICED

最佳做法:雖然您可以在指令列上設定別名,但將別名保留在 .bazelrc 內即可讓指令列變得更加雜亂。

標籤類型的建構設定

端對端範例

與其他建構設定不同,您無法使用 build_setting 規則參數定義標籤類型的設定。而是有兩個內建規則:label_flaglabel_setting。這些規則會將建構設定的實際目標,轉送給提供者。label_flaglabel_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_settingflag_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)。實作函式有兩個參數:settingsattrsettingsinputs 參數中向 transition() 宣告的所有設定字典 {String:Object}。

attr 是對應轉換規則之屬性和值的字典。做為傳出邊緣轉換時,這些屬性值的所有值都是已設定的 select-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 是擴充為其他標記的「expansion」標記。

至關重要的,--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]\)的//pkg:i_0//pkg:i_1 ( \(i\) )

假設您「實作」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 會為 \(\{0,1\}\)整個 \(b_i\) \(\{0,1\}\)產生 \(2^n\) 設定的目標:config.owner=「\(b_0b_1...b_n\)」。

這會讓建構圖以指數方式大於目標圖形,並提供對應的記憶體和效能結果。

待辦事項:新增評估和緩解這些問題的策略。

其他資訊

如要進一步瞭解如何修改建構設定,請參閱: