はじめに
Bazel は、さまざまなハードウェア、オペレーティング システム、システム構成でコードをビルドしてテストできます。これには、リンカーやコンパイラなどのビルドツールのさまざまなバージョンが関係する可能性があります。この複雑さを管理するために、Bazel には制約とプラットフォームのコンセプトがあります。
制約は、ビルドマシンまたはプロダクション マシンの識別プロパティです。一般的な制約は、CPU アーキテクチャ、GPU の有無、ローカルにインストールされたコンパイラのバージョンなどです。ただし、制約は、ビルド作業をオーケストレートするときにマシンを意味のある方法で区別できる任意のものにできます。
プラットフォームは、完全なマシンを指定する制約のコレクションです。Bazel はこのコンセプトを使用して、デベロッパーがビルド対象のマシン、コンパイル アクションとテスト アクションを実行するマシン、ビルド アクションのコンパイルに使用するツールチェーンを選択できるようにします。
デベロッパーは、制約を使用してビルドルールでカスタム プロパティまたは依存関係を選択することもできます。たとえば、「ビルドのターゲットが Arm マシンの場合は src_arm.cc を使用する」などです。
プラットフォーム型
Bazel は、プラットフォームが果たす可能性のある 3 つのロールを認識します。
- ホスト - Bazel 自体が実行されるプラットフォーム。
- 実行 - コンパイル アクションを実行してビルド出力を生成するプラットフォーム。
- ターゲット - ビルドされるコードが実行されるプラットフォーム。
ビルドとプラットフォームの関係には、一般的に次の 3 種類があります。
単一プラットフォーム ビルド - ホスト プラットフォーム、実行プラットフォーム、ターゲット プラットフォームが同じです。たとえば、リモート実行なしでデベロッパー マシンでビルドし、同じマシンでビルドされたバイナリを実行します。
クロスコンパイル ビルド - ホスト プラットフォームと実行プラットフォームは同じですが、ターゲット プラットフォームが異なります。たとえば、リモート実行なしで Macbook Pro で iOS アプリをビルドする場合などです。
マルチプラットフォーム ビルド - ホスト プラットフォーム、実行プラットフォーム、ターゲット プラットフォームがすべて異なります。たとえば、Macbook Pro で iOS アプリをビルドし、リモートの Linux マシンを使用して Xcode を必要としない C++ アクションをコンパイルします。
プラットフォームを指定する
デベロッパーがプラットフォームを使用する最も一般的な方法は、--platforms フラグで目的のターゲット マシンを指定することです。
$ bazel build //:my_linux_app --platforms=//myplatforms:linux_x86
一般的に、組織ごとにビルドマシンの設定が異なるため、組織は独自のプラットフォーム定義を維持します。
--platforms が設定されていない場合、デフォルトで @platforms//host になります。これは、ホストマシンの OS と CPU のプロパティを自動検出して、ビルドが Bazel の実行対象と同じマシンをターゲットとするように特別に定義されています。ビルドルールでは、@platforms/os 制約と @platforms/cpu 制約を使用して、これらのプロパティを選択できます。
一般的に有用な制約とプラットフォーム
エコシステムの一貫性を保つため、Bazel チームは、最も一般的な CPU アーキテクチャとオペレーティング システムの制約定義を含むリポジトリを管理しています。これらはすべて https://github.com/bazelbuild/platforms で定義されています。
Bazel には、次の特別なプラットフォーム定義(@platforms//host(@bazel_tools//tools:host_platform のエイリアス))が付属しています。これは、Bazel が実行されるマシンの OS と CPU のプロパティを自動検出します。
制約の定義
制約は constraint_setting と constraint_value のビルドルールでモデル化されます。
constraint_setting はプロパティの型を宣言します。次に例を示します。
constraint_setting(name = "cpu")
constraint_value は、そのプロパティの可能な値を宣言します。
constraint_value(
name = "x86",
constraint_setting = ":cpu"
)
これらは、プラットフォームを定義したり、そのプラットフォームでビルドルールをカスタマイズしたりする際に、ラベルとして参照できます。上記の例が cpus/BUILD で定義されている場合は、x86 制約を //cpus:x86 として参照できます。
可視性が許容される場合は、既存の constraint_setting を拡張して、独自の値で定義できます。
プラットフォームを定義する
platform ビルドルールは、プラットフォームを constraint_value のコレクションとして定義します。
platform(
name = "linux_x86",
constraint_values = [
"//oses:linux",
"//cpus:x86",
],
)
これは、//oses:linux 制約と //cpus:x86 制約の両方を満たす必要があるマシンをモデル化します。
プラットフォームは、特定の constraint_setting に対して 1 つの constraint_value のみを持つことができます。つまり、別の constraint_setting 型を作成して 2 番目の値をモデル化しない限り、プラットフォームに 2 つの CPU を含めることはできません。
互換性のないターゲットをスキップする
特定のターゲット プラットフォーム向けにビルドする場合、そのプラットフォームで動作しないターゲットをスキップすることが望ましいことがよくあります。たとえば、//... を使用して Linux マシンでビルドする場合、Windows デバイス ドライバでコンパイラ エラーが多数発生する可能性があります。target_compatible_with 属性を使用して、コードのターゲット プラットフォーム制約を Bazel に伝えます。
この属性の最も簡単な使用方法は、ターゲットを単一のプラットフォームに制限することです。すべての制約を満たしていないプラットフォームでは、ターゲットはビルドされません。次の例では、win_driver_lib.cc を 64 ビット Windows に制限しています。
cc_library(
name = "win_driver_lib",
srcs = ["win_driver_lib.cc"],
target_compatible_with = [
"@platforms//cpu:x86_64",
"@platforms//os:windows",
],
)
:win_driver_lib は 64 ビット Windows でのビルドにのみ互換性があり、それ以外とは互換性がありません。互換性のない状態は推移的です。互換性のないターゲットに推移的に依存するターゲットは、それ自体が互換性のないターゲットと見なされます。
ターゲットがスキップされるのはいつですか?
ターゲットが互換性がないと見なされ、ターゲット パターン展開の一部としてビルドに含まれている場合、ターゲットはスキップされます。たとえば、次の 2 つの呼び出しは、ターゲット パターンの展開で見つかった互換性のないターゲットをスキップします。
$ bazel build --platforms=//:myplatform //...
$ bazel build --platforms=//:myplatform //:all
--expand_test_suites を指定してコマンドラインで test_suite が指定されている場合、test_suite 内の互換性のないテストも同様にスキップされます。つまり、コマンドラインの test_suite ターゲットは :all や ... と同様に動作します。--noexpand_test_suites を使用すると、拡張が防止され、互換性のないテストを含む test_suite ターゲットも互換性がなくなります。
コマンドラインで互換性のないターゲットを明示的に指定すると、エラー メッセージが表示され、ビルドが失敗します。
$ bazel build --platforms=//:myplatform //:target_incompatible_with_myplatform
...
ERROR: Target //:target_incompatible_with_myplatform is incompatible and cannot be built, but was explicitly requested.
...
FAILED: Build did NOT complete successfully
--skip_incompatible_explicit_targets が有効になっている場合、互換性のない明示的なターゲットはサイレントにスキップされます。
より表現力の高い制約
制約をより柔軟に表現するには、どのプラットフォームも満たさない @platforms//:incompatible
constraint_value を使用します。
select() を @platforms//:incompatible と組み合わせて使用すると、より複雑な制限を表現できます。たとえば、基本的な OR ロジックを実装するために使用します。次のマークは、macOS と Linux に対応しているが、他のプラットフォームには対応していないライブラリを示します。
cc_library(
name = "unixish_lib",
srcs = ["unixish_lib.cc"],
target_compatible_with = select({
"@platforms//os:osx": [],
"@platforms//os:linux": [],
"//conditions:default": ["@platforms//:incompatible"],
}),
)
上記は次のように解釈できます。
- macOS をターゲットにしている場合、ターゲットには制約がありません。
- Linux をターゲットに設定する場合、ターゲットに制約はありません。
- それ以外の場合、ターゲットには
@platforms//:incompatible制約があります。@platforms//:incompatibleはどのプラットフォームにも含まれていないため、ターゲットは互換性がないと見なされます。
制約を読みやすくするには、skylib の selects.with_or() を使用します。
逆互換性も同様に表現できます。次の例は、ARM 以外のすべてと互換性のあるライブラリを示しています。
cc_library(
name = "non_arm_lib",
srcs = ["non_arm_lib.cc"],
target_compatible_with = select({
"@platforms//cpu:arm": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
)
bazel cquery を使用して互換性のないターゲットを検出する
bazel cquery の Starlark 出力形式で IncompatiblePlatformProvider を使用すると、互換性のないターゲットと互換性のあるターゲットを区別できます。
これは、互換性のないターゲットを除外するために使用できます。次の例では、互換性のあるターゲットのラベルのみが出力されます。互換性のないターゲットは印刷されません。
$ cat example.cquery
def format(target):
if "IncompatiblePlatformProvider" not in providers(target):
return target.label
return ""
$ bazel cquery //... --output=starlark --starlark:file=example.cquery
既知の問題
互換性のないターゲットは可視性の制限を無視します。