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

发布时间:2026/11/9 17:52:27

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/11/4 4:08:14

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

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

2026/11/10 6:10:47

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

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

2026/11/10 8:57:31

蔚县电工证快速拿证多少钱 老电工说外省证转不过来怎么办

蔚县电工证快速拿证多少钱 老电工说外省证转不过来怎么办 之前考的证在外省能不能转过来,这个问题我听太多人问了,我自己就是从河南新乡过来的,去年刚接手一个工地项目,带的几个工人拿着外省的电工证,结果在蔚县根本用不上, 多少钱…

2026/11/10 8:57:31

无锡怎样考取电工证考前押题怎么用最省时

无锡怎样考取电工证考前押题怎么用最省时 工地太忙根本没时间复习考试,电工证考试又不能拖,怎么办?很多在无锡从事建筑或者工程的朋友们都有这样的困扰,尤其是像郑州金水区的公路工程从业者,天天在工地奔波,哪有时间系统复习。不过好消息是,现在有…

2026/11/10 8:57:31

梧州考低压电工证真实经历:新手怎么避坑快速上岸

梧州考低压电工证真实经历:新手怎么避坑快速上岸 你是不是也和我一样,刚到梧州找工作,发现很多电工岗位都要求持证上岗,而你手里还什么证都没有?别急,今天我来跟你讲讲我真实经历,怎么在梧州顺利考下低压电工证,顺利找到第一份工作。…

2026/11/10 8:57:31

益阳电工证在哪里考官方报名入口在哪

益阳电工证在哪里考官方报名入口在哪 你不是学历不高就报不了名,关键是你不知道怎么操作。益阳电工证在哪里考?官方报名入口在哪?这些问题别再卡壳了,今天就给你讲明白。 一、益阳电工证在哪里考?官方报名入口你知道吗? 益阳电工证在哪里考?…

2026/11/10 8:12:38

电工证丢失还能补吗?千万别踩坑,丢了证等于失业

电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。…

2026/11/10 0:01:28

学历不高怕报不上名?考电工证报名费+网上查询全攻略来了

学历不高怕报不上名?考电工证报名费+网上查询全攻略来了 你是不是也担心自己学历不高,考电工证报名费太贵,根本报不上名?别急,今天就来给你讲讲,怎么用最少的钱,把证拿到手,还能在工地上多挣点钱。 电工证报名费多少钱?网上查询是关键…

2026/11/10 0:01:29

绍兴考电工证培训机构怎么选?2026河南最新政策别错过

绍兴考电工证培训机构怎么选?2026河南最新政策别错过 怕考不过白交培训费?你不是一个人。2026河南最新政策出台,对电工证考试、报名、培训、复审都有明确规定。选择绍兴考电工证培训机构,别再踩坑,下面一步步教你避坑。…

2026/11/9 6:30:27

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/7 14:50:12

SSM电子书店源码全解析:结构、原理、部署与排错实战

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

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

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

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