MongoDB 内嵌 protobuf 的 upb 代码生成器共享内部 API:upb_generator/common 模块全解析

发布时间:2026/9/17 4:14:00

MongoDB 内嵌 protobuf 的 upb 代码生成器共享内部 API:upb_generator/common 模块全解析 MongoDB 内嵌 protobuf 的 upb 代码生成器共享内部 APIupb_generator/common 模块全解析【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo导读本文聚焦 MongoDB 仓库内嵌的 protobuf 发行版中upb_generator/common目录README.md它是 upb 代码生成器体系里被多个生成器共同复用的内部工具层一方面提供命名与文件头生成的通用函数names库另一方面提供把 C protobuf 反射对象桥接到 upb 运行时定义def的转换工具cpp_to_upb_def库。读完本文你将理解这两个内部库每个 API 的精确语义、底层实现、Bazel 可见性约束以及它们在 upb 的 C / minitable / reflection 生成器和 hpb 生成器中的真实调用方式。一、模块定位README 定义了什么upb_generator/common目录的 README.md 只有一句话但精准划定了边界This directory contains APIs that are used by multiple code generators, but not public to users.这句话包含两个关键信息也是理解整个模块的两条主线被多个代码生成器复用这里不是某个单独生成器的私有实现而是 upb_generator 下 cC 代码生成、minitablemini table 生成、reflectionupbdefs 生成以及 protobuf 上层 hpb 生成器共用的公共设施不对用户公开这些 API 仅供生成器内部使用不构成 upb 对外的公共 API用户的生成代码不应依赖它们。从目录结构看该模块由两个 Bazel 目标组成BUILDnames纯字符串工具库仅依赖 Abseil 字符串组件cpp_to_upb_defC Descriptor 与 upb def 之间的桥接库依赖 protobuf 与 upb 的 reflection、mini_table、mem 等模块。二、names 库跨生成器的命名与头部生成工具names库的声明位于 names.h实现在 names.cc。它提供 5 个纯函数全部基于absl::string_view不触碰 protobuf 反射因此可以被打包成轻量目标供其他生成器引用而不拖入反射依赖——这一点在 BUILD 的注释中明确强调。2.1 IsDescriptorProto识别描述符原型文件bool IsDescriptorProto(absl::string_view filename) { return filename net/proto2/proto/descriptor.proto || filename google/protobuf/descriptor.proto; }用于判断给定文件是否是descriptor.proto同时兼容 Google 内部路径net/proto2/proto/descriptor.proto与开源路径google/protobuf/descriptor.proto。descriptor.proto 是描述其他 proto 文件的元原型生成器对它的处理往往需要特判。真实调用证据c/names_internal.cc当输入是 descriptor.proto 时头文件名直接使用固定的descriptor.upb.h而不是基于文件路径推导minitable/names_internal.cc同理对 descriptor.proto 使用固定文件名descriptor.upb_minitable.h。2.2 StripExtension去掉文件名扩展名std::string StripExtension(absl::string_view fname) { size_t lastdot fname.find_last_of(.); if (lastdot std::string::npos) { return std::string(fname); } return std::string(fname.substr(0, lastdot)); }取最后一个.之前的部分若没有点则原样返回。这是生成器推导输出文件基名的基础步骤例如c/generator.ccfoo.proto的 C 源文件名为StripExtension(file.name()) .upb.cminitable/generator.cc生成StripExtension(proto_filename) .upb_minitable.creflection/header.cc生成.upbdefs.hreflection/source.cc 生成.upbdefs.c。2.3 IncludeGuard 与内部辅助函数生成头文件保护宏std::string ToCIdent(absl::string_view str) { return absl::StrReplaceAll(str, {{., _}, {/, _}, {-, _}}); } std::string ToPreproc(absl::string_view str) { return absl::AsciiStrToUpper(ToCIdent(str)); } std::string IncludeGuard(absl::string_view filename) { return ToPreproc(filename) _UPB_H_; }流程清晰先把文件名中的.、/、-全部替换为_ToCIdent再转为大写ToPreproc最后拼接_UPB_H_后缀。例如google/protobuf/descriptor.proto会得到形如GOOGLE_PROTOBUF_DESCRIPTOR_PROTO_UPB_H_的宏。它被用在c/generator.cc 与 minitable/generator.cc生成各 .upb.h / .upb_minitable.h 的#ifndef头reflection/header.cc以{include_guard, IncludeGuard(file.name())}的键值对形式注入模板渲染上下文。2.4 FileWarning生成禁止手改警告头std::string FileWarning(absl::string_view filename) { return absl::Substitute( /* This file was generated by upb_generator from the input file:\n *\n * $0\n *\n * Do not edit -- your changes will be discarded when the file is\n * regenerated.\n * NO CHECKED-IN PROTOBUF GENCODE */\n \n, filename); }生成的标准警告注释块包含输入文件名并明确NO CHECKED-IN PROTOBUF GENCODE。它在 c、minitable、reflection 三个生成器中都被写入输出文件头部如 c/generator.cc、minitable/generator.cc、reflection/header.cc。2.5 PadPrefix条件性空格前缀std::string PadPrefix(absl::string_view tag) { return tag.empty() ? : absl::StrCat( , tag); }一个极简工具非空 tag 前补一个空格空 tag 返回空串。用于在生成代码中按需拼接带空格的注释/标记前缀避免出现多余的前导空格。三、cpp_to_upb_def 库C Descriptor 与 upb def 的桥接cpp_to_upb_def库cpp_to_upb_def.h / cpp_to_upb_def.cc解决一个核心矛盾代码生成器通常拿到的是 protobuf C 反射对象FileDescriptor、Descriptor等而输出 upb 代码时需要查询 upb 自己的定义池upb::DefPool。该库提供两者之间的双向查找与注册接口。3.1 ToUpbProto把 FileDescriptor 序列化回 upb 可解析的原型google_protobuf_FileDescriptorProto* ToUpbProto(const FileDescriptor* file, upb::Arena* arena) { google::protobuf::FileDescriptorProto proto; file-CopyTo(proto); std::string serialized_proto proto.SerializeAsString(); google_protobuf_FileDescriptorProto* upb_proto google_protobuf_FileDescriptorProto_parse( serialized_proto.data(), serialized_proto.size(), arena-ptr()); ABSL_CHECK(upb_proto) Failed to parse proto; return upb_proto; }实现思路是绕一圈先把 C 的FileDescriptor拷贝到FileDescriptorProto并序列化为字符串再用 upb 的解析函数google_protobuf_FileDescriptorProto_parse在临时upb::Arena上重建 upb 版本的描述符。这样两个体系通过 wire format 完成数据交换解析失败会立即触发ABSL_CHECK终止。3.2 AddFile递归注册文件及其全部依赖void AddFile(const FileDescriptor* file, upb::DefPool* pool) { const std::string name(file-name()); if (pool-FindFileByName(name.c_str())) return; // 去重 for (int i 0; i file-dependency_count(); i) { AddFile(file-dependency(i), pool); // 先注册依赖 } upb::Arena tmp_arena; upb::Status status; ABSL_CHECK(pool-AddFile(ToUpbProto(file, tmp_arena), status)) status.error_message(); }关键设计有两处去重若池中已有同名文件则直接返回避免重复注册依赖优先与google::protobuf::DescriptorPool一致upb::DefPool要求先注册全部依赖才能注册自身因此这里按dependency_count()深度优先递归。从 hpb_generator/context.h 可以看到它被 hpb 生成器在构造上下文时调用upb::generator::AddFile(file, pool_)确保后续所有查找都能命中 defpool。3.3 正向查找C 反射对象 → upb def 指针四个查找函数均以full_name()为键在 defpool 中查找查找失败即ABSL_CHECK报错函数输入输出关键约束FindMessageDefDescriptor*upb::MessageDefPtr消息必须已通过AddFile入池否则失败FindEnumDefEnumDescriptor*upb::EnumDefPtr枚举必须已在池中FindBaseFieldDefFieldDescriptor*upb::FieldDefPtr仅限非扩展字段ABSL_CHECK(!field-is_extension())按containing_type 字段号查找FindExtensionDefFieldDescriptor*upb::FieldDefPtr仅限扩展字段ABSL_CHECK(field-is_extension())直接按全名查扩展其中FindBaseFieldDef的实现cpp_to_upb_def.cc值得注意它先定位字段所属消息再调用message_def.FindFieldByNumber(field-number())即用字段号而非字段名作为跨体系匹配键——因为字段号才是 .proto 语义上稳定不变的标识。hpb_generator/context.h 中FindBaseFieldDef(pool_, field).layout_index()的用法展示了典型场景生成器需要把字段在 mini table 中的 layout index 直接写入生成的 C 代码。3.4 反向查找从 upb_MiniTableField 找回 FieldDescriptorconst FieldDescriptor* FindFieldDescriptor( const Descriptor* message, const upb_MiniTableField* field_def) { int field_number upb_MiniTableField_Number(field_def); const FieldDescriptor* field message-FindFieldByNumber(field_number); ABSL_CHECK(field) No field in message message-full_name() with number field_number; return field; }这是正向查找的逆操作当生成器手头只有 upb 的 mini table 字段结构upb_MiniTableField这是 upb 运行时紧凑内存布局的字段描述时通过upb_MiniTableField_Number取出字段号再在 C 的Descriptor中反查FieldDescriptor。这一能力让生成器可以在输出代码前把 upb 布局层面的信息如字段在 mini table 中的偏移、mode 位与 C 反射的语义信息如字段名、类型、选项对齐。四、BUILD 可见性设计如何落实不公开给用户README 中not public to users的承诺在 Bazel 层面由 BUILD 的visibility属性强制执行cc_library( name names, ... visibility [//upb_generator:__subpackages__], ) cc_library( name cpp_to_upb_def, ... visibility [ //src/google/protobuf/compiler/hpb:__subpackages__, //third_party/kotlin/protobuf/generator/native:__subpackages__, ], )names只对//upb_generator及其子包开放c、minitable、reflection 生成器均在其下cpp_to_upb_def额外对 hpb 编译器//src/google/protobuf/compiler/hpb与 Kotlin 生成器开放这与源码中 hpb_generator/context.h 的#include upb_generator/common/cpp_to_upb_def.h相互印证。同时 BUILD 顶部注释明确names库的设计约束不应依赖 upb 反射或 C proto 反射保持轻量供其他生成器引用时不会拖入两套反射体系。这一约束解释了为什么命名工具全部实现为纯字符串操作而桥接逻辑被拆到独立的cpp_to_upb_def目标中。五、在 upb 生成器体系中的整体位置将上述调用链汇总可以还原upb_generator/common在生成流程中的角色C 生成器c/generator.cc用FileWarning写警告头、IncludeGuard写保护宏、StripExtension推导.upb.c/.upb.h文件名minitable 生成器minitable/generator.cc同样的三件套外加基于StripExtension的弱 mini table 文件命名reflection 生成器reflection/header.cc / reflection/source.cc把FileWarning、IncludeGuard以键值对注入模板渲染输出.upbdefs.h/.chpb 生成器hpb_generator/context.h通过AddFile建立 defpool再以FindBaseFieldDef等桥接 API 获取 upb 视角的字段定义完成 C 反射到 upb 布局的映射。可以看出names承担文本形态的通用性文件名、宏、注释cpp_to_upb_def承担语义映射的通用性Descriptor ↔ def 双向转换。两者共同构成 upb 多生成器复用、且不对外暴露的内部工具层这正是 README 那句简短定位的完整技术内涵。六、小结与阅读指引upb_generator/common是一个小模块、大用途的典型它没有复杂的业务逻辑却是 upb 代码生成体系保持一致性统一命名规则、统一警告头、统一桥接语义的关键地基。理解它的价值不在于每个函数本身的复杂度而在于它如何通过 Bazel 可见性、依赖约束与清晰的 API 划分支撑起 c / minitable / reflection / hpb 四类生成器的并行演进。建议按以下顺序深入仓库源码names.h → names.cc掌握 5 个命名/头部工具的精确语义cpp_to_upb_def.h → cpp_to_upb_def.cc掌握 Descriptor 与 upb def 的双向桥接及依赖优先注册策略BUILD理解可见性与依赖约束如何落实内部 API定位再对照 c/generator.cc、minitable/generator.cc、reflection/header.cc、hpb_generator/context.h 观察真实调用场景。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/17 4:14:00

