構成

このページでは、Starlark 構成のメリットと基本的な使用方法について説明します。 Starlark 構成は、プロジェクトのビルド方法をカスタマイズするための Bazel の API です。ビルド設定の定義方法と例を示します。

これにより、次のことが可能になります。

その他、すべて .bzl ファイルから完全に実行できます(Bazel リリースは不要)。例については、 bazelbuild/examples リポジトリで をご覧ください。

ユーザー定義のビルド設定

ビルド設定は、1 つの 構成 情報です。構成は Key-Value マップと考えることができます。--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 属性は、 ビルド設定のタイプを指定する関数を受け取ります。タイプは、 boolstring などの基本的な Starlark タイプのセットに限定されます。詳細については、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)
)

複数セットの文字列フラグを定義する

文字列設定には、コマンドラインまたは bazelrc で フラグを複数回設定できるallow_multipleパラメータが追加されています。デフォルト 値は、文字列型の属性で設定されます。

# 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",
)

言語によっては、その言語のすべてのルール が依存するビルド設定の正規セットを作成することが望ましい場合があります。Starlark 構成の世界では、fragments のネイティブ コンセプトはハードコードされたオブジェクトとして存在しなくなりましたが、このコンセプトを変換する方法の 1 つは、一般的な暗黙的な属性のセットを使用することです。次に例を示します。

# 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

ベスト プラクティス: エイリアスを複数回設定すると、最新の ものが優先されます。意図しない解析結果を避けるため、一意のエイリアス名を使用してください。

エイリアスを使用するには、ビルド設定のターゲット パスの代わりにエイリアスを入力します。 ユーザーの .bazelrccoffee を設定した上記の例では、次のようになります。

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

これは、以下を置き換えるものです。

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

ベスト プラクティス: コマンドラインでエイリアスを設定することもできますが、 .bazelrc に残しておくと、コマンドラインがすっきりします。

ラベル型のビルド設定

エンドツーエンドの例

他のビルド設定とは異なり、ラベル型の設定は build_setting ルール パラメータを使用して定義できません。代わりに、Bazel には 2 つの組み込みルールがあります: label_flaglabel_setting。これらのルールは、ビルド設定が設定されている 実際のターゲットのプロバイダを転送します。label_flaglabel_setting は遷移によって読み取り/書き込みが可能で、他の build_setting ルールと同様に、label_flag はユーザーが設定できます。唯一の違いは、彼らが カスタム定義できないことです。

ラベル型の設定は、最終的に遅延バインディングの デフォルトの機能を置き換えます。遅延バインディングのデフォルト属性は、最終値が構成によって影響を受ける可能性があるラベル型の属性です。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()を使用して、ビルド設定の属性を構成できます。ビルド設定のターゲットは、flag_values 属性に config_setting渡すことができます。構成と一致させる値は 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 までお問い合わせください。

定義

遷移は、ルール間の構成の変更を定義します。たとえば、「親とは異なる CPU 用に依存関係をコンパイルする」などのリクエストは、遷移によって処理されます。

正式には、遷移は入力構成から 1 つ以上の 出力構成への関数です。ほとんどの遷移は 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)を受け取ります。実装関数には、settingsattr の 2 つのパラメータがあります。settings は、transition()inputs パラメータで宣言されたすべての設定の辞書 {String:Object} です。

attr は、 遷移がアタッチされているルールの属性と値の辞書です。出力エッジ遷移としてアタッチされている場合、これらの 属性の値はすべて select()解決後に構成されます。入力エッジ遷移として アタッチされている場合attrにはセレクタを使用して値を解決する属性は含まれません。 --foo--foo の入力エッジ遷移が属性 bar を読み取り、 --foo--foo で選択して属性 bar を設定すると、 入力エッジ遷移が遷移で bar の誤った値を読み取る可能性があります。

実装関数は、適用する新しいビルド設定値の辞書(複数の出力構成を持つ遷移の場合は 辞書のリスト)を返す必要があります。返される辞書キーセットには、遷移関数のoutputs パラメータに渡されるビルド設定のセットが正確に含まれている必要があります。ビルド設定が遷移中に実際に変更されない場合でも、元の値は返された辞書で明示的に渡す必要があります。

1:2 以上の遷移を定義する

エンドツーエンドの例

出力エッジ遷移は、単一の入力 構成を 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"]
)

遷移をアタッチする

エンドツーエンドの例

遷移は、入力エッジと出力エッジの 2 か所にアタッチできます。 つまり、ルールは独自の構成(入力 エッジ遷移)を遷移させ、依存関係の構成(出力 エッジ遷移)を遷移させることができます。

注: 現在、Starlark 遷移をネイティブ ルールにアタッチする方法はありません。 これを行う必要がある場合は、 bazel-discuss@googlegroups.com までお問い合わせください。回避策を見つけるお手伝いをいたします。

入力エッジ遷移

入力エッジ遷移は、transition オブジェクト (transition() で作成)を rule()'s 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's の展開を 2 か所で維持する必要があります。これは既知の 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"]
)

No-op 遷移

遷移が {}[]、または 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.最上位のターゲットとその依存関係を示すスケーラビリティ グラフ。

このグラフは、2 つのターゲット(a //pkg:1_0//pkg:1_1)に依存する最上位のターゲット //pkg:app を示しています。これらのターゲットはどちらも、2 つのターゲット(//pkg:2_0//pkg:2_1)に依存しています。これらのターゲットはどちらも、2 つのターゲット(//pkg:3_0//pkg:3_1)に依存しています。 これは、どちらも単一の ターゲット、//pkg:dep に依存する //pkg:n_0//pkg:n_1 まで続きます。

//pkg:app をビルドするには、次のターゲットが必要です。 \(2n+2\)

  • //pkg:app
  • //pkg:dep
  • //pkg:i_0//pkg:i_1( \(i\) の場合) \([1..n]\)

フラグ --//foo:owner=<STRING>実装すると、//pkg:i_bが適用されます。

depConfig = myConfig + depConfig.owner="$(myConfig.owner)$(b)"

つまり、//pkg:i_b は、すべての 依存関係の --owner の古い値に b を追加します。

これにより、次の構成済みターゲットが生成されます。

//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 は、すべての \(b_i\) で構成されたターゲット config.owner= "\(b_0b_1...b_n\)" を生成します。 \(2^n\) \(\{0,1\}\)

これにより、ビルドグラフはターゲット グラフよりも指数関数的に大きくなり、 メモリとパフォーマンスに影響します。

TODO: これらの問題の測定と軽減のための戦略を追加します。

関連情報

ビルド構成の変更について詳しくは、以下をご覧ください。