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

图 1. BEP 图。
最初,系统会发布 BuildStarted 事件。该事件会告知我们,构建是通过 bazel test 命令调用的,并会公布子事件:
OptionsParsedWorkspaceStatusCommandLineUnstructuredCommandLineBuildMetadataBuildFinishedPatternExpandedProgress
前三个事件提供了有关如何调用 Bazel 的信息。
PatternExpanded 构建事件可让您了解 ... 模式扩展到了哪些特定目标://foo:foo_lib 和
//foo:foo_test。它通过将两个 TargetConfigured 事件声明为子事件来实现此目的。请注意,TargetConfigured
事件将 Configuration 事件声明为子事件,即使 Configuration 已在 TargetConfigured 事件之前发布。
除了父子关系之外,事件还可以使用其构建事件标识符相互引用。例如,在上图中,TargetComplete 事件在其 fileSets 字段中引用了 NamedSetOfFiles 事件。
引用文件的构建事件通常不会在事件中嵌入文件名和路径。相反,它们包含 NamedSetOfFiles 事件的构建事件标识符,该标识符随后将包含实际的文件名和路径。NamedSetOfFiles
事件允许报告一组文件一次,并被许多目标引用。这种结构是必要的,因为否则在某些情况下,构建事件协议输出大小会随着文件数量的增加而呈二次方增长。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 事件,其中包含将方面应用于目标的结果。例如,如果使用
--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 事件时必须注意避免使用二次方算法,因为大型构建可能包含数万个此类事件,在具有二次方复杂度的遍历中需要数亿次运算。

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