Skip to content
251 changes: 217 additions & 34 deletions docs/topics/ksp/ksp-multiplatform.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,239 @@
[//]: # (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 or Dagger.
Comment thread
budindepunk marked this conversation as resolved.
Outdated

> 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

// This example demonstrates various ways to declare KSP dependencies
// in a Kotlin project, covering multiplatform setups.
Comment thread
budindepunk marked this conversation as resolved.
Outdated

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 {
// ===============================================================
// 1. MAIN COMPILATIONS (Production Code)
// ===============================================================

// In KMP, the global 'ksp' configuration is deprecated to avoid running
// processors on targets unnecessarily. Use target-specific configurations instead.
Comment thread
budindepunk marked this conversation as resolved.
Outdated

// Local processor for a specific target (JVM)
add("kspJvm", project(":test-processor"))

// External processor for a specific target (Android)
add("kspAndroid", "androidx.room:room-compiler:2.6.1")
Comment thread
zamulla marked this conversation as resolved.
Outdated

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

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

// iOS Targets
// KSP configurations are per-compilation target. Shared intermediate
// source sets like 'iosMain' do NOT have their own KSP configuration.
// You must specify them for each iOS target individually.
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")
)
}

// ===============================================================
// 2. TEST COMPILATIONS
// ===============================================================

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


// ===============================================================
// 3. NEW ANDROID KOTLIN MULTIPLATFORM (Android-KMP)
// Using id("com.android.kotlin.multiplatform.library")
// ===============================================================

// Main Android compilation (Target name is 'android')
add("kspAndroid", 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

// This example demonstrates various ways to declare KSP dependencies
// in a Kotlin project, covering multiplatform setups.

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"))
// ===============================================================
// 1. MAIN COMPILATIONS (Production Code)
// ===============================================================

// In KMP, the global 'ksp' configuration is deprecated to avoid running
// processors on targets unnecessarily. Use target-specific configurations instead.

// Local processor for a specific target (JVM)
add('kspJvm', project(':test-processor'))

// External processor for a specific target (Android)
add('kspAndroid', 'androidx.room:room-compiler:2.6.1')

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

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

// iOS Targets
// KSP configurations are per-compilation target. Shared intermediate
// source sets like 'iosMain' do NOT have their own KSP configuration.
// You must specify them for each iOS target individually.
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.capitalize()}", project(':test-processor'))
}

// ===============================================================
// 2. TEST COMPILATIONS
// ===============================================================

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

// ===============================================================
// 3. NEW ANDROID KOTLIN MULTIPLATFORM (Android-KMP)
// Using id("com.android.kotlin.multiplatform.library")
// ===============================================================

// Main Android compilation (Target name is 'android')
add('kspAndroid', 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 has been deprecated. Although this may lead to some duplication in
the build file, specifying each platform individually avoids the cost of applying target-specific processors to targets
in which they do nothing.

> To use the deprecated ksp(...) configuration, set the flag `ksp.allow.all.target.configuration=true`
> in gradle.properties.
Comment thread
budindepunk marked this conversation as resolved.
Outdated
>
{style=”tip”}

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