proto-build-kit
The build-script glue every proto-source Rust project ends up rewriting — staging embedded .proto bytes onto a protoc include path, compiling them via protox, reading custom MethodOptions extension values that FileDescriptorSet encoding drops, and driving tonic-prost-build with annotation-driven type_attribute(...) injection. Schema-agnostic; ~200 lines of focused infrastructure.
What's in the box
| Primitive | What it does |
|---|---|
Stager |
Accumulate (relative_path, &'static [u8]) pairs; write them to a fresh tempdir at protoc-relative paths. Duplicate-path detection. The bridge between sibling *-protos byte-crates and protoc/buf/protox consumers. |
compile_protos |
protox wrapper that returns BOTH the prost-reflect descriptor pool (preserves MethodOptions extension VALUES — the FDS-encode path drops them) AND the FDS bytes ready for tonic-build / prost-build / custom codegen. |
extract_method_string_extension |
Walk every method in a descriptor pool, read a string-typed MethodOptions extension by FQN, return a map keyed by the response-message FQN. The generic version of "find (my.envelope.etag_field) = "..." annotations and tell me which response types declared them." |
tonic_prost_build_with_attrs |
Drive tonic-prost-build codegen with a per-type-FQN attribute map (gated on the tonic feature, default-on). Inject #[derive(...)] and arbitrary #[my_attr(...)] from values you extracted via the previous primitive. |
Quick start — typical consumer build.rs
use Stager;
Hold the returned tempfile::TempDir until codegen completes — drop deletes the staged files.
The *-protos crate pattern
A proto-publishing repo ships a tiny sibling crate (typically ~30–80 lines) that embeds its .proto bytes and exposes one accessor:
// myservice-protos/src/lib.rs
const HELLO: & = include_bytes!;
const TYPES: & = include_bytes!;
The *-protos crate has zero runtime dependencies — not even proto-build-kit. It just exposes the bytes. Consumers compose any number of *-protos crates via Stager:
# use Stager;
let staged = new
.with
.with
.with
.stage?;
# Ok::
Stager::stage() errors loudly on duplicate relative paths — protects against silent shadowing when two *-protos crates collide.
Annotation-driven codegen
For services that derive Rust attributes from custom proto annotations (envelope semantics, validation rules, audit hooks):
use ;
use BTreeMap;
Runnable examples
# Stage two embedded .protos and print compiled descriptor info:
# Extract custom MethodOptions annotations from a fake service:
See examples/ in the repo for the source.
Features
-
tonic(default-on) — enablestonic_prost_build_with_attrs. Disable if you drive your own codegen (connectrpc-build,prost-build, hand-rolled) and only need stage / compile / extract:[] = { = "0.1", = false }
When to use this — and when not to
Use proto-build-kit when:
- You publish or consume
.protofiles across multiple Rust crates and want to avoidgit submodule/ hand-vendored.protocopies. - You read custom
MethodOptionsextension values at build time (envelope semantics, validation hints, audit metadata, retry policies). - You drive
tonic-prost-buildorconnectrpc-buildfrom a build script and find yourself rewriting theprotox→ descriptor-pool →type_attributeplumbing.
Don't use it when:
- You're a single-crate proto consumer with one
.protofile. Just calltonic-builddirectly. - You don't have custom
MethodOptionsextensions to read at build time. - You want a buf-CLI-driven workflow. Pair with
buf generatefrom a Makefile instead.
Minimum Supported Rust Version (MSRV)
Rust 1.88+.
Versioning
Pre-1.0: minor versions may include API breakage with migration notes in CHANGELOG. Post-1.0: standard semver.
License
MIT.
Contributing
Issues and PRs welcome. The crate aims to stay small (~200 lines) and unopinionated — new primitives need to clear a "every proto-build script reimplements this" bar before getting added.