Kotlin Multiplatform: Language, Compilers, and Engineering Practices
The article examines Kotlin Multiplatform’s language design, its three dedicated compilers, artifact structure, and plugin ecosystem, critiques Gradle’s build‑system shortcomings, and details the team’s Bazel migration that delivers faster incremental builds while highlighting trade‑offs and future improvements for Kotlin‑Native, JS inter‑op, and tooling.
This article is the second in a series on Kotlin Multiplatform (KMP) technology and practice. It examines the core technical concepts of KMP and the engineering optimizations applied by a large mobile development team.
Language and Compilation – Programming languages act as a bridge between humans and computers. The article discusses visibility modifiers, the philosophy of Kotlin’s default public visibility, and how KMP’s compilation targets (JVM, Native, JS) affect visibility and inter‑op requirements.
Compilers – KMP uses three dedicated compilers: Kotlin‑JVM, Kotlin‑Native, and Kotlin‑JS. Traditional cross‑platform languages either emit machine code (e.g., C++, Rust) or bytecode (e.g., Java, C#). KMP balances native performance and consistency by providing separate compilers for each target, at the cost of platform‑specific behavior and annotation overhead.
Kotlin/Native – The toolchain includes kotlin-native (LLVM‑based frontend and native stdlib), cinterop (generates .klib bindings from C/ObjC headers), and the klib/platform directory (pre‑bundled bindings for Android, iOS, Linux, etc.). Advantages: seamless inter‑op in practice. Drawbacks: tight coupling between Kotlin version and platform SDKs, and longer compilation times compared with JVM.
Kotlin/JS – Inter‑op with JavaScript/TypeScript is challenging due to mismatched type systems (e.g., literal types, Long). Existing tools such as dukat and karakum are limited, leading the team to rely on karakum with many patches. Additional problems include lack of precise strip capabilities, symbol‑name clashes (see JsExport‑declaration‑name‑clash‑with‑ES‑modules ), and monolithic .d.ts generation that prevents modular consumption.
Artifacts – .klib – A .klib contains Kotlin IR and platform‑specific binaries. An example layout is shown below:
skiko.klib
└── default # compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinLibraryLayout.kt
├── ir # compiler/ir/serialization.common/src/KotlinIr.proto (Kotlin IR files)
│ ├── bodies.knb
│ ├── debugInfo.knd
│ ├── files.knf
│ ├── irDeclarations.knd
│ ├── signatures.knt
│ ├── strings.knt
│ └── types.knt
├── linkdata # Kotlin type metadata for linking and IDE hints
│ ├── module
│ ├── package_org
│ │ └── 0_org.knm
│ ├── package_org.jetbrains
│ │ └── 0_jetbrains.knm
│ ├── package_org.jetbrains.skia
│ │ ├── 0_skia.knm
│ │ └── 7_skia.knm
│ ├── package_org.jetbrains.skia.icu
│ │ ├── 0_icu.knm
│ │ └── 1_icu.knm
│ └── root_package
│ └── 0_.knm
├── manifest
├── resources
└── targets # compiled binaries per platform (e.g., ios_arm64)Plugins and Compiler Extensions – Kotlin’s plugin ecosystem (KCP, KSP, Compose compiler, Serialization) supplements the language. The article compares Kotlin’s restrained evolution with Swift’s rapid “evolution” process and notes that KCP tightly couples to the compiler, making ABI compatibility costly.
Gradle Build System – Gradle’s phases (initialization, configuration, execution) and task‑based dependency model are described. Limitations highlighted include missing toolchain support for non‑JVM targets, lack of platform abstractions, absence of sandboxing, and the difficulty of handling variants and feature flags in large monorepos.
Sample Gradle task definition:
tasks.register('hello') {
doLast {
println 'Hello world!'
}
}
tasks.register('intro') {
dependsOn tasks.hello
doLast {
println "I'm Gradle"
}
}The article argues that Gradle’s “weak‑type” task dependencies hide input‑output relationships, leading to unpredictable builds, cache invalidation, and tooling friction.
Bilibili’s Engineering Solution – To overcome Gradle’s shortcomings, the team migrated the KMP build to Bazel, creating custom rules for Kotlin, Gradle‑metadata handling, and cross‑platform inter‑op. Benefits include fast incremental builds (≈1 minute for full KMP CI), flexible feature‑flag management, static build graphs, and improved two‑way inter‑op without extra configuration.
Trade‑offs of the Bazel migration are also discussed: learning curve, limited plugin ecosystem, and challenges with Apple toolchains and Kotlin‑Core’s ambiguous outputs.
Future Outlook – The article calls for native support of implementation dependencies in Kotlin‑Native, better module‑level artifact splitting, improved debug‑info handling, and a more open Kotlin LSP for non‑IntelliJ editors.
In summary, the piece provides a deep engineering analysis of KMP’s language, compiler, and build‑system landscape, proposes concrete Bazel‑based solutions, and outlines directions for the Kotlin ecosystem to better serve large‑scale mobile development.
Bilibili Tech
Provides introductions and tutorials on Bilibili-related technologies.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.