建構事件通訊協定範例

回報問題 查看來源

如需建構事件通訊協定的完整規格,請參閱其通訊協定緩衝區定義。不過,在查看規格之前,不妨先建構一些實務做法。

請考慮一個簡單的 Bazel 工作區,其中包含兩個空白的殼層指令碼 foo.shfoo_test.sh,以及以下 BUILD 檔案:

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

前三個事件提供叫用 Bazel 的方式相關資訊。

PatternExpanded 建構事件可深入分析 ... 模式擴展至 //foo:foo_lib//foo:foo_test 的特定目標。方法是將兩個 TargetConfigured 事件宣告為子項。請注意,TargetConfigured 事件會將 Configuration 事件宣告為子項事件,即使 Configuration 已在 TargetConfigured 事件之前發布也一樣。

除了父項和子項關係之外,事件也可以使用建構事件 ID 參照彼此。例如,在上方圖表中,TargetComplete 事件參照其 fileSets 欄位中的 NamedSetOfFiles 事件。

參照檔案的建構事件通常不會在事件中嵌入檔案名稱和路徑。而是包含 NamedSetOfFiles 事件的建構事件 ID,其中包含實際的檔案名稱和路徑。NamedSetOfFiles 事件允許一組檔案回報一次,並由多個目標參照。這種結構是必要的,因為在某些情況下,Build Event Protocol 的輸出大小會隨著檔案數量呈四倍成長。NamedSetOfFiles 事件也可能不會內嵌所有的檔案,而會改為透過建構事件 ID 參照其他 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) 配對相關的動作。在啟用 aspects 的情況下進行建構時,Bazel 會額外評估與 (target, configuration, aspect) 三元相關聯的目標,以瞭解每個受已啟用切面影響的目標。

即使缺少切面專屬的事件類型,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 事件時,必須小心避免使用二次演算法,因為大型建構作業可能包含數萬個這類事件,且需要在進行數億個的情況下遍歷且十分複雜。

namesetoffiles-bep-graph

圖 2. NamedSetOfFiles BEP 圖表。

NamedSetOfFiles 事件一律會在 BEP 串流中「之前」出現在參照該事件的 TargetCompleteNamedSetOfFiles 事件之前。這與「父項/子項」事件關係相反,其中第一個事件以外的所有事件都會在至少一個通知該事件後出現。NamedSetOfFiles 事件是由不具語意的 Progress 事件所宣告。

考慮到這些排序和共用限制,一般取用端必須緩衝所有 NamedSetOfFiles 事件,直到 BEP 串流用盡為止。下列 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)