[Gradle] KotlinSingleTargetExtension: Implement lateinit target as Future

It is not obvious from 'KotlinSingleTargetExtension' that calling
into 'target' is unsafe and it might throw.

Ordering code (especially with Android) is hard:
It is not clear when the target will become available.

The Future (and forAllTargets implementation) is intended
to make working with targets generically more safe.

KT-61634
This commit is contained in:
Sebastian Sellmair
2023-10-17 11:24:08 +02:00
committed by Space Team
parent 77376f0d28
commit eba13b5b91
8 changed files with 50 additions and 53 deletions
@@ -17,15 +17,21 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.internal.KOTLIN_BUILD_TOOLS_API_IMPL
import org.jetbrains.kotlin.gradle.internal.KOTLIN_MODULE_GROUP
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.CoroutineStart.Undispatched
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaTarget
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrSingleTargetPreset
import org.jetbrains.kotlin.gradle.tasks.CompileUsingKotlinDaemon
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.gradle.utils.CompletableFuture
import org.jetbrains.kotlin.gradle.utils.Future
import org.jetbrains.kotlin.gradle.utils.castIsolatedKotlinPluginClassLoaderAware
import org.jetbrains.kotlin.gradle.utils.configureExperimentalTryK2
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.tooling.core.HasMutableExtras
import org.jetbrains.kotlin.tooling.core.MutableExtras
import org.jetbrains.kotlin.tooling.core.mutableExtrasOf
@@ -180,17 +186,21 @@ open class KotlinProjectExtension @Inject constructor(project: Project) : Kotlin
abstract class KotlinSingleTargetExtension<TARGET : KotlinTarget>(project: Project) : KotlinProjectExtension(project) {
abstract val target: TARGET
internal abstract val targetFuture: Future<TARGET>
fun target(body: Action<TARGET>) = body.execute(target)
}
abstract class KotlinSingleJavaTargetExtension(project: Project) : KotlinSingleTargetExtension<KotlinWithJavaTarget<*, *>>(project)
abstract class KotlinJvmProjectExtension(project: Project) : KotlinSingleJavaTargetExtension(project) {
override lateinit var target: KotlinWithJavaTarget<KotlinJvmOptions, KotlinJvmCompilerOptions>
internal set
override val target: KotlinWithJavaTarget<KotlinJvmOptions, KotlinJvmCompilerOptions>
get() = targetFuture.getOrThrow()
open fun target(body: KotlinWithJavaTarget<KotlinJvmOptions, KotlinJvmCompilerOptions>.() -> Unit) = target.run(body)
override val targetFuture = CompletableFuture<KotlinWithJavaTarget<KotlinJvmOptions, KotlinJvmCompilerOptions>>()
open fun target(body: KotlinWithJavaTarget<KotlinJvmOptions, KotlinJvmCompilerOptions>.() -> Unit) {
project.launch(Undispatched) { targetFuture.await().body() }
}
val compilerOptions: KotlinJvmCompilerOptions = project.objects
.newInstance(KotlinJvmCompilerOptionsDefault::class.java)
@@ -206,20 +216,16 @@ abstract class KotlinJvmProjectExtension(project: Project) : KotlinSingleJavaTar
}
abstract class Kotlin2JsProjectExtension(project: Project) : KotlinSingleJavaTargetExtension(project) {
private lateinit var _target: KotlinWithJavaTarget<KotlinJsOptions, KotlinJsCompilerOptions>
override val target: KotlinWithJavaTarget<KotlinJsOptions, KotlinJsCompilerOptions>
get() {
if (!::_target.isInitialized) throw IllegalStateException("Extension target is not initialized!")
return _target
if (!targetFuture.isCompleted) throw IllegalStateException("Extension target is not initialized!")
return targetFuture.getOrThrow()
}
internal fun setTarget(target: KotlinWithJavaTarget<KotlinJsOptions, KotlinJsCompilerOptions>) {
_target = target
override val targetFuture = CompletableFuture<KotlinWithJavaTarget<KotlinJsOptions, KotlinJsCompilerOptions>>()
open fun target(body: KotlinWithJavaTarget<KotlinJsOptions, KotlinJsCompilerOptions>.() -> Unit) {
project.launch(Undispatched) { targetFuture.await().body() }
}
open fun target(body: KotlinWithJavaTarget<KotlinJsOptions, KotlinJsCompilerOptions>.() -> Unit) = target.run(body)
}
abstract class KotlinJsProjectExtension(project: Project) :
@@ -227,45 +233,31 @@ abstract class KotlinJsProjectExtension(project: Project) :
KotlinJsCompilerTypeHolder {
lateinit var irPreset: KotlinJsIrSingleTargetPreset
private val targetSetObservers = mutableListOf<(KotlinJsTargetDsl?) -> Unit>()
@Deprecated("Use js() instead", ReplaceWith("js()"))
override val target: KotlinJsTargetDsl
get() = targetFuture.lenient.getOrNull() ?: js()
@Deprecated("Because only IR compiler is left, no more necessary to know about compiler type in properties")
override val compilerTypeFromProperties: KotlinJsCompilerType? = null
// target is public property
// Users can write kotlin.target and it should work
// So call of target should init default configuration
@Deprecated("Use `target` instead", ReplaceWith("target"))
var _target: KotlinJsTargetDsl? = null
private set(value) {
field = value
targetSetObservers.forEach { it(value) }
}
override val targetFuture = CompletableFuture<KotlinJsTargetDsl>()
fun registerTargetObserver(observer: (KotlinJsTargetDsl?) -> Unit) {
targetSetObservers.add(observer)
}
@Deprecated("Use js() instead", ReplaceWith("js()"))
@Suppress("DEPRECATION")
override val target: KotlinJsTargetDsl
get() {
if (_target == null) {
js {}
}
return _target!!
project.launch(Undispatched) {
observer(targetFuture.await())
}
}
@Suppress("DEPRECATION")
private fun jsInternal(
compiler: KotlinJsCompilerType? = null,
body: KotlinJsTargetDsl.() -> Unit,
): KotlinJsTargetDsl {
if (_target == null) {
if (!targetFuture.isCompleted) {
val target: KotlinJsTargetDsl = irPreset
.createTargetInternal("js")
this._target = target
this.targetFuture.complete(target)
target.project.components.addAll(target.components)
}
@@ -317,26 +309,31 @@ abstract class KotlinJsProjectExtension(project: Project) :
)
@Suppress("DEPRECATION")
fun getTargets(): NamedDomainObjectContainer<KotlinTarget>? =
_target?.let { target ->
targetFuture.lenient.getOrNull()?.let { target ->
target.project.container(KotlinTarget::class.java)
.apply { add(target) }
}
}
abstract class KotlinCommonProjectExtension(project: Project) : KotlinSingleJavaTargetExtension(project) {
override lateinit var target: KotlinWithJavaTarget<KotlinMultiplatformCommonOptions, KotlinMultiplatformCommonCompilerOptions>
internal set
override val target: KotlinWithJavaTarget<*, *> get() = targetFuture.getOrThrow()
override val targetFuture =
CompletableFuture<KotlinWithJavaTarget<KotlinMultiplatformCommonOptions, KotlinMultiplatformCommonCompilerOptions>>()
open fun target(
body: KotlinWithJavaTarget<KotlinMultiplatformCommonOptions, KotlinMultiplatformCommonCompilerOptions>.() -> Unit,
) = target.run(body)
) = project.launch(Undispatched) {
targetFuture.await().body()
}
}
abstract class KotlinAndroidProjectExtension(project: Project) : KotlinSingleTargetExtension<KotlinAndroidTarget>(project) {
override lateinit var target: KotlinAndroidTarget
internal set
override val target: KotlinAndroidTarget get() = targetFuture.getOrThrow()
override val targetFuture = CompletableFuture<KotlinAndroidTarget>()
open fun target(body: KotlinAndroidTarget.() -> Unit) = target.run(body)
open fun target(body: KotlinAndroidTarget.() -> Unit) = project.launch(Undispatched) {
targetFuture.await().body()
}
val compilerOptions: KotlinJvmCompilerOptions = project.objects
.newInstance(KotlinJvmCompilerOptionsDefault::class.java)
@@ -52,7 +52,7 @@ internal open class Kotlin2JsPlugin(
}
) as KotlinWithJavaTarget<KotlinJsOptions, KotlinJsCompilerOptions>
(project.kotlinExtension as Kotlin2JsProjectExtension).setTarget(target)
(project.kotlinExtension as Kotlin2JsProjectExtension).targetFuture.complete(target)
super.apply(project)
}
}
@@ -38,7 +38,7 @@ internal open class KotlinAndroidPlugin(
project
).also { target ->
val kotlinAndroidExtension = project.kotlinExtension as KotlinAndroidProjectExtension
kotlinAndroidExtension.target = target
kotlinAndroidExtension.targetFuture.complete(target)
project.configureCompilerOptionsForTarget(
kotlinAndroidExtension.compilerOptions,
target.compilerOptions
@@ -47,7 +47,7 @@ internal open class KotlinCommonPlugin(
}
}
) as KotlinWithJavaTarget<KotlinMultiplatformCommonOptions, KotlinMultiplatformCommonCompilerOptions>
(project.kotlinExtension as KotlinCommonProjectExtension).target = target
(project.kotlinExtension as KotlinCommonProjectExtension).targetFuture.complete(target)
super.apply(project)
}
@@ -72,7 +72,7 @@ internal open class KotlinJvmPlugin(
disambiguationClassifier = null // don't add anything to the task names
}
val kotlinExtension = project.kotlinExtension as KotlinJvmProjectExtension
kotlinExtension.target = target
kotlinExtension.targetFuture.complete(target)
super.apply(project)
@@ -319,8 +319,7 @@ abstract class AbstractKotlinJsPluginWrapper : KotlinBasePluginWrapper() {
get() = KotlinJsProjectExtension::class
override fun whenBuildEvaluated(project: Project) = project.runProjectConfigurationHealthCheck {
val isJsTargetUninitialized = (project.kotlinExtension as KotlinJsProjectExtension)
._target == null
val isJsTargetUninitialized = !(project.kotlinExtension as KotlinJsProjectExtension).targetFuture.isCompleted
if (isJsTargetUninitialized) {
throw GradleException(
@@ -38,8 +38,7 @@ open class KotlinJsPlugin: Plugin<Project> {
}
project.runProjectConfigurationHealthCheckWhenEvaluated {
@Suppress("DEPRECATION")
if (kotlinExtension._target == null) {
if (!kotlinExtension.targetFuture.isCompleted) {
project.logger.warn(
"""
Please initialize the Kotlin/JS target. Use:
@@ -8,7 +8,9 @@ package org.jetbrains.kotlin.gradle.utils
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.CoroutineStart.Undispatched
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.launch
internal val KotlinProjectExtension.targets: Iterable<KotlinTarget>
get() = when (this) {
@@ -20,8 +22,8 @@ internal val KotlinProjectExtension.targets: Iterable<KotlinTarget>
internal fun KotlinProjectExtension.forAllTargets(action: (target: KotlinTarget) -> Unit) {
when (this) {
is KotlinSingleTargetExtension<*> -> action(this.target)
is KotlinSingleTargetExtension<*> -> project.launch(Undispatched) { action(targetFuture.await()) }
is KotlinMultiplatformExtension -> targets.all(action)
else -> error("Unexpected 'kotlin' extension $this")
}
}
}