What is the KMP default hierarchy template, and when would you customize intermediate source sets with applyDefaultHierarchyTemplate or manual dependsOn?
answer
- default hierarchy template auto-applied
- nativeMain / appleMain / iosMain intermediates
- applyDefaultHierarchyTemplate() / applyHierarchyTemplate { }
- manual dependsOn for non-standard groups (jvm+android)
- intermediate needs >=2 member targets; keep graph a DAG
basics
~10 sThe default hierarchy template auto-creates intermediate source sets (like nativeMain, appleMain, iosMain) that group related targets so you can share code among a subset of platforms without wiring them by hand.
solid answer
~50 sModern Kotlin applies the **default hierarchy template** automatically when you declare targets, generating intermediate source sets such as `nativeMain` (all Kotlin/Native), `appleMain` (iOS+macOS+tvOS+watchOS), and `iosMain` (all iOS variants) with the correct `dependsOn` edges. This lets you write code shared by, say, all Apple targets in `appleMain` instead of duplicating it per leaf. You can invoke `applyDefaultHierarchyTemplate()` explicitly, or define a custom hierarchy via `applyHierarchyTemplate { }`. When the template doesn't fit — e.g. you want a set shared by `jvm` and `android` only — you create a source set and wire it manually with `dependsOn`. A caveat: the default template only materializes an intermediate set if you've declared **two or more** of its member targets; declaring a single iOS target won't create a meaningful `iosMain` grouping beyond the platform set itself. Custom dependsOn graphs must remain a DAG and avoid clashing with the template.
code
kotlin · 11 lineskotlin {
jvm()
androidTarget()
sourceSets {
val jvmAndAndroidMain by creating {
dependsOn(commonMain.get())
}
jvmMain { dependsOn(jvmAndAndroidMain) }
androidMain { dependsOn(jvmAndAndroidMain) }
}
}go deeper
Aware that intermediate source sets exist to share code among related platforms.
Knows the default template creates nativeMain/appleMain/iosMain and reduces duplication.
Customizes hierarchies with applyHierarchyTemplate or manual dependsOn and respects the DAG/two-member constraints.
Defines a project-wide source-set strategy balancing sharing, build complexity, and template-vs-manual trade-offs across many modules.
## The problem intermediate source sets solve With only `commonMain` and leaf platform sets, code shared by *some but not all* targets (e.g. all Apple platforms) would have to be duplicated in each leaf. **Intermediate source sets** sit between `commonMain` and the leaves to hold that partially-shared code. ## The default hierarchy template Current Kotlin **automatically applies a default hierarchy template** based on the targets you declare. It creates well-known intermediate sets and wires their `dependsOn` edges: - `nativeMain` — shared by all Kotlin/Native targets. - `appleMain` — shared by iOS, macOS, tvOS, watchOS. - `iosMain` — shared by `iosArm64`, `iosSimulatorArm64`, `iosX64`. Each leaf (e.g. `iosArm64Main`) `dependsOn` the matching group (`iosMain` → `appleMain` → `nativeMain` → `commonMain`), forming a hierarchy. ```kotlin kotlin { iosArm64() iosSimulatorArm64() macosArm64() // default hierarchy gives you iosMain, appleMain, nativeMain automatically sourceSets { appleMain.dependencies { // code/deps shared by all Apple targets } } } ``` ## Controlling the template - `applyDefaultHierarchyTemplate()` — apply (or re-affirm) the built-in template explicitly. - `applyHierarchyTemplate { ... }` — define a **custom** template describing your own groupings. ## When to wire dependsOn manually The template covers standard platform families. For non-standard groupings — e.g. a set shared by **jvm + android** only — you create the source set and connect it yourself: ```kotlin kotlin { jvm(); androidTarget() sourceSets { val jvmAndAndroidMain by creating { dependsOn(commonMain.get()) } jvmMain { dependsOn(jvmAndAndroidMain) } androidMain { dependsOn(jvmAndAndroidMain) } } } ``` The graph must stay a **DAG** (no cycles) and shouldn't conflict with the auto-applied template. ## Caveats - An intermediate set is only **materialized/meaningful** when you declare **two or more** of its member targets; with a single member it adds nothing over the platform set. - Mixing manual `dependsOn` with the default template can produce duplicate edges or warnings — disable or replace the template if you fully customize. - Source-set names from the template (`appleMain`, `nativeMain`, etc.) are stable accessors you can reference directly in the DSL. ## Why it matters Getting the hierarchy right maximizes code sharing (less duplicated `actual`/utility code) while keeping platform-specific code where it belongs, which is central to a clean, maintainable KMP build.
- Why might appleMain not appear in your build?The default template only materializes an intermediate set when two or more of its member targets are declared; with one Apple target there's nothing to group.
- What constraint applies to a custom dependsOn graph?It must remain acyclic (a DAG) and not conflict with the auto-applied default hierarchy, or you get duplicate edges/warnings.
It's an org chart: commonMain is the CEO, appleMain a regional VP over the iOS/macOS teams — shared regional policy lives at the VP level instead of being copied into every team.
saying these in an interview costs you the question
- Duplicating shared Apple code in every iOS leaf instead of using appleMain
- Not knowing the default hierarchy template is auto-applied
- Creating a dependsOn cycle
- Expecting appleMain with only one Apple target declared