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
コマンドによってビルドが呼び出されたことを示し、子イベントを通知します。
OptionsParsed
WorkspaceStatus
CommandLine
UnstructuredCommandLine
BuildMetadata
BuildFinished
PatternExpanded
Progress
最初の 3 つのイベントは、Bazel がどのように呼び出されたかに関する情報を提供します。
PatternExpanded
ビルドイベントは、...
パターンが //foo:foo_lib
と //foo:foo_test
に拡張された特定のターゲットに関する分析情報を提供します。これは、2 つの TargetConfigured
イベントを子として宣言することで行います。Configuration
イベントは TargetConfigured
イベントの前に投稿されているにもかかわらず、TargetConfigured
イベントは Configuration
イベントを子イベントとして宣言します。
親子関係のほかに、イベントはビルドイベント ID を使用して相互に参照することもできます。たとえば、上記のグラフでは、TargetComplete
イベントは fileSets
フィールドの NamedSetOfFiles
イベントを参照しています。
通常、ファイルを参照するビルドイベントでは、ファイル名とパスがイベントに埋め込まれません。代わりに、NamedSetOfFiles
イベントのビルドイベント ID が含まれ、そこに実際のファイル名とパスが含まれます。NamedSetOfFiles
イベントを使用すると、一連のファイルを 1 回報告し、複数のターゲットで参照できます。この構造は、Build Event Protocol の出力サイズがファイル数に比例して二次的に増加する可能性があるため必要です。NamedSetOfFiles
イベントにすべてのファイルが埋め込まれていない場合もあります。その場合は、ビルドイベント識別子を使用して他の NamedSetOfFiles
イベントを参照します。
以下は、上記のグラフの //foo:foo_lib
ターゲットに対する TargetComplete
イベントのインスタンスで、プロトコル バッファの JSON 表現で出力されています。ビルドイベント ID には、ターゲットが不透明な文字列として含まれ、ビルドイベント ID を使用して 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)
トリプルに関連付けられたターゲットを追加で評価します。
アスペクト固有のイベントタイプがなくても、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. 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)