Skip to content
195 changes: 161 additions & 34 deletions docs/topics/ksp/ksp-multiplatform.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,183 @@
[//]: # (title: KSP with Kotlin Multiplatform)
[//]: # (description: Add KSP to a Kotlin multiplatform project)

For a quick start, see a [sample Kotlin Multiplatform project](https://github.com/google/ksp/tree/main/examples/multiplatform)
defining a KSP processor.
This document explains how to use Kotlin Symbol Processing (KSP) in a Kotlin Multiplatform project. To get started with
Comment thread
budindepunk marked this conversation as resolved.
Outdated
Kotlin Multiplatform, see the [Kotlin Multiplatform overview](https://kotlinlang.org/docs/multiplatform/kmp-overview.html).
Comment thread
budindepunk marked this conversation as resolved.
Outdated

Starting from KSP 1.0.1, applying KSP on a multiplatform project is similar to that on a single platform, JVM project.
The main difference is that, instead of writing the `ksp(...)` configuration in dependencies, `add(ksp<Target>)` or `add(ksp<SourceSet>)`
is used to specify which compilation targets need symbol processing, before compilation.
To use KSP-based processors in a multiplatform project, assign a processor to each target that needs symbol processing:

```
add("ksp<Target>", <processor>)
```

`<processor>` is a Gradle project path. It can be:

* the specific folder in your project that contains the logic for your symbol processor.
Comment thread
budindepunk marked this conversation as resolved.
Outdated

* an external processor such as Room.

> For a full list of targets, see [Multiplatform Gradle DSL reference](https://kotlinlang.org/docs/multiplatform/multiplatform-dsl-reference.html#targets)
> and [Kotlin/Native supported targets](https://kotlinlang.org/docs/native-target-support.html).
>
{style=”tip”}
Comment thread
budindepunk marked this conversation as resolved.
Outdated

For example:

<tabs group="build-script">
<tab title="Kotlin" group-key="kotlin">

```kotlin
// build.gradle.kts

plugins {
kotlin("multiplatform")
id("com.google.devtools.ksp")
// Apply the KSP plugin
id("com.google.devtools.ksp") version "%kspVersion%"
// Apply other plugins as needed (e.g., kotlin("jvm"),
// kotlin("multiplatform"), etc.)
Comment thread
budindepunk marked this conversation as resolved.
Outdated
}

kotlin {
jvm()
linuxX64 {
binaries {
executable()
}
dependencies {

// Targets with multiple processors
add("kspJvm", project(":local-processor"))
add("kspJvm", "com.example:external-processor:1.0")

add("kspAndroid", project(":test-processor"))
add("kspAndroid", "androidx.room:room-compiler:2.6.1")
Comment thread
zamulla marked this conversation as resolved.
Outdated

// Different processors for different targets
add("kspJvm", project(":jvm-only-processor"))
add("kspJs", project(":js-only-processor"))

// iOS Targets
add("kspIosX64", project(":test-processor"))
add("kspIosArm64", project(":test-processor"))
add("kspIosSimulatorArm64", project(":test-processor"))

// TIP: If you have many iOS targets, you can avoid repetition by looping:
kotlin.targets.filter { it.name.startsWith("ios") }.forEach { target ->
add(
"ksp${target.name.replaceFirstChar { it.uppercaseChar() }}",
project(":test-processor")
)
}

// Specify KSP for the test compilation of each target:
add("kspJvmTest", project(":test-processor"))
add("kspJsTest", project(":test-processor"))
add("kspIosX64Test", project(":test-processor"))

// Host Tests / Device Tests:
// KSP generates these configurations dynamically based on the source set names.
// The test source sets are named 'androidHostTest', 'androidDeviceTest', etc.

add("kspAndroidHostTest", project(":test-processor"))
add("kspAndroidDeviceTest", project(":test-processor"))
}
```

</tab>
<tab title="Groovy" group-key="groovy">

```Groovy
// build.gradle

plugins {
// Apply the KSP plugin
id 'com.google.devtools.ksp' version '%kspVersion%'

// Apply other plugins as needed (e.g., kotlin("jvm"),
// kotlin("multiplatform"), etc.)
}

dependencies {
add("kspCommonMainMetadata", project(":test-processor"))
add("kspJvm", project(":test-processor"))
add("kspJvmTest", project(":test-processor")) // Not doing anything because there's no test source set for JVM
// There is no processing for the Linux x64 main source set, because kspLinuxX64 isn't specified
// add("kspLinuxX64Test", project(":test-processor"))

// Targets with multiple processors
add('kspJvm', project(':local-processor'))
add('kspJvm', 'com.example:external-processor:1.0')

add('kspAndroid', project(':test-processor'))
add('kspAndroid', 'androidx.room:room-compiler:2.6.1')

// Different processors for different targets
add('kspJvm', project(':jvm-only-processor'))
add('kspJs', project(':js-only-processor'))

// iOS Targets
add('kspIosX64', project(':test-processor'))
add('kspIosArm64', project(':test-processor'))
add('kspIosSimulatorArm64', project(':test-processor'))

// TIP: If you have many iOS targets, you can avoid repetition by looping:
kotlin.targets.findAll { it.name.startsWith("ios") }.each { target ->
add(
"ksp${target.name.replaceFirstChar { it.toUpperCase() }}",
Comment thread
budindepunk marked this conversation as resolved.
Outdated
project(":test-processor")
Comment thread
budindepunk marked this conversation as resolved.
Outdated
)
}

// Specify KSP for the test compilation of each target:
add('kspJvmTest', project(':test-processor'))
add('kspJsTest', project(':test-processor'))
add('kspIosX64Test', project(':test-processor'))

// Host Tests / Device Tests:
// KSP generates these configurations dynamically based on the source set names.
// The test source sets are named 'androidHostTest', 'androidDeviceTest', etc.

add('kspAndroidHostTest', project(':test-processor'))
add('kspAndroidDeviceTest', project(':test-processor'))
}
```

</tab>
</tabs>

Configuration names for tests in Multiplatform depend on the exact source set names generated by the Kotlin
plugin. Run the following command in your terminal to see the definitive list of all generated KSP configurations
for your module:

```
./gradlew :<your-module-name>:dependencies | grep ksp
```

Look for configurations matching your target names to find the right one.

> Since KSP 2, the catch-all `ksp(...)` configuration is deprecated. Configure each target explicitly to avoid running

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about making this one a warning instead of note? Will it work?

> processors where they are not needed.
>
{style="note"}

To see an example of a multiplatform project with several targets using KSP,
visit the [source repository](https://github.com/google/ksp/tree/main/examples/multiplatform).

## Compilation and processing

In a multiplatform project, Kotlin compilation may happen multiple times (`main`, `test`, or other build flavors) for each platform.
So is symbol processing. A symbol processing task is created whenever there is a Kotlin compilation task and a
corresponding `ksp<Target>` or `ksp<SourceSet>` configuration is specified.
In a multiplatform project, Kotlin creates a separate compilation for each target and source set, such as `main` and
Comment thread
budindepunk marked this conversation as resolved.
Outdated
`test`. For every eligible Kotlin compilation task, KSP creates a corresponding symbol processing task.

The [example project](https://github.com/google/ksp/tree/main/examples/multiplatform) defines six targets:

* JVM

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think to move the info about main and test before this list, maybe rephrase, and in the list mention it in the way:

  • For the JVM target jvmMain and jvmTest
  • ...

* JS
* Linux X64

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we write the target names as one word?

* Android Native X64
* Android Native Arm64
* Mingw X64
Comment thread
budindepunk marked this conversation as resolved.

For example, in the above `build.gradle.kts`, there are 4 compilation tasks: common/metadata, JVM main, Linux x64 main, Linux x64 test,
and 3 symbol processing tasks: common/metadata, JVM main, Linux x64 test.
Each of these targets includes a `main` and a `test` compilation. As a result, the project creates at least 12 Kotlin
compilation tasks.

## Avoid the ksp(...) configuration on KSP 1.0.1+
In the example's `workload/build.gradle.kts`, KSP dependencies are declared for the following configurations:
Comment thread
budindepunk marked this conversation as resolved.
Outdated

Before KSP 1.0.1, there is only one, unified `ksp(...)` configuration available. Therefore, processors either applies to all
compilation targets, or nothing at all. Note that the `ksp(...)` configuration not only applies to the main source set, but also
the test source set if it exists, even on traditional, non-multiplatform projects. This brought unnecessary overheads to build time.
* `kspJvm` and `kspJvmTest`
* `kspJs` and `kspJsTest`
* `kspAndroidNativeX64` and `kspAndroidNativeX64Test`
* `kspAndroidNativeArm64` and `kspAndroidNativeArm64Test`
* `kspLinuxX64` (test is omitted/commented out)
* `kspMingwX64` (test is omitted/commented out)
Comment thread
budindepunk marked this conversation as resolved.
Outdated

Starting from KSP 1.0.1, per-target configurations are provided as shown in the above example. In the future:
1. For multiplatform projects, the `ksp(...)` configuration will be deprecated and removed.
2. For single platform projects, the `ksp(...)` configuration will only apply to the main, default compilation.
Other targets like `test` will need to specify `kspTest(...)` in order to apply processors.
KSP creates one symbol processing task for each configuration where a KSP dependency is declared. In this example, the
Comment thread
budindepunk marked this conversation as resolved.
Outdated
project creates at least 12 Kotlin compilation tasks and 10 symbol processing tasks. The remaining compilations don't
have corresponding KSP tasks because KSP isn't configured for them.

Starting from KSP 1.0.1, there is an early access flag `-DallowAllTargetConfiguration=false` to switch to the more efficient behavior.
If the current behavior is causing performance issues, please give it a try.
The default value of the flag will be flipped from `true` to `false` on KSP 2.0.
Loading