kotlin/kotlin-tooling-native-build-performance
> Diagnoses and fixes slow Kotlin/Native compilation and linking in Kotlin Multiplatform projects that target iOS. Use when the user reports slow iOS or shared-framework builds, long linkDebug*/linkRelease* or XCFramework tasks, cold CI builds that re-download the Kotlin/Native toolchain, KSP or other generated code on the native path, transitiveExport usage, or asks for a local-development versus CI build performance plan.
npx skills add https://github.com/Kotlin/kotlin-agent-skills --skill kotlin-tooling-native-build-performance
Turn "the iOS build is slow" into a measured diagnosis and a small set of safe
fixes. Two rules apply throughout:
change what CI publishes.
An unmeasured fix is a guess.
Establish four facts before editing anything: where (local or CI),
what (debug feedback loop or release/distribution artifact), state
(first build, clean, warm, or no-op), and phase (which tasks dominate the
log). Then match the dominant symptom:
| Symptom in the build log | Likely cause | Read |
|---|---|---|
| linkRelease* or *ReleaseXCFramework tasks in a local development loop | Building distribution artifacts for development | artifacts-and-targets |
| Kotlin/Native compiler distribution downloaded on every CI run | ~/.konan not preserved between runs | caching-and-gradle |
| Long pause before the first task starts | Configuration phase, no configuration cache | caching-and-gradle |
| All iOS targets build when only one simulator is needed | Broad task (build, assemble, assemble*XCFramework) or unused targets | artifacts-and-targets |
| ksp* tasks ahead of compileKotlinIos* | Generated-code work on the native path | exports-and-generated-code |
| Small source edit recompiles and relinks everything | Compiler caches disabled, or missing incrementality | caching-and-gradle, experimental |
| Machine overloaded while several link* tasks run at once | Parallel native linking | caching-and-gradle, worker-limit caveat |
scripts/audit-native-build.sh /path/to/project
It is read-only and prints file:line findings (disabled caches, broad
local tasks, transitiveExport, broad KSP configuration, missing CI
.konan cache), each pointing at the reference file with the fix.
Findings are leads, not verdicts — confirm each against project policy.
Gradle invocation inside an Xcode build phase. Optimize that command, not
a task you picked yourself.
components and fills caches; only the second and later runs are
representative. Attribute time per task before blaming the compiler:
kotlin.build.report.output=file # writes build/reports/kotlin-build/
Gradle's --scan or --profile work too.
scans, or checked-in metrics instead — and state explicitly that the
conclusion is static.
Apply fixes one at a time, re-measuring as you go:
Gradle build and configuration caches, keep ~/.konan warm in CI, update
Kotlin: references/caching-and-gradle.md
correct integration method, justified target matrix:
references/artifacts-and-targets.md
transitiveExport, narrowexport(...), scope KSP work to the native compilations that need it:
references/exports-and-generated-code.md
references/experimental.md
A developer on an Apple Silicon Mac complains that "every shared-module
change costs 12 minutes". Their loop runs ./gradlew :shared:assembleXCFramework.
A build scan of the second (warm) run shows:
:shared:linkReleaseFrameworkIosArm64 348s
:shared:linkReleaseFrameworkIosX64 341s
:shared:compileKotlinIosX64 96s
:shared:linkDebugFrameworkIosSimulatorArm64 41s
:shared:compileKotlinIosSimulatorArm64 38s
configuration phase 64s
Reasoning chain:
linkRelease* —release linking is an order of magnitude slower than debug and only CI
needs it. Replace the local command with
:shared:linkDebugFrameworkIosSimulatorArm64 (or the Xcode embed task if
Xcode drives the build). *(artifacts-and-targets)*
iosX64 work serves Intel simulators; ask whether the team stillsupports them before removing the target. *(artifacts-and-targets)*
org.gradle.configuration-cache=true once trialed. *(caching-and-gradle)*
confirm by re-running the new command twice and comparing.
assembleXCFramework untouched; note that explicitly in thereport.
build, not warm against cold.
linkRelease*,*ReleaseXCFramework, or removed generator tasks.
scripts/audit-native-build.sh reports no findings you have notconsciously accepted and documented.
Close with a short performance note:
evidence — or a statement that the analysis was static.
policy assumption, worker limits, or generated-code work deferred.
| Topic | Link |
|---|---|
| Improving Kotlin/Native compilation time | https://kotlinlang.org/docs/native-improving-compilation-time.html |
| Kotlin Gradle plugin compilation and caches | https://kotlinlang.org/docs/gradle-compilation-and-caches.html |
| iOS integration methods | https://kotlinlang.org/docs/multiplatform-ios-integration-overview.html |
| Direct integration with Xcode | https://kotlinlang.org/docs/multiplatform/multiplatform-direct-integration.html |
| Building final native binaries and XCFrameworks | https://kotlinlang.org/docs/multiplatform/multiplatform-build-native-binaries.html |
| Kotlin/Native binary options | https://kotlinlang.org/docs/native-binary-options.html |
| KSP with Kotlin Multiplatform | https://kotlinlang.org/docs/ksp-multiplatform.html |
Take kotlin/kotlin-tooling-native-build-performance 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.