Skip to content

Add anthropics/buffa plugin v0.5.2#2333

Closed
iainmcgin wants to merge 1 commit into
bufbuild:mainfrom
iainmcgin:add-buffa-plugin
Closed

Add anthropics/buffa plugin v0.5.2#2333
iainmcgin wants to merge 1 commit into
bufbuild:mainfrom
iainmcgin:add-buffa-plugin

Conversation

@iainmcgin
Copy link
Copy Markdown
Contributor

@iainmcgin iainmcgin commented Mar 17, 2026

Adds the protoc-gen-buffa plugin for Rust protobuf code generation, under the new top-level anthropics org as requested in earlier review.

About buffa

buffa is a zero-copy Rust Protobuf implementation with:

  • Editions support (2023/2024) and full proto2/proto3 coverage
  • Zero-copy view types - borrowed &str/&[u8] field access without owned allocation
  • no_std + alloc compatible
  • Proto3 JSON mapping with serde integration
  • Conformance: passes the protobuf conformance suite (5539 binary+JSON tests, 0 expected failures)

Crates are on crates.io: buffa, buffa-types, protoc-gen-buffa.

Output convention

With file_per_package=true (set in opts), the plugin emits one <dotted.package>.rs file per proto package: foo/v1/bar.proto and foo/v1/baz.proto both land in foo.v1.rs. No subdirectories, no mod.rs. This matches the prost/tonic filename convention the BSR Rust SDK lib.rs synthesis already understands, so no special-casing should be required on the BSR side.

plugin.sum files for eliza and petapis are checked in (make test PLUGINS=anthropics/buffa).

Related: #2334 (anthropics/connect-rust) declares this plugin as a dep, so this one needs to land first.

@bufdev
Copy link
Copy Markdown
Member

bufdev commented Mar 19, 2026

I think we might want to make a top-level anthropics org for this, instead of under community - also re: connect-rust.

@iainmcgin
Copy link
Copy Markdown
Contributor Author

I think we might want to make a top-level anthropics org for this, instead of under community - also re: connect-rust.

Sure thing I'll redraft as part of updating to the new versions I'll likely release next week.

@iainmcgin iainmcgin changed the title Add community/anthropics-buffa plugin v0.2.0 Add anthropics/buffa plugin v0.2.0 Apr 2, 2026
@iainmcgin iainmcgin changed the title Add anthropics/buffa plugin v0.2.0 Add anthropics/buffa plugin v0.3.0 Apr 2, 2026
@@ -0,0 +1,4 @@
source:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's supposed to be a crates section here? See the prost plugin as an ezample

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to crate ref instead of github ref 👍

@iainmcgin iainmcgin marked this pull request as ready for review April 3, 2026 15:54
Copy link
Copy Markdown
Member

@pkwarren pkwarren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To ensure reproducible builds, we check in a checksum file of generated code using the plugin. This verifies e2e code generation is functional and also ensures that the codegen doesn't change across builds.

https://github.com/bufbuild/plugins/blob/main/CONTRIBUTING.md#creating-a-new-plugin

To create the files, run make test PLUGINS=anthropics/buffa:latest and check in the plugin.sum files created under tests/testdata/buf.build/anthropics/.

@@ -0,0 +1,15 @@
# syntax=docker/dockerfile:1.19
FROM rust:1.91.1-alpine3.22 AS builder
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should update this branch to latest main then we can update this image to the latest base image:

Suggested change
FROM rust:1.91.1-alpine3.22 AS builder
FROM rust:1.95.0-alpine3.23@sha256:606fd313a0f49743ee2a7bd49a0914bab7deedb12791f3a846a34a4711db7ed2 AS builder

RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked --mount=type=cache,target=/root/target \
cargo install protoc-gen-buffa --version 0.3.0 --locked --root /app

FROM gcr.io/distroless/static-debian12:latest@sha256:87bce11be0af225e4ca761c40babb06d6d559f5767fbf7dc3c47f0f1a466b92c AS base
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
FROM gcr.io/distroless/static-debian12:latest@sha256:87bce11be0af225e4ca761c40babb06d6d559f5767fbf7dc3c47f0f1a466b92c AS base
FROM gcr.io/distroless/static-debian13:latest@sha256:47b2d72ff90843eb8a768b5c2f89b40741843b639d065b9b937b07cd59b479c6 AS base

Comment thread plugins/anthropics/buffa/v0.5.2/buf.plugin.yaml
@pkwarren pkwarren requested a review from stefanvanburen April 20, 2026 16:43
@stefanvanburen
Copy link
Copy Markdown
Contributor

  1. opts: I set json=true and views=true. If BSR has conventions for which opts to expose vs bake in, please advise.

Seems reasonable to me to enable both — since generated SDKs aren't configurable, I think including all of the features makes sense to me (users who want more control can fall back to local generation).

2. File naming: The per-file output naming (pkg.subpkg.file.rs) is flat but per-proto-file, not per-package like prost. If BSR's lib.rs synthesis expects per-package naming instead, we can add a plugin option to switch modes.

