Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,19 @@ Please refer to the [KSP2 introduction](docs/ksp2.md) for further details.

When applying KSP in your Gradle project, place symbol processor dependencies into the appropriate configuration inside the `dependencies { ... }` block of your `build.gradle.kts` (or `build.gradle`) file based on your target platforms, source sets, and build variants.

### Global (`ksp`) Configuration Behavior & `ksp.allow.all.target.configuration`

The behavior of the `ksp` dependency configuration differs depending on whether your project is single-platform or multiplatform:

- **Single-Platform Android (`com.android.application`, `com.android.library`)**: The global `ksp` configuration is **enabled by default** across all scopes and build variants. Declaring a dependency using `ksp("...")` automatically applies to all main build variants (`debug`, `release`, flavors), unit tests (`kspTest`), and instrumentation tests (`kspAndroidTest`). If you wish to disable inheritance into test scopes, set `ksp.allow.all.target.configuration=false` in `gradle.properties`.
- **Single-Platform JVM (`kotlin("jvm")`)**: The global `ksp` configuration is **enabled by default** (`ksp.allow.all.target.configuration = true`), meaning `ksp("...")` applies to both the `main` (`src/main`) and `test` (`src/test`) source sets. If you wish to restrict `ksp` strictly to `main`, set `ksp.allow.all.target.configuration=false` in `gradle.properties`.
- **Kotlin Multiplatform (`kotlin("multiplatform")`, including KMP Android libraries)**: To prevent unintentional cross-compilation contamination across diverse platforms (e.g., applying a JVM-only processor to iOS or JS targets), the global `ksp` configuration is **disallowed by default** (`ksp.allow.all.target.configuration = false`). Declaring `ksp("...")` in a KMP project will throw an exception (`InvalidUserCodeException`). You should instead use target-specific configurations (`kspJvm`, `kspAndroid`, `kspJs`, `kspIosArm64`, etc.). If you explicitly want to allow a global `ksp` configuration across all KMP targets and compilations, opt in by adding `-Pksp.allow.all.target.configuration=true` in your `gradle.properties`.

### Single-Platform (JVM & Android)

| Configuration Name / Pattern | Project Type / Target | Source Set / Scope | Usage Example (`build.gradle.kts`) | Details & Behavior |
| --- | --- | --- | --- | --- |
| `ksp` | Single-target JVM / Android | Main source set (`src/main`) | `ksp("com.example:processor:1.0")` | Applied to default JVM compilation and single-platform Android main source set. Deprecated in KMP unless `ksp.allow.all.target.configuration=true`. |
| `ksp` | Single-target JVM / Android | All scopes by default (`src/main`, `src/test`, variants) | `ksp("com.example:processor:1.0")` | Global catch-all configuration enabled by default across production and test scopes (`ksp.allow.all.target.configuration = true`). Disallowed by default in KMP. |
| `kspTest` | Single-target JVM / Android | Unit tests (`src/test`) | `kspTest("com.example:test-processor:1.0")` | Runs symbol processing exclusively for unit test sources. |
| `ksp<SourceSet>` | Single-target JVM | Custom JVM source set | `add("kspIntegrationTest", "...")` | Generates sources for custom source sets like `integrationTest`. |
| `ksp<BuildType>` | Android (Single-platform) | Specific build type (e.g., debug, release) | `add("kspDebug", "...")` | Runs processor only when compiling the specified Android build variant. |
Expand All @@ -88,6 +96,7 @@ When applying KSP in your Gradle project, place symbol processor dependencies in
| `kspCommonMainMetadata` | Kotlin Multiplatform | Common Main metadata compilation | `add("kspCommonMainMetadata", "...")` | Target-specific configuration for KMP `commonMain` metadata processing. |



## Nightly Builds
Nightly builds of KSP for the latest Kotlin stable releases are published here:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ class KspConfigurations(private val project: Project) {
private const val PREFIX = "ksp"
}

internal val allowAllTargetConfiguration =
project.providers.gradleProperty("ksp.allow.all.target.configuration")
internal val allowAllTargetConfiguration: Boolean
get() = project.providers.gradleProperty("ksp.allow.all.target.configuration")
.orNull
?.toBoolean()
?: false
?: !isMppProject()

