[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
This commit is contained in:
Yahor Berdnikau
2023-08-24 11:14:44 +02:00
committed by Space Team
parent 844ab4ee74
commit 0c0507c25f
3 changed files with 427 additions and 14 deletions
@@ -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<Jvm> = 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<Jvm>,
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<Jvm>,
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<Jvm>
toolchainJvm: Provider<Jvm>,
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>
javaLauncher: Provider<JavaLauncher>,
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)
}
}
@@ -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<KotlinJvmCompilerOptionsDefault>()
}
@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<PropertyQueryException> {
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)
}
@@ -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<String>()
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")
}
}