Build Event Protocol の完全な仕様は、プロトコル バッファ定義に記載されています。ただし、仕様を確認する前に、ある程度の直感を得ておくと役立つ場合があります。
2 つの空のシェル スクリプト foo.sh と foo_test.sh、および次の BUILD ファイルで構成される単純な Bazel ワークスペースについて考えてみましょう。
sh_library(
name = "foo_lib",
srcs = ["foo.sh"],
)
sh_test(
name = "foo_test",
srcs = ["foo_test.sh"],
deps = [":foo_lib"],
)
このプロジェクトで bazel test ... を実行すると、生成されたビルド イベントのビルドグラフは次のグラフのようになります。矢印は、前述の親子関係を示しています。簡略化のため、一部のビルド イベントとほとんどのフィールドは省略されています。

図 1.BEP グラフ。
最初に、BuildStarted イベントが公開されます。このイベントは、bazel test コマンドでビルドが呼び出されたことを通知し、子イベントをアナウンスします。
OptionsParsedWorkspaceStatusCommandLineUnstructuredCommandLineBuildMetadataBuildFinishedPatternExpandedProgress
最初の 3 つのイベントは、Bazel の呼び出し方法に関する情報を提供します。
PatternExpanded ビルド イベントは、... パターンが展開された特定のターゲット(//foo:foo_lib と //foo:foo_test)に関する情報を提供します。これを行うには、2 つの TargetConfigured イベントを子として宣言します。TargetConfigured イベントは、Configuration イベントが TargetConfigured イベントの前に投稿された場合でも、Configuration イベントを子イベントとして宣言します。
親子関係に加えて、イベントはビルド イベント識別子を使用して相互に参照することもできます。たとえば、上のグラフでは、TargetComplete イベントは fileSets フィールドの NamedSetOfFiles イベントを参照しています。
ファイルを参照するビルド イベントは、通常、ファイル名とパスをイベントに埋め込みません。代わりに、NamedSetOfFiles イベントのビルド イベント識別子が含まれます。これには、実際のファイル名とパスが含まれます。NamedSetOfFiles イベントを使用すると、ファイルのセットを一度報告し、多くのターゲットから参照できます。この構造が必要なのは、そうしないと、場合によっては Build Event Protocol の出力サイズがファイル数の 2 乗で増加するためです。NamedSetOfFiles イベントには、すべてのファイルが埋め込まれていない場合もあります。代わりに、ビルド イベント識別子を使用して他の NamedSetOfFiles イベントを参照します。
プロトコル バッファの JSON 表現で出力された、上のグラフの //foo:foo_lib ターゲットの TargetComplete イベントのインスタンスを次に示します。
ビルド イベント識別子には、ターゲットが不透明な文字列として含まれており、ビルド イベント識別子を使用して Configuration イベントを参照します。このイベントは子イベントをアナウンスしません。ペイロードには、ターゲットが正常にビルドされたかどうか、出力ファイルのセット、ビルドされたターゲットの種類に関する情報が含まれます。
{
"id": {
"targetCompleted": {
"label": "//foo:foo_lib",
"configuration": {
"id": "544e39a7f0abdb3efdd29d675a48bc6a"
}
}
},
"completed": {
"success": true,
"outputGroup": [{
"name": "default",
"fileSets": [{
"id": "0"
}]
}],
"targetKind": "sh_library rule"
}
}
BEP のアスペクトの結果
通常のビルドでは、(target, configuration) ペアに関連付けられたアクションが評価されます。アスペクトを有効にしてビルドする場合、Bazel
は、有効なアスペクトの影響を受けるターゲットごとに、(target, configuration,
aspect) の 3 つ組に関連付けられたターゲットも評価します。
アスペクト固有のイベントタイプがない場合でも、アスペクトの評価結果は BEP で確認できます。適用可能なアスペクトを持つ (target, configuration) ペアごとに、Bazel は、アスペクトをターゲットに適用した結果を含む追加の TargetConfigured イベントと TargetComplete イベントを公開します。たとえば、//:foo_lib が --aspects=aspects/myaspect.bzl%custom_aspect でビルドされる場合、このイベントも BEP に表示されます。
{
"id": {
"targetCompleted": {
"label": "//foo:foo_lib",
"configuration": {
"id": "544e39a7f0abdb3efdd29d675a48bc6a"
},
"aspect": "aspects/myaspect.bzl%custom_aspect"
}
},
"completed": {
"success": true,
"outputGroup": [{
"name": "default",
"fileSets": [{
"id": "1"
}]
}]
}
}
NamedSetOfFiles を使用する
特定のターゲット(またはアスペクト)によって生成されたアーティファクトを特定することは、一般的な BEP ユースケースであり、準備をすることで効率的に行うことができます。このセクション
では、NamedSetOfFiles
イベントによって提供される再帰的な共有構造について説明します。これは、Starlark Depset の構造と一致します。
コンシューマーは、NamedSetOfFiles イベントを処理する際に 2 次アルゴリズムを回避する必要があります。大規模なビルドには数万件のイベントが含まれる可能性があり、2 次の複雑さでトラバースするには数億回のオペレーションが必要になるためです。

図 2.NamedSetOfFiles BEP グラフ。
NamedSetOfFiles イベントは、それを参照する TargetComplete イベントまたは NamedSetOfFiles イベントの前に、常に BEP ストリームに表示されます。 これは「親子」イベント関係の逆です。最初のイベントを除くすべてのイベントは、少なくとも 1 つのイベントがアナウンスされた後に表示されます。NamedSetOfFiles イベントは、セマンティクスのない Progress イベントによってアナウンスされます。
これらの順序付けと共有の制約により、一般的なコンシューマーは、BEP ストリームが使い果たされるまで、すべての NamedSetOfFiles イベントをバッファリングする必要があります。次の JSON イベント ストリームと Python コードは、ターゲット/アスペクトから「デフォルト」出力グループのビルド済みアーティファクトへのマップを設定する方法と、ビルド済みターゲット/アスペクトのサブセットの出力を処理する方法を示しています。
named_sets = {} # type: dict[str, NamedSetOfFiles]
outputs = {} # type: dict[str, dict[str, set[str]]]
for event in stream:
kind = event.id.WhichOneof("id")
if kind == "named_set":
named_sets[event.id.named_set.id] = event.named_set_of_files
elif kind == "target_completed":
tc = event.id.target_completed
target_id = (tc.label, tc.configuration.id, tc.aspect)
outputs[target_id] = {}
for group in event.completed.output_group:
outputs[target_id][group.name] = {fs.id for fs in group.file_sets}
for result_id in relevant_subset(outputs.keys()):
visit = outputs[result_id].get("default", [])
seen_sets = set(visit)
while visit:
set_name = visit.pop()
s = named_sets[set_name]
for f in s.files:
process_file(result_id, f)
for fs in s.file_sets:
if fs.id not in seen_sets:
visit.add(fs.id)
seen_sets.add(fs.id)