このページではツールチェーン フレームワークについて説明します。ツールチェーン フレームワークを使用すると、ルール作成者は、プラットフォーム ベースのツール選択からルールロジックを切り離すことができます。続行する前に、ルールとプラットフォームのページを確認することをおすすめします。このページでは、ツールチェーンが必要な理由、ツールチェーンの定義と使用方法、Bazel がプラットフォームの制約に基づいて適切なツールチェーンを選択する方法について説明します。
目的
まず、問題のツールチェーンが解決のために設計されている仕組みを見てみましょう。プログラミング言語「bar」をサポートするルールを作成するとします。bar_binary
ルールは、barc
コンパイラを使用して *.bar
ファイルをコンパイルします。このツールは、それ自体がワークスペース内の別のターゲットとしてビルドされます。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
属性を非公開にすることでユーザーに負担を任せることです。個々のターゲットをハードコードして、特定のプラットフォーム向けにビルドすることもできます。
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
ユーザー 1 人につき要求するのは少し手間がかかります。このスタイルがワークスペース全体で一貫して使用されていない場合、ビルドは単一のプラットフォームでは正常に動作しますが、マルチプラットフォーム シナリオに拡張すると失敗します。また、既存のルールやターゲットを変更せずに新しいプラットフォームやコンパイラのサポートを追加するという問題にも対処していません。
ツールチェーン フレームワークでは、この問題を解決するために、さらに別のレベルの間接を指定できます。基本的には、ルールがターゲット ファミリーの一部 メンバー(ツールチェーン タイプ)に抽象的な依存関係を持つことを宣言します。該当するプラットフォームの制約に基づいて、Bazel がこれを特定のターゲット(ツールチェーン)に自動的に解決します。ルールの作成者もターゲットの作成者も、使用可能なプラットフォームとツールチェーンの完全なセットを知る必要はありません。
ツールチェーンを使用するルールの記述
ツールチェーン フレームワークでは、ルールはツールに直接依存するのではなく、ツールチェーン タイプに依存します。ツールチェーン タイプは、異なるプラットフォームで同じ役割を果たすツールのクラスを表す単純なターゲットです。たとえば、バーコンパイラを表す型を宣言できます。
# 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.attr
ではなく ctx.toolchains
でこの依存関係にアクセスするようになりました。
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),
],
)
オプションのツールチェーン タイプを解決できない場合、分析は続行され、ctx.toolchains["//bar_tools:toolchain_type"]
の結果は None
になります。
デフォルトでは、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_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 の解決手順でツールチェーンを使用できるようにすることだけです。これを行うには、register_toolchains()
を使用して MODULE.bazel
ファイルでツールチェーンを登録するか、--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
@platforms//os:linux
があるプラットフォームで //my_pkg:my_bar_binary
がビルドされていることを Bazel が判断し、//bar_tools:toolchain_type
参照を //bar_tools:barc_linux_toolchain
に解決します。これにより、最終的には //bar_tools:barc_linux
がビルドされますが、//bar_tools:barc_windows
はビルドされません。
ツールチェーンの解決
ツールチェーンを使用するターゲットごとに、Bazel のツールチェーンの解決手順によって、ターゲットの具体的なツールチェーンの依存関係が決定されます。この手順は、必要なツールチェーン タイプのセット、ターゲット プラットフォーム、使用可能な実行プラットフォームのリスト、使用可能なツールチェーンのリストを入力として受け取ります。出力には、ツールチェーン タイプごとに選択されたツールチェーンと、現在のターゲットに対して選択された実行プラットフォームが含まれます。
利用可能な実行プラットフォームとツールチェーンは、MODULE.bazel
ファイルの register_execution_platforms
呼び出しと register_toolchains
呼び出しを介して外部依存関係グラフから収集されます。追加の実行プラットフォームとツールチェーンも、コマンドラインで --extra_execution_platforms
と --extra_toolchains
を使用して指定できます。ホスト プラットフォームは、利用可能な実行プラットフォームとして自動的に含まれます。利用可能なプラットフォームとツールチェーンは、決定性のために順序付きリストとして追跡され、リスト内の先行するアイテムが優先されます。
使用可能なツールチェーンのセットは、優先度の高い順に、--extra_toolchains
と register_toolchains
から作成されます。
--extra_toolchains
を使用して登録されたツールチェーンが最初に追加されます。(これらの中で、最後のツールチェーンが最優先されます)。- 推移的な外部依存関係グラフで、次の順序で
register_toolchains
を使用して登録されたツールチェーン(この中で、最初のツールチェーンの優先度が最も高い)。- ルート モジュールによって登録されたツールチェーン(ワークスペースのルートの
MODULE.bazel
など)。 - ユーザーの
WORKSPACE
ファイルに登録されているツールチェーン(そこから呼び出されるマクロも含む)。 - ルート以外のモジュールによって登録されたツールチェーン(ルート モジュールによって指定された依存関係やその依存関係など)。
- 「WORKSPACE サフィックス」に登録されているツールチェーン。これは、Bazel のインストールにバンドルされている特定のネイティブ ルールでのみ使用されます。
- ルート モジュールによって登録されたツールチェーン(ワークスペースのルートの
注: :all
、:*
、/...
などの疑似ターゲットは、辞書順を使用する Bazel のパッケージ読み込みメカニズムによって順序付けられます。
解決手順は次のとおりです。
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
フラグを使用します。ツールチェーンの解決時に、このフラグにより、正規表現変数と一致するツールチェーン タイプまたはターゲット名について詳細な出力が提供されます。.*
を使用すると、すべての情報を出力できます。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