您可以在通訊協定緩衝區定義中找到 Build Event Protocol 的完整規格。不過,在查看規格之前,先建立一些直覺概念可能會有所幫助。
請考慮一個簡單的 Bazel 工作區,其中包含兩個空白的殼層指令碼 foo.sh
和 foo_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 ...
時,所產生建構事件的建構圖表會類似下圖。箭頭表示上述父項和子項的關係。請注意,為了簡化內容,我們省略了部分建構事件和大部分欄位。
圖 1. BEP 圖表。
系統一開始會發布 BuildStarted
事件。這項事件會告知我們,建構作業是透過 bazel test
指令叫用,並宣告子事件:
OptionsParsed
WorkspaceStatus
CommandLine
UnstructuredCommandLine
BuildMetadata
BuildFinished
PatternExpanded
Progress
前三個事件會提供 Bazel 的叫用方式相關資訊。
PatternExpanded
建構事件可提供洞察資料,讓您瞭解 ...
模式擴展至哪些特定目標://foo:foo_lib
和 //foo:foo_test
。方法是宣告兩個 TargetConfigured
事件做為子項。請注意,即使 Configuration
已在 TargetConfigured
事件之前發布,TargetConfigured
事件仍會將 Configuration
事件宣告為子項事件。
除了父項和子項關係之外,事件也可以使用建構事件 ID 參照彼此。舉例來說,在上述圖表中,TargetComplete
事件會參照其 fileSets
欄位的 NamedSetOfFiles
事件。
參照檔案的建構事件通常不會在事件中嵌入檔案名稱和路徑。而是包含 NamedSetOfFiles
事件的建構事件 ID,該 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)
配對相關的動作。在啟用方面建構時,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
邊際利潤率圖。
NamedSetOfFiles
事件一律會在 BEP 串流中前置 參照該事件的 TargetComplete
或 NamedSetOfFiles
事件。這與「父子」事件關係相反,在這種關係中,除了第一個事件外,所有事件都會在至少一個事件宣告後出現。NamedSetOfFiles
事件是由 Progress
事件宣告,且沒有語意。
基於這些排序和共用限制,一般消費者必須緩衝所有 NamedSetOfFiles
事件,直到 BEP 串流用盡為止。以下 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)