首页
/ protobuf-well-known-types:Rust Protobuf 发行版中 Well-Known Types 的实现与构建机制

protobuf-well-known-types:Rust Protobuf 发行版中 Well-Known Types 的实现与构建机制

2026-09-06 12:36:14作者:戚魁泉Nursing

本篇基于 protobuf 仓库中 Rust 发布 crate 包 protobuf_well_known_types 的官方说明与配套源码展开,说明该 crate 如何打包 Protocol Buffers 的 Well-Known Types(WKT)、这些预生成类型在 build.rs 代码生成流程中如何被声明为依赖,以及 Bazel 侧如何利用 protoc 完成代码生成与打包。读完本文,你可以掌握在 Rust 项目中引用 WKT crate 的正确方式,并能理解从 .proto 源文件到 .u.pb.rs 生成代码的完整链路。

1. Crate 定位:仓库中“预打包”的 Well-Known Types

crate 的 README 只有一句话,但信息密度很高:该 crate 包含 protobuf 的全部 Well-Known Types。所谓 Well-Known Types,是 Protocol Buffers 官方定义的一组跨语言通用消息类型(google.protobuf.Anygoogle.protobuf.Timestamp 等),它们随语言运行时/生成代码一起提供,使用者不需要自己维护这些 .proto 文件。

rust/release_crates 目录的说明中可以看到,该目录下“每个子目录对应一个发布到 crates.io 的 crate”,每个目录内含一个 pkg_tar()(仓库内实现为 pkg_filegroup)用于把 crate 打包成可推送的 tar 包。protobuf_well_known_types 就是其中一个,它的特殊性在于:crate 本身不含手写业务代码,主体是“预生成的 WKT 代码 + 原始 proto 源文件”两部分

2. 覆盖的 10 个 Well-Known Types

crate 生成逻辑中明确列出了所包含的全部 WKT(见 src/lib.rs):

Proto 文件 包路径前缀 典型用途
any.proto google/protobuf/any.proto 任意消息的序列化容器
api.proto google/protobuf/api.proto API 描述元数据
duration.proto google/protobuf/duration.proto 时间跨度
empty.proto google/protobuf/empty.proto 空消息占位
field_mask.proto google/protobuf/field_mask.proto 部分更新字段掩码
source_context.proto google/protobuf/source_context.proto 源文件位置信息
struct.proto google/protobuf/struct.proto 动态 JSON 风格结构
timestamp.proto google/protobuf/timestamp.proto 时间点
type.proto google/protobuf/type.proto 类型系统描述
wrappers.proto google/protobuf/wrappers.proto 标量包装类型

这一清单与仓库 C++ 侧的权威文件组 well_known_type_protos 完全一致——后者是 //src/google/protobuf 包中定义的 filegroup,同样由 any.protoapi.proto、…、wrappers.proto 共 10 个文件组成。也就是说,Rust crate 直接复用了与 C++ 实现相同的 proto 源文件集合,保证多语言 WKT 定义的单一事实来源。

3. crate 源码结构:get_dependency 是关键入口

crate 的入口文件 src/lib.rs 结构极简:

mod generated;
pub use generated::*;

pub fn get_dependency(crate_name: &str) -> Vec<protobuf_codegen::Dependency> {
    vec![protobuf_codegen::Dependency {
        crate_name: crate_name.to_string(),
        proto_import_paths: vec![Path::new(env!("CARGO_MANIFEST_DIR")).join("proto")],
        proto_files: vec![
            "google/protobuf/any.proto".to_string(),
            "google/protobuf/api.proto".to_string(),
            // ... 共 10 个 WKT
        ],
    }]
}

它由两部分构成:

  1. mod generated; + pub use generated::*;generated 模块是构建期由 protoc 生成的代码(见第 5 节),generated.rs 会把 10 个 .u.pb.rs 文件重新导出,因此下游 crate use protobuf_well_known_types::* 即可直接拿到 TimestampAnyStruct 等 Rust 类型。
  2. get_dependency():这是供下游 crate 的 build.rs 使用的函数。它返回一个 protobuf_codegen::Dependency 描述结构,告诉代码生成器两件事:
    • proto_import_paths:WKT 的 .proto 源文件位于本 crate 安装目录下的 proto/ 子目录(运行时通过 CARGO_MANIFEST_DIR 解析);
    • proto_files:生成下游代码时需要参与编译的 10 个 WKT proto 清单。

crate_name 参数则用于生成 extern crate 声明的命名,通常传入 "protobuf_well_known_types"

4. 在 Rust 项目中使用:build.rs 声明依赖

最直观的实战示例来自同目录下的 protobuf_example/build.rs

use protobuf_codegen::CodeGen;

fn main() {
    CodeGen::new()
        .inputs(["proto_example/foo.proto", "proto_example/bar/bar.proto"])
        .include("proto")
        .dependency(protobuf_well_known_types::get_dependency("protobuf_well_known_types"))
        .generate_and_compile()
        .unwrap();
}

流程解读:

  • .inputs(...) 指定本 crate 自己的 proto 文件(示例中的 foo.protobar.proto);
  • .dependency(protobuf_well_known_types::get_dependency("protobuf_well_known_types")) 把 WKT 作为依赖 proto 包注入:当 foo.proto 中出现 import "google/protobuf/timestamp.proto"; 时,代码生成器能从 WKT crate 的 proto/ 目录找到源文件,并生成对 protobuf_well_known_types 中已编译类型的引用,而不是重复生成一遍 WKT 代码。这正是 WKT crate 存在的核心价值——避免每个下游 crate 重复编译 well-known types

Cargo-template.toml 可以看到该 crate 的依赖面极小,仅依赖同族的 protobufprotobuf-codegen 两个 crate:

