
gRPC 多构建系统自动生成机制解析build.yaml 数据源与 Mako 模板渲染体系【免费下载链接】grpcC based gRPC (C, Python, Ruby, Objective-C, PHP, C#)项目地址: https://gitcode.com/GitHub_Trending/gr/grpc导读gRPC 需要同时维护 Makefile、CMake、Xcode、Bazel、PHP autoconf 等多种构建系统的工程文件若全部手写将带来巨大的维护成本。本篇文章基于仓库内 templates/README.md 文档深入讲解 gRPC 的单一事实来源 模板自动生成方案如何通过根目录的build.yaml元数据当前已拆分为手写与自动生成两份描述全部库、目标与依赖再由 Mako 模板引擎批量渲染出各构建系统所需文件。读完本文你将掌握tools/buildgen/generate_projects.sh的完整运行管线、build.yaml各字段语义、构建标签build tag与插件系统的实现原理并能据此理解或自定义类似的多构建系统生成流程。为什么 gRPC 不采用单一构建系统原文给出了这一设计的最初动机gRPC 团队从大量角度尝试过构建系统方案核心难点在于——不存在一个能同时覆盖 gRPC 全部使用场景的构建系统。不同平台、不同语言的用户C、Python、Ruby、Objective-C、PHP、C#依赖不同的工程组织方式单一构建系统无法面面俱到。因此 gRPC 采用了如下工作方式原文要点根目录的build.yaml文件是事实来源source of truth用于列出构建 gRPC 及其测试所需的全部目标targets和文件并附带一套基础的依赖描述机制gRPC 支持的大多数构建系统如 Makefile、CMake、Xcode 等都在templates目录中定义了对应模板模板消费build.yaml中的信息生成特定构建系统所需的工程文件。这样做的收益非常直接新增或删除源码时只需要维护build.yaml而无需手动同步多个构建系统的工程文件。模板只关心工程文件的结构不关心源码与目标的实际清单——清单完全由数据驱动。需要说明的是当前仓库中原始的单一build.yaml已经演化为两个文件见下文数据源章节build_handwritten.yaml手工维护的元数据与 build_autogenerated.yaml由 Bazel BUILD 文件自动提取生成两者在渲染时被合并使用。快速开始重新生成工程文件原文给出了生成流程的前置条件与入口命令前置条件依赖用途python运行生成脚本pip install mako模板渲染引擎处理.template文件pip install pyyaml读取 YAML 数据文件goBoringSSL 依赖构建所需执行生成# 使用模板重新生成工程文件以及其他生成文件 tools/buildgen/generate_projects.sh从源码看tools/buildgen/generate_projects.sh 的实际执行流程远比一条命令丰富它是一套完整的流水线检查 PyYAML 版本脚本要求 PyYAML 5.4.1否则自动安装PyYAML5.3.1PyYAML 在 5.4.1 起放弃了 Python 3.5 支持5.3.1 是可用旧版本生成build_autogenerated.yaml调用python3 tools/buildgen/extract_metadata_from_bazel_xml.py从 Bazel BUILD 文件提取库与目标元数据见 extract_metadata_from_bazel_xml.py整理手写元数据运行 build_cleaner.py 处理build_handwritten.yaml准备 Python 虚拟环境在.venv-generate-projects目录创建 venv版本不匹配时自动重建并安装grpcio-tools1.74.0用于生成 xds-protos 等辅助包合并渲染最终调用 generate_projects.py以build_handwritten.yaml、build_autogenerated.yaml及脚本中汇总的$gen_build_files为输入渲染全部模板收尾清理临时生成文件并调用tools/artifact_gen/artifact_gen.sh生成分发相关工件。数据源build.yaml的结构原文给出了build.yaml的顶层结构settings: # 全局设置如版本号 ... filegroups: # 可被自动展开的文件组 ... libs: # 需要构建的库列表 ... targets: # 需要构建的目标列表 ...当前仓库中的实际对应关系build_handwritten.yaml 承载settings、configs构建配置如 asan/dbg/gcov、defaults各第三方库的编译选项默认值、php_config_m4等手工元数据。例如其settings中记录了core_version: 56.0.0、version: 1.84.0-dev、protobuf_version: 5.35.1以及受支持的 Python 版本列表3.103.14build_autogenerated.yaml 承载filegroups、libs、targets等由 Bazel 元数据自动生成的部分例如address_sorting、gpr等底层库的public_headers、headers、src清单。两文件在generate_projects.py中通过_utils.merge_json递归合并为一份字典再交给模板渲染详见下文渲染管线。filegroups可复用的文件子集原文指出filegroups用于在多个目标中复用一组文件。单个条目的结构如下- name: arbitrary string, # 该文件组的名称 public_headers: # 该文件组定义的公开头文件列表 - ... headers: # 该文件组定义的头文件列表 - ... src: # 该文件组定义的源文件列表 - ...在libs/targets中通过filegroups字段引用文件组expand_bin_attrs.py插件负责将其在src、headers、public_headers属性上自动展开合并。libs 与 targets 条目的通用结构libs集合描述所有库有些是测试辅助库有些是可安装库有些是可安装二进制的辅助库targets数组描述所有二进制目标其中部分是可安装二进制。原文给出两者的统一结构name: arbitrary string, # 库或目标的名称 build: build type, # 在何种场景下构建/安装该库见下方 build 标签 language: ..., # 语言标签c 或 c public_headers: # 需要安装的公开头文件列表 headers: # 该目标使用的头文件列表 src: # 需要编译的文件列表 baselib: boolean, # 是否为拥有系统依赖的底层库 filegroups: # 需要合并到该工程的 filegroups 列表自动展开 deps: # 该目标依赖的库列表 dll: ... # 见下文说明从 build_autogenerated.yaml 的实际条目可以看到这些字段的真实形态例如gpr库带有build: all、language: c、完整的头文件/源文件清单与空deps。此外check_attrs.py 中定义了lib与target允许出现的完整属性白名单如asm_src、cmake_target、deps_linkage、dll、generate_plugin_registry、LDFLAGS、platforms、secure、vs_proj_dir等任何白名单之外的属性都会导致生成失败防止元数据中出现误导性字段。build标签的语义原文对build标签给出了明确的取值与含义这是理解 gRPC 构建分组的关键取值含义all在make all时构建并安装到系统plugin在make all时构建并安装但对应的 CMake 选项默认关闭目前需要手动声明该选项以支持依赖第三方库protoc一个 protoc 插件在make all时构建并安装到系统plugin_test仅在关联插件启用时才构建的测试关联插件通过plugin_option标签指定private仅用于测试的库test在make test时运行的测试二进制tool在make tools时构建的二进制原文同时强调所有目标在可行且适用的前提下都应始终存在于生成的工程文件中build标签的作用是把目标归组到同一条构建命令中。baselib布尔语义baselib为true表示该库提供 gRPC 的大部分核心特性。特别地当本地构建 OpenSSL、protobuf 或 zlib 时需要把 OpenSSL/protobuf/zlib 合并进该库内部。这种合并行为受language标签影响OpenSSL 和 zlib 面向c库合并protobuf 面向c库合并。这一机制在 Makefile.template 中有直接的体现模板会构造一个内嵌所有非系统依赖源码的grpc库sys_libs [boringssl, cares, libssl, z]之外的依赖源码全部并入而EMBED_ZLIB/EMBED_OPENSSL开关则控制 zlib 与 BoringSSL 是内嵌构建还是使用系统库。模板系统基于 Mako 的渲染gRPC 当前使用 Mako 模板 渲染器。原文指出选择 Mako 的动机是能够直接渲染纯文本文件无需拖入大量额外特性。渲染引擎与 glue 代码仅靠 Mako 本身还不够需要一些胶水代码来处理整个流程这部分位于 tools/buildgen 目录。核心思路是加载build.yaml实际为合并后的build_handwritten.yaml与build_autogenerated.yaml对数据进行加工massaging提取所需属性整理成一个 Python 字典将该字典作为渲染上下文传给模板。从源码看generate_projects.py 的preprocess_build_files()完成了这一过程它逐个读取 build 文件并用_utils.merge_json递归合并然后按文件名排序依次执行tools/buildgen/plugins/*.py中的所有插件每个插件原地修改这份字典最后通过_utils.to_bunch()将字典转换成支持点号访问dot-accessible的Bunch对象并pickle序列化到.preprocessed_build供渲染进程读取。渲染任务由 _mako_renderer.py 负责它为每个模板文件创建 MakoTemplate使用TemplateLookup以仓库根目录为查找路径并通过Context(output_file, **dictionary)注入字典渲染到输出文件。generate_projects.py会用jobset以多进程并行方式执行这些渲染任务--jobs默认取 CPU 核数全部成功后才返回出错时打印错误统计并退出非零状态。插件Plugins原文指出build.yaml的内容不会直接传给模板而是先被若干插件处理与修改。例如版本展开器就是一个插件expand_version.py。插件的结构非常简单插件必须定义mako_plugin函数接收一个 Python 字典。该字典代表build.yaml内容的当前状态插件可以按需修改它以实现所需特性。仓库中实际存在的插件及其职责均位于 tools/buildgen/plugins插件职责expand_version.py解析settings.version为每种语言core/cpp/csharp/node/objc/php/python/ruby生成xxx_version标签默认继承主版本并提供 PEP440、Ruby、PHP PECL/Composer 等不同风格的版本字符串格式化transitive_dependencies.py对每个 lib/target 计算deps的传递闭包写入transitive_deps属性结果按拓扑序排列对不存在对应库条目的依赖仍保留交由构建系统自定义处理expand_bin_attrs.py为目标/库填充可选属性默认值如flaky、platforms、ci_platforms、boringssl、zlib、ares、gtest并展开filegroups引用check_attrs.py校验 filegroup/lib/target/external_proto_library 只能出现白名单内属性非法属性或非法取值会直接抛出异常终止生成list_api.py、list_protos.py生成 API 与 proto 列表supported_bazel_versions.py、expand_supported_python_versions.py维护支持的 Bazel / Python 版本元数据verify_duplicate_sources.py校验源文件是否重复目录型模板与 foreach/cond 控制数据_mako_renderer.py还支持一种进阶用法模板文件本身可携带 YAML 控制数据control data用于按目录批量生成文件。控制数据支持foreach遍历字典中的某个列表如语言列表为每个元素渲染一份输出cond对foreach元素执行条件过滤eval求值output_name基于当前元素动态生成输出文件名模板。这让同一份模板可以为一组相似实体例如不同语言的包描述文件分别生成对应产物。模板实例剖析Makefile 模板Makefile.template 是最能体现模板只关心结构的示例。其头部注明该文件由模板自动生成请查看 templates 目录可通过tools/buildgen/generate_projects.sh重新生成。 该模板以 Mako 宏makelib(lib)为核心为每个库生成源文件列表变量LIBNAME_SRC、对象文件变量LIBNAME_OBJS静态库与共享库的构建规则含transitive_deps展开的依赖关系、Linux 下-soname与符号链接、macOS 下-install_name、Windows/MINGW 下.def导出安全相关条件当库的传递依赖含libssl时会插入ifeq ($(NO_SECURE),true)检查缺少 OpenSSL 时给出openssl_dep_error。模板还内嵌了平台检测、配置configs来自build_handwritten.yaml、zlib/BoringSSL 内嵌开关、交叉编译支持GRPC_CROSS_COMPILE与GRPC_CROSS_LDOPTS/GRPC_CROSS_AROPTS等大量 Makefile 基础设施。值得注意的是模板注释明确说明 Makefile 仅用于内部需求构建分发产物其他目标建议使用 CMake 或 Bazel。CMake 模板CMakeLists.txt.template 展示了如何将 YAML 数据映射为 CMake 目标它构建lib_map按名称索引库将库归类为gpr_libs、grpc_libs、grpcxx_libs、protoc_libs并通过cmake_target字段把 abseil 库映射到对应 CMake targetproto_replace_ext则根据external_proto_libraries的proto_prefix/strip_path_prefix把.proto路径转换为生成的.pb.cc路径。PHP autoconf 模板新一代 .inja除 Mako 的.template后缀外仓库中还存在大量.inja后缀模板如 templates/config.m4.inja、templates/grpc.gemspec.template 旁边的build_config.rb.inja、gRPC.podspec.inja等。.inja使用 inja 驱动Phase 0通过 Bazel query 导出各目标的依赖 XMLdeps(//test/...)、deps(//:all)、deps(//src/compiler/...)等并查询 cel-spec、googleapis、xds、protoc-gen-validate、opencensus-proto、envoy_api、grpc-proto 等外部仓库的 http_archive 信息Phase 1编译并运行tools/artifact_gen下的 artifact_gen 二进制入口逻辑见 render.cc它对templates目录中所有.inja文件调用inja::Environment::render将合并后的 build YAMLJSON 化作为渲染数据输出文件去掉.inja后缀写入仓库根目录。例如config.m4.inja会生成 PHP 扩展的 autoconf 配置脚本其中{{src}}由php_config_m4.srcs数据驱动{{settings.version.php}}注入 PHP 版本号——这些数据均来自build_handwritten.yaml的php_config_m4段与settings。验证与质量保障生成流程内置了两层保障属性白名单校验check_attrs.py 在渲染前强制校验所有实体属性杜绝拼写错误与误导性字段进入元数据TEST 模式回归generate_projects.sh通过export TEST${TEST:-false}支持测试模式generate_projects.py在TESTtrue时会把渲染结果写入临时文件并与现有生成产物执行diff比对确保重新生成不改动已有文件即当前工程文件与元数据保持一致。结语gRPC 的构建系统生成机制可以概括为一条清晰的数据流元数据build.yaml→ 插件预处理版本展开、传递依赖、属性校验→ Mako/inja 模板渲染 → 各构建系统工程文件。它把工程文件的结构与源码/目标的清单彻底解耦使 gRPC 能在维护近千个源文件的情况下同步支撑 Makefile、CMake、Bazel、Xcode、PHP autoconf、Ruby gemspec、Objective-C podspec 等多套构建与打包体系。对于想深入定制或借鉴该方案的开发者建议按以下路径阅读仓库源码templates/README.md设计文档→ build_handwritten.yaml 与 build_autogenerated.yaml数据源→ tools/buildgen渲染管线与插件→ templates各构建系统的模板实现。【免费下载链接】grpcC based gRPC (C, Python, Ruby, Objective-C, PHP, C#)项目地址: https://gitcode.com/GitHub_Trending/gr/grpc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考