// The "ksp" configuration, applied to every compilation.
private val configurationForAll = project.configurations.create(PREFIX).apply {
Expand Down Expand Up @@ -127,8 +127,7 @@ class KspConfigurations(private val project: Project) {
if (!reported) {
reported = true
val msg = "The 'ksp' configuration is deprecated in Kotlin Multiplatform projects. " +
"Please use target-specific configurations like 'kspJvm' instead."

"Please use target-specific configurations like 'kspJvm' instead. "
if (allowAllTargetConfiguration) {
project.logger.warn(msg)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class ProcessorClasspathConfigurationsTest(isExperimentalPsiResolution: Boolean)
val main = configurations["kspKotlinProcessorClasspath"]
val test = configurations["kspTestKotlinProcessorClasspath"]
require(main.extendsFrom.map { it.name } == listOf("ksp"))
require(test.extendsFrom.map { it.name } == listOf("kspTest"))
require(test.extendsFrom.map { it.name } == listOf("kspTest", "ksp"))
}
}
""".trimIndent()
Expand All @@ -101,6 +101,33 @@ class ProcessorClasspathConfigurationsTest(isExperimentalPsiResolution: Boolean)
.build()
}

@Test
fun testConfigurationsForSinglePlatformAppDisallowAll() {
testRule.setupAppAsJvmApp()
testRule.appModule.addSource("Foo.kt", "class Foo")
testRule.appModule.buildFileAdditions.add(
"""
$kspConfigs.all {
// Make sure ksp configs are not empty.
project.dependencies.add(name, "androidx.room:room-compiler:2.4.2")
}
tasks.register("testConfigurations") {
// Resolve all tasks to trigger classpath config creation
dependsOn(tasks["tasks"])
doLast {
val main = configurations["kspKotlinProcessorClasspath"]
val test = configurations["kspTestKotlinProcessorClasspath"]
require(main.extendsFrom.map { it.name } == listOf("ksp"))
require(test.extendsFrom.map { it.name } == listOf("kspTest"))
}
}
""".trimIndent()
)
testRule.runner()
.withArguments(":app:testConfigurations", "-Pksp.allow.all.target.configuration=false")
.build()
}

@Test
fun testConfigurationsForAndroidAppAllowAll() {
testRule.setupAppAsAndroidApp()
Expand Down Expand Up @@ -229,6 +256,7 @@ class ProcessorClasspathConfigurationsTest(isExperimentalPsiResolution: Boolean)
)
val testFreeUsDebugParentConfigs =
setOf(
"ksp",
"kspTest",
"kspTestDebug",
"kspTestFree",
Expand All @@ -238,6 +266,7 @@ class ProcessorClasspathConfigurationsTest(isExperimentalPsiResolution: Boolean)
)
val androidTestFreeUsDebugParentConfigs =
setOf(
"ksp",
"kspAndroidTest",
"kspAndroidTestDebug",
"kspAndroidTestFree",
Expand Down Expand Up @@ -266,6 +295,90 @@ class ProcessorClasspathConfigurationsTest(isExperimentalPsiResolution: Boolean)
.build()
}

@Test
fun testConfigurationsForAndroidAppDisallowAll() {
testRule.setupAppAsAndroidApp()
testRule.appModule.addSource("Foo.kt", "class Foo")
testRule.appModule.buildFileAdditions.add(
"""
android {
flavorDimensions += listOf("tier", "region")

productFlavors {
create("free") {
dimension = "tier"
}
create("premium") {
dimension = "tier"
}
create("us") {
dimension = "region"
}
create("eu") {
dimension = "region"
}
}
}
$kspConfigs.all {
// Make sure ksp configs are not empty.
project.dependencies.add(name, "androidx.room:room-compiler:2.4.2")
}
tasks.register("testConfigurations") {
// Resolve all tasks to trigger classpath config creation
dependsOn(tasks["tasks"])
doLast {
val freeUsDebugConfig = configurations["kspFreeUsDebugKotlinProcessorClasspath"]
val testFreeUsDebugConfig = configurations["kspFreeUsDebugUnitTestKotlinProcessorClasspath"]
val androidTestFreeUsDebugConfig =
configurations["kspFreeUsDebugAndroidTestKotlinProcessorClasspath"]
val freeUsDebugParentConfigs =
setOf(
"ksp",
"kspDebug",
"kspFree",
"kspUs",
"kspFreeUs",
"kspFreeUsDebug"
)
val testFreeUsDebugParentConfigs =
setOf(
"kspTest",
"kspTestDebug",
"kspTestFree",
"kspTestUs",
"kspTestFreeUs",
"kspTestFreeUsDebug"
)
val androidTestFreeUsDebugParentConfigs =
setOf(
"kspAndroidTest",
"kspAndroidTestDebug",
"kspAndroidTestFree",
"kspAndroidTestUs",
"kspAndroidTestFreeUs",
"kspAndroidTestFreeUsDebug"
)
val actualFreeUsDebug = freeUsDebugConfig.extendsFrom.map { it.name }.toSet()
require(actualFreeUsDebug == freeUsDebugParentConfigs) {
"freeUsDebugConfig: expected ${'$'}freeUsDebugParentConfigs but got ${'$'}actualFreeUsDebug"
}
val actualTestFreeUsDebug = testFreeUsDebugConfig.extendsFrom.map { it.name }.toSet()
require(actualTestFreeUsDebug == testFreeUsDebugParentConfigs) {
"testFreeUsDebugConfig: expected ${'$'}testFreeUsDebugParentConfigs but got ${'$'}actualTestFreeUsDebug"
}
val actualAndroidTestFreeUsDebug = androidTestFreeUsDebugConfig.extendsFrom.map { it.name }.toSet()
require(actualAndroidTestFreeUsDebug == androidTestFreeUsDebugParentConfigs) {
"androidTestFreeUsDebugConfig: expected ${'$'}androidTestFreeUsDebugParentConfigs but got ${'$'}actualAndroidTestFreeUsDebug"
}
}
}
""".trimIndent()
)
testRule.runner()
.withArguments(":app:testConfigurations", "-Pksp.allow.all.target.configuration=false")
.build()
}

@Test
fun testConfigurationsForMultiPlatformApp() {
testRule.setupAppAsMultiplatformApp(
Expand Down
Loading