您可以在其协议 缓冲区定义中找到 Build Event Protocol 的完整规范。不过,在查看规范之前,先建立一些直觉 可能会有所帮助。
请考虑一个简单的 Bazel 工作区,该工作区由两个空 shell 脚本
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 ... 时,生成的
build 事件的 build 图将类似于下图。箭头表示
上述父子关系。请注意,为简洁起见,我们省略了一些 build 事件和
大多数字段。

图 1。BEP 图。
最初,系统会发布 BuildStarted 事件。该事件会告知我们
build 是通过 bazel test 命令调用的,并公布子事件:
OptionsParsedWorkspaceStatusCommandLineUnstructuredCommandLineBuildMetadataBuildFinishedPatternExpandedProgress
前三个事件提供了有关如何调用 Bazel 的信息。
PatternExpanded build 事件可让您了解
哪些特定目标是 ... 模式扩展到的:
//foo:foo_lib 和 //foo:foo_test。它通过将两个
TargetConfigured 事件声明为子事件来实现此目的。请注意,TargetConfigured 事件
将 Configuration 事件声明为子事件,即使 Configuration
已在 TargetConfigured 事件之前发布。
除了父子关系之外,事件还可以使用其 build 事件标识符相互引用
。例如,在上图中,
TargetComplete 事件在其 fileSets
字段中引用了 NamedSetOfFiles 事件。
引用文件的 build 事件通常不会在事件中嵌入文件
名和路径。相反,它们包含 build 事件标识符
,该标识符随后将包含实际的文件名和
路径。NamedSetOfFilesNamedSetOfFiles 事件允许报告一组文件一次,并
由许多目标引用。这种结构是必要的,因为否则在
某些情况下,Build Event Protocol 输出大小会随着
文件数量的增加而呈二次方增长。一个 NamedSetOfFiles 事件也可能不会嵌入其所有文件
,而是通过其
build 事件标识符引用其他 NamedSetOfFiles 事件。
下面是上图中 //foo:foo_lib
目标的 TargetComplete 事件的实例,以协议缓冲区的 JSON 表示法打印。
build 事件标识符包含目标(作为不透明字符串),并使用其 build 事件标识符引用
Configuration 事件。该事件不会
公布任何子事件。载荷包含有关目标是否成功构建、输出文件集以及构建的目标类型的信息。
{
"id": {
"targetCompleted": {
"label": "//foo:foo_lib",
"configuration": {
"id": "544e39a7f0abdb3efdd29d675a48bc6a"
}
}
},
"completed": {
"success": true,
"outputGroup": [{
"name": "default",
"fileSets": [{
"id": "0"
}]
}],
"targetKind": "sh_library rule"
}
}
BEP 中的方面结果
普通 build 会评估与 (target, configuration)
对关联的操作。在启用 方面 的情况下进行构建时,Bazel
还会评估与 (target, configuration,
aspect) 三元组关联的目标,对于受给定已启用方面影响的每个目标都是如此。
尽管没有
特定于方面的事件类型,但方面评估结果仍可在 BEP 中提供。对于每个具有
适用方面的 (target, configuration) 对,Bazel 都会发布额外的 TargetConfigured 和
TargetComplete 事件,其中包含将方面应用于
目标的结果。例如,如果使用
--aspects=aspects/myaspect.bzl%custom_aspect构建//:foo_lib,则此事件也会显示在
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事件时必须注意避免使用二次方算法,因为大型 build 可能包含数万个
此类事件,在具有
二次方复杂度的遍历中需要数亿次操作。

图 2.NamedSetOfFiles BEP 图。
NamedSetOfFiles 事件始终在 BEP 流中显示在引用它的
TargetComplete 或 NamedSetOfFiles 事件之前。这与
“父子”事件关系相反,在“父子”事件关系中,除了第一个事件
之外的所有事件都至少在一个公布它的事件之后显示。一个 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)