Search code examples
kotlingradlegradle-plugingradle-kotlin-dsl

Gradle forcefully brings you the incorrect version of Kotlin dependencies


There are several questions here on Stackoverflow regarding the Kotlin compiler warning when different versions of Kotlin jars are mixed in the classpath.

This question is a different one and concerns the situation when you are developing Gradle plugins using Kotlin. When you have at least one of the following:

  • kotlin-dsl plugin is applied;
  • java-gradle-plugin plugin is applied;
  • gradleApi() dependency is added;
  • gradleKotlinDsl() dependency is added;

And you have Kotlin plugin like kotlin("jvm") version "1.4.10", e.g.:

plugins {
    java
    kotlin("jvm") version "1.4.10"
    `kotlin-dsl`
    `java-gradle-plugin`
}

You will get famous:

w: Runtime JAR files in the classpath should have the same version. These files were found in the classpath:

bla-bla-bla/gradle-6.7/lib/kotlin-stdlib-1.3.72.jar (version 1.3)
bla-bla-bla/modules-2/files-2.1/.../kotlin-stdlib-jdk8-1.4.10.jar (version 1.4)
and so on for every Kotlin jar

w: Consider providing an explicit dependency on kotlin-reflect 1.4 to prevent strange errors
w: Some runtime JAR files in the classpath have an incompatible version. Consider removing them from the classpath

Here Kotlin 1.3.72 is the embedded version for Gradle 6.7, and Kotlin 1.4.10 is the one you added manually. The problem is with gradleApi() or gradleKotlinDsl() because they add Kotlin jars directly as local filesystem files, thus bypassing Gradle dependency versions resolution.


Solution

  • The only way I found to fix this problem is to remove incorrect dependencies before incorrect configurations got resolved. Here is a part of my build.gradle.kts:

    import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency
    import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory.ClassPathNotation
    import org.gradle.internal.component.local.model.OpaqueComponentIdentifier
    
    plugins {
        java
        kotlin("jvm") version "1.4.10"
        `kotlin-dsl`
        `java-gradle-plugin`
    }
    
    project.afterEvaluate {
        // Stupid `kotlin-dsl` and `java-gradle-plugin` plugins adds gradle embedded Kotlin dependencies
        //  directly as files providing you with incorrect classpath with mixed versions of Kotlin jars.
        // This code removes such dependencies.
    
        // First create a copy of embedded Kotlin dependencies - we only need to remove Kotlin jars from them.
        configurations.create("gradlefix").also { cfg ->
            dependencies {
                add(cfg.name, gradleApi())
                add(cfg.name, gradleKotlinDsl())
            }
        }
    
        // Here are all non-Kotlin jars from gradleKotlinDsl and gradleApi.
        val neededEmbeddedDependencies = configurations["gradlefix"].files
            .filterIsInstance<File>()
            .filterNot {
                it.name.startsWith("kotlin-")
                        && it.name.endsWith(".jar")
            }
    
        // Now remove embedded Kotlin from all configuration, but keep needed embedded jars.
        // It is expected that configurations are not yet resolved.
        configurations
            .filterNot { it.name == "gradlefix" }
            .forEach { cfg ->
                cfg.resolutionStrategy.eachDependency {
                    // Needed if you use chain of `includeBuild`s
                    if (requested.group == "org.jetbrains.kotlin") {
                        useVersion("1.4.10")
                    }
                }
    
                val removed = cfg.dependencies.removeIf {
                    val notation = ((it as? DefaultSelfResolvingDependency)
                        ?.targetComponentId as? OpaqueComponentIdentifier)
                        ?.classPathNotation
    
                    notation == ClassPathNotation.GRADLE_API
                            || notation == ClassPathNotation.GRADLE_KOTLIN_DSL
                }
    
                if (removed) {
                    dependencies {
                        add(cfg.name, project.files(*neededEmbeddedDependencies.toTypedArray()))
                    }
                }
            }
    }
    
    dependencies {
        implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
        implementation("org.jetbrains.kotlin:kotlin-gradle-plugin")
        implementation("org.jetbrains.kotlin:kotlin-reflect")
        // implementation(gradleApi()) // already added by java-gradle-plugin plugin
        // implementation(gradleKotlinDsl()) // already added by kotlin-dsl plugin
    }