From 0c0507c25f21ca70d521dcb4cb3fb5f19f9de250 Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Thu, 24 Aug 2023 11:14:44 +0200 Subject: [PATCH] [Gradle] Fallback to the highest supported Kotlin JVM target If used toolchain JavaVersion is not yet supported by the current Kotlin compiler, we are now fallback to the highest supported JVM target version instead of failing the build. ^KT-58987 Fixed --- .../tasks/DefaultKotlinJavaToolchain.kt | 60 +++- ...tlinJavaToolchainJavaVersionConvertTest.kt | 79 +++++ .../kotlin/testhelpers/StubLogger.kt | 302 ++++++++++++++++++ 3 files changed, 427 insertions(+), 14 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinJavaToolchainJavaVersionConvertTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/testhelpers/StubLogger.kt diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/DefaultKotlinJavaToolchain.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/DefaultKotlinJavaToolchain.kt index 63359e58bbb..9f7b0d2335f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/DefaultKotlinJavaToolchain.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/DefaultKotlinJavaToolchain.kt @@ -10,6 +10,8 @@ import org.gradle.api.JavaVersion import org.gradle.api.Project import org.gradle.api.file.ProjectLayout import org.gradle.api.file.RegularFileProperty +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging import org.gradle.api.model.ObjectFactory import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.provider.Property @@ -33,6 +35,8 @@ internal abstract class DefaultKotlinJavaToolchain @Inject constructor( jvmCompilerOptions: () -> KotlinJvmCompilerOptions? ) : KotlinJavaToolchain { + private val logger = Logging.getLogger("KotlinJavaToolchain") + @get:Internal internal val gradleJvm: Provider = objects .property(Jvm.current()) @@ -117,18 +121,21 @@ internal abstract class DefaultKotlinJavaToolchain @Inject constructor( final override val jdk: KotlinJavaToolchain.JdkSetter = DefaultJdkSetter( providedJvm, objects, + logger, jvmCompilerOptions ) final override val toolchain: KotlinJavaToolchain.JavaToolchainSetter = DefaultJavaToolchainSetter( providedJvm, + logger, jvmCompilerOptions ) private class DefaultJdkSetter( private val providedJvm: Property, private val objects: ObjectFactory, + private val logger: Logger, private val jvmCompilerOptions: () -> KotlinJvmCompilerOptions? ) : KotlinJavaToolchain.JdkSetter { @@ -150,13 +157,14 @@ internal abstract class DefaultKotlinJavaToolchain @Inject constructor( ) jvmCompilerOptions()?.let { - wireJvmTargetToJvm(it, providedJvm) + wireJvmTargetToJvm(it, providedJvm, logger) } } } internal class DefaultJavaToolchainSetter( private val providedJvm: Property, + private val logger: Logger, private val jvmCompilerOptions: () -> KotlinJvmCompilerOptions? ) : KotlinJavaToolchain.JavaToolchainSetter { @@ -172,7 +180,7 @@ internal abstract class DefaultKotlinJavaToolchain @Inject constructor( providedJvm.set(javaLauncher.map(::mapToJvm)) jvmCompilerOptions()?.let { - wireJvmTargetToJvm(it, providedJvm) + wireJvmTargetToJvm(it, providedJvm, logger) } } } @@ -181,28 +189,52 @@ internal abstract class DefaultKotlinJavaToolchain @Inject constructor( internal fun wireJvmTargetToJvm( jvmCompilerOptions: KotlinJvmCompilerOptions, - toolchainJvm: Provider + toolchainJvm: Provider, + logger: Logger ) { jvmCompilerOptions.jvmTarget.convention( toolchainJvm.map { jvm -> - // For Java 9 and Java 10 JavaVersion returns "1.9" or "1.10" accordingly - // that is not accepted by Kotlin compiler - val normalizedVersion = when (jvm.javaVersion) { - JavaVersion.VERSION_1_9 -> "9" - JavaVersion.VERSION_1_10 -> "10" - else -> jvm.javaVersion.toString() - } - JvmTarget.fromTarget(normalizedVersion) + convertJavaVersionToJvmTarget(requireNotNull(jvm.javaVersion), logger) }.orElse(JvmTarget.DEFAULT) ) } + private fun convertJavaVersionToJvmTarget( + javaVersion: JavaVersion, + logger: Logger + ): JvmTarget { + // For Java 9 and Java 10 JavaVersion returns "1.9" or "1.10" accordingly + // that is not accepted by the Kotlin compiler + val normalizedVersion = when (javaVersion) { + JavaVersion.VERSION_1_9 -> "9" + JavaVersion.VERSION_1_10 -> "10" + else -> javaVersion.toString() + } + + // Update to the latest JDK LTS once it is released and Kotlin has JVM target with this version + return if (javaVersion > JavaVersion.VERSION_17) { + try { + JvmTarget.fromTarget(normalizedVersion) + } catch (_: IllegalArgumentException) { + val fallbackTarget = JvmTarget.values().last() + logger.warn( + "Kotlin does not yet support $normalizedVersion JDK target, falling back to Kotlin $fallbackTarget JVM target" + ) + fallbackTarget + } + } else { + JvmTarget.fromTarget(normalizedVersion) + } + } + private fun wireJvmTargetToToolchain( jvmCompilerOptions: KotlinJvmCompilerOptions, - javaLauncher: Provider + javaLauncher: Provider, + logger: Logger ): Unit = wireJvmTargetToJvm( jvmCompilerOptions, - javaLauncher.map(::mapToJvm) + javaLauncher.map(::mapToJvm), + logger ) internal fun wireJvmTargetToToolchain( @@ -216,7 +248,7 @@ internal abstract class DefaultKotlinJavaToolchain @Inject constructor( .getByType(JavaPluginExtension::class.java) .toolchain val javaLauncher = toolchainService.launcherFor(toolchainSpec) - wireJvmTargetToToolchain(compilerOptions, javaLauncher) + wireJvmTargetToToolchain(compilerOptions, javaLauncher, project.logger) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinJavaToolchainJavaVersionConvertTest.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinJavaToolchainJavaVersionConvertTest.kt new file mode 100644 index 00000000000..930ed47d6e9 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinJavaToolchainJavaVersionConvertTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.tasks + +import org.gradle.api.JavaVersion +import org.gradle.api.internal.provider.AbstractProperty.PropertyQueryException +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.ProviderFactory +import org.gradle.internal.jvm.Jvm +import org.gradle.testfixtures.ProjectBuilder +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptionsDefault +import org.jetbrains.kotlin.gradle.utils.newInstance +import org.jetbrains.kotlin.testhelpers.StubLogger +import org.junit.Before +import org.junit.jupiter.api.assertThrows +import java.io.File +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.fail + +class KotlinJavaToolchainJavaVersionConvertTest { + private lateinit var objects: ObjectFactory + private lateinit var providers: ProviderFactory + private val logger = StubLogger("JavaToolchain") + private val jvmCompilerOptions by lazy { + objects.newInstance() + } + + @Before + fun setUp() { + val project = ProjectBuilder.builder().build() + objects = project.objects + providers = project.providers + } + + @Test + fun wiresExpectedTarget() { + val jvm = jvm(JavaVersion.VERSION_19) + + DefaultKotlinJavaToolchain.wireJvmTargetToJvm(jvmCompilerOptions, providers.provider { jvm }, logger) + + assertEquals(JvmTarget.JVM_19, jvmCompilerOptions.jvmTarget.orNull) + assertTrue(logger.loggedWarnings.isEmpty()) + } + + @Test + fun throwsExceptionOnUnsupportedLowerTarget() { + val jvm = jvm(JavaVersion.VERSION_1_7) + + DefaultKotlinJavaToolchain.wireJvmTargetToJvm(jvmCompilerOptions, providers.provider { jvm }, logger) + + val exception = assertThrows { + jvmCompilerOptions.jvmTarget.orNull + } + assertTrue(exception.cause is IllegalArgumentException) + } + + @Test + fun fallbacksToHighestSupportedVersionWithLogMessage() { + val jvm = jvm(JavaVersion.VERSION_24) + + DefaultKotlinJavaToolchain.wireJvmTargetToJvm(jvmCompilerOptions, providers.provider { jvm }, logger) + + val expectedKotlinTarget = JvmTarget.values().last() + assertEquals(expectedKotlinTarget, jvmCompilerOptions.jvmTarget.orNull) + assertTrue(logger.loggedWarnings.isNotEmpty()) + assertEquals( + "Kotlin does not yet support ${jvm.javaVersion} JDK target, falling back to Kotlin $expectedKotlinTarget JVM target", + logger.loggedWarnings.single() + ) + } + + private fun jvm(javaVersion: JavaVersion) = Jvm.discovered(File("/tmp"), "any", javaVersion) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/testhelpers/StubLogger.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/testhelpers/StubLogger.kt new file mode 100644 index 00000000000..5e91eb39a1a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/testhelpers/StubLogger.kt @@ -0,0 +1,302 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.testhelpers + +import org.gradle.api.logging.LogLevel +import org.gradle.api.logging.Logger +import org.slf4j.Marker + +class StubLogger( + private val name: String +) : Logger { + override fun getName(): String = name + + override fun isTraceEnabled(): Boolean { + TODO("Not yet implemented") + } + + override fun isTraceEnabled(marker: Marker?): Boolean { + TODO("Not yet implemented") + } + + override fun trace(msg: String?) { + TODO("Not yet implemented") + } + + override fun trace(format: String?, arg: Any?) { + TODO("Not yet implemented") + } + + override fun trace(format: String?, arg1: Any?, arg2: Any?) { + TODO("Not yet implemented") + } + + override fun trace(format: String?, vararg arguments: Any?) { + TODO("Not yet implemented") + } + + override fun trace(msg: String?, t: Throwable?) { + TODO("Not yet implemented") + } + + override fun trace(marker: Marker?, msg: String?) { + TODO("Not yet implemented") + } + + override fun trace(marker: Marker?, format: String?, arg: Any?) { + TODO("Not yet implemented") + } + + override fun trace(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) { + TODO("Not yet implemented") + } + + override fun trace(marker: Marker?, format: String?, vararg argArray: Any?) { + TODO("Not yet implemented") + } + + override fun trace(marker: Marker?, msg: String?, t: Throwable?) { + TODO("Not yet implemented") + } + + override fun isDebugEnabled(): Boolean { + TODO("Not yet implemented") + } + + override fun isDebugEnabled(marker: Marker?): Boolean { + TODO("Not yet implemented") + } + + override fun debug(message: String?, vararg objects: Any?) { + TODO("Not yet implemented") + } + + override fun debug(msg: String?) { + TODO("Not yet implemented") + } + + override fun debug(format: String?, arg: Any?) { + TODO("Not yet implemented") + } + + override fun debug(format: String?, arg1: Any?, arg2: Any?) { + TODO("Not yet implemented") + } + + override fun debug(msg: String?, t: Throwable?) { + TODO("Not yet implemented") + } + + override fun debug(marker: Marker?, msg: String?) { + TODO("Not yet implemented") + } + + override fun debug(marker: Marker?, format: String?, arg: Any?) { + TODO("Not yet implemented") + } + + override fun debug(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) { + TODO("Not yet implemented") + } + + override fun debug(marker: Marker?, format: String?, vararg arguments: Any?) { + TODO("Not yet implemented") + } + + override fun debug(marker: Marker?, msg: String?, t: Throwable?) { + TODO("Not yet implemented") + } + + override fun isInfoEnabled(): Boolean { + TODO("Not yet implemented") + } + + override fun isInfoEnabled(marker: Marker?): Boolean { + TODO("Not yet implemented") + } + + override fun info(message: String?, vararg objects: Any?) { + TODO("Not yet implemented") + } + + override fun info(msg: String?) { + TODO("Not yet implemented") + } + + override fun info(format: String?, arg: Any?) { + TODO("Not yet implemented") + } + + override fun info(format: String?, arg1: Any?, arg2: Any?) { + TODO("Not yet implemented") + } + + override fun info(msg: String?, t: Throwable?) { + TODO("Not yet implemented") + } + + override fun info(marker: Marker?, msg: String?) { + TODO("Not yet implemented") + } + + override fun info(marker: Marker?, format: String?, arg: Any?) { + TODO("Not yet implemented") + } + + override fun info(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) { + TODO("Not yet implemented") + } + + override fun info(marker: Marker?, format: String?, vararg arguments: Any?) { + TODO("Not yet implemented") + } + + override fun info(marker: Marker?, msg: String?, t: Throwable?) { + TODO("Not yet implemented") + } + + var isWarnEnabledFlag = true + override fun isWarnEnabled(): Boolean = isWarnEnabledFlag + override fun isWarnEnabled(marker: Marker?): Boolean = isWarnEnabledFlag + + val loggedWarnings = mutableListOf() + + override fun warn(msg: String) { + loggedWarnings.add(msg) + } + + override fun warn(format: String, arg: Any) { + loggedWarnings.add(format.format(arg)) + } + + override fun warn(format: String, vararg arguments: Any) { + loggedWarnings.add(format.format(arguments)) + } + + override fun warn(format: String, arg1: Any, arg2: Any) { + loggedWarnings.add(format.format(arg1, arg2)) + } + + override fun warn(msg: String, t: Throwable) { + loggedWarnings.add(msg) + } + + override fun warn(marker: Marker, msg: String) { + loggedWarnings.add(msg) + } + + override fun warn(marker: Marker, format: String, arg: Any) { + loggedWarnings.add(format.format(arg)) + } + + override fun warn(marker: Marker, format: String, arg1: Any, arg2: Any) { + loggedWarnings.add(format.format(arg1, arg2)) + } + + override fun warn(marker: Marker, format: String, vararg arguments: Any) { + loggedWarnings.add(format.format(arguments)) + } + + override fun warn(marker: Marker, msg: String, t: Throwable) { + loggedWarnings.add(msg) + } + + override fun isErrorEnabled(): Boolean { + TODO("Not yet implemented") + } + + override fun isErrorEnabled(marker: Marker?): Boolean { + TODO("Not yet implemented") + } + + override fun error(msg: String?) { + TODO("Not yet implemented") + } + + override fun error(format: String?, arg: Any?) { + TODO("Not yet implemented") + } + + override fun error(format: String?, arg1: Any?, arg2: Any?) { + TODO("Not yet implemented") + } + + override fun error(format: String?, vararg arguments: Any?) { + TODO("Not yet implemented") + } + + override fun error(msg: String?, t: Throwable?) { + TODO("Not yet implemented") + } + + override fun error(marker: Marker?, msg: String?) { + TODO("Not yet implemented") + } + + override fun error(marker: Marker?, format: String?, arg: Any?) { + TODO("Not yet implemented") + } + + override fun error(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) { + TODO("Not yet implemented") + } + + override fun error(marker: Marker?, format: String?, vararg arguments: Any?) { + TODO("Not yet implemented") + } + + override fun error(marker: Marker?, msg: String?, t: Throwable?) { + TODO("Not yet implemented") + } + + override fun isLifecycleEnabled(): Boolean { + TODO("Not yet implemented") + } + + override fun lifecycle(message: String?) { + TODO("Not yet implemented") + } + + override fun lifecycle(message: String?, vararg objects: Any?) { + TODO("Not yet implemented") + } + + override fun lifecycle(message: String?, throwable: Throwable?) { + TODO("Not yet implemented") + } + + override fun isQuietEnabled(): Boolean { + TODO("Not yet implemented") + } + + override fun quiet(message: String?) { + TODO("Not yet implemented") + } + + override fun quiet(message: String?, vararg objects: Any?) { + TODO("Not yet implemented") + } + + override fun quiet(message: String?, throwable: Throwable?) { + TODO("Not yet implemented") + } + + override fun isEnabled(level: LogLevel?): Boolean { + TODO("Not yet implemented") + } + + override fun log(level: LogLevel?, message: String?) { + TODO("Not yet implemented") + } + + override fun log(level: LogLevel?, message: String?, vararg objects: Any?) { + TODO("Not yet implemented") + } + + override fun log(level: LogLevel?, message: String?, throwable: Throwable?) { + TODO("Not yet implemented") + } +} \ No newline at end of file