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 674388b9204..4dca09228f0 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 @@ -325,19 +325,6 @@ private val Project.allProjectsData: Map { return rootProject.allprojects.associateBy { it.path }.mapValues { (path, currentProject) -> - - /* - We're calling into various different projects (Note: This implementation will change with Project Isolation) - Since not all projects might have the Kotlin Gradle Plugin applied we do call into 'idOfRootModule' in two different ways: - - 1) If KGP is applied, we use the lifecycle APIs to safely get a value after the DSL was finalised. - 2) If KGP was *not* applied, we create a Future which - - creates a lazy once the project is evaluated - - only evaluates the lazy once the moduleId is actually accessed - - This double deferral ensures that the value indeed is accessed as late as possible. - Unwrapping the lazy before the buildscript is evaluated will fail with 'future not completed' - */ val moduleId = currentProject.future { ModuleIds.idOfRootModuleSafe(currentProject) }.lenient GranularMetadataTransformation.ProjectData( diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt index e3b8f96ce41..bb940e5affb 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt @@ -26,34 +26,32 @@ import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled import org.jetbrains.kotlin.gradle.tooling.buildKotlinToolingMetadataTask +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.projectStoredProperty import org.jetbrains.kotlin.gradle.utils.whenEvaluated -import org.jetbrains.kotlin.gradle.utils.withPluginId -private val Project.completableFutureForKotlinMultiplatformRootPublication: CompletableFuture +private val Project.kotlinMultiplatformRootPublicationImpl: CompletableFuture by projectStoredProperty { CompletableFuture() } internal val Project.kotlinMultiplatformRootPublication: Future - get() = completableFutureForKotlinMultiplatformRootPublication + get() = kotlinMultiplatformRootPublicationImpl -internal val MultiplatformPublishingSetupAction = KotlinProjectSetupAction { - project.withPluginId( - pluginId = "maven-publish", - whenApplied = { - if (project.kotlinPropertiesProvider.createDefaultMultiplatformPublications) { - project.extensions.configure(PublishingExtension::class.java) { publishing -> - createRootPublication(project, publishing).also(completableFutureForKotlinMultiplatformRootPublication::complete) - createTargetPublications(project, publishing) - } - } else { - completableFutureForKotlinMultiplatformRootPublication.complete(null) - } +internal val MultiplatformPublishingSetupAction = KotlinProjectSetupCoroutine { + if (!project.kotlinPropertiesProvider.createDefaultMultiplatformPublications) { + kotlinMultiplatformRootPublicationImpl.complete(null) + return@KotlinProjectSetupCoroutine + } - project.components.add(project.multiplatformExtension.rootSoftwareComponent) - }, - whenNotApplied = { completableFutureForKotlinMultiplatformRootPublication.complete(null) } - ) + if (isPluginApplied("maven-publish")) { + project.extensions.configure(PublishingExtension::class.java) { publishing -> + createRootPublication(project, publishing).also(kotlinMultiplatformRootPublicationImpl::complete) + createTargetPublications(project, publishing) + } + project.components.add(project.multiplatformExtension.rootSoftwareComponent) + } else { + kotlinMultiplatformRootPublicationImpl.complete(null) + } } /** diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/PluginManagerUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/PluginManagerUtils.kt index 26db825f196..0ea3165f554 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/PluginManagerUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/PluginManagerUtils.kt @@ -10,18 +10,21 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.AfterEvalu import org.jetbrains.kotlin.gradle.plugin.launchInStage /** - * Executes the given block of code if the Gradle Plugin with [pluginId] is applied to the project. Otherwise, - * executes another block of code. + * Returns true as soon as Gradle plugin with [pluginId] is applied. + * Returns false if plugin wasn't applied during */ -internal fun Project.withPluginId( - pluginId: String, - whenApplied: () -> T, - whenNotApplied: () -> T, -): Future { - val result = CompletableFuture() - pluginManager.withPlugin(pluginId) { result.complete(whenApplied()) } +internal suspend fun Project.isPluginApplied(pluginId: String): Boolean { + val result = CompletableFuture() + pluginManager.withPlugin(pluginId) { + check(!result.isCompleted) { + "Plugin '$pluginId' was applied too late. It was expected to be applied during build script evaluation" + } + result.complete(true) + } // All plugins must be applied during evaluation of build script - launchInStage(AfterEvaluateBuildscript) { if (!result.isCompleted) result.complete(whenNotApplied()) } + launchInStage(AfterEvaluateBuildscript) { + if (!result.isCompleted) result.complete(false) + } - return result + return result.await() } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/PluginManagerUtilTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/PluginManagerUtilTest.kt index fab2eb09829..3f114951a04 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/PluginManagerUtilTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/PluginManagerUtilTest.kt @@ -8,15 +8,17 @@ package org.jetbrains.kotlin.gradle.unitTests import org.gradle.api.Project +import org.gradle.api.ProjectConfigurationException import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.AfterEvaluateBuildscript import org.jetbrains.kotlin.gradle.plugin.currentKotlinPluginLifecycle -import org.jetbrains.kotlin.gradle.util.buildProject +import org.jetbrains.kotlin.gradle.plugin.launch +import org.jetbrains.kotlin.gradle.plugin.launchInStage +import org.jetbrains.kotlin.gradle.util.allCauses import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP import org.jetbrains.kotlin.gradle.util.kotlin import org.jetbrains.kotlin.gradle.util.runLifecycleAwareTest -import org.jetbrains.kotlin.gradle.utils.withPluginId -import kotlin.test.Test -import kotlin.test.assertTrue +import org.jetbrains.kotlin.gradle.utils.isPluginApplied +import kotlin.test.* class PluginManagerUtilTest { @@ -30,46 +32,48 @@ class PluginManagerUtilTest { } @Test - fun `test - withPluginId - when plugin applied`() { - val project = buildProject { + fun `test - isPluginApplied - when plugin applied`() { + val project = buildSampleProject { plugins.apply("maven-publish") } - val future = project.withPluginId( - pluginId = "maven-publish", - whenApplied = { true }, - whenNotApplied = { false } - ) - - assertTrue(future.getOrThrow()) + project.runLifecycleAwareTest { + assertTrue(isPluginApplied("maven-publish")) + } } @Test - fun `test - withPluginId - call withPluginId before plugin is applied`() { - val project = buildProject {} - val future = project.withPluginId( - pluginId = "maven-publish", - whenApplied = { true }, - whenNotApplied = { false } - ) - project.plugins.apply("maven-publish") - - assertTrue(future.getOrThrow()) + fun `test - isPluginApplied - call withPluginId before plugin is applied`() { + val project = buildSampleProject {} + project.runLifecycleAwareTest { + launch { assertTrue(isPluginApplied("maven-publish")) } + project.plugins.apply("maven-publish") + } } @Test fun `test - withPluginId - plugin not applied`() { val project = buildSampleProject() - val future = project.withPluginId( - pluginId = "some-plugin-id", - whenApplied = { false }, - whenNotApplied = { true }, - ) - project.runLifecycleAwareTest { - assertTrue(future.await()) + assertFalse(isPluginApplied("maven-publish")) val currentStage = currentKotlinPluginLifecycle().stage assertTrue(currentStage <= AfterEvaluateBuildscript) } } + + @Test + fun `test - withPluginId - plugin can't be applied after build script evaluation`() { + val project = buildSampleProject() + + val error = assertFails { + project.runLifecycleAwareTest { + launchInStage(AfterEvaluateBuildscript.nextOrLast) { plugins.apply("maven-publish") } + isPluginApplied("maven-publish") + } + } + + val errorMessages = error.allCauses.map { it.message } + val expectedErrorMessage = "Plugin 'maven-publish' was applied too late. It was expected to be applied during build script evaluation" + if (expectedErrorMessage !in errorMessages) fail("Expected to fail with: $expectedErrorMessage") + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/util/exceptionUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/util/exceptionUtils.kt new file mode 100644 index 00000000000..c1479236a31 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/util/exceptionUtils.kt @@ -0,0 +1,28 @@ +/* + * 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.util + +import org.gradle.internal.exceptions.MultiCauseException + +val Throwable.allCauses: Set + get() { + val result = mutableSetOf() + val queue = ArrayDeque() + queue.add(this) + + while (queue.isNotEmpty()) { + val error = queue.removeFirst() + if (!result.add(error)) continue + + if (error is MultiCauseException) { + queue.addAll(error.causes) + } else { + if (error.cause != null) queue.addLast(error.cause!!) + } + } + + return result + } \ No newline at end of file