What is the default hierarchy template in Kotlin Multiplatform, and how does it wire intermediate source sets like `nativeMain` and `appleMain` automatically?
answer
- applyDefaultHierarchyTemplate, auto since 1.9.20
- nativeMain > appleMain > iosMain chain
- Intermediate set only if >=2 child targets
- Declaring targets triggers it
- actual can sit at any level up the chain
basics
~10 sWhen you declare targets, Kotlin auto-creates shared groups like nativeMain and appleMain and links them between common and each platform, so you do not have to wire them by hand.
solid answer
~40 sThe **default hierarchy template** is a built-in source-set graph that the Kotlin Gradle plugin applies automatically (since Kotlin 1.9.20) once you declare your targets. Instead of you manually calling `dependsOn`, declaring e.g. `iosArm64()`, `iosX64()`, `macosArm64()`, `jvm()` makes the plugin create intermediate source sets — `appleMain` (parent of all Apple targets), `nativeMain` (parent of all Kotlin/Native targets), `iosMain` (parent of the iOS variants) — and wire the chain `iosArm64Main → iosMain → appleMain → nativeMain → commonMain`. Each intermediate set only appears if two or more of its children targets exist. You can write platform-shared code (e.g. POSIX or Apple Foundation calls) in `nativeMain`/`appleMain` without duplicating it per leaf. You enable/customize it via `applyDefaultHierarchyTemplate()` or by defining a custom template; the plugin warns if you also wire `dependsOn` manually in a conflicting way.
code
kotlin · 9 lineskotlin {
applyDefaultHierarchyTemplate() // explicit; usually implicit
iosArm64(); iosSimulatorArm64(); macosArm64()
sourceSets {
// appleMain materializes because >=2 Apple targets exist
appleMain.dependencies { /* Foundation-shared code lives here */ }
}
}go deeper
Knows there is an automatic setup so you don't wire every source set by hand.
Names applyDefaultHierarchyTemplate, the nativeMain/appleMain/iosMain chain, and that declaring targets triggers it.
Explains the >=2-children rule, that actuals can live at any intermediate level, and how compilation gathers the chain per leaf.
Decides when to extend vs. replace the template, weighs maintainability of intermediate grouping, and reasons about migration from manual wiring across a large module graph.
## The problem it solves Before the template, you wrote `dependsOn` by hand for every shared group, e.g. creating an `iosMain` and pointing `iosArm64Main`, `iosX64Main`, `iosSimulatorArm64Main` at it, and that at `commonMain`. Tedious and error-prone. ## What the default hierarchy template is Since **Kotlin 1.9.20** the Kotlin Gradle plugin ships a **default hierarchy template** — a predefined map from groups of targets to intermediate source sets. When you declare your targets, the plugin **applies it automatically** (you can also call `applyDefaultHierarchyTemplate()` explicitly). It creates and wires intermediate source sets so each leaf inherits a sensible chain up to `commonMain`. ```kotlin kotlin { // declaring targets is enough to trigger the template jvm() iosArm64() iosSimulatorArm64() macosArm64() // Auto-created/wired (if >=2 children exist): // iosArm64Main -> iosMain // iosSimulatorArm64Main -> iosMain // iosMain -> appleMain // macosArm64Main -> appleMain // appleMain -> nativeMain // nativeMain -> commonMain // jvmMain -> commonMain } ``` ## The standard intermediate groups - **`nativeMain`** — parent of ALL Kotlin/Native targets (iOS, macOS, watchOS, tvOS, linux, mingw). Good for POSIX-level shared code. - **`appleMain`** — parent of all Apple targets (iOS/macOS/watchOS/tvOS). Good for Foundation/Darwin shared code. - **`iosMain`** — parent of the iOS device + simulator variants. - Test mirrors exist too: `nativeTest`, `appleTest`, `iosTest`. ## The "only-if-2+-children" rule An intermediate set materializes **only when at least two of its child targets are declared**. If you declare a single `iosArm64()` and nothing else Apple, the plugin won't bother creating `appleMain` (there is nothing to share). This keeps the graph minimal. ## Why each leaf sees the right expect/actual set Because the chain is transitive, when `iosArm64` is compiled the compiler gathers `commonMain + nativeMain + appleMain + iosMain + iosArm64Main`. An `expect` in `commonMain` can be satisfied by an `actual` placed at ANY level on that chain — e.g. one `actual` in `appleMain` covers all Apple leaves, avoiding duplication. ## Customizing / disabling - `applyDefaultHierarchyTemplate { /* custom */ }` to extend it. - If you set up conflicting manual `dependsOn`, the plugin emits a warning; the recommended path is to rely on the template and only add custom intermediate sets when you need a grouping the template doesn't provide.
- Why might `appleMain` not exist in a project that only targets `iosArm64`?The intermediate set is created only when at least two child targets are present; a single Apple target gives nothing to share, so the plugin skips it.
- Where can an `actual` for a common `expect` live if it should be shared by all Apple targets?In `appleMain` — because every Apple leaf transitively dependsOn it, one `actual` there covers them all.
saying these in an interview costs you the question
- Thinking you must always wire dependsOn manually
- Not knowing the >=2-children materialization rule
- Claiming nativeMain includes the JVM target
- Believing actuals can only live in leaf source sets
- Confusing appleMain with iosMain (appleMain is broader)