skip to content

What is the KMP default hierarchy template, and when would you customize intermediate source sets with applyDefaultHierarchyTemplate or manual dependsOn?

level: seniorimportance: nice to knowfreq 35%

answer

  1. default hierarchy template auto-applied
  2. nativeMain / appleMain / iosMain intermediates
  3. applyDefaultHierarchyTemplate() / applyHierarchyTemplate { }
  4. manual dependsOn for non-standard groups (jvm+android)
  5. intermediate needs >=2 member targets; keep graph a DAG

basics

~10 s

The 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 s

Modern 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 lines
kotlin
kotlin {
    jvm()
    androidTarget()
    sourceSets {
        val jvmAndAndroidMain by creating {
            dependsOn(commonMain.get())
        }
        jvmMain { dependsOn(jvmAndAndroidMain) }
        androidMain { dependsOn(jvmAndAndroidMain) }
    }
}

go deeper

for a junior

Aware that intermediate source sets exist to share code among related platforms.

for a middle

Knows the default template creates nativeMain/appleMain/iosMain and reduces duplication.

for a senior

Customizes hierarchies with applyHierarchyTemplate or manual dependsOn and respects the DAG/two-member constraints.

for a principal

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

context