diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index f203762..7ca2a83 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -2,6 +2,6 @@
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 9d92d41..d4d04d8 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ KMapper is a Kotlin compiler plugin that provides code generation capabilities f
## Features
-- **Kotlin 2.0+ Support**: Built with K2 compiler support (Kotlin 2.3.10)
+- **Kotlin 2.0+ Support**: Built with K2 compiler support (Kotlin 2.4.0)
- **Fluent DSL**: Intuitive assignment-based mapping syntax with `property = value`
- **Compile-time Validation**: Ensures all required constructor parameters are mapped
- **IR-Based Generation**: Uses Kotlin's IR (Intermediate Representation) for robust code generation
@@ -16,7 +16,7 @@ KMapper is a Kotlin compiler plugin that provides code generation capabilities f
## Requirements
-- Kotlin 2.3.10 or later
+- Kotlin 2.4.0 or later
- JVM 17+
- Gradle build system
@@ -29,7 +29,7 @@ Add the plugin to your project's `build.gradle.kts`:
build.gradle.kts
```kotlin
plugins {
- kotlin("jvm") version "2.3.10"
+ kotlin("jvm") version "2.4.0"
id("community.flock.kmapper") version "0.0.0-SNAPSHOT"
}
```
@@ -52,7 +52,7 @@ Load the KMapper Maven integration by adding it as a dependency of kotlin-maven-
- Auto-register the KMapper Kotlin compiler plugin (transitively on the plugin classpath)
- Ensure the runtime library (compiler-runtime) is on your project compile classpath
-Kotlin version used/tested: 2.3.10.
+Kotlin version used/tested: 2.4.0.
Minimal setup:
@@ -74,7 +74,7 @@ Minimal setup:
org.jetbrains.kotlin
kotlin-maven-plugin
- 2.3.10
+ 2.4.0
...(other plugin configuration)
@@ -90,7 +90,7 @@ Minimal setup:
Troubleshooting:
-- Ensure kotlin-maven-plugin version is 2.3.10 (matching our tested Kotlin version).
+- Ensure kotlin-maven-plugin version is 2.4.0 (matching our tested Kotlin version).
- Make sure the KMapper maven-plugin dependency is placed under kotlin-maven-plugin’s (not in the project section).
- In multi-module builds, add the kotlin-maven-plugin configuration in each module that compiles Kotlin (you can use in the parent for reuse).
- You can set a property 0.0.0-SNAPSHOT and the extension will use it to resolve the runtime version if needed.
diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts
index 1fc8187..420b7b2 100644
--- a/benchmarks/build.gradle.kts
+++ b/benchmarks/build.gradle.kts
@@ -1,6 +1,6 @@
plugins {
- kotlin("jvm") version "2.3.20"
- kotlin("plugin.allopen") version "2.3.20"
+ kotlin("jvm") version "2.4.0"
+ kotlin("plugin.allopen") version "2.4.0"
id("org.jetbrains.kotlinx.benchmark") version "0.4.13"
id("community.flock.kmapper") version "0.0.0-SNAPSHOT"
application
diff --git a/build.gradle.kts b/build.gradle.kts
index e21f873..202443b 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,5 +1,5 @@
plugins {
- kotlin("jvm") version "2.3.20"
+ kotlin("jvm") version "2.4.0"
id("io.github.gradle-nexus.publish-plugin") version "2.0.0"
id("org.jetbrains.dokka") version "2.0.0" apply false
}
diff --git a/compiler-plugin/build.gradle.kts b/compiler-plugin/build.gradle.kts
index b81fdf1..a1e167a 100644
--- a/compiler-plugin/build.gradle.kts
+++ b/compiler-plugin/build.gradle.kts
@@ -1,8 +1,9 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
- kotlin("jvm") version "2.3.20"
+ kotlin("jvm") version "2.4.0"
id("maven-publish")
id("org.jetbrains.dokka")
id("com.gradleup.kctf").version("2.3.10-0.0.2-SNAPSHOT-a524b7d38d0ad625c3b891df859cc0be4b9c339b")
@@ -22,7 +23,14 @@ val kMapperRuntimeClasspath: Configuration by configurations.creating {
dependencies {
compileOnly(kotlin("compiler-embeddable"))
- testImplementation("com.gradleup.kctf:kctf-runtime:2.3.10-0.0.2-SNAPSHOT-a524b7d38d0ad625c3b891df859cc0be4b9c339b")
+ // kctf-runtime is not published for Kotlin 2.4 yet, so the compiler test
+ // framework is depended on directly and kctf's classpath-based stdlib path
+ // provider is vendored in src/test/kotlin/kctf.
+ testImplementation(kotlin("compiler", getKotlinPluginVersion()))
+ testImplementation(kotlin("compiler-internal-test-framework", getKotlinPluginVersion()))
+ testRuntimeOnly(kotlin("reflect", getKotlinPluginVersion()))
+ testRuntimeOnly(kotlin("script-runtime", getKotlinPluginVersion()))
+ testRuntimeOnly(kotlin("annotations-jvm", getKotlinPluginVersion()))
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testImplementation(project(":compiler-runtime"))
add(kMapperRuntimeClasspath.name, project(":compiler-runtime"))
@@ -34,7 +42,6 @@ kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
- compilerOptions.freeCompilerArgs.add("-Xcontext-parameters")
}
val javadocJar by tasks.registering(Jar::class) {
diff --git a/compiler-plugin/src/main/kotlin/community/flock/kmapper/compiler/KMapperExtension.kt b/compiler-plugin/src/main/kotlin/community/flock/kmapper/compiler/KMapperExtension.kt
index 76a9b1e..4a8ffa8 100644
--- a/compiler-plugin/src/main/kotlin/community/flock/kmapper/compiler/KMapperExtension.kt
+++ b/compiler-plugin/src/main/kotlin/community/flock/kmapper/compiler/KMapperExtension.kt
@@ -7,6 +7,7 @@ import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
+import org.jetbrains.kotlin.ir.util.patchDeclarationParents
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
/**
@@ -15,5 +16,9 @@ import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
class KMapperExtension(val collector: MessageCollector) : IrGenerationExtension {
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
moduleFragment.transformChildrenVoid(KMapperIrBuildMapperVisitor(pluginContext, collector))
+ // Declarations synthesized by the visitor (e.g. the lambdas built for
+ // List mappings) are created detached; Kotlin >= 2.4 fails IR lowering
+ // when it encounters a declaration without an initialized parent.
+ moduleFragment.patchDeclarationParents()
}
}
diff --git a/compiler-plugin/src/test/kotlin/kctf/ClasspathBasedStandardLibrariesPathProvider.kt b/compiler-plugin/src/test/kotlin/kctf/ClasspathBasedStandardLibrariesPathProvider.kt
new file mode 100644
index 0000000..031d09f
--- /dev/null
+++ b/compiler-plugin/src/test/kotlin/kctf/ClasspathBasedStandardLibrariesPathProvider.kt
@@ -0,0 +1,75 @@
+// Vendored from GradleUp/kctf (MIT), adapted to the Kotlin 2.4 test framework:
+// KotlinStandardLibrariesPathProvider is an interface since 2.4 and kctf-runtime
+// is not published for Kotlin 2.4 yet. The `kctf` package is kept so the
+// kctf-generated test sources keep referring to this provider.
+package kctf
+
+import org.jetbrains.kotlin.platform.wasm.WasmTarget
+import org.jetbrains.kotlin.test.services.KotlinStandardLibrariesPathProvider
+import java.io.File
+
+object ClasspathBasedStandardLibrariesPathProvider : KotlinStandardLibrariesPathProvider {
+ private val SEP = "\\${File.separator}"
+
+ private val GRADLE_DEPENDENCY =
+ (".*?" +
+ SEP +
+ "(?[^$SEP]*)" +
+ SEP +
+ "(?[^$SEP]*)" +
+ SEP +
+ "[^$SEP]*" +
+ SEP +
+ "\\1-\\2\\.jar")
+ .toRegex()
+
+ private val jars =
+ System.getProperty("java.class.path")
+ .split("\\${File.pathSeparator}".toRegex())
+ .dropLastWhile(String::isEmpty)
+ .map(::File)
+ .associateBy {
+ GRADLE_DEPENDENCY.matchEntire(it.path)?.let { it.groups["name"]!!.value } ?: it.name
+ }
+
+ private fun getFile(name: String): File {
+ return jars[name]
+ ?: error("Jar $name not found in classpath:\n${jars.entries.joinToString("\n")}")
+ }
+
+ override fun runtimeJarForTests(): File = getFile("kotlin-stdlib")
+
+ override fun runtimeJarForTestsWithJdk8(): File = getFile("kotlin-stdlib-jdk8")
+
+ override fun minimalRuntimeJarForTests(): File = getFile("kotlin-stdlib")
+
+ override fun reflectJarForTests(): File = getFile("kotlin-reflect")
+
+ override fun kotlinTestJarForTests(): File = getFile("kotlin-test")
+
+ override fun scriptRuntimeJarForTests(): File = getFile("kotlin-script-runtime")
+
+ override fun jvmAnnotationsForTests(): File = getFile("kotlin-annotations-jvm")
+
+ override fun getAnnotationsJar(): File = getFile("kotlin-annotations-jvm")
+
+ override fun fullJsStdlib(): File = getFile("kotlin-stdlib-js")
+
+ override fun defaultJsStdlib(): File = getFile("kotlin-stdlib-js")
+
+ override fun kotlinTestJsKLib(): File = getFile("kotlin-test-js")
+
+ override fun fullWasmStdlib(target: WasmTarget): File = TODO("Wasm is not supported by these tests")
+
+ override fun kotlinTestWasmKLib(target: WasmTarget): File = TODO("Wasm is not supported by these tests")
+
+ override fun webStdlibForTests(): File = TODO("Web is not supported by these tests")
+
+ override fun commonStdlibForTests(): File {
+ TODO("Not yet implemented")
+ }
+
+ override fun scriptingPluginFilesForTests(): Collection {
+ TODO("KT-67573")
+ }
+}
diff --git a/compiler-runtime/build.gradle.kts b/compiler-runtime/build.gradle.kts
index 5d83779..b5b0d95 100644
--- a/compiler-runtime/build.gradle.kts
+++ b/compiler-runtime/build.gradle.kts
@@ -1,7 +1,7 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
- kotlin("jvm") version "2.3.20"
+ kotlin("jvm") version "2.4.0"
id("maven-publish")
id("org.jetbrains.dokka")
signing
diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts
index 1de71e2..0afda4b 100644
--- a/gradle-plugin/build.gradle.kts
+++ b/gradle-plugin/build.gradle.kts
@@ -26,7 +26,11 @@ sourceSets {
}
dependencies {
- implementation(kotlin("gradle-plugin-api"))
+ // compileOnly so the consumer project's own Kotlin Gradle Plugin provides
+ // this API at runtime. With implementation scope the published POM would
+ // drag this version of KGP onto the consumer's plugin classpath, silently
+ // overriding the Kotlin version the project asked for.
+ compileOnly(kotlin("gradle-plugin-api"))
testImplementation(kotlin("test-junit5"))
}
diff --git a/gradle-plugin/src/community/flock/kmapper/gradle/plugin/KMapperGradlePlugin.kt b/gradle-plugin/src/community/flock/kmapper/gradle/plugin/KMapperGradlePlugin.kt
index 8fbbc70..7628c2f 100644
--- a/gradle-plugin/src/community/flock/kmapper/gradle/plugin/KMapperGradlePlugin.kt
+++ b/gradle-plugin/src/community/flock/kmapper/gradle/plugin/KMapperGradlePlugin.kt
@@ -2,8 +2,10 @@ package community.flock.kmapper.gradle.plugin
import community.flock.kmapper.BuildConfig
import community.flock.kmapper.BuildConfig.ANNOTATIONS_LIBRARY_COORDINATES
+import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.provider.Provider
+import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin
import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact
@@ -11,8 +13,32 @@ import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
@Suppress("unused") // Used via reflection.
class KMapperGradlePlugin : KotlinCompilerPluginSupportPlugin {
+
+ companion object {
+ // The compiler plugin is built against the Kotlin 2.4 compiler API and
+ // cannot be loaded by older compilers; fail fast with a clear message
+ // instead of crashing compilation (see issue #31 for the 2.3-on-2.4
+ // counterpart of that crash).
+ private val MINIMUM_KOTLIN_VERSION = KotlinVersion(2, 4, 0)
+ }
+
override fun apply(target: Project) {
target.extensions.create("flockPlugin", KMapperGradleExtension::class.java)
+ target.plugins.withType(KotlinBasePlugin::class.java).configureEach { kotlinPlugin ->
+ checkKotlinVersion(kotlinPlugin.pluginVersion)
+ }
+ }
+
+ private fun checkKotlinVersion(version: String) {
+ val parts = version.substringBefore('-').split('.').map { it.toIntOrNull() ?: return }
+ if (parts.size < 3) return
+ if (KotlinVersion(parts[0], parts[1], parts[2]) < MINIMUM_KOTLIN_VERSION) {
+ throw GradleException(
+ "kmapper requires Kotlin $MINIMUM_KOTLIN_VERSION or newer, but this project uses " +
+ "Kotlin $version. Older compilers cannot load the kmapper compiler plugin; " +
+ "either upgrade Kotlin or use a kmapper release built for Kotlin $version."
+ )
+ }
}
override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = true
diff --git a/test-integration/build.gradle.kts b/test-integration/build.gradle.kts
index bcefcd9..c14a698 100644
--- a/test-integration/build.gradle.kts
+++ b/test-integration/build.gradle.kts
@@ -1,5 +1,5 @@
plugins {
- kotlin("jvm") version "2.3.20"
+ kotlin("jvm") version "2.4.0"
}
group = rootProject.group
@@ -10,7 +10,7 @@ dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.2")
implementation(gradleTestKit())
- implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:2.3.20")
+ implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:2.4.0")
}
kotlin {
diff --git a/test-integration/src/test/kotlin/community/flock/kmapper/BasicMappingTest.kt b/test-integration/src/test/kotlin/community/flock/kmapper/BasicMappingTest.kt
index d71665f..04bd79e 100644
--- a/test-integration/src/test/kotlin/community/flock/kmapper/BasicMappingTest.kt
+++ b/test-integration/src/test/kotlin/community/flock/kmapper/BasicMappingTest.kt
@@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test
class BasicMappingTest {
val options = IntegrationTest.Options(
- kotlinVersion = "2.3.10",
+ kotlinVersion = "2.4.0",
)
@Test
@@ -579,8 +579,8 @@ class BasicMappingTest {
"""
|plugins {
| id("community.flock.kmapper") version "0.0.0-SNAPSHOT"
- | kotlin("jvm") version "2.3.10"
- | kotlin("plugin.serialization") version "2.3.10"
+ | kotlin("jvm") version "2.4.0"
+ | kotlin("plugin.serialization") version "2.4.0"
| application
|}
|repositories {
diff --git a/test-integration/src/test/kotlin/community/flock/kmapper/EnumMappingTest.kt b/test-integration/src/test/kotlin/community/flock/kmapper/EnumMappingTest.kt
index ba387c3..df3a1a3 100644
--- a/test-integration/src/test/kotlin/community/flock/kmapper/EnumMappingTest.kt
+++ b/test-integration/src/test/kotlin/community/flock/kmapper/EnumMappingTest.kt
@@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test
class EnumMappingTest {
val options = IntegrationTest.Options(
- kotlinVersion = "2.3.10",
+ kotlinVersion = "2.4.0",
)
@Test
diff --git a/test-integration/src/test/kotlin/community/flock/kmapper/KotlinVersionGuardTest.kt b/test-integration/src/test/kotlin/community/flock/kmapper/KotlinVersionGuardTest.kt
new file mode 100644
index 0000000..2ede1fc
--- /dev/null
+++ b/test-integration/src/test/kotlin/community/flock/kmapper/KotlinVersionGuardTest.kt
@@ -0,0 +1,32 @@
+package community.flock.kmapper
+
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Test
+
+class KotlinVersionGuardTest {
+
+ val options = IntegrationTest.Options(
+ kotlinVersion = "2.3.21",
+ )
+
+ @Test
+ fun shouldFailFast_onKotlinOlderThanMinimum() {
+ IntegrationTest(options)
+ .file("App.kt") {
+ """
+ |package sample
+ |
+ |fun main() {
+ | println("should never compile")
+ |}
+ |
+ """.trimMargin()
+ }
+ .compileFail { output ->
+ assertTrue(
+ output.contains("kmapper requires Kotlin 2.4.0 or newer"),
+ "Expected a clear minimum-Kotlin-version error, got:\n$output"
+ )
+ }
+ }
+}
diff --git a/test-integration/src/test/kotlin/community/flock/kmapper/ListMappingTest.kt b/test-integration/src/test/kotlin/community/flock/kmapper/ListMappingTest.kt
index 3340be6..726bd99 100644
--- a/test-integration/src/test/kotlin/community/flock/kmapper/ListMappingTest.kt
+++ b/test-integration/src/test/kotlin/community/flock/kmapper/ListMappingTest.kt
@@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test
class ListMappingTest {
val options = IntegrationTest.Options(
- kotlinVersion = "2.3.10",
+ kotlinVersion = "2.4.0",
)
@Test
diff --git a/test-integration/src/test/kotlin/community/flock/kmapper/NullableAndDefaultsTest.kt b/test-integration/src/test/kotlin/community/flock/kmapper/NullableAndDefaultsTest.kt
index f7c3a6a..5dc1949 100644
--- a/test-integration/src/test/kotlin/community/flock/kmapper/NullableAndDefaultsTest.kt
+++ b/test-integration/src/test/kotlin/community/flock/kmapper/NullableAndDefaultsTest.kt
@@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test
class NullableAndDefaultsTest {
val options = IntegrationTest.Options(
- kotlinVersion = "2.3.10",
+ kotlinVersion = "2.4.0",
)
@Test
diff --git a/test-integration/src/test/kotlin/community/flock/kmapper/NumericWideningTest.kt b/test-integration/src/test/kotlin/community/flock/kmapper/NumericWideningTest.kt
index f6efe49..ca8a950 100644
--- a/test-integration/src/test/kotlin/community/flock/kmapper/NumericWideningTest.kt
+++ b/test-integration/src/test/kotlin/community/flock/kmapper/NumericWideningTest.kt
@@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test
class NumericWideningTest {
val options = IntegrationTest.Options(
- kotlinVersion = "2.3.10",
+ kotlinVersion = "2.4.0",
)
@Test
diff --git a/test-integration/src/test/kotlin/community/flock/kmapper/SerializableTest.kt b/test-integration/src/test/kotlin/community/flock/kmapper/SerializableTest.kt
index 52c38f7..e47f29d 100644
--- a/test-integration/src/test/kotlin/community/flock/kmapper/SerializableTest.kt
+++ b/test-integration/src/test/kotlin/community/flock/kmapper/SerializableTest.kt
@@ -6,8 +6,8 @@ import org.junit.jupiter.api.Test
class SerializableTest {
val options = IntegrationTest.Options(
- kotlinVersion = "2.3.10",
- additionalPlugins = listOf("""kotlin("plugin.serialization") version "2.3.10""""),
+ kotlinVersion = "2.4.0",
+ additionalPlugins = listOf("""kotlin("plugin.serialization") version "2.4.0""""),
additionalDependencies = listOf("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1"),
)
diff --git a/test-integration/src/test/kotlin/community/flock/kmapper/ValueClassTest.kt b/test-integration/src/test/kotlin/community/flock/kmapper/ValueClassTest.kt
index b64d5e4..9bc29f8 100644
--- a/test-integration/src/test/kotlin/community/flock/kmapper/ValueClassTest.kt
+++ b/test-integration/src/test/kotlin/community/flock/kmapper/ValueClassTest.kt
@@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test
class ValueClassTest {
val options = IntegrationTest.Options(
- kotlinVersion = "2.3.10",
+ kotlinVersion = "2.4.0",
)
@Test