[package]
name = "protobuf-well-known-types"
version = "{PROTOBUF_RUST_VERSION}"
edition = "2021"
description = "Protobuf Well-Known Types"
license = "BSD-3-Clause"

[dependencies]
protobuf = { version = "{PROTOBUF_LEGACY_VERSION}", path = "../protobuf", package = "protobuf" }
protobuf-codegen = { version = "{PROTOBUF_LEGACY_VERSION}", path = "../protobuf_codegen", package = "protobuf-codegen" }

注意 version 字段是模板占位符 {PROTOBUF_RUST_VERSION}:发布前由 substitute_rust_release_version.bzl 对应的 Bazel 规则 substitute_rust_release_versionCargo-template.toml 渲染成带真实版本号的 Cargo.toml(见 BUILD.bazel 中的调用)。因此源码树中只维护模板,版本号由发布流程统一注入。

5. 生成代码的构建链路:protoc + upb 代码生成

crate 内的 generated 模块并非手写,而是 Bazel 构建时现产。BUILD.bazel 中的 genrule 定义了完整命令:

genrule(
    name = "gen_well_known_types",
    srcs = ["//src/google/protobuf:well_known_type_protos"],
    outs = [
        "google/protobuf/any.u.pb.rs",
        "google/protobuf/api.u.pb.rs",
        # ... duration / empty / field_mask / source_context /
        #     struct / timestamp / type / wrappers 共 10 个
        "google/protobuf/generated.rs",
    ],
    cmd = "echo $(SRCS) | sed 's?src/??g' | xargs $(location //:protoc) "
          "-I src --rust_out=$(@D) "
          "--rust_opt=experimental-codegen=enabled,kernel=upb",
    tools = ["//:protoc"],
)

关键细节:

  • 输入:直接依赖 //src/google/protobuf:well_known_type_protos 文件组(即第 2 节列出的 10 个 proto),sed 's?src/??g' 把绝对源路径裁剪后传给 protoc
  • -I src:proto 的 import 根是仓库的 src/ 目录,因此文件内路径统一写作 google/protobuf/*.proto,与 get_dependency()proto_files 的字符串一一对应;
  • --rust_opt=experimental-codegen=enabled,kernel=upb:启用实验性代码生成器且以内核为 upb,即生成的 .u.pb.rs(文件名中的 u 正对应 upb 内核);
  • 输出:每个 WKT 一个 .u.pb.rs 外加聚合入口 generated.rs,共 11 个产物,被 pkg_filessrc 为前缀打进 crate(BUILD.bazel),与 src/lib.rsmod generated; 精确衔接。

同时,原始 proto 文件通过 pkg_filesproto/google/protobuf 前缀打包(BUILD.bazel),这正对应 get_dependency()CARGO_MANIFEST_DIR/proto 的 import 路径——打包布局与运行时声明是同一份契约。

6. 打包与发布:pkg_filegroup 组装 crate

最终发布产物由 BUILD.bazel 顶部的 pkg_filegroup 组装:

pkg_filegroup(
    name = "protobuf_well_known_types_crate",
    srcs = [
        ":crate_root_files",        # Cargo.toml + src/lib.rs 等根文件
        ":well_known_types",        # proto/google/protobuf/*.proto
        ":well_known_types_gencode",# src/google/protobuf/*.u.pb.rs + generated.rs
        "//rust/release_crates:license",
    ],
    prefix = "protobuf_well_known_types",
    visibility = ["//rust:__subpackages__"],
)

四个组成块拼出一个可直接 cargo package / 推送 crates.io 的目录:根文件(crate_root_files,用 strip_prefix.from_root 剥掉 rust/release_crates/protobuf_well_known_types 前缀)、proto 源文件、生成代码、许可证。整个发布链路可以概括为:

src/google/protobuf/*.proto
        │  (well_known_type_protos 文件组)
        ▼
protoc --rust_opt=experimental-codegen=enabled,kernel=upb
        ▼
10 个 .u.pb.rs + generated.rs ──┐
proto/google/protobuf/*     ───┼──▶ pkg_filegroup ──▶ crates.io
Cargo.toml(模板渲染)+ lib.rs ┘

7. 使用前提与限制

  • 该 crate 属于 protobuf 官方 Rust 发行族(protobufprotobuf-codegenprotobuf-well-known-types 等由 rust/release_crates/Cargo.toml 统一管理的 workspace),版本号由发布流程替换,源码树中的 {PROTOBUF_RUST_VERSION} / {PROTOBUF_LEGACY_VERSION} 是占位符,直接读源码树不应按字面值理解依赖版本;
  • 生成代码使用 experimental-codegen=enabled 选项,从命令名可以推断当前 WKT 代码走的是实验性生成器 + upb 内核路径;下游 crate 若要复用同样的类型布局,build.rs 中通过 get_dependency() 声明依赖即可,无需自行重新生成 WKT;
  • crate 只覆盖上表 10 个 WKT,descriptor.proto 等元描述文件不在其中(后者属于 descriptor_proto_srcs 文件组),如果你的 proto 依赖的是 descriptor.proto,不能指望本 crate 提供。

小结protobuf-well-known-types 是 protobuf Rust 发行版中专门承载 10 个 well-known types 的 crate——它把 src/google/protobuf 下的 WKT proto 源文件、由 protoc(upb 内核实验性代码生成)产出的预编译 Rust 代码、以及 get_dependency() 声明接口三者打包为一个单元,使任意使用 protobuf-codegen 的下游 crate 都能以一行 build.rs 代码复用官方 WKT,而不用各自维护重复的 proto 与生成逻辑。

登录后查看全文
热门项目推荐
相关项目推荐