Spring Boot旅游信息网站开发全解析:从源码到部署与论文写作

做Java Web项目,尤其是旅游信息网站,Spring Boot基本是绕不开的选项。这套Springboot旅游信息网就是一个非常典型的全栈练习项目,代码结构清晰,功能覆盖用户端和管理端,数据库、源码、调试部署说明、开发环境全都有&am…

2026/9/17 4:09:00

WorkBuddy更新实测:自动化任务流与智能纪要如何提升效率

1. 这次更新到底改了什么工作群里看到好几个人转发WorkBuddy的更新公告,说“打工人要爽了”,我一开始是持怀疑态度的。效率工具这些年我见得多了,大部分更新都是换换皮肤、改改交互、加几个模板,真正能省时间的其实没几个。不过这…

2026/9/17 5:09:02

嵌入式软件架构入门:从分层、状态机到事件驱动的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/17 5:09:02

微信考试小程序源码解析:前后端分离、题库设计与部署实践

简介:这套微信考试答题小程序是一份适合毕业设计、也可用于实际场景的完整前后端源码包,包含数据库文件,覆盖小程序端、后台管理与接口服务。功能涉及单题/列表答题模式、分数查看、错题记录、历史记录、图片题库以及海报生成等,可…

2026/9/17 5:09:02

