建構事件通訊協定範例

您可以在通訊協定緩衝區定義中找到建構事件通訊協定的完整規格。不過,在查看規格前,事先構思一些直覺,可能會很有幫助。

假設有一個簡單的 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 事件宣告為子項。請注意,即使 ConfigurationTargetConfigured 事件之前發布,TargetConfigured 事件也都會將 Configuration 事件宣告為子項事件。

除了父項與子項關係之外,事件也可以使用建構事件 ID 互相參照。舉例來說,在上方圖表中,TargetComplete 事件參照 fileSets 欄位中的 NamedSetOfFiles 事件。

參照檔案的建構事件通常不會嵌入事件名稱和路徑。而是包含 NamedSetOfFiles 事件的建構事件 ID,其中會包含實際檔案名稱和路徑。NamedSetOfFiles 事件可讓一組檔案回報一次,並由多個目標參照。這種結構是必要的,因為在某些情況下,建構事件通訊協定的輸出內容大小會隨檔案數量而成長。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) 配對相關的動作。在啟用切面的情況下進行建構時,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 事件時,消費者必須小心避免二次演算法運算,因為大型建構作業可能包含數萬個事件,因為大型建構作業可能會需要處理數億個且二次複雜的操作。

<名名稱>setoffiles-bep-graph

圖 2. NamedSetOfFiles 的 BEP 圖表。

NamedSetOfFiles 事件一律會在參照該事件的 TargetCompleteNamedSetOfFiles 事件「之前」,顯示在 BEP 串流中。這與「父項-子項」事件關係相反,也就是在至少一項事件宣布該事件之後,除了第一個事件以外,其他所有事件都會顯示。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)