Gradle 强行给你带来错误版本的 Kotlin 依赖项
Gradle forcefully brings you the incorrect version of Kotlin dependencies
当不同版本的 Kotlin jar 混合在类路径中时,Whosebug 上有几个关于 Kotlin 编译器警告的问题。
这个问题是另一个问题,它涉及您使用 Kotlin 开发 Gradle 插件时的情况。当您至少有以下情况之一时:
kotlin-dsl
插件已应用;
java-gradle-plugin
插件已应用;
gradleApi()
添加依赖;
gradleKotlinDsl()
添加依赖;
并且您有像 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)
}
当不同版本的 Kotlin jar 混合在类路径中时,Whosebug 上有几个关于 Kotlin 编译器警告的问题。
这个问题是另一个问题,它涉及您使用 Kotlin 开发 Gradle 插件时的情况。当您至少有以下情况之一时:
kotlin-dsl
插件已应用;java-gradle-plugin
插件已应用;gradleApi()
添加依赖;gradleKotlinDsl()
添加依赖;
并且您有像 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)
}