ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Protobuf 打包子系统详解:基于 Bazel 的 protoc 二进制打包、源码分发与 cc_dist_library 分发库规则

Protobuf 打包子系统详解:基于 Bazel 的 protoc 二进制打包、源码分发与 cc_dist_library 分发库规则 Protobuf 打包子系统详解基于 Bazel 的 protoc 二进制打包、源码分发与 cc_dist_library 分发库规则【免费下载链接】protobufProtocol Buffers - Googles data interchange format项目地址: https://gitcode.com/GitHub_Trending/pr/protobuf本文以 pkg/README.md 为主线解析 Protobuf 仓库pkg/目录下的三类 Bazel 打包机制protoc 编译器二进制分发包、按语言切分的源码分发流程含 Bazel 与 CMake 之间的文件清单桥接以及将 Bazel 细粒度cc_library合并为可分发单体库的cc_dist_library规则。读完后你将理解 Protobuf 发布产物如protoc-37.0-linux-x86_64.zip、源码 tarball、libprotobuf.a等背后的构建链路并能基于 pkg/BUILD.bazel 和 pkg/cc_dist_library.bzl 验证这些产物是如何一步步组装出来的。一、pkg 目录定位发布产物的构建层pkg/README.md 开宗明义pkg/目录包含用于构建打包与分发产物的 Bazel 规则且目录内所有内容都应视为内部实现、可能随时变更Everything in this directory should be considered internal and subject to change。换言之这里不是对外 API而是 Protobuf 发布流水线release pipeline的工程实现。当前仓库中pkg/的结构非常精炼文件/目录职责pkg/BUILD.bazel打包目标的具体定义protoc 发布 zip、源码文件清单、各 C 分发库pkg/build_systems.bzlgen_file_lists宏与 CMake 文件清单生成规则Bazel → CMake 桥接pkg/cc_dist_library.bzlcc_dist_library自定义规则的实现pkg/test/针对文件清单生成的 golden 文件测试README 将其内容归纳为三大块Protocol compiler binary packagingprotoc 二进制打包、Source distribution packaging源码分发打包、C runtime binary distributionC 运行时二进制分发。下面逐一展开并结合源码把 README 中的每句话落到实处。二、protoc 二进制打包protoc_release 的组装链路README 第一节的原意是protoc 在多处需要以二进制形式使用仓库中有一组规则把它连同常用的.proto文件一起打包以便分发。对应实现集中在 pkg/BUILD.bazel 的第 84 行附近pkg_zip目标protoc_release。2.1 打包产物与四个组成部分最终的 zip 包由四个输入组成pkg_zip( name protoc_release, srcs [ :compiler_plugin_protos_files, :protoc_files, :protoc_readme, :release_all_options_protos_files, ], package_file_name protoc-{version}-{platform}.zip, package_variables :protobuf_pkg_naming, )各部分的来源与结构:protoc_files— 编译器本体。它通过select按平台选择可执行文件名Windows 下为bin/protoc.exe其余为bin/protoc并用pkg_attributes(mode 0555)保证可执行权限、prefix bin/保证落在包内bin/目录下。可执行文件本身由两个genrulerename_protoc/rename_protoc_exe从根目录目标//:protoc_static复制重命名而来即分发包里的 protoc 是静态链接版本静态链接可避免用户对第三方运行时的依赖。:release_all_options_protos_files— 将//src/google/protobuf:release_all_options_proto_srcs下的.proto文件以include/google/protobuf为前缀放入包中供用户编译时引用 Well-known Types 等公共 proto。:compiler_plugin_protos_files— 将//src/google/protobuf/compiler:compiler_plugin_protos_files如compiler_plugin.proto以include/google/protobuf/compiler为前缀放入包中供第三方编写 protoc 插件时使用。:protoc_readme— 一个genrule生成的readme.txt内容说明了该包是预编译的 protoc 二进制面向不想自行编译 protoc 的用户请将二进制放入 PATH若要用到包内 Well-known Types还需把include目录内容复制到如/usr/local/include/。2.2 包名中的 version 与 platform 从何而来package_file_name protoc-{version}-{platform}.zip里的两个占位符由:protobuf_pkg_naming提供其实现是根目录的 protobuf_release.bzlversion取自 protobuf_version.bzl 中的PROTOC_VERSION当前仓库为37.0同文件还定义了 Java/Python/PHP/Ruby/Rust 各语言包的版本如PROTOBUF_PYTHON_VERSION 7.37.0。platform规则通过find_cpp_toolchain推断当前 C 工具链的cpu与target_gnu_system_name再做如下映射见 protobuf_release.bzlCPU 改名systemz→s390_64、aarch64→aarch_64、ppc64→ppcle_64系统判定含apple→osx-{cpu}含linux→linux-{cpu}含mingw→win64x86_64或win32其余为unknown。因此一次典型构建会得到形如protoc-37.0-linux-x86_64.zip的产物。注意这条链路的前提是包名平台标识反映的是构建该包所用 C 工具链的目标平台跨平台分发需要在对应平台或对应工具链下分别构建。三、源码分发打包Bazel 无法闭环处与 CMake 文件清单桥接3.1 README 给出的关键约束README 第二节的要点有三Protobuf 的发布包含源码分发source distribution并按目标语言C、Java 等切片本包的规则用于定义这些源码归档。归档内容的获取依赖仓库其他位置定义的pkg_files规则。源码分发应当包含autogen.sh的输出但Bazel 无法可靠地做到这一点因此要产出功能完整的源码分发必须在构建归档前手动运行autogen.sh它会直接把所需文件填充进源码树。第 3 点揭示了这套打包体系的一个重要边界Bazel 负责能确定性构建的部分而 autotools 式生成configure、Makefile.in 等需要在源码树内预先执行。阅读 pkg/BUILD.bazel 可以看到pkg_files规则的实际用法例如release_all_options_protos_files与compiler_plugin_protos_files均使用prefix将文件重映射到归档内的include/google/protobuf/...路径且可见性为//visibility:private——它们只服务于打包链路不对外暴露。3.2 gen_file_lists从 Bazel 规则生成 CMake 文件清单源码分发之所以能被 CMake/Make 等非 Bazel 构建系统消费靠的是 pkg/build_systems.bzl 中的gen_file_lists宏它在 Bazel 侧遍历库目标、抽取源文件列表生成一份 CMake 语法的清单文件再被手写的 CMake 文件include使用。实现原理aspect 抽取文件清单核心是两条路径的文件抽取cc_file_list_aspect定义在 pkg/cc_dist_library.bzl对任意带CcInfo的 C 目标cc_library等遍历其srcs/hdrs/textual_hdrs属性按扩展名把c/cc/cpp/cxx文件归入srcs、其余归入internal_hdrs并沿deps传递汇总产出CcFileListprovider含srcs、hdrs、internal_hdrs、textual_hdrs四个 depset 字段。file_list_aspect定义在 pkg/build_systems.bzl对proto_library目标识别ProtoInfo抽取.proto源文件并按命名约定合成生成物路径——每个foo.proto推断出同目录的foo.pb.cc与foo.pb.h产出ProtoFileListprovider。生成过程_create_file_list_implgen_cmake_file_lists规则pkg/build_systems.bzl的实现按src_libs字典逐项处理key 是 Bazel 目标value 形如libname[,gencode_dir]——libname用于生成变量名可选的第二段gencode_dir用于替换生成文件所在的目录README 与源码注释均强调生成文件路径不含bazel-bin/这类输出根前缀若目标携带CcFileList输出{libname}_srcs与{libname}_hdrshdrs 含 textual_hdrs若携带ProtoFileList输出{libname}_proto_srcs、{libname}_srcs合成的.pb.cc、{libname}_hdrs合成的.pb.h若目标是pkg_files/pkg_filegroup或普通文件/filegroup则输出{libname}_files对pkg_files使用目标内的 destination 路径所有路径拼上source_prefixCMake 侧为${protobuf_SOURCE_DIR}/并用short_path剥离生成文件的输出前缀。生成的文件头部_header属性默认值写明了契约This file contains lists of sources based on Bazel rules. It should be included from a hand-written CMake file that defines targets. Changes to this file will be overwritten based on Bazel definitions.并在 CMake ≥ 3.10 时加include_guard()防止重复包含。一个实现细节值得注意_cmake_var_fragment会把路径中的wkt/google/protobuf/前缀剥掉——源码注释解释这是为了让checked-in 与生成的 Well-known Types 代码可以共存见 pkg/build_systems.bzl。在仓库中的实际接线pkg/BUILD.bazel 中定义了gen_src_file_listsout_stem src_file_liststestonly 1其src_libs字典把 Bazel 目标映射为 CMake 侧的库名覆盖了运行时与测试的全部所需gen_file_lists( name gen_src_file_lists, testonly 1, out_stem src_file_lists, src_libs { :protobuf: libprotobuf, :protobuf_lite: libprotobuf_lite, :protoc_public: libprotoc_public, :protoc: libprotoc, :upb: libupb, :protoc-gen-upb: protoc-gen-upb, # ... 以及 conformance、各测试库与测试 proto 清单 }, )其产出物被提交在 src/file_lists.cmake首行即# Auto-generated by //pkg:gen_src_file_lists_cmake全文约 1500 行逐行列出libprotobuf_srcs、libprotobuf_hdrs等变量的文件列表。CMake 侧的每个库定义文件都以include(${protobuf_SOURCE_DIR}/src/file_lists.cmake)开头消费它例如 cmake/libprotobuf.cmakeinclude(${protobuf_SOURCE_DIR}/src/file_lists.cmake) add_library(libprotobuf ${protobuf_SHARED_OR_STATIC} ${libprotobuf_srcs} ${libprotobuf_hdrs} ${protobuf_version_rc_file})同样的 include 模式也出现在 cmake/libprotobuf-lite.cmake、cmake/libprotoc.cmake、cmake/libupb.cmake、cmake/install.cmake、cmake/conformance.cmake 与 cmake/tests.cmake 中。这套机制保证了 Bazel 依赖图与 CMake 源码构建共用同一份权威文件清单避免两处维护漂移。四、C 运行时分发cc_dist_library 规则4.1 问题背景Bazel 的细粒度库模型README 第三节的解释是Bazel 采用细粒度fine-grained库模型——每个cc_library只产出自己的库产物、不含传递依赖因此直接把某个cc_library的.a发出去并不能完整分发一个库。cc_dist_library规则的作用就是把若干cc_library目标合并成一个适合分发的单体库。4.2 规则参数与输出规则定义与文档字符串在 pkg/cc_dist_library.bzl三个用户可见属性属性类型说明depslabel_list要包含的库及其传递依赖输出即由这些目标的对象文件合并而成dist_depslabel_list需要从输出中排除的其他cc_dist_library依赖避免重复包含已单独分发的库linkoptsstring_list仅附加到动态库链接命令上的额外链接器标志输出物见 pkg/cc_dist_library.bzllibname.a— 由非 PIC 对象文件归档而成若存在非 PIC 对象libname.pic.a— 由 PIC 对象文件归档而成若存在 PIC 对象绝大多数平台都有动态库 — 通过cc_common.link(output_type dynamic_library)按工具链定义链接linkopts在此生效。README 中创建复合库composite libraries的表述对应的正是多个库的对象文件合并进同一归档/DSO这一行为。规则文档还特别指出它与 Bazel 实验性shared_cc_library的两点区别一是同时产出静态归档二是本身不直接作为 C 依赖使用如需强制下游使用 DSO推荐cc_import方式见 pkg/cc_dist_library.bzl 注释。4.3 关键实现链路对象文件收集_collect_linker_input_objectspkg/cc_dist_library.bzl遍历每个直接依赖的linking_context只保留owner dep_label的 linker input即跳过传递依赖把其中的objects/pic_objects分别累积到两个列表——这保证了输出的库恰好覆盖deps的完整传递闭包且无重复。排除 dist_deps_subtract_filespkg/cc_dist_library.bzl实现inputs collect(deps) - collect(dist_deps)。其中有一处特殊处理src/google/protobuf/testing/file.cc在减法时被刻意保留源码注释解释protoc 只用了 file.cc 的一部分文件为了拿到全部符号用于测试仍须编译链接进测试。静态归档动作_create_archive_actionpkg/cc_dist_library.bzl仿照 Bazel 自带cc_import的做法用cc_common.create_link_variablescpp_link_static_library动作构造归档命令mnemonic CppArchiveDist参数经 param file 传递。动态库链接_create_dso_link_actionpkg/cc_dist_library.bzl用cc_common.link完成并对 solib 符号链接做了resolved_symlink_*优先处理保证DefaultInfo报告解析后的真实路径。provider 输出规则同时返回合并后的CcFileList这使得上层gen_file_lists能把cc_dist_library目标直接当作源文件清单的来源src_libs的合法 provider 之一见 pkg/build_systems.bzl 的providers列表。4.4 仓库中的分发库定义pkg/BUILD.bazel 定义了全部 C 分发库均带tags [manual]表示不参与常规build //...仅发布流程显式构建:protobuf完整版 C 运行时deps除//src/google/protobuf外还合并了util下的json_util、time_util、type_resolver、differencer等与//src/google/protobuf/compiler:importerlinkopts按平台区分——MSVC 下为空其他平台为[-lz, -lpthread]zlib 与线程库在 CMake 侧是显式链接的依赖。:protobuf_litedeps为arena_alignprotobuf_lite非 MSVC 平台linkopts [-lpthread]。:protocdeps为command_line_interface加上全部代码生成器cpp、csharp、java、kotlin、objectivec、php、python、ruby、rustdist_deps [:protobuf, :protobuf_lite, :upb]—— 即 protoc 的 DSO 动态链接这三个已单独分发的库其对象文件不重复编入libprotoc。:protoc_public仅含command_line_interface与各生成器的names目标dist_deps排除:protobuf/:protobuf_lite。:upb合并 upb/ 下 message/wire/text/json/mini_table/util 等子库:protoc-gen-upb、:protoc-gen-upbdefs、:protoc-gen-upb_minitable三个 upb 系生成器也各自以cc_dist_library定义dist_deps排除:upb部分还排除:protobuf。测试用库testonly 1:conformance_cpp、:conformance_runner、:upb_test_util、:lite_test_util、:test_util、:common_test它们通过dist_deps排除运行时库避免归档重复。从源码结构看dist_deps的使用模式非常一致上层库排除已单独分发的底层库与规则文档中结果包含全部传递依赖但排除从dist_deps可达的部分或定义在独立仓库如 Abseil中的部分的描述相符——注意 Abseil 等外部仓库的依赖天然不会出现在归档中_flatten_target_files过滤了外部 workspace 的目标见 pkg/cc_dist_library.bzl这类依赖通过动态库链接解决。五、测试与验证golden 文件保障清单正确性README 未展开但仓库自带了对打包桥接层的验证pkg/test/BUILD.bazel 用一个最小cc_librarytest_lib套上cc_dist_library再经gen_file_lists生成file_lists.cmake由sh_testpkg/test/gen_file_lists_golden_test.sh脚本用cmp对比并以diff -u输出差异与 golden 文件 pkg/test/file_lists.cmake.golden 比对。golden 文件展示了生成物的标准形态# Auto-generated by //pkg/test:gen_file_lists_cmake ... # //pkg/test:test_lib_dist set(libtest_srcs ${protobuf_SOURCE_DIR}/pkg/test/test_lib.cc ) # //pkg/test:test_lib_dist set(libtest_hdrs ${protobuf_SOURCE_DIR}/pkg/test/test_lib.h )这组测试保证了aspect 抽取 → fragment 拼接 → 路径前缀整条链路的行为稳定任何修改build_systems.bzl的人都可以通过该测试快速发现回归。六、小结回到 pkg/README.md 的三节内容可以把它与仓库证据一一对应protoc 二进制打包pkg_zip 平台select 静态链接的//:protoc_static包名由package_naming规则版本取自 protobuf_version.bzl平台取自 C 工具链推断拼出实现见 pkg/BUILD.bazel 与 protobuf_release.bzl。源码分发打包内容依赖全仓库的pkg_files规则Bazel 负责生成 CMake 文件清单pkg/build_systems.bzl → src/file_lists.cmake而autogen.sh的产出必须手动预生成——这是文档明确声明、且源码中无法从 Bazel 侧闭环的部分。cc_dist_library以 aspect 抽取文件、按 owner 收集对象文件、用dist_deps做集合减法产出.a/.pic.a/DSO 三类分发产物服务于 cmake/ 与安装流程。需要再次强调 README 的告诫pkg/内所有目标与规则都应视为内部实现、随时可能变动。若你的项目需要消费 Protobuf 的分发产物请以发布物zip/tarball/CMake 安装为准而非依赖本目录的目标名。【免费下载链接】protobufProtocol Buffers - Googles data interchange format项目地址: https://gitcode.com/GitHub_Trending/pr/protobuf创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表