kotlin/update-shim
> Modify, build, publish, and verify the Kotlin/Native shim artifacts (gRPC shim and protobuf shim) in native-deps/shims/. Use this skill whenever the user wants to change shim C/C++ source code, update shim headers or .def files, bump a shim upstream version, add or remove archives from the overlap excludes list, patch KLIB metadata, update the annotation module, run shim fixture tests, publish shim artifacts locally, or debug any native shim build failure. Also trigger when the user mentions "shim", "cinterop", "native interop", "grpc shim", "protobuf shim", "KLIB patcher", "overlap archives", "native-deps", "Bazel shim build", or "shim verification".
npx skills add https://github.com/Kotlin/kotlinx-rpc --skill update-shim
Native shims are Kotlin/Native cinterop wrappers around third-party C/C++ libraries
(gRPC core and protobuf wire). They live in native-deps/shims/ as a **standalone
Gradle build** (separate from the main kotlinx-rpc project). Each shim produces a
patched KLIB that requires explicit @OptIn to use, keeping native internals out
of the public API surface.
Use the running_gradle_builds skill for all Gradle build/publish tasks and the
running_gradle_tests skill for all Gradle test tasks. Do NOT run ./gradlew
directly via shell.
Because the shims build is a standalone Gradle project, you must set projectRoot
to the absolute path of native-deps/shims/ when invoking Gradle skills for shim
tasks. For verification tasks that run in the main kotlinx-rpc project, use the
default projectRoot (omit it or point to the repo root).
native-deps/shims/
grpc/ # :kotlinx-rpc-grpc-core-shim
src/cpp/kgrpc.cpp # C shim wrapper source
src/nativeInterop/cinterop/
grpcCoreInterop.def # CInterop definition (template — staticLibraries injected at build)
include/kgrpc.h # Shim header
BUILD.bazel # Bazel target :grpc_shim
MODULE.bazel # Bazel module (GRPC_VERSION synced from version catalog)
overlap-archive-excludes.txt # Archives excluded because protobuf-shim already provides them
tools/analyze_overlap.py # Symbol overlap analysis helper
build.gradle.kts
protobuf/ # :kotlinx-rpc-protobuf-shim
src/cpp/protowire.cpp # C shim wrapper source
src/nativeInterop/cinterop/
libprotowire.def # CInterop definition (per-target static libs listed inline)
include/protowire.h # Shim header
BUILD.bazel # Bazel target :protowire_fat
MODULE.bazel # Bazel module (PROTOBUF_VERSION synced from version catalog)
build.gradle.kts
annotation/ # :kotlinx-rpc-native-shims-annotation
src/commonMain/kotlin/
kotlinx/rpc/grpc/internal/shim/InternalNativeRpcApi.kt
kotlinx/rpc/protobuf/internal/shim/InternalNativeProtobufApi.kt
build.gradle.kts
klib-patcher/ # Internal build tool (not published)
src/main/kotlin/.../KlibPatcher.kt
tests/ # Gradle TestKit fixture tests
src/test/kotlin/...
settings.gradle.kts
gradle.properties
native-deps/scripts/
build_target.sh # Runs Bazel for one target, copies static library out
The build helper build_target.sh lives in the sibling native-deps/scripts/
directory (alongside the other native-deps utility scripts). It is still invoked
from each shim module directory (which owns the Bazel workspace) by the
registerNativeShimBazelBuildTask Gradle helper.
The native-deps/grpc-c-prebuilt/ sibling directory publishes raw .a static
archives and headers consumed by the gRPC shim. The protobuf shim builds its own
fat archive via Bazel directly.
build_target.sh wraps bazel build)..def file + headers + static libs.@RequiresOptIn annotations on alldeclarations, so consumers must @OptIn(InternalNativeRpcApi::class) (or the protobuf
equivalent) to use them.
Versions live in versions-root/libs.versions.toml:
| Alias | Current | Format |
|---|---|---|
| internal-native-grpc-shim | 1.80.0-1 | <upstream-grpc-version>-<shim-revision> |
| internal-native-protobuf-shim | 34.0-1 | <upstream-protobuf-version>-<shim-revision> |
| internal-native-shim-annotation | 0.1.0 | Independent semver |
When making shim code changes (not upstream bumps), bump only the shim revision
(the part after the dash). When bumping the upstream library version, update the
base version and reset the shim revision to 1.
The base version is automatically synced to the corresponding MODULE.bazel file
(GRPC_VERSION or PROTOBUF_VERSION variable) by a Gradle task at build time.
The shim C/C++ source lives alongside its Bazel build:
grpc/src/cpp/kgrpc.cpp + grpc/include/kgrpc.hprotobuf/src/cpp/protowire.cpp + protobuf/include/protowire.hAfter editing:
.def fileto reflect the new API surface. The .def file controls what CInterop exposes
to Kotlin.
versions-root/libs.versions.toml.grpc/src/nativeInterop/cinterop/grpcCoreInterop.def — this is atemplate. The __STATIC_LIBRARIES__ placeholder is replaced at build time
with the full archive list from the unpacked gRPC bundle. Edit filter rules,
header includes, strict enums, and noStringConversion entries here.
protobuf/src/nativeInterop/cinterop/libprotowire.def — this filelists per-target staticLibraries inline (one staticLibraries.{target} entry
per native target). Edit header includes and filter rules here.
Annotations live in annotation/src/commonMain/kotlin/. Both are @RequiresOptIn(level = ERROR).
Changes here are rare — typically only when adding a new shim family.
klib-patcher/src/main/kotlin/kotlinx/rpc/nativedeps/tooling/KlibPatcher.kt
patches KLIB metadata post-cinterop. It uses kotlinx-metadata-klib to rewrite
.knm files, adding the opt-in annotation to every declaration in the target
package. Changes here are delicate — test thoroughly.
gRPC and protobuf both bundle native Abseil/protobuf archives. When a final binary
links both shims, duplicate symbols can cause linker failures (especially on Linux).
Overlap excludes file: grpc/overlap-archive-excludes.txt
all:<archive> (every target) or <target>:<archive> (target-specific)lib/foo/bar.a) or flattenedinterop form (foo+__bar.a)
utf8_range from gRPC shim since protobuf-shim provides itSymbol rewrite workaround (Linux only, temporary — remove when protobuf becomes
Kotlin-only): The protobuf shim build rewrites AbslInternalGetFileMappingHint to
KrpcProtobuf_AbslInternalGetFileMappingHint using llvm-objcopy --redefine-sym.
This lives in protobuf/build.gradle.kts.
To investigate new overlaps:
cd native-deps/shims
python3 grpc/tools/analyze_overlap.py \
--grpc-klib <path-to-grpc-cinterop-klib> \
--protobuf-klib <path-to-protobuf-cinterop-klib> \
--target linux_x64
Use --write-excludes <file> --exclude-scope <target> to auto-append entries.
versions-root/libs.versions.toml (e.g., changeinternal-native-grpc-shim from 1.80.0-1 to 1.81.0-1).
MODULE.bazel version variable is synced automatically by thesyncGrpcVersionToBazelModule / syncProtobufVersionToBazelModule Gradle task.
grpc-c-prebuilt version if the prebuilt archiveversion needs to match.
All tasks in this section target the shims standalone build (projectRoot: native-deps/shims/).
Prerequisite: Bazel (or Bazelisk) must be on PATH. On macOS, Xcode (not just
CommandLineTools) should be installed for cross-compilation to iOS/tvOS/watchOS.
:kotlinx-rpc-grpc-core-shim:publishAllPublicationsToNativeDepsBuildRepoRepository :kotlinx-rpc-native-shims-annotation:publishAllPublicationsToNativeDepsBuildRepoRepository
:kotlinx-rpc-protobuf-shim:publishAllPublicationsToNativeDepsBuildRepoRepository :kotlinx-rpc-native-shims-annotation:publishAllPublicationsToNativeDepsBuildRepoRepository
Published artifacts land in native-deps/build/repo/. The main kotlinx-rpc project
is configured to resolve from this path during development.
Task: :kotlinx-rpc-grpc-core-shim:publishAllPublicationsToNativeDepsBuildRepoRepository :kotlinx-rpc-protobuf-shim:publishAllPublicationsToNativeDepsBuildRepoRepository :kotlinx-rpc-native-shims-annotation:publishAllPublicationsToNativeDepsBuildRepoRepository
:kotlinx-rpc-grpc-core-shim:assemble:kotlinx-rpc-protobuf-shim:assembleThe tests/ module uses Gradle TestKit to publish shim artifacts to a temporary
verification repo and compile throwaway consumer projects against them.
Via running_gradle_tests (projectRoot: native-deps/shims/), task: :tests:test
This runs 8 test cases (4 per shim):
| Test | What it verifies |
|--------------|--------------------------------------------------------------------------------------------------------|
| negative | Code using shim symbols without @OptIn fails to compile with the expected diagnostic message |
| positive | Code with @OptIn(InternalNativeRpcApi::class) compiles successfully |
| scope | Unrelated native code is unaffected by shim markers |
| artifact | Published KLIB manifest contains the annotation dependency and .knm metadata carries annotation markers |
Single test category examples:
:tests:test --tests "*grpcNegativeTest":tests:test --tests "*protobufArtifactTest"After publishing locally, build the shim consumers in the main project
(default projectRoot) via running_gradle_builds:
:protobuf:protobuf-lite:compileKotlinMacosArm64:grpc:grpc-core:compileKotlinMacosArm64Use whichever native target matches your host. If both compile cleanly, the shim
changes integrate correctly with the rest of the project.
If you modified archive contents, overlap excludes, or bumped an upstream version:
cd native-deps/shims
python3 grpc/tools/analyze_overlap.py \
--grpc-klib build/classes/kotlin/macosArm64/main/cinterop/cinterop-grpcCoreInterop.klib \
--protobuf-klib build/classes/kotlin/macosArm64/main/cinterop/cinterop-libprotowire.klib \
--target macos_arm64
Check Linux targets separately (they have the KRPC-540 symbol rewrite):
python3 grpc/tools/analyze_overlap.py \
--grpc-klib build/classes/kotlin/linuxX64/main/cinterop/cinterop-grpcCoreInterop.klib \
--protobuf-klib build/classes/kotlin/linuxX64/main/cinterop/cinterop-libprotowire.klib \
--target linux_x64
Bazel not found: Install Bazelisk (brew install bazelisk on macOS) — it
manages Bazel versions automatically.
Xcode issues on macOS: The build needs full Xcode for Apple platform SDKs.
If DEVELOPER_DIR is not set, the build script defaults to
/Applications/Xcode.app/Contents/Developer.
KONAN_HOME resolution: The build downloads Kotlin/Native automatically via the
:core:downloadKotlinNativeDistribution task from the main project. If this fails,
check that the main project builds first.
Linux symbol collision at link time: If you see duplicate
AbslInternalGetFileMappingHint errors, the KRPC-540 workaround in
protobuf/build.gradle.kts may need updating. Check that llvm-objcopy is
available (the build searches Konan dependencies, then PATH).
KLIB patcher failures: Usually caused by a Kotlin version mismatch in the
metadata API. Check that klib-patcher/build.gradle.kts dependencies match the
Kotlin version used by the shim build.
Clean rebuild (projectRoot: native-deps/shims/):
clean taskbazel clean --expunge via shell for Bazel cache:kotlinx-rpc-grpc-core-shim:assemble taskTake kotlin/update-shim from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference brew.
Without those the skill loads but fails at the first command.