Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ 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
- **Symmetric Enum Mapping**: When source and target enum entries share the same names, the plugin automatically maps them without additional configuration

## Requirements

- Kotlin 2.3.10 or later
- Kotlin 2.4.0 or later
- JVM 17+
- Gradle build system

Expand All @@ -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"
}
```
Expand All @@ -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:

Expand All @@ -74,7 +74,7 @@ Minimal setup:
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>2.3.10</version>
<version>2.4.0</version>
...(other plugin configuration)
<dependencies>
<dependency>
Expand All @@ -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 <dependencies> (not in the project <dependencies> section).
- In multi-module builds, add the kotlin-maven-plugin configuration in each module that compiles Kotlin (you can use <pluginManagement> in the parent for reuse).
- You can set a property <kmapper.version>0.0.0-SNAPSHOT</kmapper.version> and the extension will use it to resolve the runtime version if needed.
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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
}
Expand Down
13 changes: 10 additions & 3 deletions compiler-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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")
Expand All @@ -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"))
Expand All @@ -34,7 +42,6 @@ kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
compilerOptions.freeCompilerArgs.add("-Xcontext-parameters")
}

val javadocJar by tasks.registering(Jar::class) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand All @@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -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 +
"(?<name>[^$SEP]*)" +
SEP +
"(?<version>[^$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<File> {
TODO("KT-67573")
}
}
2 changes: 1 addition & 1 deletion compiler-runtime/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 5 additions & 1 deletion gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,43 @@ 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
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
Expand Down
4 changes: 2 additions & 2 deletions test-integration/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
kotlin("jvm") version "2.3.20"
kotlin("jvm") version "2.4.0"
}

group = rootProject.group
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading