One contract, two generator toolchains. Both read proto/, which scripts/sync-protos.sh
copies from upstream at the SHA recorded in ocp.lock.
sync-protos.sh clones ocp-protobuf-api, checks out the requested ref, copies only the
contract .proto files (upstream's buf.yaml, buf.lock, and buf.gen.yaml describe how the
contract repo builds Go, and are not part of what this SDK ships), and rewrites ocp.lock.
This repo owns the java_package namespace. Upstream ships com.codeinc.gen.*; the Android
app has always consumed com.codeinc.opencode.gen.*, via a sed/awk pass at the end of its
old fetch-protos.sh. sync-protos.sh does that rewrite instead, so the published artifact
needs no consumer-side post-processing, and it fails the sync if any com.codeinc.gen. survives
the pass. Swift is unaffected: java_package does not influence swift-protobuf naming.
proto_deps/validate/validate.proto is an include-path dependency and is never generated. The
Gradle side gets it from the protovalidate plugin's own JAR; the Swift script passes
proto_deps/ on the include path but keeps the file off the generation list. The iOS build used
to generate it and then delete the resulting validate_validate.pb.swift — that step is gone.
Generated by the Gradle build, from versions pinned in build.gradle.kts rather than inherited
from a consumer, so the artifact is reproducible from that file alone:
| Generator | Version | Output |
|---|---|---|
| protoc | 4.35.1 | java and kotlin builtins, both lite |
| protoc-gen-grpc-java | 1.83.1 | grpc stubs, lite |
| protoc-gen-grpc-kotlin | 1.4.1 | grpckt stubs, lite |
| protoc-gen-validate-kt | 0.1.1 | validate-kt, via the dev.bmcreations.protovalidate plugin in PGV mode |
Two things that are easy to trip over:
- The JVM and Android variants of the protobuf Gradle plugin differ. The JVM variant
registers the
javabuiltin by default; the Android variant does not, which is why the app declaredjavaas a plugin and this repo configures the builtin instead. - Coroutines are an explicit dependency. The generated
grpcktstubs referencekotlinx.coroutines.flow.Flow. The app got that from elsewhere in its graph; a standalone artifact has to declare it, which is whykotlinx-coroutines-coreisapihere. So isprotovalidate-runtime, for the same reason on thevalidate-ktside.
grpc-kotlin sits at 1.4.1 because that is what the Android app generated with at the cutover.
1.5.0 was tested and differs only in line wrapping — 344 lines, no API change — so the bump is
safe, but it belongs in its own commit.
Generated Kotlin is not committed. It is a build input to a published JAR, so the reviewable-diff argument that applies to the Swift below does not apply to it.
Sources/OCPClientProtocol/ is committed, because SPM ships plain source: for the Swift half,
the generated code is the artifact. That makes it possible to change a .proto and forget to
regenerate, so CI re-runs scripts/generate-swift.sh and fails if the tree moves.
The generators are pinned in scripts/toolchain.env, and install-swift-toolchain.sh builds
the plugins against those pins into .tools/:
PROTOC_VERSION=33.1
SWIFT_PROTOBUF_VERSION=1.33.3
GRPC_SWIFT_PROTOBUF_VERSION=2.1.1
GRPC_SWIFT_VERSION=2.2.0
One of those pins is transitive, and that is the whole reason this file exists. The gRPC stub
text is rendered by grpc-swift-2's GRPCCodeGen, which grpc-swift-protobuf pulls in through a
floating from: requirement — so the output moves when grpc-swift-2 releases, with the plugin's
own version unchanged. 2.2.1 added Sendable to the metadata enums; 2.3.0 added type: to every
MethodDescriptor. That was ~840 changed lines across 9 files for no contract change.
brew install protoc-gen-grpc-swift reproduced the committed output in August 2026 and does not
now.
Bumping any of the four is a real change with a real diff. Do it in its own commit and expect
Sources/ to move.
.github/workflows/ci.yml runs on every push and PR — a release should never be the first time
codegen runs against a new contract or toolchain version.
- Kotlin:
./gradlew build(protoc plus compiling the result — if the generated code does not compile, the published JAR is broken) and./gradlew publishToMavenLocal, which catches a missing POM field or an unbuildable sources jar here rather than on the Central upload, where the failure costs a burnt version number. It also writes per-generator file counts to the job summary. - Swift:
swift build, then regenerate andgit diff --quiet -- Sources. The toolchain cache key isscripts/toolchain.env, so a bump rebuilds the plugins and nothing else does.