org.jetbrains.kotlin.gradle.utils.Future.kt Maven / Gradle / Ivy
/*
* 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.utils
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.HasProject
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.CoroutineStart.Undispatched
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.IllegalLifecycleException
import org.jetbrains.kotlin.gradle.plugin.kotlinPluginLifecycle
import org.jetbrains.kotlin.tooling.core.ExtrasLazyProperty
import org.jetbrains.kotlin.tooling.core.HasMutableExtras
import org.jetbrains.kotlin.tooling.core.extrasLazyProperty
import java.io.Serializable
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
/**
* See [KotlinPluginLifecycle]:
* This [Future] represents a value that will be available in some 'future' time.
*
*
* #### Simple use case example: Deferring the value of a property to a given [KotlinPluginLifecycle.Stage]:
*
* ```kotlin
* val myFutureProperty: Future = project.future {
* await(FinaliseDsl) // <- suspends
* 42
* }
* ```
*
* Futures can also be used to dynamically extend entities implementing [HasMutableExtras] and [HasProject]
* #### Example Usage: Extending KotlinSourceSet with a property that relies on the final refines edges.
*
* ```kotlin
* internal val KotlinSourceSet.dependsOnCommonMain: Future by lazyFuture("dependsOnCommonMain") {
* await(AfterFinaliseRefinesEdges)
* return dependsOn.contains { it.name == "commonMain") }
* }
* ```
*/
internal interface Future {
suspend fun await(): T
fun getOrThrow(): T
}
internal interface LenientFuture : Future {
fun getOrNull(): T?
}
internal interface CompletableFuture : Future {
val isCompleted: Boolean
fun complete(value: T)
}
internal fun Future.map(transform: (T) -> R): Future {
return MappedFutureImpl(this, transform)
}
internal fun CompletableFuture.complete() = complete(Unit)
/**
* Extend a given [Receiver] with data produced by [block]:
* This uses the [HasMutableExtras] infrastructure to store/share the produced future entity to the given [Receiver]
* Note: The [block] will be lazily launched on first access to this extension!
* @param name: The name of the extras key being used to store the future (see [extrasLazyProperty])
*/
internal inline fun futureExtension(
name: String? = null, noinline block: suspend Receiver.() -> T,
): ExtrasLazyProperty> where Receiver : HasMutableExtras, Receiver : HasProject {
return extrasLazyProperty>(name) {
project.future { block() }
}
}
internal fun Project.future(
start: KotlinPluginLifecycle.CoroutineStart = Undispatched,
block: suspend Project.() -> T,
): Future = kotlinPluginLifecycle.future(start) { block() }
internal val Future.lenient: LenientFuture get() = LenientFutureImpl(this)
/**
* Shortcut for
* ```kotlin
* lazy { future { block() } }
* ```
*
* basically creating a future, which is launched lazily
*/
internal fun Project.lazyFuture(block: suspend Project.() -> T): Future = LazyFutureImpl(lazy { future { block() } })
internal fun KotlinPluginLifecycle.future(
start: KotlinPluginLifecycle.CoroutineStart = Undispatched,
block: suspend () -> T,
): Future {
return FutureImpl(lifecycle = this).also { future ->
launch(start) { future.completeWith(runCatching { block() }) }
}
}
internal fun CompletableFuture(): CompletableFuture {
return FutureImpl()
}
private class FutureImpl(
private val deferred: Completable = Completable(),
private val lifecycle: KotlinPluginLifecycle? = null,
) : CompletableFuture, Serializable {
fun completeWith(result: Result) = deferred.completeWith(result)
override val isCompleted: Boolean
get() = deferred.isCompleted
override fun complete(value: T) {
deferred.complete(value)
}
override suspend fun await(): T {
return deferred.await()
}
override fun getOrThrow(): T {
return if (deferred.isCompleted) deferred.getCompleted() else throw IllegalLifecycleException(
"Future was not completed yet" + if (lifecycle != null) " '$lifecycle'"
else ""
)
}
private fun writeReplace(): Any {
return Surrogate(getOrThrow())
}
private class Surrogate(private val value: T) : Serializable {
private fun readResolve(): Any {
return FutureImpl(Completable(value))
}
}
}
private class MappedFutureImpl(
private val future: Future,
private var transform: (T) -> R,
) : Future, Serializable {
private val value = Completable()
override suspend fun await(): R {
if (value.isCompleted) return value.getCompleted()
value.complete(transform(future.await()))
transform = { throw IllegalStateException("Unexpected 'transform' in future") }
return value.getCompleted()
}
override fun getOrThrow(): R {
if (value.isCompleted) return value.getCompleted()
value.complete(transform(future.getOrThrow()))
transform = { throw IllegalStateException("Unexpected 'transform' in future") }
return value.getCompleted()
}
private fun writeReplace(): Any {
return Surrogate(getOrThrow())
}
private class Surrogate(private val value: T) : Serializable {
private fun readResolve(): Any {
return FutureImpl(Completable(value))
}
}
}
private class LenientFutureImpl(
private val future: Future,
) : LenientFuture, Serializable {
override suspend fun await(): T {
return future.await()
}
override fun getOrThrow(): T {
return future.getOrThrow()
}
override fun getOrNull(): T? {
return try {
future.getOrThrow()
} catch (t: IllegalLifecycleException) {
return null
}
}
private fun writeReplace(): Any {
return Surrogate(getOrNull())
}
private class Surrogate(private val value: T) : Serializable {
private fun readResolve(): Any {
return LenientFutureImpl(FutureImpl(Completable(value)))
}
}
}
private class LazyFutureImpl(private val future: Lazy>) : Future, Serializable {
override suspend fun await(): T {
return future.value.await()
}
override fun getOrThrow(): T {
return future.value.getOrThrow()
}
private fun writeReplace(): Any {
return Surrogate(getOrThrow())
}
private class Surrogate(private val value: T) : Serializable {
private fun readResolve(): Any {
return FutureImpl(Completable(value))
}
}
}
/**
* Simple, Single Threaded, replacement for kotlinx.coroutines.CompletableDeferred.
*/
private class Completable(
private var value: Result? = null,
) {
constructor(value: T) : this(Result.success(value))
val isCompleted: Boolean get() = value != null
private val waitingContinuations = mutableListOf>>()
fun completeWith(result: Result) {
check(value == null) { "Already completed with $value" }
value = result
/* Capture and clear current waiting continuations */
val continuations = waitingContinuations.toList()
waitingContinuations.clear()
continuations.forEach { continuation ->
continuation.resume(result)
}
/*
Safety check:
We do not expect any coroutines waiting:
Any continuation that, during its above .resume, calls into '.await()' shall
directly resume and receive the value currently set.
If the waiting continuations are not empty, then those would be leaking.
*/
assert(waitingContinuations.isEmpty())
}
fun complete(value: T) {
completeWith(Result.success(value))
}
fun getCompleted(): T {
val value = this.value ?: throw IllegalStateException("Not completed yet")
return value.getOrThrow()
}
suspend fun await(): T {
val value = this.value
if (value != null) {
return value.getOrThrow()
}
return suspendCoroutine> { continuation ->
waitingContinuations.add(continuation)
}.getOrThrow()
}
}