Kotlin 1.9.20: Streamlining Source Sets in Multiplatform Project
Tags: kotlin, kotlinmultiplatform, gradle, kmp
Published: 2024-03-26 13:57:17

The release of Kotlin 1.9.20 marked a significant milestone for Kotlin Multiplatform enthusiasts, as this update moved Kotlin Multiplatform to Stable. Kotlin 1.9.20 introduces several notable [features and enhancements](https://kotlinlang.org/docs/whatsnew1920.html). Among these updates, the new source set hierarchy template by the Kotlin Gradle Plugin stands out to me for its ability to simplify configuration complexities. It was launched as experimental in Kotlin 1.8.20 and has been made default in Kotlin 1.9.20.

## Simplified iOS Target Configuration

In the common Kotlin Multiplatform project set-up, you would have seen specific source set setup for two or three iOS targets like for X64, Arm64.

Instead of the common and confusing 'ios()' target, one must always declare specific targets like 'iosArm64()' as required. Once one of the iOS targets is declared, the default template would automatically create `iosMain` source set. So no more `val iosMain by creating` boilerplate required.

<table>
<tr>
<td> Before 1.9.20 </td> <td> After 1.9.20</td>
</tr>
<tr>
<td>

<pre>
kotlin {
    ios()
    iosSimulatorArm64()

    sourceSets {
        val commonMain by getting

        val iosMain by creating {
            dependsOn(commonMain)
        }

        val iosX64Main by getting {
            dependsOn(iosMain)
        }

        val iosArm64Main by getting {
            dependsOn(iosMain)
        }

        val iosSimulatorArm64Main by getting {
            dependsOn(iosMain)
        }
    }
}

</pre>
</td>
<td>

<pre>
kotlin {
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    // The iosMain source set is now created automatically
    // You can directly use the reference now
    iosMain.dependencies {
        implementation("...")
    }
}
</pre>
</td>
</tr>
</table>

> If you've an old KMP project and still have reference of `darwinMain`, you can simply rename it to `appleMain` now as that encapsulates all other apple targets.

[Checkout](https://kotlinlang.org/docs/whatsnew1920.html#see-the-full-hierarchy-template) the full hierarchy template!

## Streamlined Custom Source Sets

Managing custom source sets in Kotlin Multiplatform projects is now more straightforward with Kotlin 1.9.20. The update simplifies the process of setting up dependencies between source sets, reducing the need for extensive boilerplate code and enhancing overall readability.

For example, let's assume that you already have `kotlin/JS` support with `jsMain/jsTest` source sets. Now, you want to add `kotlinWasm` support. Since `wasmJs` and `kotlinJs` can share a common source, you want to introduce a new `jsAndWasmJsMain` parent source set for common code sharing between them. Instead of using `by creating`, you can declare new custom source set inside `applyDefaultHierarchyTemplate {}`.

<table>
<tr>
<td> Before 1.9.20 </td> <td> After 1.9.20</td>
</tr>
<tr>
<td>
<pre>
kotlin {
    js()
    wasmJs()

    sourceSets {
        val commonMain by getting
        val commonTest by getting

        val jsAndWasmJsMain by creating {
            dependsOn(commonMain)
            getByName("jsMain").dependsOn(this)
            getByName("wasmMain").dependsOn(this)
        }

        val jsAndWasmJsTest by creating {
            dependsOn(commonTest)
            getByName("jsTest").dependsOn(this)
            getByName("wasmTest").dependsOn(this)
        }
    }
}
</pre>
</td>
<td>
<pre>
kotlin {
    js()
    wasmJs()

    // This is an experimental API, so opt-in is required
    @OptIn(ExperimentalKotlinGradlePluginApi::class)
    applyDefaultHierarchyTemplate {
        // create a new group that
        // depends on `common`
        common {
            // Define group name without
            // `Main` as suffix
            group("jsAndWasmJs") {
                // Provide which targets would
                // be part of this group
                withJs()
                withWasm()
            }
        }
    }

    // Now you will already have `jsAndWasmJsMain`
}
</pre>
</td>
</tr>
</table>

### Manually apply the template

Note that if you have explict `by creating` usage for custom source set then defauly template hierarchy would not apply automatically and you would see warning about the same.

```
w: The Default Kotlin Hierarchy Template was not applied to 'project ':XXX'':
Explicit .dependsOn() edges were configured for the following source sets:
[XXX]
```

In order to get the default hierachy, you would have to call `applyDefaultHierarchyTemplate()` manually once you declare all the targets.

> Also, checkout [Opting Out of Default Templates](#opting-out-of-default-templates)

## Enhanced Code Completion Support

The updated source set hierarchy template enhances code completion support in the IDE. Developers can now utilize predefined source sets directly, eliminating the need for repetitive declarations and enhancing overall development efficiency.

So even in simple cases, you can still clean up your source set declarations, and make them more readable.

<table>
<tr>
<td> Default before 1.9.20 </td> <td> With 1.9.20 </td>
</tr>
<tr>
<td>
<pre>
kotlin {
    // ...
    sourceSets {
        val commonMain by getting {
            dependencies {
                // ...
            }
        }
        val androidMain by getting {
            dependencies {
                // ...
            }
        }
        val iosMain by creating {
            dependencies {
                // ...
            }
        }
    }
}
</pre>
</td>
<td>
<pre>
kotlin {
    // ...
    sourceSets {
        commonMain.dependencies {
            // ...
        }
        androidMain.dependencies {
            // ..
        }
        iosMain.dependencies {
            // ...
        }
    }
}
</pre>
</td>
</tr>
</table>

## Android instrumented test changes

With introduction of the new Android source set layout (now default starting Kotlin 1.9.0), the `androidInstrumentedTest` source set doesn't automatically depend on `commonTest`.

In order to have that dependency set-up, with 1.9.0, you would need to explicitly set the `dependsOn` relationship between them, and with 1.9.20, you now just need to set the correct `sourceSetTree` property inside android target.

<table>
<tr>
<td> With 1.9.0 </td> <td> With 1.9.20</td>
</tr>
<tr>
<td>
<pre>
kotlin {
    // ...
    sourceSets {
        val commonTest by getting
        val androidInstrumentedTest by getting {
            dependsOn(commonTest)
        }
    }
}
</pre>
</td>
<td>
<pre>
kotlin {
    androidTarget {
        // This is an experimental API, so opt-in is required
        @OptIn(ExperimentalKotlinGradlePluginApi::class)
        instrumentedTestVariant {
            // This makes instrumented tests depend on commonTest source
            sourceSetTree.set(KotlinSourceSetTree.test)
        }
    }
}
</pre>
</td>
</tr>
</table>

## Opting Out of Default Templates

For projects with existing complex source set hierarchies, Kotlin 1.9.20 offers the flexibility to opt-out of the default template. By setting `kotlin.mpp.applyDefaultHierarchyTemplate=false` in the `gradle.properties`, developers can retain their current configurations.

## Conclusion

As you transition to Kotlin 1.9.20, consider how these improvements can optimize your Gradle configurations and enhance your multiplatform development workflow.

We recently updated most of our OSS projects to use Kotlin 1.9.22, leveraging the benefits of these enhancements. We hope you find the transition as beneficial as we have.

---
Share your experiences and questions with us on #touchlab-tools channel on Kotlin Slack. Also, you can reach out to me at @shaktiman_droid on Twitter(X) or LinkedIn. Happy coding!