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

問題を報告 ソースを表示 Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

ビルド イベント プロトコルの完全な仕様は、プロトコル バッファ定義で確認できます。ただし、仕様を確認する前に、ある程度の直感を養っておくと役立つ場合があります。

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

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

ファイルを参照するビルドイベントでは、通常、ファイル名とパスはイベントに埋め込まれません。代わりに、NamedSetOfFiles イベントのビルドイベント識別子が含まれます。この識別子には、実際のファイル名とパスが含まれます。NamedSetOfFiles イベントを使用すると、一連のファイルを一度だけレポートして、多くのターゲットから参照できます。この構造が必要なのは、そうしないと、場合によっては Build Event Protocol の出力サイズがファイル数に対して 2 次関数的に増加するためです。NamedSetOfFiles イベントにすべてのファイルが埋め込まれておらず、ビルドイベント識別子を介して他の NamedSetOfFiles イベントを参照している場合もあります。

以下は、上記のグラフの //foo:foo_lib ターゲットの TargetComplete イベントのインスタンスで、プロトコル バッファの JSON 表現で出力されています。ビルドイベント識別子には、ターゲットが不透明な文字列として含まれており、ビルドイベント識別子を使用して 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 ユースケースであり、準備を整えることで効率的に実行できます。このセクションでは、Starlark の Depset の構造と一致する NamedSetOfFiles イベントによって提供される再帰的な共有構造について説明します。

大規模なビルドには数万件の NamedSetOfFiles イベントが含まれる可能性があり、二次複雑性のトラバーサルで数億件のオペレーションが必要になるため、NamedSetOfFiles イベントを処理する際は二次アルゴリズムを避けるように注意する必要があります。

namedsetoffiles-bep-graph

図 2. NamedSetOfFiles BEP グラフ。

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)