[Gradle] Change withPluginId to isPluginApplied

It is more convenient to use.
Also added test coverage for cases when plugin is applied too late.

^KT-62877 Verification Pending
This commit is contained in:
Anton Lakotka
2023-10-26 14:02:41 +02:00
committed by Space Team
parent 5607ae1bc3
commit d3f25d81de
5 changed files with 93 additions and 73 deletions
@@ -325,19 +325,6 @@ private val Project.allProjectsData: Map<String, GranularMetadataTransformation.
private fun Project.collectAllProjectsData(): Map<String, GranularMetadataTransformation.ProjectData> { private fun Project.collectAllProjectsData(): Map<String, GranularMetadataTransformation.ProjectData> {
return rootProject.allprojects.associateBy { it.path }.mapValues { (path, currentProject) -> 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 val moduleId = currentProject.future { ModuleIds.idOfRootModuleSafe(currentProject) }.lenient
GranularMetadataTransformation.ProjectData( GranularMetadataTransformation.ProjectData(
@@ -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.plugin.sources.sourceSetDependencyConfigurationByScope
import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled
import org.jetbrains.kotlin.gradle.tooling.buildKotlinToolingMetadataTask 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.CompletableFuture
import org.jetbrains.kotlin.gradle.utils.Future import org.jetbrains.kotlin.gradle.utils.Future
import org.jetbrains.kotlin.gradle.utils.projectStoredProperty import org.jetbrains.kotlin.gradle.utils.projectStoredProperty
import org.jetbrains.kotlin.gradle.utils.whenEvaluated import org.jetbrains.kotlin.gradle.utils.whenEvaluated
import org.jetbrains.kotlin.gradle.utils.withPluginId
private val Project.completableFutureForKotlinMultiplatformRootPublication: CompletableFuture<MavenPublication?> private val Project.kotlinMultiplatformRootPublicationImpl: CompletableFuture<MavenPublication?>
by projectStoredProperty { CompletableFuture() } by projectStoredProperty { CompletableFuture() }
internal val Project.kotlinMultiplatformRootPublication: Future<MavenPublication?> internal val Project.kotlinMultiplatformRootPublication: Future<MavenPublication?>
get() = completableFutureForKotlinMultiplatformRootPublication get() = kotlinMultiplatformRootPublicationImpl
internal val MultiplatformPublishingSetupAction = KotlinProjectSetupAction { internal val MultiplatformPublishingSetupAction = KotlinProjectSetupCoroutine {
project.withPluginId( if (!project.kotlinPropertiesProvider.createDefaultMultiplatformPublications) {
pluginId = "maven-publish", kotlinMultiplatformRootPublicationImpl.complete(null)
whenApplied = { return@KotlinProjectSetupCoroutine
if (project.kotlinPropertiesProvider.createDefaultMultiplatformPublications) { }
project.extensions.configure(PublishingExtension::class.java) { publishing ->
createRootPublication(project, publishing).also(completableFutureForKotlinMultiplatformRootPublication::complete)
createTargetPublications(project, publishing)
}
} else {
completableFutureForKotlinMultiplatformRootPublication.complete(null)
}
project.components.add(project.multiplatformExtension.rootSoftwareComponent) if (isPluginApplied("maven-publish")) {
}, project.extensions.configure(PublishingExtension::class.java) { publishing ->
whenNotApplied = { completableFutureForKotlinMultiplatformRootPublication.complete(null) } createRootPublication(project, publishing).also(kotlinMultiplatformRootPublicationImpl::complete)
) createTargetPublications(project, publishing)
}
project.components.add(project.multiplatformExtension.rootSoftwareComponent)
} else {
kotlinMultiplatformRootPublicationImpl.complete(null)
}
} }
/** /**
@@ -10,18 +10,21 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.AfterEvalu
import org.jetbrains.kotlin.gradle.plugin.launchInStage 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, * Returns true as soon as Gradle plugin with [pluginId] is applied.
* executes another block of code. * Returns false if plugin wasn't applied during
*/ */
internal fun <T> Project.withPluginId( internal suspend fun Project.isPluginApplied(pluginId: String): Boolean {
pluginId: String, val result = CompletableFuture<Boolean>()
whenApplied: () -> T, pluginManager.withPlugin(pluginId) {
whenNotApplied: () -> T, check(!result.isCompleted) {
): Future<T> { "Plugin '$pluginId' was applied too late. It was expected to be applied during build script evaluation"
val result = CompletableFuture<T>() }
pluginManager.withPlugin(pluginId) { result.complete(whenApplied()) } result.complete(true)
}
// All plugins must be applied during evaluation of build script // 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()
} }
@@ -8,15 +8,17 @@
package org.jetbrains.kotlin.gradle.unitTests package org.jetbrains.kotlin.gradle.unitTests
import org.gradle.api.Project 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.KotlinPluginLifecycle.Stage.AfterEvaluateBuildscript
import org.jetbrains.kotlin.gradle.plugin.currentKotlinPluginLifecycle 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.buildProjectWithMPP
import org.jetbrains.kotlin.gradle.util.kotlin import org.jetbrains.kotlin.gradle.util.kotlin
import org.jetbrains.kotlin.gradle.util.runLifecycleAwareTest import org.jetbrains.kotlin.gradle.util.runLifecycleAwareTest
import org.jetbrains.kotlin.gradle.utils.withPluginId import org.jetbrains.kotlin.gradle.utils.isPluginApplied
import kotlin.test.Test import kotlin.test.*
import kotlin.test.assertTrue
class PluginManagerUtilTest { class PluginManagerUtilTest {
@@ -30,46 +32,48 @@ class PluginManagerUtilTest {
} }
@Test @Test
fun `test - withPluginId - when plugin applied`() { fun `test - isPluginApplied - when plugin applied`() {
val project = buildProject { val project = buildSampleProject {
plugins.apply("maven-publish") plugins.apply("maven-publish")
} }
val future = project.withPluginId( project.runLifecycleAwareTest {
pluginId = "maven-publish", assertTrue(isPluginApplied("maven-publish"))
whenApplied = { true }, }
whenNotApplied = { false }
)
assertTrue(future.getOrThrow())
} }
@Test @Test
fun `test - withPluginId - call withPluginId before plugin is applied`() { fun `test - isPluginApplied - call withPluginId before plugin is applied`() {
val project = buildProject {} val project = buildSampleProject {}
val future = project.withPluginId( project.runLifecycleAwareTest {
pluginId = "maven-publish", launch { assertTrue(isPluginApplied("maven-publish")) }
whenApplied = { true }, project.plugins.apply("maven-publish")
whenNotApplied = { false } }
)
project.plugins.apply("maven-publish")
assertTrue(future.getOrThrow())
} }
@Test @Test
fun `test - withPluginId - plugin not applied`() { fun `test - withPluginId - plugin not applied`() {
val project = buildSampleProject() val project = buildSampleProject()
val future = project.withPluginId(
pluginId = "some-plugin-id",
whenApplied = { false },
whenNotApplied = { true },
)
project.runLifecycleAwareTest { project.runLifecycleAwareTest {
assertTrue(future.await()) assertFalse(isPluginApplied("maven-publish"))
val currentStage = currentKotlinPluginLifecycle().stage val currentStage = currentKotlinPluginLifecycle().stage
assertTrue(currentStage <= AfterEvaluateBuildscript) 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")
}
} }
@@ -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<Throwable>
get() {
val result = mutableSetOf<Throwable>()
val queue = ArrayDeque<Throwable>()
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
}