资讯动态

gRPC C++ `impl/codegen` 目录解析:生成代码的最小依赖设计与使用边界

发布时间:2026/9/16 13:56:51 来源:尧图企业网站定制
gRPC Cimpl/codegen目录解析生成代码的最小依赖设计与使用边界【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo导读在 MongoDB 仓库的第三方依赖中gRPC C 提供了一个特殊的头文件目录include/grpcpp/impl/codegen。它既不是公共 API也不是纯粹的内部实现而是专门服务于proto 生成代码由 protoc gRPC C 插件自动产出的*.pb.h/*.grpc.pb.h的最小依赖集合。本指南将结合仓库内的头文件实现与 Bazel 构建规则剖析该目录存在的原因、它包含哪些内容、为什么用户代码必须绕开它以及在 Bazel 多语言proto_library场景下它如何帮助控制构建规模。读完本文你将理解 gRPC C 头文件分层的完整脉络并掌握正确的 include 边界与构建依赖组织方式。一、impl/codegen目录为什么存在关联文档 src/third_party/grpc/dist/include/grpcpp/impl/codegen/README.md 开宗明义地给出了这个目录的定位该目录的存在是为了让生成代码可以按需包含其依赖的少量头文件而无需依赖整个 gRPC C 库。这一设计对 Bazel 用户尤其重要特别是使用多语言proto_library目标类型时。生成的代码只需要依赖与这些头文件对应的 gRPC C 目标而不是整个 gRPC C 代码库——如果后者成立那么这些目标的构建时间会急剧膨胀尤其当这些 proto 目标本身甚至不是 C 专属时例如同一份.proto还要产出 Java、Python、Go 代码。换句话说codegen目录是 gRPC C 在依赖粒度上做的一层精细切分把生成代码编译所必需的、与 protobuf 序列化/反序列化相关的头文件单独隔离出来与运行完整 gRPC 服务/客户端所需的全部实现解耦。从仓库目录布局看gRPC C 的头文件体系实际被划分为四个层次层次路径性质公共 APIinclude/grpcpp/如grpcpp.h、channel.h、server_builder.h用户代码应从这里 include内部实现不稳定include/grpcpp/impl/仅 gRPC 库内部使用API 不稳定生成代码专用include/grpcpp/impl/codegen/仅生成代码与 gRPC 库代码使用可访问支持层include/grpcpp/support/用户代码可访问的辅助头文件其中impl/目录自身的 README.md 明确警告该目录中的 API 不稳定这些头文件需要被安装但不属于公共 API用户不应直接使用。而codegen目录正是在这个不稳定内部层之下、专为编译期依赖切分而设的进一步细分。二、codegen目录内容全景该目录下实际包含约 50 个头文件覆盖了生成代码编译时所需的几乎全部基础类型。从功能上可以归纳为以下几类以下分类依据文件名与头文件内容推断可对照目录逐一验证调用链与 RPC 模型call.h、call_op_set.h、call_op_set_interface.h、call_hook.h、rpc_method.h、rpc_service_method.h、service_type.h同步与异步流sync.h、sync_stream.h、async_stream.h、async_unary_call.h、client_unary_call.h回调风格 APIcallback_common.h、client_callback.h、server_callback.h、server_callback_handlers.h拦截器体系interceptor.h、interceptor_common.h、client_interceptor.h、server_interceptor.h、intercepted_channel.h、delegating_channel.h序列化与 protobuf 桥接serialization_traits.h、proto_utils.h、proto_buffer_reader.h、proto_buffer_writer.h、byte_buffer.h、slice.h、message_allocator.h上下文与状态client_context.h、server_context.h、server_interface.h、channel_interface.h、status.h、status_code_enum.h、string_ref.h、time.h、metadata_map.h、stub_options.h服务端框架method_handler.h、method_handler_impl.h、async_generic_service.h、completion_queue.h、completion_queue_tag.h配置与兼容层config.h、config_protobuf.h、create_auth_context.h以及子目录 security/auth_context.h。值得注意的是这份列表与grpcpp/support/下的支持头文件高度重合——async_stream.h、byte_buffer.h、status.h、string_ref.h、slice.h等在同一份文件在两层目录中同时存在这正是下文要说的兼容性转发机制。三、从源码看头文件分层的实际实现3.1 公共 API 的入口grpcpp.h用户代码正确的人手点是 src/third_party/grpc/dist/include/grpcpp/grpcpp.h。它通过 IWYU 的begin_exports/end_exports标注聚合导出了公共 API 的顶层头文件grpc/grpc.h grpcpp/channel.h grpcpp/client_context.h grpcpp/completion_queue.h grpcpp/create_channel.h grpcpp/create_channel_posix.h grpcpp/server.h grpcpp/server_builder.h grpcpp/server_context.h grpcpp/server_posix.h grpcpp/version_info.h并声明了grpc::Version()这一全局版本查询函数。用户代码包含grpcpp/grpcpp.h即可获得完整且稳定的 C API而无需触及任何impl/codegen内部文件。3.2 兼容性转发头文件codegen目录中的一部分头文件当前扮演的是转发占位角色。例如 config.h 的完整实现是#ifndef GRPCPP_IMPL_CODEGEN_CONFIG_H #define GRPCPP_IMPL_CODEGEN_CONFIG_H // IWYU pragma: private /// TODO(chengyuc): Remove this file after solving compatibility. #include grpcpp/support/config.h #endif // GRPCPP_IMPL_CODEGEN_CONFIG_Hstatus.h 采用完全相同的模式直接转发到grpcpp/support/status.h。这类文件上有两条关键信息IWYU pragma: private告知 include-what-you-use 工具该头文件是私有的用户不应直接包含TODO(chengyuc): Remove this file after solving compatibility.说明这是一段过渡期的兼容层一旦外部依赖如旧版生成代码迁移完毕这些转发文件会被删除。这从源码层面印证了关联文档的论断codegen目录并非 gRPC C 的既定长期公共结构而是为了兼容与依赖切分而存在的中间态。四、Bazel 构建视角最小依赖如何落地4.1 生成代码的编译期依赖在 Bazel 中proto 生成代码的依赖切分体现在 gRPC 仓库根 BUILD 中的grpc_codegen_proto目标第 2561 行附近grpc_cc_library( name grpc_codegen_proto, hdrs [include/grpcpp/impl/generic_serialize.h], external_deps [ absl/strings:cord, protobuf_headers, protobuf, ], public_hdrs [ include/grpc/impl/codegen/proto_utils.h, include/grpcpp/impl/codegen/proto_buffer_reader.h, include/grpcpp/impl/codegen/proto_buffer_writer.h, include/grpcpp/impl/codegen/proto_utils.h, include/grpcpp/impl/proto_utils.h, ], deps [ grpc_config_proto, grpc_public_hdrs, grpcpp_status, ], )可以看到这个目标只向生成代码暴露与 protobuf 序列化、generic_serialize相关的最小头文件集合外加 protobuf 与 absl 的外部依赖而不是整个 gRPC 运行时channel、server、completion queue 的实现全部被排除在外。这正是关联文档所描述的只依赖与这些头文件关联的目标在 BUILD 层面的直接落地。4.2cc_grpc_library的 codegen 子目标生成代码的实际装配发生在 bazel/cc_grpc_library.bzl 的cc_grpc_library规则中第 103123 行附近。其核心逻辑是若未设置grpc_only先分别产出proto_library与cc_proto_library目标第 8597 行生成一个内部子目标_name_grpc_codegen通过generate_cc调用//src/compiler:grpc_cpp_plugin生成*.grpc.pb.h/.cc第 104113 行最终cc_library的srcs与hdrs均取自该 codegen 子目标并在deps中只追加Label(//:grpc_codegen_proto)第 115123 行。也就是说每个通过cc_grpc_library生成的 C gRPC 库其依赖都被精确收敛到grpc_codegen_proto这个最小集合。此时若构建的是一个多语言共享的proto_library如 MongoDB 这类大型仓库中同时产出 C/Python/JS 的 protoC 侧只承担序列化桥接的少量编译开销而非整套 gRPC C 的编译与链接成本。此外dist/third_party/BUILD第 131132 行中还存在alias( name grpc_codegen_proto, actual com_github_grpc_grpc//:grpc_codegen_proto, )这表明外部 Bazel 工程可以通过com_github_grpc_grpc//:grpc_codegen_proto直接引用这个最小依赖目标。五、使用边界用户代码应该怎么做关联文档对使用者给出了两条明确的红线与一条建议禁止用户代码不得从include/grpcpp/impl/codegen/包含任何头文件允许只有生成代码与 gRPC 库自身代码可以包含该目录内容建议用户代码应从主grpcpp目录或其可访问的子组件如grpcpp/support包含所需头文件。grpcpp/support/目录列表正是为此准备的用户可访问层包含channel_arguments.h、error_details.h、global_callback_hook.h、validate_service_config.h等 25 个支持头文件以及与 codegen 目录同名的一批基础类型。下表给出常见需求的正确与错误用法对照需求错误用法内部正确用法公共/支持层获取完整 C API#include grpcpp/impl/codegen/...#include grpcpp/grpcpp.h使用grpc::Status#include grpcpp/impl/codegen/status.h#include grpcpp/support/status.h使用grpc::Slice/ByteBuffer#include grpcpp/impl/codegen/slice.h#include grpcpp/support/slice.h使用grpc::string_ref#include grpcpp/impl/codegen/string_ref.h#include grpcpp/support/string_ref.h违反了这条边界会带来两重风险其一这些头文件不保证 API 稳定升级 gRPC 版本时可能直接编译失败其二它破坏了codegen目录赖以存在的依赖切分初衷把最小依赖重新拉回整库。六、未来展望与兼容性过渡关联文档最后指出如果该目录存在的动机不再强烈gRPC 可能会整体移除impl/codegen。触发条件包括大多数用户迁移离开proto_library目标类型依赖整个 gRPC C 库的额外开销不再显著。结合前文源码中的TODO(chengyuc): Remove this file after solving compatibility.注释可以推断这一过渡目前仍在进行中codegen目录内的转发头文件会随着下游兼容问题的解决而被逐步删除最终该目录或将整体并入support/与impl/的既有结构。对仓库使用者而言这意味着两条长期有效的工程建议新代码一律走公共 API#include grpcpp/grpcpp.h或#include grpcpp/support/xxx.h为未来目录调整留足迁移余地构建层面锁定最小依赖Bazel 场景下应通过grpc_codegen_proto或本地等价目标承载生成代码的依赖避免无谓拉高编译规模。七、小结include/grpcpp/impl/codegen是 gRPC C 在生成代码编译依赖与完整库依赖之间做出的精细折中它用一层仅约 50 个头的隔离目录换来了 Bazel 多语言proto_library场景下 C 构建开销的大幅收敛。理解它的存在动机依赖切分、内容构成序列化/调用链/拦截器等基础头、落地方式grpc_codegen_proto目标与cc_grpc_library规则以及使用红线用户代码禁止 include是正确使用 gRPC C 与组织相关构建依赖的前提。对 MongoDB 这类在 Bazel 体系中深度使用 gRPC 的大型项目这条边界直接关系到构建系统的健康度。gRPC Cimpl/codegen目录解析生成代码的最小依赖设计与使用边界导读在 MongoDB 仓库的第三方依赖中gRPC C 提供了一个特殊的头文件目录include/grpcpp/impl/codegen。它既不是公共 API也不是纯粹的内部实现而是专门服务于proto 生成代码由 protoc gRPC C 插件自动产出的*.pb.h/*.grpc.pb.h的最小依赖集合。本指南将结合仓库内的头文件实现与 Bazel 构建规则剖析该目录存在的原因、它包含哪些内容、为什么用户代码必须绕开它以及在 Bazel 多语言proto_library场景下它如何帮助控制构建规模。读完本文你将理解 gRPC C 头文件分层的完整脉络并掌握正确的 include 边界与构建依赖组织方式。一、impl/codegen目录为什么存在关联文档 src/third_party/grpc/dist/include/grpcpp/impl/codegen/README.md 开宗明义地给出了这个目录的定位该目录的存在是为了让生成代码可以按需包含其依赖的少量头文件而无需依赖整个 gRPC C 库。这一设计对 Bazel 用户尤其重要特别是使用多语言proto_library目标类型时。生成的代码只需要依赖与这些头文件对应的 gRPC C 目标而不是整个 gRPC C 代码库——如果后者成立那么这些目标的构建时间会急剧膨胀尤其当这些 proto 目标本身甚至不是 C 专属时例如同一份.proto还要产出 Java、Python、Go 代码。换句话说codegen目录是 gRPC C 在依赖粒度上做的一层精细切分把生成代码编译所必需的、与 protobuf 序列化/反序列化相关的头文件单独隔离出来与运行完整 gRPC 服务/客户端所需的全部实现解耦。从仓库目录布局看gRPC C 的头文件体系实际被划分为四个层次层次路径性质公共 APIinclude/grpcpp/如grpcpp.h、channel.h、server_builder.h用户代码应从这里 include内部实现不稳定include/grpcpp/impl/仅 gRPC 库内部使用API 不稳定生成代码专用include/grpcpp/impl/codegen/仅生成代码与 gRPC 库代码使用可访问支持层include/grpcpp/support/用户代码可访问的辅助头文件其中impl/目录自身的 README.md 明确警告该目录中的 API 不稳定这些头文件需要被安装但不属于公共 API用户不应直接使用。而codegen目录正是在这个不稳定内部层之下、专为编译期依赖切分而设的进一步细分。二、codegen目录内容全景该目录下实际包含约 50 个头文件覆盖了生成代码编译时所需的几乎全部基础类型。从功能上可以归纳为以下几类以下分类依据文件名与头文件内容推断可对照目录逐一验证调用链与 RPC 模型call.h、call_op_set.h、call_op_set_interface.h、call_hook.h、rpc_method.h、rpc_service_method.h、service_type.h同步与异步流sync.h、sync_stream.h、async_stream.h、async_unary_call.h、client_unary_call.h回调风格 APIcallback_common.h、client_callback.h、server_callback.h、server_callback_handlers.h拦截器体系interceptor.h、interceptor_common.h、client_interceptor.h、server_interceptor.h、intercepted_channel.h、delegating_channel.h序列化与 protobuf 桥接serialization_traits.h、proto_utils.h、proto_buffer_reader.h、proto_buffer_writer.h、byte_buffer.h、slice.h、message_allocator.h上下文与状态client_context.h、server_context.h、server_interface.h、channel_interface.h、status.h、status_code_enum.h、string_ref.h、time.h、metadata_map.h、stub_options.h服务端框架method_handler.h、method_handler_impl.h、async_generic_service.h、completion_queue.h、completion_queue_tag.h配置与兼容层config.h、config_protobuf.h、create_auth_context.h以及子目录 security/auth_context.h。值得注意的是这份列表与grpcpp/support/下的支持头文件高度重合——async_stream.h、byte_buffer.h、status.h、string_ref.h、slice.h等在两层目录中同时存在这正是下文要说的兼容性转发机制。三、从源码看头文件分层的实际实现3.1 公共 API 的入口grpcpp.h用户代码正确的人手点是 src/third_party/grpc/dist/include/grpcpp/grpcpp.h。它通过 IWYU 的begin_exports/end_exports标注聚合导出了公共 API 的顶层头文件grpc/grpc.h grpcpp/channel.h grpcpp/client_context.h grpcpp/completion_queue.h grpcpp/create_channel.h grpcpp/create_channel_posix.h grpcpp/server.h grpcpp/server_builder.h grpcpp/server_context.h grpcpp/server_posix.h grpcpp/version_info.h并声明了grpc::Version()这一全局版本查询函数。用户代码包含grpcpp/grpcpp.h即可获得完整且稳定的 C API而无需触及任何impl/codegen内部文件。3.2 兼容性转发头文件codegen目录中的一部分头文件当前扮演的是转发占位角色。例如 config.h 的完整实现是#ifndef GRPCPP_IMPL_CODEGEN_CONFIG_H #define GRPCPP_IMPL_CODEGEN_CONFIG_H // IWYU pragma: private /// TODO(chengyuc): Remove this file after solving compatibility. #include grpcpp/support/config.h #endif // GRPCPP_IMPL_CODEGEN_CONFIG_Hstatus.h 采用完全相同的模式直接转发到grpcpp/support/status.h。这类文件上有两条关键信息IWYU pragma: private告知 include-what-you-use 工具该头文件是私有的用户不应直接包含TODO(chengyuc): Remove this file after solving compatibility.说明这是一段过渡期的兼容层一旦外部依赖如旧版生成代码迁移完毕这些转发文件会被删除。这从源码层面印证了关联文档的论断codegen目录并非 gRPC C 的既定长期公共结构而是为了兼容与依赖切分而存在的中间态。四、Bazel 构建视角最小依赖如何落地4.1 生成代码的编译期依赖在 Bazel 中proto 生成代码的依赖切分体现在 gRPC 仓库根 BUILD 中的grpc_codegen_proto目标第 2561 行附近grpc_cc_library( name grpc_codegen_proto, hdrs [include/grpcpp/impl/generic_serialize.h], external_deps [ absl/strings:cord, protobuf_headers, protobuf, ], public_hdrs [ include/grpc/impl/codegen/proto_utils.h, include/grpcpp/impl/codegen/proto_buffer_reader.h, include/grpcpp/impl/codegen/proto_buffer_writer.h, include/grpcpp/impl/codegen/proto_utils.h, include/grpcpp/impl/proto_utils.h, ], deps [ grpc_config_proto, grpc_public_hdrs, grpcpp_status, ], )可以看到这个目标只向生成代码暴露与 protobuf 序列化、generic_serialize相关的最小头文件集合外加 protobuf 与 absl 的外部依赖而不是整个 gRPC 运行时channel、server、completion queue 的实现全部被排除在外。这正是关联文档所描述的只依赖与这些头文件关联的目标在 BUILD 层面的直接落地。4.2cc_grpc_library的 codegen 子目标生成代码的实际装配发生在 bazel/cc_grpc_library.bzl 的cc_grpc_library规则中第 103123 行附近。其核心逻辑是若未设置grpc_only先分别产出proto_library与cc_proto_library目标第 8597 行生成一个内部子目标_name_grpc_codegen通过generate_cc调用//src/compiler:grpc_cpp_plugin生成*.grpc.pb.h/.cc第 104113 行最终cc_library的srcs与hdrs均取自该 codegen 子目标并在deps中只追加Label(//:grpc_codegen_proto)第 115123 行。也就是说每个通过cc_grpc_library生成的 C gRPC 库其依赖都被精确收敛到grpc_codegen_proto这个最小集合。此时若构建的是一个多语言共享的proto_library如 MongoDB 这类大型仓库中同时产出 C/Python/JS 的 protoC 侧只承担序列化桥接的少量编译开销而非整套 gRPC C 的编译与链接成本。此外dist/third_party/BUILD第 131132 行中还存在alias( name grpc_codegen_proto, actual com_github_grpc_grpc//:grpc_codegen_proto, )这表明外部 Bazel 工程可以通过com_github_grpc_grpc//:grpc_codegen_proto直接引用这个最小依赖目标。五、使用边界用户代码应该怎么做关联文档对使用者给出了两条明确的红线与一条建议禁止用户代码不得从include/grpcpp/impl/codegen/包含任何头文件允许只有生成代码与 gRPC 库自身代码可以包含该目录内容建议用户代码应从主grpcpp目录或其可访问的子组件如grpcpp/support包含所需头文件。grpcpp/support/目录列表正是为此准备的用户可访问层包含channel_arguments.h、error_details.h、global_callback_hook.h、validate_service_config.h等 25 个支持头文件以及与 codegen 目录同名的一批基础类型。下表给出常见需求的正确与错误用法对照需求错误用法内部正确用法公共/支持层获取完整 C API#include grpcpp/impl/codegen/...#include grpcpp/grpcpp.h使用grpc::Status#include grpcpp/impl/codegen/status.h#include grpcpp/support/status.h使用grpc::Slice/ByteBuffer#include grpcpp/impl/codegen/slice.h#include grpcpp/support/slice.h使用grpc::string_ref#include grpcpp/impl/codegen/string_ref.h#include grpcpp/support/string_ref.h违反了这条边界会带来两重风险其一这些头文件不保证 API 稳定升级 gRPC 版本时可能直接编译失败其二它破坏了codegen目录赖以存在的依赖切分初衷把最小依赖重新拉回整库。六、未来展望与兼容性过渡关联文档最后指出如果该目录存在的动机不再强烈gRPC 可能会整体移除impl/codegen。触发条件包括大多数用户迁移离开proto_library目标类型依赖整个 gRPC C 库的额外开销不再显著。结合前文源码中的TODO(chengyuc): Remove this file after solving compatibility.注释可以推断这一过渡目前仍在进行中codegen目录内的转发头文件会随着下游兼容问题的解决而被逐步删除最终该目录或将整体并入support/与impl/的既有结构。对仓库使用者而言这意味着两条长期有效的工程建议新代码一律走公共 API#include grpcpp/grpcpp.h或#include grpcpp/support/xxx.h为未来目录调整留足迁移余地构建层面锁定最小依赖Bazel 场景下应通过grpc_codegen_proto或本地等价目标承载生成代码的依赖避免无谓拉高编译规模。七、小结include/grpcpp/impl/codegen是 gRPC C 在生成代码编译依赖与完整库依赖之间做出的精细折中它用一层仅约 50 个头的隔离目录换来了 Bazel 多语言proto_library场景下 C 构建开销的大幅收敛。理解它的存在动机依赖切分、内容构成序列化/调用链/拦截器等基础头、落地方式grpc_codegen_proto目标与cc_grpc_library规则以及使用红线用户代码禁止 include是正确使用 gRPC C 与组织相关构建依赖的前提。对 MongoDB 这类在 Bazel 体系中深度使用 gRPC 的大型项目这条边界直接关系到构建系统的健康度。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价