构建事件协议示例

报告问题 查看来源 每晚 · 7.2。 · 7.1敬上 · 7.0 · 6.5 · 6.4

构建事件协议的完整规范可在其协议中找到 缓冲区定义。不过,积累一些直觉可能会有所帮助 然后再查看具体规范

假设一个简单的 Bazel 工作区包含两个空 Shell 脚本 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 ... 时,生成的 build 图 构建事件将类似于下图。箭头表示 上述父级与子级关系。请注意,有些构建事件和 为简洁起见,已省略大多数字段。

bep-graph

图 1. BEP 图。

最初,系统会发布 BuildStarted 事件。该事件通知我们 build 是通过 bazel test 命令调用的,并公布子事件:

  • OptionsParsed
  • WorkspaceStatus
  • CommandLine
  • UnstructuredCommandLine
  • BuildMetadata
  • BuildFinished
  • PatternExpanded
  • Progress

前三个事件提供有关如何调用 Bazel 的信息。

PatternExpanded 构建事件提供了数据分析 ... 模式扩展为以下特定目标: //foo:foo_lib//foo:foo_test。为此,它会声明两个 TargetConfigured 事件作为子级。请注意,TargetConfigured 事件 将 Configuration 事件声明为子事件,即使 Configuration 已在 TargetConfigured 事件之前发布。

除了父级与子级关系之外,事件还可能相互引用 构建事件标识符例如,在上图中, TargetComplete 事件在其 fileSets 中引用 NamedSetOfFiles 事件 字段。

引用文件的构建事件通常不会嵌入文件 事件的名称和路径而是包含构建事件标识符 NamedSetOfFiles 事件的名称,其中将包含实际文件名和 路径。NamedSetOfFiles 事件允许将一组文件报告一次, 被许多目标引用的情况此结构是必要的,因为 在某些情况下,Build Event Protocol 输出大小会随着 文件数量。NamedSetOfFiles 事件也可能不包含其所有文件 而是通过其NamedSetOfFiles build 事件标识符。

以下是 //foo:foo_libTargetComplete 事件实例 上图中的目标,以 Protocol Buffer 的 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 的切面结果

普通 build 会评估与 (target, configuration) 关联的操作 对。在启用 aspects 的情况下进行构建时,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 事件,因为大型 build 可能包含数万个 需要一次以数亿次的遍历操作, 二次复杂度。

namedsetoffiles-bep-graph

图 2. NamedSetOfFiles BEP 图表。

NamedSetOfFiles 事件始终会在 BEP 视频流之前显示, 引用它的 TargetCompleteNamedSetOfFiles 事件。这是 是“父级-子级”的事件关系,在这种情况下,除第一个事件 会在至少一个事件播报后显示。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)