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
By kotlin · 492 installs
npx skills add kotlin/kotlin-agent-skills --skill kotlin-tooling-native-build-performance
Source repository · Upstream listing
Kotlin/Native Build Performance
Turn "the iOS build is slow" into a measured diagnosis and a small set of safe
fixes. Two rules apply throughout:
1. Never trade away required release behavior. A faster local loop must not
change what CI publishes.
2. Measure before and after with the same command and the same build state.
An unmeasured fix is a guess.
Step 0: Classify the Slow Scenario
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](references/artifacts and targets.md)
Kotlin/Native compiler distribution downloaded on every CI run ~/.konan not preserved between runs [caching and gradle](references/caching and gradle.md)
Long pause before the first task starts Configuration phase, no configuration cache [caching and gradle](references/caching and gradle.md)
All iOS targets build when only one simulator is needed Broad task ( build , assemble , assemble XCFramework ) or unused targets [artifacts and targets](references/artifacts and targets.md)
ksp tasks ahead of compileKotlinIos Generated code work on the native path [exports and generated code](references/exports and generated code.md)
Small source edit recompiles and relinks everything Compiler caches disabled, or missing incrementality [caching and gradle](references/caching and gradle.md), [experimental](references/experimental.md)
Machine overloaded while several link tasks run at once Parallel native linking [caching and gradle](references/caching and gradle.md), worker limit caveat
Step 1: Audit and Measure
1. Run the static audit from the project root:
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.
2. Find the command the user actually waits for: a script, a CI step, or the
Gradle invocation inside an Xcode build phase. Optimize that command, not
a task you picked yourself.
3. Run it twice when practical. The first build downloads Kotlin/Native
components and fills caches; only the second and later runs are
representative. Attribute time per task before blaming the compiler:
Gradle's scan or profile work too.
4. If you cannot run the build (no macOS host, no Xcode), analyze logs, build
scans, or checked in metrics instead — and state explicitly that the
conclusion is static.
Step 2: Fix in Safe Order
Apply fixes one at a time, re measuring as you go:
1. Restore healthy defaults — remove cache/daemon workarounds, enable
Gradle build and configuration caches, keep ~/.konan warm in CI, update
Kotlin: [references/caching and gradle.md](references/caching and gradle.md)
2. Build only what the feedback loop needs — one specific task per loop,
correct integration method, justified target matrix:
[references/artifacts and targets.md](references/artifacts and targets.md)
3. Cut export and generated code cost — drop transitiveExport , narrow
export(...) , scope KSP work to the native compilations that need it:
[references/exports and generated code.md](references/exports and generated code.md)
4. Experimental switches last, with the user's agreement :
[references/experimental.md](references/experimental.md)
Worked Example
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:
Reasoning chain:
The loop is local + debug + warm , but ~690s goes to 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)
All iosX64 work serves Intel simulators; ask whether the team still
supports them before removing the target. (artifacts and targets)
64s of configuration on every run disappears behind
org.gradle.configuration cache=true once trialed. (caching and gradle)
Expected loop after the change: ~40s compile + ~40s link on warm builds —
confirm by re running the new command twice and comparing.
CI keeps assembleXCFramework untouched; note that explicitly in the
report.
Verify
[ ] Re run the exact baseline command; compare warm build against warm
build, not warm against cold.
[ ] Second run with the configuration cache reports it is being reused.
[ ] The local development log no longer contains linkRelease ,
ReleaseXCFramework , or removed generator tasks.
[ ] CI still produces every required release artifact, unchanged.
[ ] Tests pass and the app still runs from Xcode.
[ ] scripts/audit native build.sh reports no findings you have not
consciously accepted and documented.
Report Your Changes
Close with a short performance note:
The slow scenario (local/CI, debug/release, cold/warm) and the measured
evidence — or a statement that the analysis was static.
Each change, and why it is safe for release behavior.
The before/after commands the user can run to confirm the win.
Remaining tradeoffs: experimental flags enabled, targets removed under a
policy assumption, worker limits, or generated code work deferred.
Links to the relevant official documentation below.
Official Documentation
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