Bazel Build Event Protocol(BEP)完全指南:事件模型、消费方式与 Build Event Service
Bazel Build Event ProtocolBEP完全指南事件模型、消费方式与 Build Event Service【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazelBuild Event ProtocolBEP是 Bazel 为第三方程序提供的一次构建调用invocation的标准化数字孪生它把构建与测试结果、构建进度、构建配置等信息以结构化 protobuf 事件流的形式对外发布供 IDE 插件、构建结果仪表盘、CI 系统等程序化消费。读完本文你将掌握 BEP 的三种事件要素标识符、子事件、负载、事件图的生命周期结构、二进制/文本/JSON 三种本地消费方式以及通过 Build Event ServiceBES将事件流发布到远程后端并配合远程缓存落地的完整实战方案。BEP 是什么程序化洞察 Bazel 调用的标准化协议BEP 允许第三方程序深入了解一次 Bazel 调用。例如你可以用 BEP 为 IDE 插件或展示构建结果的仪表盘收集信息。该协议是一组带语义的 protocol buffer 消息包含构建与测试结果、构建进度、构建配置等信息。BEP 面向程序化消费而设计它的出现让解析 Bazel 命令行输出成为历史——消费方不再需要从人类可读的终端文本里做脆弱的字符串匹配而是直接处理结构化的强类型消息。当前仓库中 BEP 的完整协议定义位于 build_event_stream.protosyntax proto3包名build_event_streamJava 类名为BuildEventStreamProtos。该文件不仅是协议的唯一事实来源其顶部注释也直接定义了事件链的完整语义Events are chained via the event id as follows: each event has an id and a set of ids of children events such that apart from the initial event each event has an id that is mentioned as child id in an earlier event and a build invocation is complete if and only if all direct and indirect children of the initial event have been posted.这段注释同时给出了 BEP 的两条核心不变量除首个事件外每个事件都必然被先前的事件声明为子事件当且仅当初始事件的所有直接与间接子事件都发布完毕一次构建调用才算完整。任何 BEP 消费者都可以据此判断自己收到的流是否残缺。BEP 事件模型标识符、子事件与负载BEP 用事件build event表示一次构建的信息。一个构建事件是包含三部分的 protobuf 消息一个构建事件标识符build event identifier、一组子事件标识符child event identifiers和一个负载payload。构建事件标识符Build Event Identifier根据事件类型的不同它可能是一个不透明字符串如NamedSetOfFilesId中仅对当前事件流实例有效的id也可能是结构化信息如TargetCompletedId包含label、configuration、aspect三个字段。构建事件标识符在一次构建内是唯一的。proto 中BuildEventId消息通过oneof id枚举了全部标识符类型从ProgressId、BuildStartedId、TargetConfiguredId、TestResultId到BuildFinishedId、BuildMetricsId等每个标识符本身就携带了它所指事件的结构化语义。子事件Children一个构建事件可以通过children字段声明其他构建事件。例如PatternExpanded事件会把它展开出的目标声明为子事件。协议保证除第一个事件外所有事件都由先前某个事件声明过。这意味着消费者总能沿着父子关系重建事件树而不必依赖事件的到达顺序。负载Payload负载包含该构建事件的结构化信息编码为该事件专属的 protobuf 消息BuildEvent消息中的oneof payload。需要注意的是负载未必是预期类型——如果构建提前中止负载可能是Aborted消息例如负载本应是TargetComplete时被替换为Aborted。事件图一次构建的生命周期骨架所有构建事件通过父子关系形成一个有向无环图DAG。除初始事件外每个构建事件都有一个或多个父事件。需要注意子事件的父事件不一定都先于子事件发布例如TargetConfigured声明的Configuration子事件可能已提前发布。当构建完成无论成功或失败时所有已声明的事件都会被发布而在 Bazel 崩溃或网络传输失败的情况下部分已声明的事件可能永远不会发布——消费者必须容忍这种不完整性。事件图的结构反映了命令的生命周期每个 BEP 图都有如下特征形状根事件永远是BuildStarted事件所有其他事件都是它的后代BuildStarted的直接子事件包含命令的元数据如OptionsParsed、CommandLine、UnstructuredCommandLine、WorkspaceStatus、BuildMetadata承载命令产出数据的事件出现在BuildFinished之前如构建出的文件NamedSetOfFiles、测试结果TestResult/TestSummary、目标完成TargetCompleteBuildFinished事件之后可能跟随后续事件其中包含构建的汇总信息如BuildMetrics度量数据、BuildToolLogs工具日志。上述各个事件类型的详细语义与 JSON 示例参见仓库中的 BEP 词汇表。下图展示了一个典型 Bazel 工作区执行bazel test ...时生成的事件图箭头表示父子关系部分事件与字段为简洁而省略从图中可以看到PatternExpanded将...模式展开为//foo:foo_lib与//foo:foo_test两个目标并声明了对应的TargetConfigured子事件TargetComplete则通过fileSets字段引用NamedSetOfFiles事件。关于这张图对应的完整示例工作区下文实战示例一节会展开叙述。消费 BEP三种本地文件格式二进制格式要以二进制格式消费 BEP通过--build_event_binary_file/path/to/file让 Bazel 把 protobuf 消息序列化到文件中。文件包含若干序列化的 protobuf 消息每条消息都是长度分隔length delimited的每条消息前有一个以变长整数varint编码的长度前缀。该格式可直接用 protobuf 库的parseDelimitedFrom(InputStream)方法读取。编写程序从序列化的 protobuf 消息中提取相关信息。文本与 JSON 格式以下 Bazel 命令行标志可以把 BEP 输出为人类可读的文本与 JSON 格式--build_event_text_file --build_event_json_fileJSON 格式尤其适合快速调试可以用jq之类的工具直接检查事件结构也可以把事件流喂给脚本做原型验证。从源码看输出选项的实现细节仓库中 BuildEventStreamOptions.java 是这三个输出标志及相关选项的权威实现其中不少细节在主文档之外值得注意--build_event_text_file、--build_event_binary_file、--build_event_json_file的默认值均为空字符串即不输出。其中二进制与 JSON 两个选项隐含--bes_upload_modewait_for_upload_complete即构建结束时会等待上传含生命周期事件完成后再退出。每个文件输出选项都配有对应的--build_event_format_file_upload_mode取值为wait_for_upload_complete默认、nowait_for_upload_complete、fully_async三者之一控制上传是阻塞构建结束、还是延迟到下次调用再阻塞。--build_event_format_file_path_conversion默认true控制事件中的路径是否尽量转换为全局有效的 URI若设为false则一律使用file://URI scheme。--build_event_publish_all_actions默认false决定是否发布所有ActionExecuted事件。默认情况下ActionExecuted只针对失败的动作发布以帮助定位构建失败根因开启后则发布所有动作的执行细节相应会增加 BEP 体积。--build_event_max_named_set_of_file_entries默认5000限制单个NamedSetOfFiles事件的最大条目数小于 2 的值被忽略且不进行事件拆分。它用于间接限制 BEP 中单个事件的最大体积事件总大小还取决于集合结构、文件与 URI 长度。BEP 核心事件类型速览BEP 事件类型众多下面按构建生命周期整理核心类型完整清单与 JSON 示例见 BEP 词汇表事件类型作用关键说明BuildStartedBEP 流中的第一个事件包含命令执行前的元数据是事件图的根OptionsParsed列出作用于命令的所有选项区分 startup 选项与命令选项含 InvocationPolicyCommandLine命令行参数的结构化表示分original、canonical、tool三种标签UnstructuredCommandLine展开.bazelrc与--config后的原始命令行可精确复现一次命令执行Configuration顶层目标使用的每个配置其id被TargetConfigured/TargetComplete复用WorkspaceStatus/WorkspaceConfig工作区状态与配置含执行根目录等信息BuildMetadata--build_metadata标志的解析结果用于向其他工具链传递外部数据如标识符PatternExpanded命令行模式展开出的目标集合目标作为children声明TargetConfigured目标完成分析阶段权威的rule kind来源TargetComplete目标完成执行阶段含成功/失败与输出组output groupsTargetSummary每个(target, configuration)的聚合结果汇总目标执行与所有应用 aspect 的结果ActionExecuted单个动作的执行细节默认仅发布失败动作TestResult每个测试 attempt/shard/run 的结果可精确定位失败测试动作及其日志TestSummary每个测试(target, configuration)的汇总用 attempts/runs 区分FLAKY与FAILEDProgress构建过程的 stdout/stderr也用于声明无逻辑父事件的事件NamedSetOfFiles文件集合对应depset一次报告、多处引用控制 BEP 体积BuildFinished命令结束携带权威退出码BuildMetrics构建度量计数器/仪表只统计实际完成的工作不含缓存复用BuildToolLogs工具生成的日志文件 URI如command.profile.gzAborted替换其他事件的提前终止标记无独立 ID 类型含终止原因枚举Fetch外部资源抓取缓存命中时不出现该事件ConvenienceSymlinksIdentified便捷符号链接管理实验性配合--experimental_convenience_symlinks_bep_event下面通过两个示例体会事件负载的形态。OptionsParsed事件列出全部生效选项区分 startup 选项与命令选项{ id: { optionsParsed: {} }, optionsParsed: { startupOptions: [ --max_idle_secs10800, --output_user_root/tmp/.cache/bazel/_bazel_foo ], cmdLine: [ --enable_platform_specific_config, --build_event_json_file/tmp/bep.json ], explicitCmdLine: [ --build_event_json_file/tmp/bep.json ], invocationPolicy: {} } }BuildMetrics事件在每次命令结束时发送一次量化构建工具的行为。注意若命令执行期间没有发生 Java 垃圾回收memory_metrics可能不会被填充设置--memory_profile/dev/null可强制命令结束时运行 GC 以填充该字段{ id: { buildMetrics: {} }, buildMetrics: { actionSummary: { actionsExecuted: 1 }, targetMetrics: { targetsLoaded: 9, targetsConfigured: 19 }, packageMetrics: { packagesLoaded: 5 }, timingMetrics: { cpuTimeInMs: 1590, wallTimeInMs: 359 } } }实战示例一次bazel test的事件流仓库中的 BEP 示例页 用一个最小工作区演示了完整事件流。假设工作区包含两个空 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 ...后对应前文 BEP 事件图首先发布BuildStarted事件告知构建通过bazel test命令发起并声明子事件OptionsParsed、WorkspaceStatus、CommandLine、UnstructuredCommandLine、BuildMetadata、BuildFinished、PatternExpanded、Progress。前三者提供 Bazel 如何被调用的信息。PatternExpanded揭示...模式展开为//foo:foo_lib和//foo:foo_test两个目标并通过声明两个TargetConfigured子事件实现。注意TargetConfigured声明Configuration为其子事件即便Configuration早已发布——这印证了前文父子关系不依赖发布顺序的语义。TargetComplete事件通过fileSets字段引用NamedSetOfFiles事件。下面是从上图摘出的//foo:foo_lib目标的TargetComplete事件protobuf JSON 表示。其标识符把目标作为结构化字段labelconfiguration.id负载包含构建是否成功、输出文件集合以及目标类型{ id: { targetCompleted: { label: //foo:foo_lib, configuration: { id: 544e39a7f0abdb3efdd29d675a48bc6a } } }, completed: { success: true, outputGroup: [{ name: default, fileSets: [{ id: 0 }] }], targetKind: sh_library rule } }Aspect 结果在 BEP 中的呈现普通构建只评估(target, configuration)对启用 aspects 后Bazel 还会为每个受影响的(target, configuration, aspect)三元组评估动作。BEP 中没有 aspect 专用的事件类型但 aspect 的评估结果同样会被发布对每个应用了 aspect 的(target, configuration)对Bazel 会额外发布携带该 aspect 结果的TargetConfigured与TargetComplete事件。例如用--aspectsaspects/myaspect.bzl%custom_aspect构建//:foo_lib时会出现标识符中带aspect字段的事件。唯一区别在于标识符中的aspect字段——如果消费工具不检查该字段就按目标累加输出文件可能把目标输出与 aspect 输出混为一谈。高效消费NamedSetOfFiles确定某个目标或 aspect产出的工件是 BEP 最常见的用例。NamedSetOfFiles事件总是先于引用它的TargetComplete或NamedSetOfFiles事件出现在流中这与父先于子的父子关系恰好相反并由无语义的Progress事件声明。它对应 Starlark 的 Depset 结构同一文件集合可被多个目标引用从而避免 BEP 体积随文件数量呈二次方增长。消费方必须避免二次复杂度算法——大型构建中NamedSetOfFiles事件可达数万个二次遍历意味着数亿次操作。由于存在上述排序与共享约束典型消费者需要先缓冲全部NamedSetOfFiles事件直到 BEP 流耗尽。以下 Python 代码演示了如何建立目标/aspect → 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)Build Event Service把事件流发布到远程后端协议与端点配置Build Event ServiceBES是一个通用的 gRPC 服务协议用于发布构建事件。BES 协议独立于 BEP它把 BEP 事件当作不透明字节处理。Bazel 自带该协议的 gRPC 客户端实现负责发布 BEP 事件。通过--bes_backendHOST:PORT指定事件发送到的端点。若后端使用 gRPC必须以合适的 scheme 作为地址前缀grpc://—— 明文 gRPCgrpcs://—— 启用 TLS 的 gRPC。从源码 BuildEventServiceOptions.java 可以看到更精确的语义--bes_backend的格式为[SCHEME://]HOST[:PORT]默认值为空即默认禁用 BES 上传支持grpc与grpcs两种 scheme若未提供 schemeBazel 默认假定为grpcs。BES 相关标志详解Bazel 提供若干与 BES 协议相关的标志除主文档列出的五项外仓库源码还揭示了更多实用选项--bes_backendBES 后端端点格式[SCHEME://]HOST[:PORT]空值禁用上传。--[no]bes_lifecycle_events是否发布 BES 生命周期事件如InvocationAttemptStarted、BuildEnqueued、FinishInvocationAttempt、FinishBuild默认true。--bes_results_url用户在何处查看流式上传到 BES 的信息的基础 URL。Bazel 会把该 URL 拼接上 invocation id 输出到终端。--bes_timeout构建与测试结束后 Bazel 等待 BES/BEP 上传完成的时间。合法值是自然数加单位天d、小时h、分钟m、秒s、毫秒ms默认0s表示不设超时。--bes_instance_nameBES 持久化上传 BEP 所用的实例名默认null旧名为--project_id。--bes_upload_modeBES 上传的阻塞模式取值为wait_for_upload_complete默认在本次调用结束时阻塞直到所有事件含生命周期事件上传并被后端确认nowait_for_upload_complete在下一次调用开始时阻塞直到所有事件上传并被确认fully_async在下次调用开始时阻塞到上传完成但不等待确认出现瞬时故障时可能丢事件后端可能把流报告为不完整且不保证发送FinishInvocationAttempt/FinishBuild生命周期事件。其他可选标志源码可见--bes_header以NAMEVALUE形式附加到 BES 请求头可多次指定、--bes_keywords/--bes_system_keywords通知关键词默认关键词集为command_name{command_name}与protocol_nameBEP、--bes_outerr_buffer_size默认 10240stdout/stderr 在聚合为 Progress 事件前的缓冲上限、--bes_outerr_chunk_size默认 1048576单条消息中 stdout/stderr 的大小上限、--bes_proxy经代理连接 BES目前仅支持 Unix 域套接字unix:/path/to/socket、--bes_check_preceding_lifecycle_events让 BES 校验先前是否收到匹配的InvocationAttemptStarted与BuildEnqueued、--bes_oom_finish_upload_timeout默认10mJVM 发生 GC thrashing 时的上传兜底超时。以上各标志的完整说明也可在仓库的 命令行参考 中查阅。认证与安全Bazel 的 BES 实现支持认证与 TLS通过以下标志控制。注意这些标志同时用于 Bazel 的 Remote Execution这意味着 BES 与远程执行端点必须共享同一套认证与 TLS 基础设施详见源码 AuthAndTLSOptions.java--[no]google_default_credentials是否使用 Google Application Default Credentials 认证默认关闭。--google_credentials认证凭据文件路径空值重置为默认。--google_auth_scopesGoogle Cloud 认证作用域的逗号分隔列表默认https://www.googleapis.com/auth/cloud-platform。--tls_certificate受信任的服务器证书签名证书路径。--tls_client_certificate/--tls_client_key启用 mTLS 客户端认证时使用的客户端证书与密钥需成对提供。需要说明的是主文档中列出的--[no]tls_enabled在当前仓库版本的AuthAndTLSOptions源码中已不存在——当前版本改用--bes_backend的 scheme 直接控制 TLS 开关grpc://明文、grpcs://启用 TLS缺省 scheme 视为grpcs。写作时请以当前仓库源码为准。BES 与远程缓存配合BEP 通常包含大量对日志文件的引用如test.log、test.xml这些文件存放在 Bazel 运行的机器上。远程 BES 服务器通常无法访问这些文件因为它们位于不同机器。解决这一问题的常规做法是让 Bazel 配合远程缓存使用Bazel 会把所有输出文件上传到远程缓存包括 BEP 引用的文件BES 服务器随后即可从缓存中拉取被引用的文件。这样CI 上的构建结果页就能直接展示日志与测试 XML而不必让 BES 直接访问构建机文件系统。小结BEP 把一次 Bazel 调用结构化地暴露给任何程序通过BuildEventId、children与payload三要素构成的事件 DAG消费方可以从BuildStarted出发追踪到每一次目标配置、执行、测试与度量。本地可用--build_event_binary_file/--build_event_text_file/--build_event_json_file三种格式落盘规模化场景下则通过--bes_backend把事件流发布到 gRPC 后端并配合--bes_upload_mode、认证/TLS 标志与远程缓存构建完整的可观测体系。无论是为 IDE 插件补充构建信息、搭建构建仪表盘还是实现基于 CI 的构建结果归档BEP 都是值得优先采用的标准化数据源。【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考