プラットフォーム

はじめに

Bazel は、さまざまなハードウェア、オペレーティング システム、システム構成でコードをビルドしてテストできます。これには、リンカーやコンパイラなどのビルドツールのさまざまなバージョンが関係する可能性があります。こうした複雑さを管理しやすくするために、Bazel には 制約プラットフォームという概念があります。

制約とは、ビルドマシンまたは本番環境マシンの識別プロパティです。 一般的な制約としては、CPU アーキテクチャ、GPU の有無、ローカルにインストールされたコンパイラのバージョンなどがあります。ただし、制約は、ビルド作業のオーケストレーション時にマシンを意味のある方法で区別できるものであれば、何でもかまいません。

プラットフォームとは、マシン全体を指定する制約のコレクションです。 Bazel はこの概念を使用して、デベロッパーがビルド対象のマシン、コンパイル アクションとテスト アクションを実行するマシン、ビルド アクションのコンパイルに使用する ツールチェーンを選択できるようにします。

デベロッパーは制約を使用して、ビルドルールのカスタム プロパティまたは依存関係を選択 することもできます。たとえば、「ビルドのターゲットが Arm マシンの場合は use 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 しか設定できません。 つまり、2 番目の値をモデル化する別の constraint_setting 型を作成しない限り、プラットフォームに 2 つの CPU を設定することはできません。

互換性のないターゲットのスキップ

特定のターゲット プラットフォーム用にビルドする場合、そのプラットフォームで動作しないターゲットをスキップすることが望ましい場合があります。たとえば、Windows デバイス ドライバは、//... を使用して Linux マシンでビルドすると、多くのコンパイラ エラーを生成する可能性があります。 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

test_suite 内の互換性のないテストは、test_suite がコマンドラインで --expand_test_suites を使用して指定されている場合も同様にスキップされます。 つまり、コマンドラインの 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"],
    }),
)

上記は次のように解釈できます。

  1. macOS をターゲットにしている場合、ターゲットには制約がありません。
  2. Linux をターゲットにしている場合、ターゲットには制約がありません。
  3. それ以外の場合、ターゲットには @platforms//:incompatible 制約があります。@platforms//:incompatible はどのプラットフォームにも含まれていないため、ターゲットは互換性がないと見なされます。

制約を読みやすくするには、 skylib's 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 cqueryStarlark 出力 形式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

既知の問題

互換性のないターゲットは、可視性の制限 を無視します