Gradle 强行给你带来错误版本的 Kotlin 依赖项

Gradle forcefully brings you the incorrect version of Kotlin dependencies

当不同版本的 Kotlin jar 混合在类路径中时,Whosebug 上有几个关于 Kotlin 编译器警告的问题。

这个问题是另一个问题,它涉及您使用 Kotlin 开发 Gradle 插件时的情况。当您至少有以下情况之一时:

并且您有像 kotlin("jvm") version "1.4.10" 这样的 Kotlin 插件,例如:

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

你会出名:

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

这里Kotlin 1.3.72是Gradle6.7的内嵌版本,Kotlin 1.4.10是你手动添加的。 问题出在 gradleApi()gradleKotlinDsl() 上,因为它们直接将 Kotlin jar 添加为本地文件系统文件,从而绕过了 Gradle 依赖版本解析。

我发现解决此问题的唯一方法是在解决不正确的配置之前删除不正确的依赖项。这是我的一部分 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
}

我找到了类似的解决方案,但仍然非常不同:

我发现我可以撤消添加默认命名的 Gradle 依赖项(gradleApi()gradleKotlinDsl()gradleTestKit())并提供我自己的那些没有的版本包含嵌入式 Kotlin 依赖项。

You can find it in real context here

fun Project.replaceGradlePluginAutoDependenciesWithoutKotlin() {
    plugins.withId("org.gradle.java-gradle-plugin") {
        @Suppress("DEPRECATION")
        dependencies {
            // Undo org.gradle.plugin.devel.plugins.JavaGradlePluginPlugin.applyDependencies
            if (configurations[JavaPlugin.COMPILE_CONFIGURATION_NAME].dependencies.remove(gradleApi())) {
                add(JavaPlugin.COMPILE_CONFIGURATION_NAME, gradleApiWithoutKotlin())
            }

            // Undo org.gradle.plugin.devel.plugins.JavaGradlePluginPlugin.TestKitAndPluginClasspathDependenciesAction
            afterEvaluate {
                gradlePlugin.testSourceSets.forEach {
                    if (configurations[it.compileConfigurationName].dependencies.remove(gradleTestKit())) {
                        add(it.compileConfigurationName, gradleTestKitWithoutKotlin())
                    }
                }
            }
        }
    }

    plugins.withId("org.gradle.kotlin.kotlin-dsl.base") { // applied from org.gradle.kotlin.kotlin-dsl
        dependencies {
            // based on org.gradle.kotlin.dsl.plugins.embedded.kotlinArtifactConfigurationNames (in EmbeddedKotlinPlugin.kt)
            val kotlinArtifactConfigurationNames = listOf(
                JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME,
                JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME
            )
            kotlinArtifactConfigurationNames.forEach {
                // Undo org.gradle.kotlin.dsl.plugins.base.KotlinDslBasePlugin.addGradleKotlinDslDependencyTo
                if (configurations[it].dependencies.remove(gradleKotlinDsl())) {
                    add(it, gradleKotlinDslWithoutKotlin())
                }
            }
        }
    }
}

fun DependencyHandler.gradleApiWithoutKotlin(): Dependency =
    withoutKotlin(ClassPathNotation.GRADLE_API)

fun DependencyHandler.gradleKotlinDslWithoutKotlin(): Dependency =
    withoutKotlin(ClassPathNotation.GRADLE_KOTLIN_DSL)

fun DependencyHandler.gradleTestKitWithoutKotlin(): Dependency =
    withoutKotlin(ClassPathNotation.GRADLE_TEST_KIT)

private fun DependencyHandler.withoutKotlin(notation: ClassPathNotation): Dependency {
    // Originally created in org.gradle.api.internal.notations.DependencyClassPathNotationConverter.create
    val gradleApi = create(notation) as FileCollectionDependency
    val filteredSource = gradleApi.files.filter { !it.name.startsWith("kotlin-") }
    val displayName = OpaqueComponentIdentifier("${notation.displayName} (without Kotlin)")
    return DefaultSelfResolvingDependency(displayName, filteredSource as FileCollectionInternal)
}