ビルド イベント プロトコルの例

Build Event Protocol の完全な仕様については、プロトコル バッファの定義をご覧ください。ただし、仕様を確認する前に、ある程度の直感を得ておくと役立つ場合があります 。

2 つの空のシェル スクリプト foo.shfoo_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 ... を実行すると、生成された ビルド イベントのビルドグラフは次のグラフのようになります。矢印は、 前述の親子関係を示しています。簡略化のため、一部のビルド イベントと ほとんどのフィールドは省略されています。

bep-graph

図 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 イベントを子イベントとして宣言します。

親子関係に加えて、イベントはビルド イベント識別子を使用して相互に参照することもできます 。たとえば、上のグラフでは、 TargetComplete イベントは NamedSetOfFiles イベントの fileSets フィールドを参照しています。

ファイルを参照するビルド イベントでは、通常、ファイル 名とパスはイベントに埋め込まれません。代わりに、ビルド イベント識別子 が含まれます。これには、実際のファイル名と パスが含まれます。NamedSetOfFilesNamedSetOfFiles イベントを使用すると、ファイルのセットを一度報告し、 多くのターゲットから参照できます。この構造が必要なのは、そうしないと、 場合によっては Build Event Protocol の出力サイズが ファイル数の 2 乗に比例して増加するためです。A 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 は、アスペクトをターゲットに適用した結果を含む追加の TargetConfiguredTargetComplete イベントを公開します。たとえば、//: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 次複雑度のトラバーサルでは、数億回のオペレーションが必要になります。

namedsetoffiles-bep-graph

図 2.NamedSetOfFiles BEP グラフ。

A NamedSetOfFiles イベントは、それを参照する TargetComplete イベントまたは NamedSetOfFiles イベントの前に、常に BEP ストリームに表示されます。これは「親子」イベント関係の逆です。最初のイベントを除くすべてのイベントは、少なくとも 1 つのイベントが通知された後に表示されます。NamedSetOfFiles イベントは Progress イベントによって通知されます。

これらの順序付けと共有の制約により、一般的なコンシューマーは BEP ストリームが使い果たされるまで、すべての NamedSetOfFiles イベントをバッファリングする必要があります。次の JSON イベント ストリームと Python コードは、ターゲット/アスペクトから「default」出力グループのビルド済みアーティファクトへのマップを設定する方法と、ビルド済みターゲット/アスペクトのサブセットの出力を処理する方法を示しています。

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)