WS2812驱动实战:时序精度、信号完整性与工业级避坑指南

1. 为什么WS2812不是“普通LED”,而是一套精密的微型嵌入式系统?WS2812这三个字母,对刚接触智能灯带的朋友来说,可能只是淘宝搜索框里敲出的几个字符;但对做过三年以上嵌入式开发、亲手焊过几十块PCB、在凌晨三点调试过…

2026/9/17 5:09:02

Mask2Former实战:一套架构统一语义/实例/全景分割

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/17 5:04:02

智能体评测体系搭建实战:从架构设计到判分机制

做智能体开发这段时间,我最大的感触是:写一个能跑通的Agent不难,难的是你根本说不清它到底行不行。同一个任务,今天跑通明天翻车,换几个字描述结果就完全不一样,你说它聪明吧,偶尔蠢得离谱&…

2026/9/16 12:52:37

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/17 0:03:13

WiFi密码安全测试:从原理到实战的字典暴力破解指南

1. 写在前面:我为什么要研究WiFi密码这件事先交代一下背景。我身边有不少朋友,家里的WiFi密码常年是"12345678"或者"88888888",问就是"好记"。直到有一次,隔壁邻居蹭网蹭到我家路由器后台都进不去&…

2026/9/17 0:03:13

redis-py服务控制与监控函数实战:从ping到slowlog的巡检指南

我用 redis-py 写了快五年的业务代码,坦白说,真正让我觉得这个客户端“像一个成熟工具箱”的,不是 get/set 那套基本操作,而是它那批专门做服务控制与状态监控的辅助函数。日常开发里,大家把redis.Redis(host..., deco…

2026/9/17 0:03:13

SpringBoot+Vue3实现中小企业设备管理系统开发实践

1. 项目概述与核心价值中小企业设备管理系统是制造业、服务业等领域的基础信息化工具。传统设备管理往往依赖Excel表格或纸质记录,存在数据孤岛、流程混乱、维护成本高等痛点。这套基于Java SpringBootVue3MyBatis的技术方案,通过前后端分离架构实现了设…

2026/9/16 22:55:57

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/16 22:56:09

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/16 22:56:16

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码