永続ワーカーの作成

永続ワーカーを使用すると、ビルドを高速化できます。ビルドで繰り返し実行されるアクションの起動コストが高い場合や、アクション間のキャッシュ保存が有効な場合は、これらのアクションを実行する独自の永続ワーカーを実装することをおすすめします。

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

ワーカーの実装は次の 2 つの部分で構成されます。

ワーカーを作成する

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

  • `stdin` から WorkRequests を読み取ります。
  • stdoutWorkResponsesWorkResponse のみ)を書き込みます。
  • --persistent_worker フラグを受け入れます。ラッパーは --persistent_worker コマンドライン フラグを認識し、そのフラグが渡された場合にのみ永続化する必要があります。それ以外の場合は、1 回限りのコンパイルを実行して終了する必要があります。

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

処理リクエスト

WorkRequest には、ワーカーへの引数のリスト、ワーカーがアクセスできる入力を表すパスとダイジェストのペアのリスト(強制ではありませんが、この情報をキャッシュ保存に使用できます)、リクエスト ID が含まれます。リクエスト 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、ゼロまたはゼロ以外の終了コード、リクエストの処理または実行中に発生したエラーを説明する出力メッセージが含まれます。ワーカーは、呼び出すツールの stdoutstderr をキャプチャし、WorkResponse を介して報告する必要があります。ワーカー プロセスの stdout に書き込むと、ワーカー プロトコルに干渉するため安全ではありません。 ワーカー プロセスの stderr に書き込むのは安全ですが、結果は個々のアクションに帰属するのではなく、ワーカーごとのログファイルに収集されます。

{
  "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 が同じである必要があります。そのため、リクエスト ID がゼロ以外の場合は指定する必要があります。これは有効な WorkResponse です。

{
  "requestId" : 12,
}

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

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

キャンセル

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

キャンセル リクエストWorkRequestcancel フィールドが設定されたものです( 同様に、キャンセル レスポンスWorkResponsewas_cancelled フィールドが設定されたものです)。キャンセル リクエストまたはキャンセル レスポンスに含める必要があるフィールドは、キャンセルするリクエストを示す request_id のみです。request_id フィールドは、シングルプレックス ワーカーの場合は 0、マルチプレックス ワーカーの場合は以前に送信された 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」リストの最後の要素は、flag-file(@ が付加された)引数です。ワーカーは、WorkRequest ごとに指定されたフラグファイルから引数を読み取ります。ルールでは、ワーカーの起動以外の引数をこのフラグファイルに書き込むことができます。

  • 実行要件」フィールド。 "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 には他にも多くの例があります