このページでは、ルール作成者がルールロジックをプラットフォーム ベースのツールの選択から 切り離す方法であるツールチェーンフレームワークについて説明します。続行する前に、 ルールとプラットフォーム のページを読むことをおすすめします。このページでは、ツールチェーンが必要な理由、ツールチェーンの 定義と使用方法、Bazel がツールチェーンの プラットフォームの制約に基づいて適切なツールチェーンを選択する方法について説明します。
目的
まず、ツールチェーンが解決するように設計されている問題を見てみましょう。「bar」プログラミング言語をサポートするルールを作成するとします。bar_binary
ルールは、*.bar ファイルを barc コンパイラを使用してコンパイルします。このコンパイラは、ワークスペース内の別のターゲットとしてビルドされるツールです。bar_binary
ターゲットを作成するユーザーがコンパイラへの依存関係を指定する必要がないように、ルール定義に非公開属性として追加して
暗黙的な依存関係にします。
bar_binary = rule(
implementation = _bar_binary_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
...
"_compiler": attr.label(
default = "//bar_tools:barc_linux", # the compiler running on linux
providers = [BarcInfo],
),
},
)
//bar_tools:barc_linux はすべての bar_binary ターゲットの依存関係になったため、
bar_binary ターゲットの前にビルドされます。他の属性と同様に、ルールの
実装関数からアクセスできます。
BarcInfo = provider(
doc = "Information about how to invoke the barc compiler.",
# In the real world, compiler_path and system_lib might hold File objects,
# but for simplicity they are strings for this example. arch_flags is a list
# of strings.
fields = ["compiler_path", "system_lib", "arch_flags"],
)
def _bar_binary_impl(ctx):
...
info = ctx.attr._compiler[BarcInfo]
command = "%s -l %s %s" % (
info.compiler_path,
info.system_lib,
" ".join(info.arch_flags),
)
...
ここでの問題は、コンパイラのラベルが bar_binary にハードコードされていることです。ただし、
ターゲットは、ビルド対象のプラットフォームとビルド元のプラットフォーム(それぞれ
ターゲットプラットフォームと実行プラットフォーム)に応じて、異なるコンパイラを必要とする場合があります。また、ルール
作成者は、使用可能なツールとプラットフォームのすべてを知っているとは限りません。そのため、
ルール定義にハードコードすることはできません。
あまり理想的ではない解決策として、
属性を非公開にすることで、ユーザーに負担をかける方法があります。_compilerこれにより、個々のターゲットを
ハードコードして、1 つのプラットフォームまたは別のプラットフォーム用にビルドできます。
bar_binary(
name = "myprog_on_linux",
srcs = ["mysrc.bar"],
compiler = "//bar_tools:barc_linux",
)
bar_binary(
name = "myprog_on_windows",
srcs = ["mysrc.bar"],
compiler = "//bar_tools:barc_windows",
)
この解決策は、select を使用してプラットフォームに基づいて compiler
を選択することで改善できます:
config_setting(
name = "on_linux",
constraint_values = [
"@platforms//os:linux",
],
)
config_setting(
name = "on_windows",
constraint_values = [
"@platforms//os:windows",
],
)
bar_binary(
name = "myprog",
srcs = ["mysrc.bar"],
compiler = select({
":on_linux": "//bar_tools:barc_linux",
":on_windows": "//bar_tools:barc_windows",
}),
)
ただし、これは面倒であり、すべての bar_binary ユーザーに要求するには少し無理があります。
このスタイルがワークスペース全体で一貫して使用されていない場合、
単一のプラットフォームでは正常に動作するビルドが、
マルチプラットフォームシナリオに拡張すると失敗します。また、既存のルールやターゲットを変更せずに新しいプラットフォームとコンパイラのサポートを追加するという問題にも対応していません
。
ツールチェーン フレームワークは、間接参照のレベルを 追加することでこの問題を解決します。基本的に、ルールがターゲットファミリー(ツールチェーン タイプ)のメンバーに抽象的な依存関係 を持つことを宣言すると、Bazel は適用可能なプラットフォームの制約に基づいて、これを特定のターゲット(ツールチェーン)に自動的に解決します。ルール作成者もターゲット作成者も 使用可能なプラットフォームとツールチェーンの完全なセットを知る必要はありません。
ツールチェーンを使用するルールの作成
ツールチェーン フレームワークでは、ルールはツールに直接依存するのではなく、 代わりにツールチェーン タイプに依存します。ツールチェーンタイプは、さまざまなプラットフォームで同じ役割を果たすツールのクラスを表すシンプルなターゲット です。たとえば、bar コンパイラを表すタイプを宣言できます。
# By convention, toolchain_type targets are named "toolchain_type" and
# distinguished by their package path. So the full path for this would be
# //bar_tools:toolchain_type.
toolchain_type(name = "toolchain_type")
前のセクションのルール定義は、コンパイラを属性として受け取るのではなく、
ツールチェーンを使用することを宣言するように変更されています。//bar_tools:toolchain_type
bar_binary = rule(
implementation = _bar_binary_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
...
# No `_compiler` attribute anymore.
},
toolchains = ["//bar_tools:toolchain_type"],
)
実装関数は、ツールチェーン タイプをキーとして使用して、ctx.toolchains
ではなくctx.attrでこの依存関係にアクセスします。
def _bar_binary_impl(ctx):
...
info = ctx.toolchains["//bar_tools:toolchain_type"].barcinfo
# The rest is unchanged.
command = "%s -l %s %s" % (
info.compiler_path,
info.system_lib,
" ".join(info.arch_flags),
)
...
ctx.toolchains["//bar_tools:toolchain_type"] は、Bazel がツールチェーンの依存関係を解決したターゲットの
ToolchainInfo プロバイダ
を返します。
ToolchainInfo オブジェクトのフィールドは、基盤となるツールのルールによって設定されます。次の
セクションでは、barcinfo フィールドが存在するようにこのルールが定義されています。このフィールドは、BarcInfo オブジェクトをラップします。
ツールチェーンをターゲットに解決する Bazel の手順は
次のとおりです。解決されたツールチェーン ターゲットのみが、候補のツールチェーンのスペース全体ではなく、
bar_binary ターゲットの依存関係になります。
必須ツールチェーンとオプションのツールチェーン
デフォルトでは、ルールがベアラベルを使用してツールチェーン タイプの依存関係を表す場合 (上記のとおり)、ツールチェーン タイプは必須と見なされます。Bazel が必須のツールチェーン タイプに一致するツールチェーンを見つけられない場合(下記の ツールチェーンの解決を参照)、エラーが発生して分析が停止します。
代わりに、次のようにオプションのツールチェーン タイプの依存関係を宣言することもできます。
bar_binary = rule(
...
toolchains = [
config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
],
)
オプションのツールチェーン タイプを解決できない場合、分析は続行され、
の結果は None になります。ctx.toolchains["//bar_tools:toolchain_type"]
config_common.toolchain_type
関数はデフォルトで必須です。
次の形式を使用できます。
- 必須のツールチェーン タイプ:
toolchains = ["//bar_tools:toolchain_type"]toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type")]toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = True)]
- オプションのツールチェーン タイプ:
toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False)]
bar_binary = rule(
...
toolchains = [
"//foo_tools:toolchain_type",
config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
],
)
同じルールで形式を混在させることもできます。ただし、同じ ツールチェーン タイプが複数回リストされている場合は、最も厳格なバージョンが使用されます 。必須はオプションよりも厳格です。
ツールチェーンを使用するアスペクトの作成
アスペクトはルールと同じツールチェーン API にアクセスできます。必要な ツールチェーン タイプを定義し、コンテキストを介してツールチェーンにアクセスし、ツールチェーンを使用して新しい アクションを生成できます。
bar_aspect = aspect(
implementation = _bar_aspect_impl,
attrs = {},
toolchains = ['//bar_tools:toolchain_type'],
)
def _bar_aspect_impl(target, ctx):
toolchain = ctx.toolchains['//bar_tools:toolchain_type']
# Use the toolchain provider like in a rule.
return []
ツールチェーンの定義
特定のツールチェーン タイプのツールチェーンを定義するには、次の 3 つが必要です。
ツールの種類またはツールスイートを表す言語固有のルール。慣例により、このルールの名前には「_toolchain」という接尾辞が付きます。
- 注:
\_toolchainルールはビルド アクションを作成できません。 代わりに、他のルールからアーティファクトを収集し、ツールチェーンを使用する ルールに転送します。そのルールは、すべての ビルド アクションを作成します。
- 注:
さまざまなプラットフォームのツールまたはツール スイートのバージョンを表す、このルールタイプの複数のターゲット。
このようなターゲットごとに、汎用
toolchainルールの関連ターゲット。ツールチェーン フレームワークで使用されるメタデータを提供します。このtoolchainターゲットは、このツールチェーンに関連付けられたtoolchain_typeも参照します。 つまり、特定の_toolchainルールは任意のtoolchain_typeに関連付けることができ、この_toolchainルールを使用するtoolchainインスタンスでのみ、ルールがtoolchain_typeに関連付けられます。
実行中の例として、bar_toolchain ルールの定義を示します。この
例ではコンパイラのみを使用していますが、リンカーなどの他のツールをその下に
グループ化することもできます。
def _bar_toolchain_impl(ctx):
toolchain_info = platform_common.ToolchainInfo(
barcinfo = BarcInfo(
compiler_path = ctx.attr.compiler_path,
system_lib = ctx.attr.system_lib,
arch_flags = ctx.attr.arch_flags,
),
)
return [toolchain_info]
bar_toolchain = rule(
implementation = _bar_toolchain_impl,
attrs = {
"compiler_path": attr.string(),
"system_lib": attr.string(),
"arch_flags": attr.string_list(),
},
)
ルールは ToolchainInfo プロバイダを返す必要があります。これは、使用するルールが
ctx.toolchains とツールチェーンタイプのラベルを使用して取得するオブジェクトになります。ToolchainInfo は、structと同様に、任意のフィールドと値のペアを保持できます。ToolchainInfo
に追加されるフィールドの仕様は、ツールチェーン タイプで明確に文書化する必要があります。この例では、上記で定義したスキーマを再利用するために、値は
BarcInfo オブジェクトでラップされて返されます。この
スタイルは、検証とコードの再利用に役立ちます。
これで、特定の barc コンパイラのターゲットを定義できます。
bar_toolchain(
name = "barc_linux",
arch_flags = [
"--arch=Linux",
"--debug_everything",
],
compiler_path = "/path/to/barc/on/linux",
system_lib = "/usr/lib/libbarc.so",
)
bar_toolchain(
name = "barc_windows",
arch_flags = [
"--arch=Windows",
# Different flags, no debug support on windows.
],
compiler_path = "C:\\path\\on\\windows\\barc.exe",
system_lib = "C:\\path\\on\\windows\\barclib.dll",
)
最後に、2 つの bar_toolchain ターゲットの toolchain 定義を作成します。
これらの定義は、言語固有のターゲットをツールチェーン タイプにリンクし、
特定のプラットフォームにツールチェーンが
適切なタイミングを Bazel に伝える制約情報を提供します。
toolchain(
name = "barc_linux_toolchain",
exec_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
target_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
toolchain = ":barc_linux",
toolchain_type = ":toolchain_type",
)
toolchain(
name = "barc_windows_toolchain",
exec_compatible_with = [
"@platforms//os:windows",
"@platforms//cpu:x86_64",
],
target_compatible_with = [
"@platforms//os:windows",
"@platforms//cpu:x86_64",
],
toolchain = ":barc_windows",
toolchain_type = ":toolchain_type",
)
上記の相対パス構文を使用すると、これらの定義はすべて同じパッケージに含まれていることがわかりますが、ツールチェーンタイプ、言語固有のツールチェーン ターゲット、toolchain 定義ターゲットをすべて別のパッケージに含めることもできます。
実際の例については、go_toolchain
をご覧ください。
ツールチェーンと構成
ルール作成者にとって重要な問題は、bar_toolchainターゲットが
分析されるときに、どのような構成が表示され、依存関係にどのような遷移
を使用する必要があるかということです。上記の例では文字列属性を使用していますが、
Bazel リポジトリ内の他のターゲットに依存する複雑なツールチェーンの場合はどうなるでしょうか。
bar_toolchain のより複雑なバージョンを見てみましょう。
def _bar_toolchain_impl(ctx):
# The implementation is mostly the same as above, so skipping.
pass
bar_toolchain = rule(
implementation = _bar_toolchain_impl,
attrs = {
"compiler": attr.label(
executable = True,
mandatory = True,
cfg = "exec",
),
"system_lib": attr.label(
mandatory = True,
cfg = "target",
),
"arch_flags": attr.string_list(),
},
)
attr.label の使用は標準ルールと同じですが、
cfg パラメータの意味は少し異なります。
ツールチェーンの解決を介してターゲット(「親」と呼ばれる)からツールチェーンへの依存関係では、「ツールチェーン
遷移」と呼ばれる特別な構成遷移が使用されます。ツールチェーン遷移では、構成は同じままですが、ツールチェーンの実行プラットフォームが親と同じになるように強制されます(そうしないと、ツールチェーンのツールチェーンの解決で任意の実行プラットフォームを選択でき、親と同じになるとは限りません)。これにより、ツールチェーンのexec 依存関係を親の
ビルド アクションでも実行できるようになります。cfg =
"target"を使用する(または「target」がデフォルトであるためcfgを指定しない)ツールチェーンの依存関係は、親と同じターゲットプラットフォーム用に
ビルドされます。これにより、ツールチェーンルールは
必要なビルドルールにライブラリ(上記の system_lib 属性)とツール(
compiler 属性)の両方を提供できます。システム ライブラリ
は最終的なアーティファクトにリンクされるため、同じ
プラットフォーム用にビルドする必要があります。一方、コンパイラはビルド中に呼び出されるツールであり、実行プラットフォームで実行できる
必要があります。
ツールチェーンを使用した登録とビルド
これで、すべてのビルディング ブロックが組み立てられたので、Bazel の解決手順でツールチェーンを使用できるようにする必要があります。これを行うには、
MODULE.bazel ファイルにツールチェーンを登録するか、
register_toolchains() を使用して、
コマンドラインでツールチェーンのラベルを--extra_toolchains フラグを使用して渡します。
register_toolchains(
"//bar_tools:barc_linux_toolchain",
"//bar_tools:barc_windows_toolchain",
# Target patterns are also permitted, so you could have also written:
# "//bar_tools:all",
# or even
# "//bar_tools/...",
)
ターゲット パターンを使用してツールチェーンを登録する場合、個々のツールチェーンが登録される順序は、次のルールによって決まります。
- パッケージのサブパッケージで定義されたツールチェーンは、 パッケージ自体で定義されたツールチェーンよりも前に登録されます。
- パッケージ内では、ツールチェーンは 名前の辞書順で登録されます。
これで、ツールチェーン タイプに依存するターゲットをビルドすると、ターゲット プラットフォームと実行プラットフォームに基づいて適切な ツールチェーンが選択されます。
# my_pkg/BUILD
platform(
name = "my_target_platform",
constraint_values = [
"@platforms//os:linux",
],
)
bar_binary(
name = "my_bar_binary",
...
)
bazel build //my_pkg:my_bar_binary --platforms=//my_pkg:my_target_platform
Bazel は、//my_pkg:my_bar_binary が
@platforms//os:linux を持つプラットフォームでビルドされていることを確認し、
//bar_tools:toolchain_type 参照を //bar_tools:barc_linux_toolchain に解決します。
これにより、//bar_tools:barc_linux はビルドされますが、
//bar_tools:barc_windows はビルドされません。
ツールチェーンの解決
ツールチェーンを使用するターゲットごとに、Bazel のツールチェーン解決手順 によって、ターゲットの具体的なツールチェーンの依存関係が決定されます。この手順では、必要なツールチェーンタイプのセット、ターゲット プラットフォーム、使用可能な実行プラットフォームのリスト、使用可能なツールチェーンのリストを入力として受け取ります。出力は、ツールチェーンタイプごとに選択されたツールチェーンと、現在のターゲット用に選択された実行 プラットフォームです。
使用可能な実行プラットフォームとツールチェーンは、
外部依存関係グラフから
register_execution_platforms
および
register_toolchains呼び出しを介して、
MODULE.bazelファイルで収集されます。
追加の実行プラットフォームとツールチェーンは、
コマンドラインで
--extra_execution_platforms
と
--extra_toolchainsを使用して指定することもできます。
ホストプラットフォームは、使用可能な実行プラットフォームとして自動的に含まれます。
使用可能なプラットフォームとツールチェーンは、決定論のために順序付きリストとして追跡され、
リスト内の前の項目が優先されます。
優先順位順に並べられた使用可能なツールチェーンのセットは、
--extra_toolchains と register_toolchains
から作成されます。
--extra_toolchainsを使用して登録されたツールチェーンが最初に追加されます。(この中で、 **最後の** ツールチェーンが最も優先されます)。- 推移的な外部依存関係グラフで
register_toolchainsを使用して登録されたツールチェーン。次の順序で登録されます。(この中で、最初に 言及されたツールチェーンが最も優先されます)。- ルート モジュール(
MODULE.bazelが ワークスペース ルートにある場合など)によって登録されたツールチェーン。 - ユーザーの
WORKSPACEファイルに登録されたツールチェーン(そこから呼び出される マクロを含む)。 - ルート以外のモジュールによって登録されたツールチェーン(ルート モジュールで指定された 依存関係とその依存関係など)。
- 「WORKSPACE サフィックス」に登録されたツールチェーン。これは、 Bazel インストールにバンドルされている特定のネイティブ ルールでのみ使用されます。
- ルート モジュール(
注: `:`、`:`、`:` などの疑似ターゲットは、辞書順を使用する Bazel のパッケージ読み込みメカニズムによって順序付けされます。:all:*/...
解決手順は次のとおりです。
target_compatible_with句またはexec_compatible_with句は、リスト内のconstraint_valueごとに、プラットフォームにもそのconstraint_valueがある場合(明示的またはデフォルトとして)、プラットフォームと一致します。プラットフォームに、句で 参照されていない
constraint_settingのconstraint_valueがある場合でも、一致には影響しません。ビルド対象のターゲットが
exec_compatible_with属性 を指定している場合(またはそのルール定義でexec_compatible_with引数を指定している場合)、 使用可能な実行プラットフォームのリストがフィルタされ、 実行制約に一致しないものが削除されます。使用可能なツールチェーンのリストがフィルタされ、現在の構成に一致しない
target_settingsを指定するツールチェーンが削除されます。使用可能な実行プラットフォームごとに、各ツールチェーン タイプを この実行プラットフォームとターゲット プラットフォームと互換性のある最初の使用可能なツールチェーンに関連付けます(存在する場合)。
ツールチェーン タイプのいずれかに互換性のある必須ツールチェーン を見つけられなかった実行プラットフォームは除外されます。残りのプラットフォームのうち、最初のプラットフォームが現在のターゲットの実行プラットフォームになり、関連付けられたツールチェーン(存在する場合)がターゲットの依存関係になります。
選択した実行プラットフォームは、ターゲット が生成するすべてのアクションを実行するために使用されます。
同じビルド内で同じターゲットを複数の構成( 異なる CPU など)でビルドできる場合、解決手順はターゲットの各バージョンに 個別に適用されます。
ルールが実行グループを使用する場合、各実行 グループはツールチェーンの解決を個別に行い、それぞれ独自の実行 プラットフォームとツールチェーンを持ちます。
ツールチェーンのデバッグ
既存のルールにツールチェーンのサポートを追加する場合は、
--toolchain_resolution_debug=regex フラグを使用します。ツールチェーンの解決中に、このフラグ
は regex 変数に一致するツールチェーンのタイプまたはターゲット名の詳細出力を提供します。You
は、.*を使用してすべての情報を出力できます。Bazel は、解決プロセス中にチェックしてスキップするツールチェーンの名前を出力します。
cquery の依存関係がツールチェーン
の解決によるものかどうかを確認するには、cquery の --transitions フラグを使用します。
# Find all direct dependencies of //cc:my_cc_lib. This includes explicitly
# declared dependencies, implicit dependencies, and toolchain dependencies.
$ bazel cquery 'deps(//cc:my_cc_lib, 1)'
//cc:my_cc_lib (96d6638)
@bazel_tools//tools/cpp:toolchain (96d6638)
@bazel_tools//tools/def_parser:def_parser (HOST)
//cc:my_cc_dep (96d6638)
@local_config_platform//:host (96d6638)
@bazel_tools//tools/cpp:toolchain_type (96d6638)
//:default_host_platform (96d6638)
@local_config_cc//:cc-compiler-k8 (HOST)
//cc:my_cc_lib.cc (null)
@bazel_tools//tools/cpp:grep-includes (HOST)
# Which of these are from toolchain resolution?
$ bazel cquery 'deps(//cc:my_cc_lib, 1)' --transitions=lite | grep "toolchain dependency"
[toolchain dependency]#@local_config_cc//:cc-compiler-k8#HostTransition -> b6df211