永続ワーカーの作成

問題を報告 ソースを表示

永続ワーカーを使用すると、ビルドを高速化できます。起動コストが高い、またはクロスアクション キャッシュが役立つと思われるアクションがビルド内で繰り返されている場合は、そのようなアクションを実行する独自の永続ワーカーを実装することをおすすめします。

Bazel サーバーは、stdin/stdout を使用してワーカーと通信します。プロトコル バッファまたは JSON 文字列の使用をサポートします。

ワーカーの実装には 2 つの部分があります。

ワーカーを

永続ワーカーには、いくつかの要件があります。

  • その stdin から WorkRequests を読み取ります。
  • WorkResponses(および WorkResponse のみ)を stdout に書き込みます。
  • --persistent_worker フラグを受け入れます。ラッパーは --persistent_worker コマンドライン フラグを認識し、そのフラグが渡された場合にのみ自身の永続化を行います。それ以外の場合は、ワンショット コンパイルを実行して終了します。

プログラムがこれらの要件を維持すれば、永続ワーカーとして使用できます。

処理リクエスト

WorkRequest には、ワーカーへの引数のリスト、ワーカーがアクセスできる入力を表すパス ダイジェストのペアのリスト(必須ではありませんが、キャッシュに使用できます)、リクエスト ID(シングルプレックス ワーカーの場合は 0)が含まれます。

注: プロトコル バッファ仕様では「スネークケース」(request_id)を使用していますが、JSON プロトコルでは「キャメルケース」(requestId)を使用しています。このドキュメントでは、JSON の例ではキャメルケースを使用しますが、プロトコルに関係なくフィールドについて説明する場合はスネークケースを使用します。

{
  "arguments" : ["--some_argument"],
  "inputs" : [
    { "path": "/path/to/my/file/1", "digest": "fdk3e2ml23d"},
    { "path": "/path/to/my/file/2", "digest": "1fwqd4qdd" }
 ],
  "requestId" : 12
}

オプションの verbosity フィールドを使用すると、ワーカーから追加のデバッグ出力をリクエストできます。何をどのように出力するかは、ワーカーに完全に委ねられます。値が大きいほど出力が詳細になります。--worker_verbose フラグを Bazel に渡すと、verbosity フィールドが 10 に設定されますが、出力の量に応じて、小さい値または大きい値を手動で使用できます。

オプションの sandbox_dir フィールドは、マルチプレックス サンドボックスをサポートするワーカーでのみ使用されます。

仕事用のレスポンス

WorkResponse には、リクエスト ID、ゼロまたはゼロ以外の終了コード、リクエストの処理または実行中に発生したエラーを説明する出力文字列が含まれます。output フィールドには簡単な説明が含まれます。完全なログはワーカーの stderr に書き込まれる場合があります。ワーカーは WorkResponsesstdout に書き込むだけなので、使用するツールの stdoutstderr にリダイレクトするのが一般的です。

{
  "exitCode" : 1,
  "output" : "Action failed with the following message:\nCould not find input
    file \"/path/to/my/file/1\"",
  "requestId" : 12
}

protobuf の通常では、すべてのフィールドは省略可能です。ただし、Bazel では WorkRequest とそれに対応する WorkResponse が同じリクエスト ID である必要があるため、0 以外の場合はリクエスト ID を指定する必要があります。これは有効な WorkResponse です。

{
  "requestId" : 12,
}

request_id が 0 の場合は「シングルプレックス」リクエストであり、このリクエストが他のリクエストと並行して処理できない場合に使用されます。サーバーは、指定されたワーカーが request_id 0 のみ、または 0 より大きい request_id のみを含むリクエストを受信することを保証します。シングルプレックス リクエストは順次送信されます。たとえば、サーバーがレスポンスを受信するまで別のリクエストを送信しない場合です(キャンセル リクエストを除く。以下を参照)。

メモ

  • 各プロトコル バッファの前には、varint 形式の長さ(MessageLite.writeDelimitedTo() を参照)が付加されます。
  • JSON のリクエストとレスポンスの前にサイズ インジケーターはありません。
  • JSON リクエストは protobuf と同じ構造を維持しますが、標準の JSON を使用し、すべてのフィールド名にキャメルケースを使用します。
  • protobuf と同じ下位互換性と上位互換性プロパティを維持するため、JSON ワーカーはこれらのメッセージ内の不明なフィールドを許容し、欠損値に protobuf のデフォルトを使用する必要があります。
  • Bazel は、リクエストを protobuf として保存し、protobuf の JSON 形式を使用して JSON に変換します。

キャンセル

ワーカーは、必要に応じて処理リクエストが完了する前にキャンセルできます。これは、ローカル実行が高速リモート実行によって定期的に中断される動的実行と併用する場合に特に便利です。キャンセルを許可するには、execution-requirements フィールドに supports-worker-cancellation: 1 を追加し(以下を参照)、--experimental_worker_cancellation フラグを設定します。

キャンセル リクエストは、cancel フィールドが設定された WorkRequest です(同様に、キャンセル レスポンスは、was_cancelled フィールドが設定された WorkResponse です)。キャンセル リクエストまたはキャンセル レスポンスに含める必要があるその他のフィールドは、request_id のみです。これはどのリクエストをキャンセルするかを示します。シングルプレックス ワーカーの場合、request_id フィールドは 0 になります。Multiplex ワーカーの場合は、以前に送信された WorkRequest の 0 でない request_id になります。サーバーは、ワーカーがすでに応答したリクエストに対してキャンセル リクエストを送信する可能性があります。その場合は、キャンセル リクエストを無視する必要があります。

解約以外の各 WorkRequest メッセージには、解約されたかどうかにかかわらず、1 回だけ応答する必要があります。サーバーがキャンセル リクエストを送信すると、ワーカーは request_id を設定し、was_cancelled フィールドを true に設定した WorkResponse で応答できます。通常の WorkResponse を送信することもできますが、output フィールドと exit_code フィールドは無視されます。

WorkRequest に対するレスポンスを送信した後は、ワーカーは作業ディレクトリ内のファイルにアクセスできません。一時ファイルを含むファイルを自由にクリーンアップできます。

ワーカーを使用するルールの作成

また、ワーカーが実行するアクションを生成するルールを作成する必要もあります。ワーカーを使用する Starlark ルールの作成は、他のルールの作成と同じです。

また、ルールにはワーカー自体への参照を含める必要があります。また、ルールが生成するアクションにはいくつかの要件があります。

ワーカーへの言及

ワーカーを使用するルールには、ワーカー自体を参照するフィールドを含める必要があります。そのため、\*\_binary ルールのインスタンスを作成してワーカーを定義する必要があります。ワーカーの名前が MyWorker.Java の場合、これは関連するルールの可能性があります。

java_binary(
    name = "worker",
    srcs = ["MyWorker.Java"],
)

これにより、ワーカー バイナリを参照する「worker」ラベルが作成されます。次に、ワーカーを使用するルールを定義します。このルールでは、ワーカー バイナリを参照する属性を定義する必要があります。

ビルドしたワーカー バイナリが、ビルドのトップレベルにある「work」という名前のパッケージにある場合、属性定義は次のようになります。

"worker": attr.label(
    default = Label("//work:worker"),
    executable = True,
    cfg = "exec",
)

cfg = "exec" は、ターゲット プラットフォームではなく実行プラットフォームで実行するようにワーカーをビルドすることを示します(つまり、ワーカーはビルド中にツールとして使用されます)。

作業要件

ワーカーを使用するルールは、ワーカーが実行するアクションを作成します。これらのアクションには、いくつかの要件があります。

  • "arguments" フィールドこれは、文字列のリストを受け取ります。文字列のリストのうち、最後のもの以外はすべて起動時にワーカーに渡されます。"arguments" リストの最後の要素は flag-file(@-preceded)引数です。ワーカーは、WorkRequest ごとに指定されたフラグファイルから引数を読み取ります。ルールでは、ワーカーの起動以外の引数をこのフラグファイルに書き込むことができます。

  • 「execution-requirements」フィールド。"supports-workers" : "1""supports-multiplex-workers" : "1"、またはその両方を含む辞書を指定します。

    [arguments] フィールドと [execution-requirements] フィールドは、ワーカーに送信されるすべてのアクションに必要です。また、JSON ワーカーが実行するアクションでは、実行要件フィールドに "requires-worker-protocol" : "json" を含める必要があります。"requires-worker-protocol" : "proto" も有効な実行要件ですが、proto ワーカーでデフォルトであるため必須ではありません。

    実行要件に worker-key-mnemonic を設定することもできます。これは、複数のアクション タイプの実行ファイルを再利用していて、このワーカーでアクションを区別する場合に便利です。

  • アクションの過程で生成された一時ファイルは、ワーカーのディレクトリに保存する必要があります。これにより、サンドボックス化が可能になります。

上記の「worker」属性を含むルール定義の場合、入力を表す「srcs」属性、出力を表す「output」属性、ワーカーの起動引数を表す「args」属性に加えて、ctx.actions.run の呼び出しは次のようになります。

ctx.actions.run(
  inputs=ctx.files.srcs,
  outputs=[ctx.outputs.output],
  executable=ctx.executable.worker,
  mnemonic="someMnemonic",
  execution_requirements={
    "supports-workers" : "1",
    "requires-worker-protocol" : "json"},
  arguments=ctx.attr.args + ["@flagfile"]
 )

別の例については、永続ワーカーの実装をご覧ください。

Bazel コードベースは、統合テストで使用される サンプル JSON ワーカーに加えて、Java コンパイラ ワーカーを使用します。

スキャフォールディングを使用して、正しいコールバックを渡すことで、任意の Java ベースのツールをワーカーにできます。

ワーカーを使用するルールの例については、Bazel のワーカー統合テストをご覧ください。

外部のコントリビューターがさまざまな言語でワーカーを実装しています。Bazel 永続ワーカーの多言語実装をご覧ください。GitHub には他にも多くの例があります