diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt index 7f50ec78b99..889a6425de6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt @@ -17,10 +17,7 @@ import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ArtifactMetadataProvider import org.jetbrains.kotlin.gradle.plugin.sources.internal -import org.jetbrains.kotlin.gradle.utils.Future -import org.jetbrains.kotlin.gradle.utils.LazyResolvedConfiguration -import org.jetbrains.kotlin.gradle.utils.future -import org.jetbrains.kotlin.gradle.utils.getOrPut +import org.jetbrains.kotlin.gradle.utils.* import java.util.* internal sealed class MetadataDependencyResolution( @@ -116,8 +113,8 @@ internal class GranularMetadataTransformation( class ProjectData( val path: String, - val sourceSetMetadataOutputs: Future>, - val moduleId: Future + val sourceSetMetadataOutputs: LenientFuture>, + val moduleId: LenientFuture ) { override fun toString(): String = "ProjectData[path='$path']" } @@ -339,11 +336,11 @@ private fun Project.collectAllProjectsData(): Map GranularMetadataTransformation.ProjectData( path = path, - sourceSetMetadataOutputs = currentProject.future { currentProject.collectSourceSetMetadataOutputs() }, + sourceSetMetadataOutputs = currentProject.future { currentProject.collectSourceSetMetadataOutputs() }.lenient, moduleId = currentProject.future { await(KotlinPluginLifecycle.Stage.AfterFinaliseDsl) ModuleIds.idOfRootModule(currentProject) - } + }.lenient ) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/Future.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/Future.kt index a316ef01f31..2bb3b2286bf 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/Future.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/Future.kt @@ -47,6 +47,12 @@ internal interface Future { fun getOrThrow(): T } +internal interface LenientFuture : Future { + override suspend fun await(): T + override fun getOrThrow(): T + fun getOrNull(): T? +} + internal interface CompletableFuture : Future { fun complete(value: T) } @@ -69,6 +75,8 @@ internal inline fun futureExtension( internal fun Project.future(block: suspend Project.() -> T): Future = kotlinPluginLifecycle.future { block() } +internal val Future.lenient: LenientFuture get() = LenientFutureImpl(this) + /** * Shortcut for * ```kotlin @@ -106,7 +114,48 @@ private class FutureImpl( override fun getOrThrow(): T { return if (deferred.isCompleted) deferred.getCompleted() else throw IllegalLifecycleException( - "Future was not completed yet" + if (lifecycle != null) " (stage '${lifecycle.stage}')" else "" + "Future was not completed yet" + if (lifecycle != null) " (stage '${lifecycle.stage}') (${lifecycle.project.displayName})" + else "" ) } + + private fun writeReplace(): Any { + return Surrogate(getOrThrow()) + } + + private class Surrogate(private val value: T) : Serializable { + private fun readResolve(): Any { + return FutureImpl(CompletableDeferred(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(CompletableDeferred(value))) + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/FutureTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/FutureTest.kt index 4c9110d8c43..4c3d7531de4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/FutureTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/FutureTest.kt @@ -7,6 +7,8 @@ package org.jetbrains.kotlin.gradle.unitTests +import org.jetbrains.kotlin.gradle.idea.testFixtures.utils.deserialize +import org.jetbrains.kotlin.gradle.idea.testFixtures.utils.serialize import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.IllegalLifecycleException import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.FinaliseDsl import org.jetbrains.kotlin.gradle.plugin.await @@ -14,10 +16,15 @@ import org.jetbrains.kotlin.gradle.plugin.currentKotlinPluginLifecycle import org.jetbrains.kotlin.gradle.plugin.startKotlinPluginLifecycle import org.jetbrains.kotlin.gradle.util.buildProject import org.jetbrains.kotlin.gradle.util.runLifecycleAwareTest +import org.jetbrains.kotlin.gradle.utils.CompletableFuture +import org.jetbrains.kotlin.gradle.utils.LenientFuture import org.jetbrains.kotlin.gradle.utils.future +import org.jetbrains.kotlin.gradle.utils.lenient import org.junit.Test +import org.junit.jupiter.api.assertThrows import kotlin.test.assertEquals import kotlin.test.assertFailsWith +import kotlin.test.assertNull class FutureTest { @@ -67,4 +74,34 @@ class FutureTest { assertEquals(42, future.getOrThrow()) } -} \ No newline at end of file + + @Test + fun `test - lenient future`() { + val future = CompletableFuture() + assertNull(future.lenient.getOrNull()) + assertThrows { future.lenient.getOrThrow() } + + future.complete(42) + assertEquals(42, future.lenient.getOrThrow()) + assertEquals(42, future.lenient.getOrNull()) + } + + @Test + fun `test - lenient future serialize`() { + val future = CompletableFuture() + assertFailsWith { future.serialize() } + + run { + val futureBinary = future.lenient.serialize() + val deserializedFuture = futureBinary.deserialize() as LenientFuture<*> + assertNull(deserializedFuture.getOrNull()) + } + + run { + future.complete(42) + val futureBinary = future.lenient.serialize() + val deserializedFuture = futureBinary.deserialize() as LenientFuture<*> + assertEquals(42, deserializedFuture.getOrNull()) + } + } +}