永続ワーカーを使用すると、ビルドを高速化できます。ビルドに起動コストが高いアクションや、アクション間のキャッシュ保存のメリットがあるアクションが繰り返し含まれている場合は、これらのアクションを実行するために独自の永続ワーカーを実装することをおすすめします。
Bazel サーバーは stdin
/stdout
を使用してワーカーと通信します。プロトコル バッファまたは JSON 文字列の使用をサポートしています。
ワーカーの実装は次の 2 つの部分で構成されています。
ワーカーの作成
永続ワーカーは、次の要件を満たします。
stdin
から WorkRequests を読み取ります。- WorkResponses(
WorkResponse
のみ)をstdout
に書き込みます。 --persistent_worker
フラグを受け取ります。ラッパーは--persistent_worker
コマンドライン フラグを認識し、そのフラグが渡された場合にのみ永続化する必要があります。それ以外の場合は、1 回限りのコンパイルを実行して終了する必要があります。
プログラムがこれらの要件を満たしていれば、永続ワーカーとして使用できます。
作業リクエスト
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、ゼロまたはゼロ以外の終了コード、リクエストの処理または実行中に発生したエラーを説明する出力メッセージが含まれます。ワーカーは、呼び出すツールの stdout
と stderr
をキャプチャし、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 に変換します。
キャンセル
Worker は、必要に応じて、完了前に作業リクエストをキャンセルできるようにすることができます。これは、ローカル実行が高速なリモート実行によって定期的に中断される動的実行に関連して特に便利です。キャンセルを許可するには、execution-requirements
フィールドに supports-worker-cancellation: 1
を追加し(下記参照)、--experimental_worker_cancellation
フラグを設定します。
キャンセル リクエストは、cancel
フィールドが設定された WorkRequest
です(同様に、キャンセル レスポンスは、was_cancelled
フィールドが設定された WorkResponse
です)。キャンセル リクエストまたはキャンセル レスポンスに含める必要がある唯一のフィールドは、キャンセルするリクエストを示す 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" フィールド。これは文字列のリストを受け取ります。最後の文字列を除くすべての文字列は、起動時にワーカーに渡される引数です。「arguments」リストの最後の要素は
flag-file
(@ が付いた)引数です。ワーカーは、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 には、他にも多くの例があります。