ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Bazel 构建事件协议(BEP)实战指南:事件图解析、NamedSetOfFiles 共享结构与消费端实现

Bazel 构建事件协议(BEP)实战指南:事件图解析、NamedSetOfFiles 共享结构与消费端实现 Bazel 构建事件协议BEP实战指南事件图解析、NamedSetOfFiles 共享结构与消费端实现【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel导读构建事件协议Build Event ProtocolBEP是 Bazel 面向第三方程序开放的一次构建调用的结构化数字孪生涵盖了构建与测试结果、进度、配置等信息。本文以 Bazel 官方 BEP 示例文档为主体通过一个最小sh_library/sh_test工作区逐步剖析完整 BEP 事件图深入讲解NamedSetOfFiles共享文件集合的设计动机与排序约束、Aspect 结果在 BEP 中的表达方式并给出可直接落地的 Python 消费代码帮助你理解 BEP 的底层原理并写出高效、无二次复杂度陷阱的 BEP 消费者。从一个最小工作区理解 BEP 事件图BEP 的完整规范定义在协议缓冲区Protocol Buffer定义文件中见 build_event_stream.proto。在查阅规范之前先通过一个具体例子建立直觉。考虑一个仅包含两个空 shell 脚本foo.sh、foo_test.sh以及如下BUILD文件的简单 Bazel 工作区sh_library( name foo_lib, srcs [foo.sh], ) sh_test( name foo_test, srcs [foo_test.sh], deps [:foo_lib], )对该项目运行bazel test ...时生成的事件流构成下图所示的构建事件图。图中箭头表示前文提到的父事件与子事件关系为简洁起见部分事件及大多数字段被省略。图 1.bazel test ...的 BEP 事件图。BuildStarted一切事件的根构建开始时Bazel 首先发布一个BuildStarted事件。该事件告诉我们构建是通过bazel test命令发起的并宣布了以下子事件OptionsParsedWorkspaceStatusCommandLineUnstructuredCommandLineBuildMetadataBuildFinishedPatternExpandedProgress前三个事件OptionsParsed、WorkspaceStatus、CommandLine提供关于 Bazel 是如何被调用的信息。从 build_event_stream.proto 中可以看到BuildStarted的 payload 携带了命令名command、工作目录、工作区目录、服务器 PID、主机名与用户名等元数据而CommandLine事件则进一步包含originalBazel 客户端收到的原始命令行、canonical展开.rc文件并应用调用策略后的有效命令行与tool通过--experimental_tool_command_line注入的包装工具命令行三种表示详见 BEP 术语表。PatternExpanded模式展开的结果PatternExpanded事件揭示了...模式具体展开到了哪些目标//foo:foo_lib和//foo:foo_test。它通过把两个TargetConfigured事件声明为自己的子事件来实现这一点。值得注意的是TargetConfigured事件将Configuration事件声明为子事件即使Configuration在TargetConfigured之前就已经发布。这印证了 BEP 的核心语义之一父事件不必先于子事件发布。在 BEP 主文档 bep.mdx 中对此有明确说明所有构建事件通过父子关系构成一张有向无环图DAG除首个事件外每个事件都有至少一个父事件而所有事件必须由之前的某个事件宣布是协议的基本保证。也就是说事件在流中的物理发布顺序与其在图中的逻辑层级关系是解耦的。事件间的第二种关联方式按 ID 相互引用除了父子关系事件还可以通过构建事件标识符build event identifier互相引用。例如在图 1 中TargetComplete事件通过其fileSets字段引用NamedSetOfFiles事件。这种引用机制建立在事件 ID 的唯一性之上根据 build_event_stream.proto 中的注释每个事件拥有一个 ID 与一组子事件 ID除初始事件外每个事件的 ID 都曾被某个更早的事件作为子 ID 提及一次构建调用完成当且仅当初始事件的所有直接与间接子事件都已被发布。NamedSetOfFiles防止 BEP 输出体积二次膨胀的共享文件集合引用文件的构建事件通常不会把文件名和路径直接内嵌在事件中。相反它们携带一个NamedSetOfFiles事件的构建事件标识符由后者承载真实的文件名与路径。NamedSetOfFiles事件允许一组文件只被报告一次、被多个目标反复引用。这种结构是必要的——否则在某些情况下BEP 的输出体积会随文件数量二次方增长。设想一个大型构建大量目标共享同一组头文件或资源文件若每个目标都把完整文件列表内嵌在自己的事件里事件流的总大小将迅速失控。更进一步一个NamedSetOfFiles事件甚至可以不完全内嵌所有文件而是通过事件 ID 引用其他NamedSetOfFiles事件。这与 Starlark 中 Depset 的结构完全对应——NamedSetOfFiles正是 depset 在 BEP 事件流中的投影允许集合共享与嵌套组合。相关的底层定义见 build_event_stream.proto。剖析一个真实的 TargetComplete 事件下面是对上图//foo:foo_lib目标的TargetComplete事件实例以 Protocol Buffer 的 JSON 表示打印。事件 ID 将目标作为不透明字符串包含在内并通过其构建事件 ID 引用Configuration事件该事件没有宣布任何子事件payload 则包含目标是否成功构建、输出文件集合以及目标种类等信息。{ id: { targetCompleted: { label: //foo:foo_lib, configuration: { id: 544e39a7f0abdb3efdd29d675a48bc6a } } }, completed: { success: true, outputGroup: [{ name: default, fileSets: [{ id: 0 }] }], targetKind: sh_library rule } }逐字段解读id.targetCompleted.label目标标签作为不透明字符串使用id.targetCompleted.configuration.id配置 ID。根据 proto 注释build_event_stream.proto消费者不应假设该 ID 有任何内部结构也不应假设它跨事件流保持一致特殊值none表示空配置null configuration用于不可配置的目标如源文件completed.success目标构建是否成功completed.outputGroup目标请求的输出组列表每个输出组如default通过fileSets[].id引用NamedSetOfFiles事件completed.targetKind目标种类例如sh_library rule。注意这里fileSets[].id为0—— 一个不透明字符串仅在当前事件流实例内有效见 NamedSetOfFilesId 定义跨构建没有任何可复用的含义。Aspect 结果在 BEP 中的表达普通构建只评估与(target, configuration)二元组关联的 action。当启用 aspects 构建时Bazel 会额外评估与(target, configuration, aspect)三元组关联的目标——对每个受已启用 aspect 影响的目标都会如此。尽管 BEP没有专门的 aspect 专用事件类型Aspect 的评估结果仍然完整可用对于每个存在适用 aspect 的(target, configuration)二元组Bazel 会额外发布一个承载将该 aspect 应用到该目标结果的TargetConfigured与TargetComplete事件。例如若以--aspectsaspects/myaspect.bzl%custom_aspect构建//:foo_libBEP 中还会出现如下事件{ id: { targetCompleted: { label: //foo:foo_lib, configuration: { id: 544e39a7f0abdb3efdd29d675a48bc6a }, aspect: aspects/myaspect.bzl%custom_aspect } }, completed: { success: true, outputGroup: [{ name: default, fileSets: [{ id: 1 }] }] } }注意普通事件与 aspect 事件 ID 之间的唯一区别是aspect字段的有无TargetConfiguredId与TargetCompletedId中均为可选字段见 build_event_stream.proto 与 TargetCompletedId 定义。因此一个不检查aspectID 字段、仅按目标累计输出文件的工具可能会把目标输出与 aspect 输出混为一谈。编写消费者时务必把(target, configuration, aspect)三元组作为一个整体键来处理。高效消费 NamedSetOfFiles避免二次复杂度算法确定某个目标或 aspect产生的制品是 BEP 最常见的用例之一且通过少量准备工作即可高效完成。本节讨论NamedSetOfFiles事件提供的递归、共享结构——它与 Starlark Depset 的结构一致。为什么必须警惕二次复杂度消费者在遍历NamedSetOfFiles事件时必须小心避免二次复杂度的算法大型构建可能包含数万个这样的事件一个二次复杂度的遍历意味着数亿次操作。这正是NamedSetOfFiles引入共享机制的根本原因——复用集合引用而非复制文件列表让消费端能够以近似线性的代价完成遍历。图 2.NamedSetOfFilesBEP 事件图。排序约束先于引用者出现NamedSetOfFiles事件在 BEP 流中的出现位置有一条重要规则一个NamedSetOfFiles事件总是出现在引用它的TargetComplete或NamedSetOfFiles事件之前。这与父子事件关系正好相反——父子关系中除首个事件外所有事件都出现在至少一个宣布它的事件之后。而NamedSetOfFiles事件由一条无语义的Progress事件宣布。Progress事件在 build_event_stream.proto 中的定义是汇总构建到目前为止进度的事件的 payload同时承担着在逻辑父事件因信息尚不完整而无法发布时充当父事件的职责——NamedSetOfFiles正是这一职责的典型受益者。结合排序与共享约束的典型消费模式鉴于上述排序与共享约束一个典型的消费者必须缓冲所有NamedSetOfFiles事件直到 BEP 流耗尽才能安全地解析引用。下面的 JSON 事件流示例与 Python 代码演示了如何构建从目标/aspect到default 输出组内制品的映射以及如何处理已构建目标/aspect 子集的输出。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)该代码的关键设计点第一遍建索引。named_sets字典以集合 ID 为键缓冲所有NamedSetOfFiles事件outputs字典以(label, configuration.id, aspect)三元组为键记录每个目标/aspect 的各输出组引用的文件集合 ID 列表。把aspect纳入键正是为了避免上一节提到的目标输出与 aspect 输出混为一谈的陷阱。第二遍惰性展开。仅对感兴趣的目标子集relevant_subset的结果做展开seen_sets集合保证每个NamedSetOfFiles集合最多被访问一次——这是把共享结构从二次复杂度拉回线性复杂度的核心即使多个目标引用同一个集合该集合也只被展开一次输出文件只会被process_file处理一次。延伸阅读与工程落地理解示例后你可以进一步在工程中落地如何消费 BEP 流Bazel 支持--build_event_binary_file长度前缀分隔的二进制 protobuf可用parseDelimitedFrom(InputStream)读取、--build_event_text_file与--build_event_json_file三种输出格式也可以使用--bes_backendHOST:PORT将事件推送到 gRPC 的 Build Event Service 端点纯文本 gRPC 用grpc://前缀启用 TLS 用grpcs://详见 BEP 主文档。事件类型速查BuildStarted、PatternExpanded、TargetConfigured、TargetComplete、TestResult、TestSummary、BuildFinished、BuildMetrics、Aborted等每个事件类型的语义与 JSON 示例见 BEP 术语表。协议底层定义所有事件 ID 的 oneof 结构、payload 消息与字段注释均可在 build_event_stream.proto 中查阅——它同时是本文所有 JSON 示例字段命名的权威出处。把握住三条主线——父子关系与 ID 引用的双通道关联、NamedSetOfFiles的共享与先行排序约束、以及以(target, configuration, aspect)为完整键的消费模型——你就能写出既正确又高效近线性复杂度的 BEP 消费者为 IDE 插件、构建结果看板或 CI 集成提供可靠的构建洞察。【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表