🎉Celebrating Kotlin 2.0🎉

Upgrading a simple Kotlin and PicoCLI project to Kotlin 2.0 in under 5 minutes to celebrate the new version coming up!

🎉Celebrating Kotlin 2.0🎉
Photo by Adi Goldstein / Unsplash

Upgrading a simple Kotlin and PicoCLI project to Kotlin 2.0 in under 5 minutes. TL;DR: You can see the diff here.

Introduction

As you may or may not know, KotlinConf will be kicking off tomorrow and Kotlin 2.0 will be announced during the Keynote. I can't be there this year, but I'm celebrating in my own way by upgrading one of my projects tonight 😊. I'll be upgrading SwaCLI, a StarWars CLI demo app I've used in talks to demo the amazing PicoCLI project.

I have been following the whole 2.0 project from very far away, having mostly done Java for the past months, so I really don't know what to expect. Hopefully a smooth experience!

First, this is what SwaCLI does : It lists Star Wars planets or characters, in many different ways. This is the paginated version

When running $ swacli planets for example, it gives us a sorted list of planets.

A paginated list of Star Wars planets

As any typical Kotlin project, it can be compiled using $./gradlew build.

The gradle.build file looks like this:

plugins {
    id 'java'
    id 'org.jetbrains.kotlin.jvm' version '1.4.10'
    id 'org.jetbrains.kotlin.plugin.serialization' version '1.4.10'
}

apply plugin: 'kotlin-kapt'


group 'nl.lengrand'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib"

    implementation 'info.picocli:picocli:4.7.6'
    implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3'

    implementation 'com.github.kittinunf.fuel:fuel:2.3.1'
    implementation 'com.github.kittinunf.fuel:fuel-kotlinx-serialization:2.3.1'

    testImplementation "org.junit.jupiter:junit-jupiter-api:5.10.2"
    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.10.2"

    kapt 'info.picocli:picocli-codegen:4.7.6'
}

compileKotlin {
    kotlinOptions.jvmTarget = "21"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "21"
}

kapt {
    arguments {
        arg("project", "${project.group}/${project.name}")
    }
}

tasks.register('customFatJar', Jar) {
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE

    manifest {
        attributes 'Main-Class': 'nl.lengrand.swacli.SwaCLIPaginate'
    }
    archiveBaseName = 'all-in-one-jar'
    from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

Yup, I haven't touched that project in a looooong time, we're still rocking kotlin 1.4.10 😅.

Rambo style, without reading anything I'll just update my plugins version to 2.0.0, let's see what happens. I saw it pop up yesterday on Twitter, so I'm curious.

plugins {
    id 'java'
    id 'org.jetbrains.kotlin.jvm' version '2.0.0'
    id 'org.jetbrains.kotlin.plugin.serialization' version '2.0.0'
}

Downloading, and migrations; that's a good sign!

No migration detected

Ok, First thing I'm seeing is a deprecation on the kotlinOptions. Let's change that. Without checking the docs, it looks like we want the kotlin.compilerOptions now.

Uh, error.

Build file '/Users/julienlengrand-lambert/Developer/swacli/build.gradle' line: 38

A problem occurred evaluating root project 'swacli'.
> Cannot set the value of property 'jvmTarget' of type org.jetbrains.kotlin.gradle.dsl.JvmTarget using an instance of type java.lang.String.

Ohhhhh, looks like our targets have their own enum now! Let's change that!

compileKotlin {
    kotlin.compilerOptions.jvmTarget = JvmTarget.JVM_21
}
compileTestKotlin {
    kotlin.compilerOptions.jvmTarget = JvmTarget.JVM_21
}

Now, that was easy! Haven't touched the doc yet 😬. Let's keep going. We run ./gradlew. build again :

 $ ./gradlew build       

> Task :kaptGenerateStubsKotlin
w: Kapt currently doesn't support language version 2.0+. Falling back to 1.9.

> Task :kaptGenerateStubsTestKotlin
w: Kapt currently doesn't support language version 2.0+. Falling back to 1.9.

> Task :kaptTestKotlin
warning: The following options were not recognized by any processor: '[project, kapt.kotlin.generated]'

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.7/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD SUCCESSFUL in 6s
8 actionable tasks: 8 executed

Ok, build successful,  amazing. However, I see that the kapt (Kotlin Annotation Processing Tool) plugin automagically detects Kotlin 2.0 and knows it doesn't support it and reverts back to 1.9. Amazing developer experience if you ask me (really, kudos for the gentle fallback!), but it also means we're not running 2.0 just yet 😱.

Fortunately, picoCLI only uses kapt to generate the docs and CLI options in our tool. Very useful, but I can leave without to try the new shiny version. Let's comment kapt out.

import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
    id 'java'
    id 'org.jetbrains.kotlin.jvm' version '2.0.0'
    id 'org.jetbrains.kotlin.plugin.serialization' version '2.0.0'
}

//apply plugin: 'kotlin-kapt'

group 'nl.lengrand'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib"

    implementation 'info.picocli:picocli:4.7.6'
    implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3'

    implementation 'com.github.kittinunf.fuel:fuel:2.3.1'
    implementation 'com.github.kittinunf.fuel:fuel-kotlinx-serialization:2.3.1'

    testImplementation "org.junit.jupiter:junit-jupiter-api:5.10.2"
    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.10.2"

//    kapt 'info.picocli:picocli-codegen:4.7.6'
}

test {
    useJUnitPlatform()
}

compileKotlin {
    kotlin.compilerOptions.jvmTarget = JvmTarget.JVM_21
}
compileTestKotlin {
    kotlin.compilerOptions.jvmTarget = JvmTarget.JVM_21
}

//kapt {
//    arguments {
//        arg("project", "${project.group}/${project.name}")
//    }
//}

tasks.register('customFatJar', Jar) {
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE

    manifest {
        attributes 'Main-Class': 'nl.lengrand.swacli.SwaCLIPaginate'
    }
    archiveBaseName = 'all-in-one-jar'
    from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

And when we build again:

$ ./gradlew build                                                         

BUILD SUCCESSFUL in 2s
4 actionable tasks: 4 executed

Let's run the tool :

$ java -cp build/libs/all-in-one-jar-1.0-SNAPSHOT.jar nl.lengrand.swacli.SwaCLIPaginate planets tat
$

And just like that, our project is running Kotlin 2.0. Haven't even opened the docs yet, that's a smooth upgrade if I've seen one! You can check the complete PR here.

Of course, I do realise that my project is ultra simple, isn't containing anything fancy or multiplatform; but still it gives me enough confidence to move forward and keep upgrading my other projects. Remember, I was running 1.4 a couple minutes ago.

Once more, 🎉congrats to the whole team at JetBrains for the release🎉, and have a lot of fun at KotlinConf tomorrow all of you! I'll be looking into the new stuff over the coming days and probably come back with some nice nuggets!