Skip to content

Kotlin Multiplatform

KMP checks: source-set complexity, target bloat, expect/actual consistency, and cross-module version drift.

15 checks.

kmp-common-code-fragmented

Severity: Low

Fragmenting common code across many intermediate source sets makes it hard to locate logic and raises the cost of every cross-platform change.

How to fix: Collapse redundant intermediate source sets; prefer the default hierarchy template for common→platform sharing.


kmp-deep-source-set-nesting

Severity: Low

Deeply nested intermediate source sets make it hard to know where code belongs and complicate adding/removing a target.

How to fix: Flatten the hierarchy using the Kotlin default hierarchy template instead of hand-rolled intermediate source sets.


kmp-empty-target-source-set

Severity: Low

Empty source-set blocks are dead configuration: they enlarge the source-set graph and confuse readers without holding any platform code.

How to fix: Remove the empty source-set block, or move shared code into it. Default target source sets do not need an explicit empty block.


kmp-excessive-targets

Severity: Low

Every extra target compiles and tests separately — build/CI time and the source-set matrix grow with the target count. Unused targets are pure overhead.

How to fix: Declare only the targets you actually ship. Drop unused platforms (or gate them behind a flag) to shrink the build matrix.


kmp-ios-targets-without-integration

Severity: Low

Apple targets that nothing integrates still cost build/CI time and signal an abandoned or externally-consumed iOS surface that may be unintentional.

How to fix: Add the iOS app/integration (Xcode project, SwiftPM, or CocoaPods consuming the framework), or remove the Apple targets if they are not shipped from this repo.


kmp-kotlin-plugin-version-drift

Severity: Low

Mixed Kotlin compiler versions across modules produce incompatible klib/metadata and fail the multiplatform build or cause subtle ABI mismatches.

How to fix: Pin one Kotlin version (e.g. via a version catalog or the root plugins block) and reference it from every module.


kmp-large-source-set-matrix

Severity: Low

A sprawling source-set matrix increases cognitive load, build graph size, and the chance of misplaced platform code.

How to fix: Consolidate intermediate source sets and rely on the default hierarchy template where possible.


kmp-legacy-native-memory-api

Severity: Low

The new Kotlin/Native memory manager is the default since 1.7.20 and the only one since 1.9.20. freeze()/isFrozen/@SharedImmutable/ensureNeverFrozen are no-ops or removed — the code still compiles but the freezing guarantees are gone. It is dead weight that misleads anyone reasoning about cross-thread sharing.

How to fix: Remove freeze()/isFrozen/ensureNeverFrozen calls and the @SharedImmutable annotation; share state directly under the new memory manager. If you are still on the legacy manager, migrate (kotlin.native.binary.memoryModel=experimental is no longer needed).


kmp-library-version-drift

Severity: Low

Different versions of the same library across modules invite classpath conflicts, duplicated transitive graphs, and runtime errors that depend on resolution order.

How to fix: Declare shared dependency versions once (version catalog / platform BOM) and reference them from all modules.


kmp-missing-actual

Severity: Medium

Every expect needs a matching actual per target. With none, the build fails to compile — or, if hidden by a missing target, ships a broken platform.

How to fix: Provide an actual for each expect in the relevant platform source sets (e.g. androidMain, iosMain), or delete the expectation.


kmp-native-cache-disabled

Severity: Medium–Low

Disabling the cache is the standard workaround for a specific Kotlin/Native compiler bug, and it is almost always meant to be temporary. The bug gets fixed in a later Kotlin release; the property stays. From then on every debug build of the iOS app pays a full recompile that nothing reports as a problem — the build is correct, just minutes slower than it should be, permanently.

How to fix: Remove kotlin.native.cacheKind=none and check whether the compiler bug it worked around is fixed in the Kotlin version now in use. If it is still needed, scope it to the affected target and leave the issue id next to it so it can be retired.


kmp-orphan-actual

Severity: Low

An actual with no corresponding expect is dead or mismatched code — usually a leftover from a removed expectation.

How to fix: Remove the orphan actual, or restore the expect declaration it implements.


kmp-partial-actual-coverage

Severity: Low

A platform source set with expectations upstream but no actuals can fail to compile for that target while others build fine.

How to fix: Confirm each platform implements its expectations (directly or via a shared intermediate source set); add the missing actuals.


kmp-toolchain-version-drift

Severity: Low

Mismatched JVM targets across modules can produce class-version errors at runtime and inconsistent desugaring/behavior.

How to fix: Configure a single jvmToolchain version in a convention plugin or the root build and apply it everywhere.


kmp-transitive-export-enabled

Severity: Medium

The framework and its generated Objective-C header both grow with the whole transitive graph, which slows Xcode down and lengthens link time — and every iOS consumer can now see and call symbols the shared module never intended to publish, so removing the flag later becomes a source-breaking change for them.

How to fix: Drop transitiveExport = true and export(...) exactly the dependencies whose types appear in the shared module's public API. Keep the exported set explicit so an accidental new dependency does not silently widen the framework.