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