Skip to content
218 changes: 180 additions & 38 deletions docs/topics/ksp/ksp-multiplatform.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,198 @@
[//]: # (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.
Here you can learn how to use Kotlin Symbol Processing (KSP) in a Kotlin Multiplatform project. For a quick start, see

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.

Suggested change
Here you can learn how to use Kotlin Symbol Processing (KSP) in a Kotlin Multiplatform project. For a quick start, see
Here you'll learn how to use Kotlin Symbol Processing (KSP) in a Kotlin Multiplatform project. For a quick start, see

an example of a multiplatform project with several targets using KSP in the
[source repository](https://github.com/google/ksp/tree/main/examples/multiplatform). The processor in this example
generates a `Foo` class used by the project.

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.
## Add KSP to a multiplatform project

In the client module's `build.gradle.kts` file, assign a processor to each target that needs symbol processing in the

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 is a client module? 🤔

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.

I'm not sure that we use this term anywhere, maybe I'm wrong?

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.

Why do we use "assign" as a word here? Isn't it just adding a dependency?
Have we ever used "assign" to explain something in the KSP?

`dependencies {}` block:

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

`<Target>` is one of the targets used in your multiplatform project. For a full list of targets, see

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.

For a full list of target — could be a tip, no?
It is not that important rn since we expect that person knows about targets already

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.

You can place both Target and Processor in a list, so the info about them would be structured.

[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).

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

* a specific directory in your project that contains the logic for your symbol processor:

```
add("kspJvm", project(":local-processor"))
```

* an external processor such as Room:

```
add("kspJvm", "androidx.room:room-compiler:2.6.1")
```

A single target can have multiple processors:

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.

Don't you want to put these different build set ups into separte subsections?


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

```kotlin
plugins {
kotlin("multiplatform")
id("com.google.devtools.ksp")
}
add("kspAndroid", project(":test-processor"))
add("kspAndroid", "androidx.room:room-compiler:2.6.1")
```

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

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

</tab>
</tabs>

The same processor can be used in multiple targets:

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

```kotlin
add("kspIosX64", project(":test-processor"))
add("kspIosArm64", project(":test-processor"))
add("kspIosSimulatorArm64", project(":test-processor"))
```

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

```Groovy
add('kspIosX64', project(':test-processor'))
add('kspIosArm64', project(':test-processor'))
add('kspIosSimulatorArm64', project(':test-processor'))
```

</tab>
</tabs>

If you have many iOS targets, you can avoid repetition by looping:

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

kotlin {
jvm()
linuxX64 {
binaries {
executable()
}
}
```kotlin
kotlin.targets.filter { it.name.startsWith("ios") }.forEach { target ->
add(
"ksp${target.name.replaceFirstChar { it.uppercaseChar() }}",
project(":test-processor")
)
}
```

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"))
</tab>
<tab title="Groovy" group-key="groovy">

```Groovy
kotlin.targets.filter { it.name.startsWith("ios") }.forEach { target ->
add(
"ksp${target.name.replaceFirstChar { it.uppercaseChar() }}",
project(":test-processor")
)
}
```

</tab>
</tabs>

You can also configure KSP for test compilations:

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

```kotlin
add("kspJvmTest", project(":test-processor"))
add("kspJsTest", project(":test-processor"))
add("kspIosX64Test", project(":test-processor"))
```

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

```Groovy
add('kspJvmTest', project(':test-processor'))
add('kspJsTest', project(':test-processor'))
add('kspIosX64Test', project(':test-processor'))
```

</tab>
</tabs>

For Android host and device tests, KSP derives configuration names from the corresponding source set names:

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

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

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

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

</tab>
</tabs>

## Find KSP configuration names

KSP configuration names depend on the source set names generated by the Kotlin plugin. To view the complete

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 is the Kotlin plugin? Is it KGP (Kotlin Gradle Plugin)? Or Kotlin IntelliJ Plugin? Or Kotlin Multiplatfrom Plugin?

list of KSP configurations for a module, run:

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

Look for the configuration names that correspond to your target source sets.

> 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"}

## 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](https://kotlinlang.org/docs/multiplatform/multiplatform-advanced-project-structure.html#compilations)
for each target and source set, such as `main` and`test`. For every eligible Kotlin compilation task, KSP creates a

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 is eligible Kotlin compilation task?

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.

Can we just omit it?

corresponding symbol processing task.

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

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.
* 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.

## Avoid the ksp(...) configuration on KSP 1.0.1+
Each of these targets includes a `main` and a `test` compilation. As a result, the project creates at least 12 Kotlin
compilation tasks.

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.
In the example's `workload/build.gradle.kts` file, KSP dependencies are declared for the following configurations:

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.
* `kspJvm` and `kspJvmTest`
* `kspJs` and `kspJsTest`
* `kspAndroidNativeX64` and `kspAndroidNativeX64Test`
* `kspAndroidNativeArm64` and `kspAndroidNativeArm64Test`
* `kspLinuxX64`
* `kspMingwX64`

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.
KSP creates a symbol processing task for each configuration where a KSP dependency is declared. In this example, the
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.
Loading