[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_BUILD_TOOLS_API_IMPL
import org.jetbrains.kotlin.gradle.internal.KOTLIN_MODULE_GROUP import org.jetbrains.kotlin.gradle.internal.KOTLIN_MODULE_GROUP
import org.jetbrains.kotlin.gradle.plugin.* 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.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaTarget
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrSingleTargetPreset import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrSingleTargetPreset
import org.jetbrains.kotlin.gradle.tasks.CompileUsingKotlinDaemon import org.jetbrains.kotlin.gradle.tasks.CompileUsingKotlinDaemon
import org.jetbrains.kotlin.gradle.tasks.withType 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.castIsolatedKotlinPluginClassLoaderAware
import org.jetbrains.kotlin.gradle.utils.configureExperimentalTryK2 import org.jetbrains.kotlin.gradle.utils.configureExperimentalTryK2
import org.jetbrains.kotlin.konan.target.CompilerOutputKind 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.HasMutableExtras
import org.jetbrains.kotlin.tooling.core.MutableExtras import org.jetbrains.kotlin.tooling.core.MutableExtras
import org.jetbrains.kotlin.tooling.core.mutableExtrasOf 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 class KotlinSingleTargetExtension<TARGET : KotlinTarget>(project: Project) : KotlinProjectExtension(project) {
abstract val target: TARGET abstract val target: TARGET
internal abstract val targetFuture: Future<TARGET>
fun target(body: Action<TARGET>) = body.execute(target) fun target(body: Action<TARGET>) = body.execute(target)
} }
abstract class KotlinSingleJavaTargetExtension(project: Project) : KotlinSingleTargetExtension<KotlinWithJavaTarget<*, *>>(project) abstract class KotlinSingleJavaTargetExtension(project: Project) : KotlinSingleTargetExtension<KotlinWithJavaTarget<*, *>>(project)
abstract class KotlinJvmProjectExtension(project: Project) : KotlinSingleJavaTargetExtension(project) { abstract class KotlinJvmProjectExtension(project: Project) : KotlinSingleJavaTargetExtension(project) {
override lateinit var target: KotlinWithJavaTarget<KotlinJvmOptions, KotlinJvmCompilerOptions> override val target: KotlinWithJavaTarget<KotlinJvmOptions, KotlinJvmCompilerOptions>
internal set 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 val compilerOptions: KotlinJvmCompilerOptions = project.objects
.newInstance(KotlinJvmCompilerOptionsDefault::class.java) .newInstance(KotlinJvmCompilerOptionsDefault::class.java)
@@ -206,20 +216,16 @@ abstract class KotlinJvmProjectExtension(project: Project) : KotlinSingleJavaTar
} }
abstract class Kotlin2JsProjectExtension(project: Project) : KotlinSingleJavaTargetExtension(project) { abstract class Kotlin2JsProjectExtension(project: Project) : KotlinSingleJavaTargetExtension(project) {
private lateinit var _target: KotlinWithJavaTarget<KotlinJsOptions, KotlinJsCompilerOptions>
override val target: KotlinWithJavaTarget<KotlinJsOptions, KotlinJsCompilerOptions> override val target: KotlinWithJavaTarget<KotlinJsOptions, KotlinJsCompilerOptions>
get() { get() {
if (!::_target.isInitialized) throw IllegalStateException("Extension target is not initialized!") if (!targetFuture.isCompleted) throw IllegalStateException("Extension target is not initialized!")
return targetFuture.getOrThrow()
return _target
} }
internal fun setTarget(target: KotlinWithJavaTarget<KotlinJsOptions, KotlinJsCompilerOptions>) { override val targetFuture = CompletableFuture<KotlinWithJavaTarget<KotlinJsOptions, KotlinJsCompilerOptions>>()
_target = target 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) : abstract class KotlinJsProjectExtension(project: Project) :
@@ -227,45 +233,31 @@ abstract class KotlinJsProjectExtension(project: Project) :
KotlinJsCompilerTypeHolder { KotlinJsCompilerTypeHolder {
lateinit var irPreset: KotlinJsIrSingleTargetPreset 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") @Deprecated("Because only IR compiler is left, no more necessary to know about compiler type in properties")
override val compilerTypeFromProperties: KotlinJsCompilerType? = null override val compilerTypeFromProperties: KotlinJsCompilerType? = null
// target is public property override val targetFuture = CompletableFuture<KotlinJsTargetDsl>()
// 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) }
}
fun registerTargetObserver(observer: (KotlinJsTargetDsl?) -> Unit) { fun registerTargetObserver(observer: (KotlinJsTargetDsl?) -> Unit) {
targetSetObservers.add(observer) project.launch(Undispatched) {
} observer(targetFuture.await())
@Deprecated("Use js() instead", ReplaceWith("js()"))
@Suppress("DEPRECATION")
override val target: KotlinJsTargetDsl
get() {
if (_target == null) {
js {}
}
return _target!!
} }
}
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
private fun jsInternal( private fun jsInternal(
compiler: KotlinJsCompilerType? = null, compiler: KotlinJsCompilerType? = null,
body: KotlinJsTargetDsl.() -> Unit, body: KotlinJsTargetDsl.() -> Unit,
): KotlinJsTargetDsl { ): KotlinJsTargetDsl {
if (_target == null) { if (!targetFuture.isCompleted) {
val target: KotlinJsTargetDsl = irPreset val target: KotlinJsTargetDsl = irPreset
.createTargetInternal("js") .createTargetInternal("js")
this._target = target this.targetFuture.complete(target)
target.project.components.addAll(target.components) target.project.components.addAll(target.components)
} }
@@ -317,26 +309,31 @@ abstract class KotlinJsProjectExtension(project: Project) :
) )
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
fun getTargets(): NamedDomainObjectContainer<KotlinTarget>? = fun getTargets(): NamedDomainObjectContainer<KotlinTarget>? =
_target?.let { target -> targetFuture.lenient.getOrNull()?.let { target ->
target.project.container(KotlinTarget::class.java) target.project.container(KotlinTarget::class.java)
.apply { add(target) } .apply { add(target) }
} }
} }
abstract class KotlinCommonProjectExtension(project: Project) : KotlinSingleJavaTargetExtension(project) { abstract class KotlinCommonProjectExtension(project: Project) : KotlinSingleJavaTargetExtension(project) {
override lateinit var target: KotlinWithJavaTarget<KotlinMultiplatformCommonOptions, KotlinMultiplatformCommonCompilerOptions> override val target: KotlinWithJavaTarget<*, *> get() = targetFuture.getOrThrow()
internal set override val targetFuture =
CompletableFuture<KotlinWithJavaTarget<KotlinMultiplatformCommonOptions, KotlinMultiplatformCommonCompilerOptions>>()
open fun target( open fun target(
body: KotlinWithJavaTarget<KotlinMultiplatformCommonOptions, KotlinMultiplatformCommonCompilerOptions>.() -> Unit, body: KotlinWithJavaTarget<KotlinMultiplatformCommonOptions, KotlinMultiplatformCommonCompilerOptions>.() -> Unit,
) = target.run(body) ) = project.launch(Undispatched) {
targetFuture.await().body()
}
} }
abstract class KotlinAndroidProjectExtension(project: Project) : KotlinSingleTargetExtension<KotlinAndroidTarget>(project) { abstract class KotlinAndroidProjectExtension(project: Project) : KotlinSingleTargetExtension<KotlinAndroidTarget>(project) {
override lateinit var target: KotlinAndroidTarget override val target: KotlinAndroidTarget get() = targetFuture.getOrThrow()
internal set 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 val compilerOptions: KotlinJvmCompilerOptions = project.objects
.newInstance(KotlinJvmCompilerOptionsDefault::class.java) .newInstance(KotlinJvmCompilerOptionsDefault::class.java)
@@ -52,7 +52,7 @@ internal open class Kotlin2JsPlugin(
} }
) as KotlinWithJavaTarget<KotlinJsOptions, KotlinJsCompilerOptions> ) as KotlinWithJavaTarget<KotlinJsOptions, KotlinJsCompilerOptions>
(project.kotlinExtension as Kotlin2JsProjectExtension).setTarget(target) (project.kotlinExtension as Kotlin2JsProjectExtension).targetFuture.complete(target)
super.apply(project) super.apply(project)
} }
} }
@@ -38,7 +38,7 @@ internal open class KotlinAndroidPlugin(
project project
).also { target -> ).also { target ->
val kotlinAndroidExtension = project.kotlinExtension as KotlinAndroidProjectExtension val kotlinAndroidExtension = project.kotlinExtension as KotlinAndroidProjectExtension
kotlinAndroidExtension.target = target kotlinAndroidExtension.targetFuture.complete(target)
project.configureCompilerOptionsForTarget( project.configureCompilerOptionsForTarget(
kotlinAndroidExtension.compilerOptions, kotlinAndroidExtension.compilerOptions,
target.compilerOptions target.compilerOptions
@@ -47,7 +47,7 @@ internal open class KotlinCommonPlugin(
} }
} }
) as KotlinWithJavaTarget<KotlinMultiplatformCommonOptions, KotlinMultiplatformCommonCompilerOptions> ) as KotlinWithJavaTarget<KotlinMultiplatformCommonOptions, KotlinMultiplatformCommonCompilerOptions>
(project.kotlinExtension as KotlinCommonProjectExtension).target = target (project.kotlinExtension as KotlinCommonProjectExtension).targetFuture.complete(target)
super.apply(project) super.apply(project)
} }
@@ -72,7 +72,7 @@ internal open class KotlinJvmPlugin(
disambiguationClassifier = null // don't add anything to the task names disambiguationClassifier = null // don't add anything to the task names
} }
val kotlinExtension = project.kotlinExtension as KotlinJvmProjectExtension val kotlinExtension = project.kotlinExtension as KotlinJvmProjectExtension
kotlinExtension.target = target kotlinExtension.targetFuture.complete(target)
super.apply(project) super.apply(project)
@@ -319,8 +319,7 @@ abstract class AbstractKotlinJsPluginWrapper : KotlinBasePluginWrapper() {
get() = KotlinJsProjectExtension::class get() = KotlinJsProjectExtension::class
override fun whenBuildEvaluated(project: Project) = project.runProjectConfigurationHealthCheck { override fun whenBuildEvaluated(project: Project) = project.runProjectConfigurationHealthCheck {
val isJsTargetUninitialized = (project.kotlinExtension as KotlinJsProjectExtension) val isJsTargetUninitialized = !(project.kotlinExtension as KotlinJsProjectExtension).targetFuture.isCompleted
._target == null
if (isJsTargetUninitialized) { if (isJsTargetUninitialized) {
throw GradleException( throw GradleException(
@@ -38,8 +38,7 @@ open class KotlinJsPlugin: Plugin<Project> {
} }
project.runProjectConfigurationHealthCheckWhenEvaluated { project.runProjectConfigurationHealthCheckWhenEvaluated {
@Suppress("DEPRECATION") if (!kotlinExtension.targetFuture.isCompleted) {
if (kotlinExtension._target == null) {
project.logger.warn( project.logger.warn(
""" """
Please initialize the Kotlin/JS target. Use: 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.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension 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.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.launch
internal val KotlinProjectExtension.targets: Iterable<KotlinTarget> internal val KotlinProjectExtension.targets: Iterable<KotlinTarget>
get() = when (this) { get() = when (this) {
@@ -20,8 +22,8 @@ internal val KotlinProjectExtension.targets: Iterable<KotlinTarget>
internal fun KotlinProjectExtension.forAllTargets(action: (target: KotlinTarget) -> Unit) { internal fun KotlinProjectExtension.forAllTargets(action: (target: KotlinTarget) -> Unit) {
when (this) { when (this) {
is KotlinSingleTargetExtension<*> -> action(this.target) is KotlinSingleTargetExtension<*> -> project.launch(Undispatched) { action(targetFuture.await()) }
is KotlinMultiplatformExtension -> targets.all(action) is KotlinMultiplatformExtension -> targets.all(action)
else -> error("Unexpected 'kotlin' extension $this") else -> error("Unexpected 'kotlin' extension $this")
} }
} }