Hierarchical project structure
Kotlin Multiplatform projects support hierarchical source set structures. This means you can arrange a hierarchy of intermediate source sets for sharing the common code among some, but not all, supported targets. Using intermediate source sets helps you to:
Provide a specific API for some targets. For example, a library can add native-specific APIs in an intermediate source set for Kotlin/Native targets but not for Kotlin/JVM ones.
Consume a specific API for some targets. For example, you can benefit from a rich API that the Kotlin Multiplatform library provides for some targets that form an intermediate source set.
Use platform-dependent libraries in your project. For example, you can access iOS-specific dependencies from the intermediate iOS source set.
The Kotlin toolchain ensures that each source set has access only to the API that is available for all targets to which that source set compiles. This prevents cases like using a Windows-specific API and then compiling it to macOS, resulting in linkage errors or undefined behavior at runtime.
The recommended way to set up the source set hierarchy is to use the default hierarchy template. The template covers the most popular cases. If you have a more advanced project, you can configure it manually. This is a more low-level approach: it's more flexible but requires more effort and knowledge.
Default hierarchy template
The Kotlin Gradle plugin has a built-in default hierarchy template. It contains predefined intermediate source sets for some popular use cases. The plugin sets up those source sets automatically based on the targets specified in your project.
Consider the following example:
When you declare the targets androidTarget
, iosArm64
, and iosSimulatorArm64
in your code, the Kotlin Gradle plugin finds suitable shared source sets from the template and creates them for you. The resulting hierarchy looks like this:
Colored source sets are actually created and present in the project, while gray ones from the default template are ignored. The Kotlin Gradle plugin hasn't created the watchos
source set, for example, because there are no watchOS targets in the project.
If you add a watchOS target, like watchosArm64
, the watchos
source set is created, and the code from the apple
, native
, and common
source sets is compiled to watchosArm64
as well.
The Kotlin Gradle plugin provides both type-safe and static accessors for all of the source sets from the default hierarchy template, so you can reference them without by getting
or by creating
constructs compared to the manual configuration.
If you try to access the source set without declaring the corresponding target first, you'll see a warning:
Additional configuration
You might need to make adjustments to the default hierarchy template. If you have previously introduced intermediate sources manually with dependsOn
calls, it cancels the use of the default hierarchy template and leads to this warning:
To solve this issue, configure your project by doing one of the following:
Replace your manual configuration with the default hierarchy template
Create additional source sets in the default hierarchy template
Modify the source sets created by the default hierarchy template
Replacing a manual configuration
Case. All of your intermediate source sets are currently covered by the default hierarchy template.
Solution. Remove all manual dependsOn()
calls and source sets with by creating
constructions. To check the list of all default source sets, see the full hierarchy template.
Creating additional source sets
Case. You want to add source sets that the default hierarchy template doesn't provide yet, for example, one between a macOS and a JVM target.
Solution:
Reapply the template by explicitly calling
applyDefaultHierarchyTemplate()
.Configure additional source sets manually using
dependsOn()
:kotlin { jvm() macosArm64() iosArm64() iosSimulatorArm64() // Apply the default hierarchy again. It'll create, for example, the iosMain source set: applyDefaultHierarchyTemplate() sourceSets { // Create an additional jvmAndMacos source set: val jvmAndMacos by creating { dependsOn(commonMain.get()) } macosArm64Main.get().dependsOn(jvmAndMacos) jvmMain.get().dependsOn(jvmAndMacos) } }kotlin { jvm() macosArm64() iosArm64() iosSimulatorArm64() // Apply the default hierarchy again. It'll create, for example, the iosMain source set: applyDefaultHierarchyTemplate() sourceSets { // Create an additional jvmAndMacos source set: jvmAndMacos { dependsOn(commonMain.get()) } macosArm64Main { dependsOn(jvmAndMacos.get()) } jvmMain { dependsOn(jvmAndMacos.get()) } } }
Modifying source sets
Case. You already have the source sets with the exact same names as those generated by the template, but shared among different sets of targets in your project. For example, a nativeMain
source set is shared only among the desktop-specific targets: linuxX64
, mingwX64
, and macosX64
.
Solution. There's currently no way to modify the default dependsOn
relations among the template's source sets. It's also important that the implementation and the meaning of source sets, for example, nativeMain
, are the same in all projects.
However, you still can do one of the following:
Find different source sets for your purposes, either in the default hierarchy template or ones that have been manually created.
Opt out of the template completely by adding
kotlin.mpp.applyDefaultHierarchyTemplate=false
to yourgradle.properties
file and manually configure all source sets.
See the full hierarchy template
When you declare the targets to which your project compiles, the plugin picks the shared source sets based on the specified targets from the template and creates them in your project.
Manual configuration
You can manually introduce an intermediate source in the source set structure. It will hold the shared code for several targets.
For example, here’s what to do if you want to share code among native Linux, Windows, and macOS targets (linuxX64
, mingwX64
, and macosX64
):
Add the intermediate source set
desktopMain
, which holds the shared logic for these targets.Specify the source set hierarchy using the
dependsOn
relation.
The resulting hierarchical structure will look like this:
You can have a shared source set for the following combinations of targets:
JVM or Android + JS + Native
JVM or Android + Native
JS + Native
JVM or Android + JS
Native
Kotlin doesn't currently support sharing a source set for these combinations:
Several JVM targets
JVM + Android targets
Several JS targets
If you need to access platform-specific APIs from a shared native source set, IntelliJ IDEA will help you detect common declarations that you can use in the shared native code. For other cases, use the Kotlin mechanism of expected and actual declarations.