What would be most helpful here is actually if both buffa and connect-rust included the lib.rs generation logic themselves (enabled with a plugin option; generate_librs=true strawman name; disabled by default) so that the BSR doesn't need any sort of special-casing for generating it on our end — is that possible to do?

@iainmcgin
Copy link
Copy Markdown
Contributor Author

@stefanvanburen buffa has a plugin for local usage, protoc-gen-buffa-packaging, which produces a consolidated mod.rs separately from the core plugin. This was the structure that @bufdev wanted me to use because that plugin requires strategy: all to get visibility across all files in one pass. I'm not sure if we can generate lib.rs without strategy: all for similar reasons, but open to suggestions.

@iainmcgin
Copy link
Copy Markdown
Contributor Author

[claude code] We'll add a file_per_package=true opt to protoc-gen-buffa that emits one <dotted.package>.rs per proto package (concatenating what currently goes into the per-proto-file outputs). That matches the convention the existing cargo-SDK lib.rs synthesis already handles for prost, so no special-casing should be needed on the BSR side. It's also correct under strategy: directory for any PACKAGE_DIRECTORY_MATCH-clean module, so it avoids the strategy: all dependency @bufdev flagged earlier.

connect-rust will get the same opt and also emit <dotted.package>.rs — since the buffa and connect-rust SDKs are separate crates there's no collision, and the service stubs land at the proto package path (<connect_sdk>::pet::v1::PetServiceClient) rather than under an extra submodule the way tonic's <pkg>.tonic.rs ends up.

Targeting v0.4.1 for both. I'll update these PRs once that's released.

Separately: as far as I can tell registry.cargo only exposes rust_version and deps, so the filename→module-tree synthesis is an implicit contract that plugins have to reverse-engineer from prost's output. Would it be worth either documenting that convention in CONTRIBUTING.md, or adding something like registry.cargo.lib_rs: plugin so a plugin can opt into emitting its own lib.rs and have BSR use it verbatim? Happy to open a separate issue if that's a better place for it.

@stefanvanburen
Copy link
Copy Markdown
Contributor

We'll add a file_per_package=true opt to protoc-gen-buffa that emits one <dotted.package>.rs per proto package (concatenating what currently goes into the per-proto-file outputs).

@iainmcgin sounds good, thanks for your persistence with this, it's appreciated!

Separately: as far as I can tell registry.cargo only exposes rust_version and deps, so the filename→module-tree synthesis is an implicit contract that plugins have to reverse-engineer from prost's output. Would it be worth either documenting that convention in CONTRIBUTING.md, or adding something like registry.cargo.lib_rs: plugin so a plugin can opt into emitting its own lib.rs and have BSR use it verbatim? Happy to open a separate issue if that's a better place for it.

You're basically on-the-nose here, unfortunately: with basically every other package ecosystem, it's relatively straightforward to package up plugin output; Rust is a different story, and the BSR has some workarounds here to handle prost / tonic as a special snowflake, given that they were the only real game in town when we built out Rust generated SDKs. Now that there's more to the ecosystem, we'll hopefully be able to abstract that away a little more.

iainmcgin added a commit to anthropics/buffa that referenced this pull request Apr 29, 2026
…73)

Adds a `file_per_package` option to `CodeGenConfig` (and the
`protoc-gen-buffa` CLI) that emits one `<dotted.package>.rs` per proto
package instead of the default per-proto-file split. The single file
inlines what the `<pkg>.mod.rs` stitcher would otherwise `include!`,
producing the identical `__buffa::` module structure.

## Why

BSR's cargo-SDK pipeline assembles `lib.rs` server-side by splitting
each output filename on `.` and building a nested `pub mod` tree — the
same convention prost's per-package output follows. Our default per-file
output (`pet.v1.pet.rs`, `pet.v1.pet.__view.rs`, `pet.v1.mod.rs`, ...)
would synthesize to garbage under that algorithm. With
`file_per_package=true` set in the BSR plugin manifest's
`registry.opts`, our output slots into the existing synthesis with no
special-casing on Buf's side. See bufbuild/plugins#2333.

This mode is correct under `strategy: directory` for
`PACKAGE_DIRECTORY_MATCH`-clean modules (one directory == one package,
so each invocation sees a complete package), so it does not introduce a
`strategy: all` requirement.

## Implementation

- `PackageSections` intermediate holds per-section `Vec<TokenStream>`s;
`generate_package_mod` now consumes it uniformly. The per-file path
builds sections from `include!` token streams; the per-package path
inlines the content directly. Module structure is identical between
modes.
- `package_to_filename()` helper for `<pkg>.rs` naming.
- `GeneratedFileKind::PackageMod` is reused for the single-file output.

## Deferred (Low review findings)

- `OutputLayout` enum instead of `bool` — only one alternate layout
exists; revisit if more emerge.
- Unnamed-package output is `__buffa.rs`, which under BSR's synthesis
would double-nest. Buf's `PACKAGE_DEFINED` lint (MINIMAL group) makes
this a near-zero case for SDK gen, and it mirrors the existing per-file
path's behavior.
@iainmcgin iainmcgin changed the title Add anthropics/buffa plugin v0.3.0 Add anthropics/buffa plugin v0.5.2 May 11, 2026
@iainmcgin
Copy link
Copy Markdown
Contributor Author

[claude code] Updated to protoc-gen-buffa v0.5.2 with file_per_package=true set in opts. The plugin now emits one <dotted.package>.rs per proto package — eliza produces buf.connect.demo.eliza.v1.rs, petapis produces pet.v1.rs — matching the prost filename convention. plugin.sum files are checked in (make test PLUGINS=anthropics/buffa passes locally).

@stefanvanburen following up from the earlier discussionfile_per_package landed; if the BSR lib.rs synthesis recognizes this output shape with no further special-casing, this should be good to go from my side. Let me know if you'd like anything else changed.

@iainmcgin iainmcgin requested review from bufdev and pkwarren May 11, 2026 21:31
@pkwarren
Copy link
Copy Markdown
Member

Taking a look at the latest changes today. Will let you know if there are any further changes necessary to work with gen SDKs.

@iainmcgin
Copy link
Copy Markdown
Contributor Author

[claude code] Superseded by #2334, which now contains both anthropics/buffa and anthropics/connect-rust and can land standalone. Closing this one to keep review in one place.

@iainmcgin iainmcgin closed this May 12, 2026
pkwarren pushed a commit that referenced this pull request May 13, 2026
Adds the `protoc-gen-buffa` and `protoc-gen-connect-rust` plugins for
Rust protobuf and ConnectRPC code generation, under the new top-level
`anthropics` org as requested in earlier review.

This PR contains both plugins, so it can land on its own. (It was
originally stacked on #2333, which adds `anthropics/buffa` alone — #2333
is now superseded by this PR.)

## buffa

[buffa](https://github.com/anthropics/buffa) is a zero-copy Rust
Protobuf implementation with:
- **Editions support** (2023/2024) and full proto2/proto3 coverage
- **Zero-copy view types** - borrowed `&str`/`&[u8]` field access
without owned allocation
- **no_std + alloc** compatible
- **Proto3 JSON mapping** with serde integration
- **Conformance**: passes the protobuf conformance suite (5539
binary+JSON tests, 0 expected failures)

Crates on crates.io: [`buffa`](https://crates.io/crates/buffa),
[`buffa-types`](https://crates.io/crates/buffa-types),
[`protoc-gen-buffa`](https://crates.io/crates/protoc-gen-buffa).

`protoc-gen-buffa` generates message types. With `file_per_package=true`
(set in `opts`), it emits **one `<dotted.package>.rs` file per proto
package**: `foo/v1/bar.proto` and `foo/v1/baz.proto` both land in
`foo.v1.rs`. No subdirectories, no `mod.rs`. This matches the
prost/tonic filename convention the BSR Rust SDK `lib.rs` synthesis
already understands.

## connect-rust

[connect-rust](https://github.com/anthropics/connect-rust) is a
Tower-based Rust implementation of the ConnectRPC protocol:
- **Three protocols**: Connect, gRPC, gRPC-Web - same service impl
handles all three
- **Full conformance**: 3600 server tests, 2580/1454/2838 client tests
across Connect/gRPC/gRPC-Web
- **Zero-copy request handling** via buffa view types - `request.name`
is a `&str` borrow into the decoded buffer
- **Tower integration**: composes with axum, hyper, tower middleware

Crates on crates.io:
[`connectrpc`](https://crates.io/crates/connectrpc),
[`connectrpc-codegen`](https://crates.io/crates/connectrpc-codegen),
[`connectrpc-build`](https://crates.io/crates/connectrpc-build).

`protoc-gen-connect-rust` emits **service stubs only** - server traits,
typed clients, monomorphic dispatchers. Message types come from
`buf.build/anthropics/buffa` (declared in `deps:`). Generated stubs
reference message types via absolute Rust paths configured with
`extern_path`. It also takes `file_per_package`, mirroring the
`anthropics/buffa` opt.

**Dockerfile note**: the `protoc-gen-connect-rust` binary is a `[[bin]]`
target inside the `connectrpc-codegen` crate, so `cargo install
connectrpc-codegen` is what produces it.

## extern_path handling

`protoc-gen-connect-rust` needs an `extern_path=.=<rust_path>` (or
shorthand `buffa_module=<rust_path>`) catch-all so it knows where the
buffa-generated message types live. Per @stefanvanburen's review,
`opts:` is left as just `[file_per_package]` — the BSR injects
`extern_path` from the `deps:` chain at SDK build time using the same
prost syntax as tonic.

The standalone test harness has no dep chain, so this PR adds a
`testOverrideOptions` entry passing a placeholder `extern_path=.=crate`
— the same pattern `neoeinstein-prost-crate` (`no_features`) and
`neoeinstein-tonic` (`no_include`) use to make their isolated test runs
succeed without affecting production opts.

`plugin.sum` files for both plugins (`eliza` and `petapis`) are checked
in; `make test PLUGINS="anthropics/buffa anthropics/connect-rust"`
passes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants