[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> {
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(
@@ -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<MavenPublication?>
private val Project.kotlinMultiplatformRootPublicationImpl: CompletableFuture<MavenPublication?>
by projectStoredProperty { CompletableFuture() }
internal val Project.kotlinMultiplatformRootPublication: Future<MavenPublication?>
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)
}
}
/**
@@ -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 <T> Project.withPluginId(
pluginId: String,
whenApplied: () -> T,
whenNotApplied: () -> T,
): Future<T> {
val result = CompletableFuture<T>()
pluginManager.withPlugin(pluginId) { result.complete(whenApplied()) }
internal suspend fun Project.isPluginApplied(pluginId: String): Boolean {
val result = CompletableFuture<Boolean>()
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()
}
@@ -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")
}
}
@@ -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
}