gradleApi() kotlin side-effects
While writing a gradle plugin, I’ve come across a weird behavior, when the gradleApi() dependency is added.
TLDR: The gradleApi()
contains the needed interfaces, such as Plugin
, Project
and Task
. It basically just adds the current gradle to our classpath, with that comes a little surprise, it locks our kotlin version and sets is to the current gradle’s kotlin version. This is a problem when we want to run a kotlin code written in newer version, especially when this new version comes with some breaking changes.
Lets start with a simple library
Apart from a regular build.gradle.kts, we need to add the maven and maven-publish plugins and the publishing block, I am using mavenLocal for publication as we only need to use it on our machine to reproduce this issue. The projects’s name is ‘gradle-library’
plugins {
java
maven
`maven-publish`
kotlin("jvm") version "1.4.10"
}
group = "2bad2furious"
version = "0.0.1"
repositories { mavenCentral() }
dependencies {
implementation(kotlin("stdlib"))
testImplementation("junit", "junit", "4.12")
}
publishing {
repositories { mavenLocal() }
publications { create<MavenPublication>("maven") { from(components["java"]) } }
}
Now we need a piece of code with some breaking changes.
I’ve experienced issues with simple method references. So I’ve added a utils.kt file with just one really useless extension function, which serves well-enough to express the issue.
package tbtf
fun Iterable<String>.joinToString(): String {
return joinToString(transform = String::toString)
}
Now we need a project to use this library
Again, starting with a basic build.gradle.kts file, I’ve added the mavenLocal() repository and the dependency for our ‘gradle-library’
plugins {
java
kotlin("jvm") version "1.4.10"
}
group = "2bad2furious"
version = "0.0.1"
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
implementation("2bad2furious:gradle-library:0.0.1")
implementation(kotlin("stdlib"))
testImplementation("junit", "junit", "4.12")
}
All we need now is a piece of code to use said library.
I’ve created a Main.kt file, which uses our joinToString() extension function.
import tbtf.joinToString
object Main {
@JvmStatic
fun main(args: Array<String>) {
println(listOf("Hello", "World").joinToString())
}
}
If we run this, we should see the expected ‘Hello, World’, all is well, right?
Well, let’s say we want to make this a gradle plugin
For that, we need to add the gradleApi() dependency. This dependency is essential, because it contains all the interfaces required for writing our own plugin.
We can do this by either adding it to our dependencies block or by using the java-gradle-plugin plugin. I chose the former.
dependencies {
implementation(gradleApi())
implementation("2bad2furious:gradle-library:0.0.1")
implementation(kotlin("stdlib"))
testImplementation("junit", "junit", "4.12")
}
When we run this now, we get this error.
Exception in thread "main" java.lang.NoSuchMethodError: 'void kotlin.jvm.internal.FunctionReferenceImpl.<init>(int, java.lang.Class, java.lang.String, java.lang.String, int)'
at tbtf.UtilsKt$joinToString$1.<init>(utils.kt)
at tbtf.UtilsKt$joinToString$1.<clinit>(utils.kt)
at tbtf.UtilsKt.joinToString(utils.kt:3)
at Main.main(Main.kt:6)
Other problem I’ve experienced was java.lang.NoClassDefFoundError: kotlin.KotlinNothingValueException
, but there are most definitely more and will change with every version of kotlin.
From what I can tell, this is caused by the forced version of kotlin, this version should equal the current gradle’s kotlin version, which we can see by running the gradlew -v
command, that should output something like this.
------------------------------------------------------------
Gradle 6.6.1
------------------------------------------------------------Build time: 2020-08-25 16:29:12 UTC
Revision: f2d1fb54a951d8b11d25748e4711bec8d128d7e3Kotlin: 1.3.72
Groovy: 2.5.12
Ant: Apache Ant(TM) version 1.10.8 compiled on May 10 2020
JVM: 1.8.0_191 (Oracle Corporation 25.191-b12)
OS: Windows 10 10.0 amd64
This theory still holds, when we print our current KotlinVersion. All we need to add is this simple line println(KotlinVersion.CURRENT)
.
With the gradleApi() dependency, it shows us the 1.3.72 version, as opposed to the 1.4.10 it shows without it, as it’s declared in the plugins block in both our projects.
Let’s fix this
The only fix that worked for me, was to downgrade the library’s kotlin version. That means changing the plugin’s version to 1.3.72 and publishing it.
plugins {
java
maven
`maven-publish`
kotlin("jvm") version "1.3.72"
}
group = "2bad2furious"
version = "0.0.2"
repositories { mavenCentral() }
dependencies {
implementation(kotlin("stdlib"))
testImplementation("junit", "junit", "4.12")
}
publishing {
repositories { mavenLocal() }
publications { create<MavenPublication>("maven") { from(components["java"]) } }
}
Now we need to change the used version in our plugin project. Notice the version of kotlin plugin as it has no effect.
plugins {
java
kotlin("jvm") version "1.4.10"
}
group = "2bad2furious"
version = "0.0.1"
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
implementation(gradleApi())
implementation("2bad2furious:gradle-library:0.0.2")
implementation(kotlin("stdlib"))
testImplementation("junit", "junit", "4.12")
}
Now our code should output the desired result.
1.3.72
Hello, World
I hope this helps anyone trying to figure this out, bacause I personally haven’t found much information about this out there.