Back to Runanywhere Sdks

Generated code contract

docs/reference/generated-code-contract.md

0.20.255.3 KB
Original Source

Generated code contract

Linked from AGENTS.md. Read this before touching idl/*.proto or debugging a build that can't find generated types.

Nothing generated is tracked

A fresh clone has no C++, Kotlin, Swift, TypeScript, Dart, React Native or Python bindings until codegen runs. ./scripts/setup/setup.sh runs it first for exactly that reason; ./run codegen runs it on demand. The hooks below mean almost nobody has to know that.

treewho generates itwhen
core/src/generated/proto/ (76 files, ~336k lines)core/CMakeLists.txt, at configure time when the files are absentevery cmake --preset …, i.e. all ~29 native CI runner instances, the Electron addon, the Python wheel, rcli and WASM
core/include/rac/rac_defaults_generated.hsame blocksame. A SHIPPED public header: install(DIRECTORY include/) puts it in the XCFramework Headers/ and the Linux/Windows dist, and five shipped rac_{llm,stt,tts,vad,vlm}_types.h #include it — so it must exist before packaging, which configure time guarantees
bindings/kotlin/.../sdk/generated/ (373 files)the generateIdlKotlinBindings Gradle task, wired into preBuildevery assemble* / compile*Kotlin / test* / ktlint / detekt, including JitPack
bindings/swift/Sources/RunAnywhere/Generated/sync-dist-repo.shships in the SwiftPM tag
bindings/proto-ts/src/ and dist/each package-sdk.shdist ships in 7 npm packages
bindings/flutter/packages/runanywhere/lib/generated/bindings/flutter/scripts/package-sdk.shships in the pub package
the two RADefaultsPool.kt under flutter/ and react-native/the same packaging scriptsship inside the pub / npm packages
bindings/python/runanywhere/_proto/, _generated_{errors,defaults}.pythe in-tree PEP 517 backendship in the sdist + wheel

Two CI jobs read generated C/C++ without configuring CMake and therefore carry an explicit generate-idl step with cpp: pr-build.rn-typecheck (-fsyntax-only over core/include) and release.native_rcli_macos (swift build over the root Package.swift).

idl/codegen/generated_trees.txt is the machine-readable version of that table, plus the eight hand-written files that live inside those trees and stay tracked (the .gitignore negations exist for them, and check_generated_trees.sh fails if one ever stops being tracked — and fails the other way if a generated file becomes tracked).

The toolchain is downloaded, not assumed

protoc stamps its own patch version into every C++ header (#if PROTOBUF_VERSION != 7035001) and every ts-proto banner, and Wire renames files between releases, so the output is a function of the tool versions and not only of the schemas. The package managers this repo would otherwise reach for do not offer that guarantee — brew install protobuf gives whatever is current, apt-get install protobuf-compiler gives whatever the distro froze, neither selects a per-platform archive by checksum, and Homebrew's wire is a different product entirely. protobuf and Maven Central both publish immutable per-platform archives, so the pins are obtainable:

scriptresolvespinned byverified against
idl/codegen/bootstrap_protoc.shprotoccore/VERSIONS::PROTOC_VERSIONidl/codegen/protoc.sha256
idl/codegen/bootstrap_wire.shwire-compilercore/VERSIONS::WIRE_VERSIONidl/codegen/wire.sha256
idl/codegen/bootstrap_pyproto.sha python3 with google.protobuf + yamlcore/VERSIONS::PYTHON_PROTOBUF_VERSIONpip, into a cached venv

Each prints one path on stdout, uses a matching tool already on PATH when there is one, caches under ${XDG_CACHE_HOME:-~/.cache}/runanywhere/, and refuses to install anything whose checksum is not recorded — so bumping a pin without refreshing the .sha256 file is a hard error rather than an unverified download. RAC_PROTOC / RAC_WIRE_COMPILER / RAC_PYTHON override; RAC_PROTOC_NO_DOWNLOAD=1, RAC_WIRE_NO_DOWNLOAD=1 and RAC_PY_NO_INSTALL=1 make an air-gapped host fail loudly instead of reaching out.

Every publish path generates before packaging

A de-committed tree that ships inside an artifact must exist at pack time or the published package is broken in a way that no build step notices — npm pack packs an empty dist/, flutter pub publish --dry-run validates a package with no lib/generated/, and a Python wheel installs fine and fails at import. So each packaging script calls idl/codegen/ensure_generated.sh --only <lang> first, and the Python SDK carries an in-tree PEP 517 backend (bindings/python/_build/) so even a bare pip install cannot skip it.

Schema version

idl/VERSION is hand-maintained semver for the .proto surface; idl/SCHEMA_LOCK is machine-written by generate_all.sh and records a digest of every idl/*.proto. Because it is tracked and the bindings are not, the lock is the drift signal: editing a schema without re-running codegen leaves it stale, and CI fails. Changing the schema without bumping idl/VERSION also fails.

bash
./idl/codegen/schema_lock.sh --print   # which IDL is this checkout?
./idl/codegen/ci-drift-check.sh        # the whole gate, exactly as CI runs it

CI idl-drift-check.yml is generate, then verify — not "regenerate and diff", which cannot fail for an ignored file.