[Gradle] Implement initial 'KotlinMultiplatformPluginLifecycle'
KT-34662
This commit is contained in:
committed by
Space Team
parent
cf8814823b
commit
cc9bab4105
+103
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.plugin
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginLifecycle.IllegalLifecycleException
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginLifecycle.Stage
|
||||
import org.jetbrains.kotlin.gradle.utils.getOrPut
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
internal val Project.kotlinMultiplatformPluginLifecycle: KotlinMultiplatformPluginLifecycle
|
||||
get() = extraProperties.getOrPut(KotlinMultiplatformPluginLifecycle::class.java.name) { KotlinMultiplatformPluginLifecycleImpl() }
|
||||
|
||||
internal fun Project.startKotlinMultiplatformPluginLifecycle() {
|
||||
(kotlinMultiplatformPluginLifecycle as KotlinMultiplatformPluginLifecycleImpl).start(this)
|
||||
}
|
||||
|
||||
internal inline fun Project.enqueue(stage: Stage, crossinline action: Project.() -> Unit) {
|
||||
kotlinMultiplatformPluginLifecycle.enqueue(stage) { action() }
|
||||
}
|
||||
|
||||
internal interface KotlinMultiplatformPluginLifecycle {
|
||||
enum class Stage {
|
||||
Configure,
|
||||
AfterEvaluate,
|
||||
FinaliseRefinesEdges,
|
||||
FinaliseCompilations,
|
||||
Finalised
|
||||
}
|
||||
|
||||
val stage: Stage
|
||||
|
||||
fun enqueue(stage: Stage, action: () -> Unit)
|
||||
|
||||
class IllegalLifecycleException(message: String) : IllegalStateException(message)
|
||||
}
|
||||
|
||||
private class KotlinMultiplatformPluginLifecycleImpl : KotlinMultiplatformPluginLifecycle {
|
||||
private val enqueuedStages = ArrayDeque(Stage.values().toList())
|
||||
private val enqueuedActions = Stage.values().associateWith { ArrayDeque<() -> Unit>() }
|
||||
private var configureLoopRunning = false
|
||||
private var isStarted = AtomicBoolean(false)
|
||||
|
||||
fun start(project: Project) {
|
||||
check(!isStarted.getAndSet(true)) {
|
||||
"${KotlinMultiplatformPluginLifecycle::class.java.name} already started"
|
||||
}
|
||||
|
||||
check(!project.state.executed) {
|
||||
"${KotlinMultiplatformPluginLifecycle::class.java.name} cannot be started in ProjectState '${project.state}'"
|
||||
}
|
||||
|
||||
project.whenEvaluated {
|
||||
executeStage(project, enqueuedStages.removeFirst())
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeStage(project: Project, stage: Stage) {
|
||||
this.stage = stage
|
||||
val queue = enqueuedActions.getValue(stage)
|
||||
do {
|
||||
val action = queue.removeFirstOrNull()
|
||||
action?.invoke()
|
||||
} while (action != null)
|
||||
val nextStage = enqueuedStages.removeFirstOrNull() ?: return
|
||||
|
||||
project.afterEvaluate {
|
||||
executeStage(project, nextStage)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startConfigureLoopIfNecessary() {
|
||||
check(stage == Stage.Configure) { "Cannot start 'configure loop' on stage '$stage'" }
|
||||
if (configureLoopRunning) return
|
||||
configureLoopRunning = true
|
||||
try {
|
||||
val queue = enqueuedActions.getValue(Stage.Configure)
|
||||
do {
|
||||
val action = queue.removeFirstOrNull()
|
||||
action?.invoke()
|
||||
} while (action != null)
|
||||
} finally {
|
||||
configureLoopRunning = false
|
||||
}
|
||||
}
|
||||
|
||||
override var stage: Stage = enqueuedStages.removeFirst()
|
||||
|
||||
override fun enqueue(stage: Stage, action: () -> Unit) {
|
||||
if (stage < this.stage) {
|
||||
throw IllegalLifecycleException("Cannot enqueue Action for stage '${this.stage}' in current stage '${this.stage}'")
|
||||
}
|
||||
|
||||
enqueuedActions.getValue(stage).addLast(action)
|
||||
|
||||
if (stage == Stage.Configure) {
|
||||
startConfigureLoopIfNecessary()
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -323,6 +323,7 @@ abstract class AbstractKotlinMultiplatformPluginWrapper : KotlinBasePluginWrappe
|
||||
|
||||
override fun apply(project: Project) {
|
||||
super.apply(project)
|
||||
project.startKotlinMultiplatformPluginLifecycle()
|
||||
project.runMultiplatformAndroidGradlePluginCompatibilityHealthCheckWhenAndroidIsApplied()
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -8,7 +8,9 @@
|
||||
package org.jetbrains.kotlin.gradle.plugin.sources
|
||||
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginLifecycle
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
||||
import org.jetbrains.kotlin.gradle.plugin.kotlinMultiplatformPluginLifecycle
|
||||
import org.jetbrains.kotlin.gradle.utils.MutableObservableSet
|
||||
import org.jetbrains.kotlin.gradle.utils.MutableObservableSetImpl
|
||||
import org.jetbrains.kotlin.gradle.utils.ObservableSet
|
||||
@@ -31,6 +33,11 @@ abstract class AbstractKotlinSourceSet : InternalKotlinSourceSet {
|
||||
|
||||
final override fun dependsOn(other: KotlinSourceSet) {
|
||||
if (other == this) return
|
||||
|
||||
assert(project.kotlinMultiplatformPluginLifecycle.stage <= KotlinMultiplatformPluginLifecycle.Stage.FinaliseRefinesEdges) {
|
||||
"Illegal 'dependsOn' call in stage '${project.kotlinMultiplatformPluginLifecycle.stage}'"
|
||||
}
|
||||
|
||||
/*
|
||||
Circular dependsOn hierarchies are not allowed:
|
||||
Throw if this SourceSet is already present in the dependsOnClosure of 'other'
|
||||
|
||||
+1
-1
@@ -175,7 +175,7 @@ class KotlinMetadataTargetConfigurator :
|
||||
private fun createMetadataCompilationsForCommonSourceSets(
|
||||
target: KotlinMetadataTarget,
|
||||
allMetadataJar: TaskProvider<out Jar>
|
||||
) = target.project.whenEvaluated {
|
||||
) = target.project.enqueue(KotlinMultiplatformPluginLifecycle.Stage.FinaliseCompilations) {
|
||||
// Do this after all targets are configured by the user build script
|
||||
|
||||
val publishedCommonSourceSets: Set<KotlinSourceSet> = getCommonSourceSetsForMetadataCompilation(project)
|
||||
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@file:Suppress("FunctionName")
|
||||
|
||||
package org.jetbrains.kotlin.gradle.unitTests
|
||||
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginLifecycle
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginLifecycle.Stage.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.kotlinMultiplatformPluginLifecycle
|
||||
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.test.*
|
||||
|
||||
class KotlinMultiplatformPluginLifecycleTest {
|
||||
|
||||
private val project = buildProjectWithMPP()
|
||||
private val lifecycle = project.kotlinMultiplatformPluginLifecycle
|
||||
|
||||
@Test
|
||||
fun `test - configure phase is executed right away`() {
|
||||
val invocations = AtomicInteger(0)
|
||||
lifecycle.enqueue(Configure) {
|
||||
invocations.incrementAndGet()
|
||||
}
|
||||
assertEquals(1, invocations.get(), "Expected one invocation")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - configure phase - nested enqueue - is executed as queue`() {
|
||||
val outerInvocations = AtomicInteger(0)
|
||||
val nestedAInvocations = AtomicInteger(0)
|
||||
val nestedBInvocations = AtomicInteger(0)
|
||||
val nestedCInvocations = AtomicInteger(0)
|
||||
lifecycle.enqueue(Configure) {
|
||||
assertEquals(1, outerInvocations.incrementAndGet())
|
||||
|
||||
lifecycle.enqueue(Configure) nestedA@{
|
||||
assertEquals(0, nestedBInvocations.get(), "Expected nestedA to be executed before nestedB")
|
||||
assertEquals(1, nestedAInvocations.incrementAndGet())
|
||||
|
||||
lifecycle.enqueue(Configure) nestedC@{
|
||||
assertEquals(1, nestedBInvocations.get(), "Expected nestedB to be executed before nestedC")
|
||||
assertEquals(1, nestedCInvocations.incrementAndGet())
|
||||
}
|
||||
}
|
||||
|
||||
lifecycle.enqueue(Configure) nestedB@{
|
||||
assertEquals(1, nestedAInvocations.get(), "Expected nestedA to be executed before nestedB")
|
||||
assertEquals(0, nestedCInvocations.get(), "Expected nestedB to be executed before nestedC")
|
||||
assertEquals(1, nestedBInvocations.incrementAndGet())
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(1, outerInvocations.get())
|
||||
assertEquals(1, nestedAInvocations.get())
|
||||
assertEquals(1, nestedBInvocations.get())
|
||||
assertEquals(1, nestedCInvocations.get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - all stages are executed in order`() {
|
||||
val invocations = KotlinMultiplatformPluginLifecycle.Stage.values().associateWith { AtomicInteger(0) }
|
||||
KotlinMultiplatformPluginLifecycle.Stage.values().toList().forEach { stage ->
|
||||
lifecycle.enqueue(stage) {
|
||||
KotlinMultiplatformPluginLifecycle.Stage.values().forEach { otherStage ->
|
||||
when {
|
||||
otherStage.ordinal < stage.ordinal -> assertEquals(1, invocations.getValue(otherStage).get())
|
||||
otherStage.ordinal == stage.ordinal -> assertEquals(1, invocations.getValue(stage).incrementAndGet())
|
||||
else -> assertEquals(0, invocations.getValue(otherStage).get())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project.evaluate()
|
||||
|
||||
invocations.forEach { (stage, invocations) ->
|
||||
assertEquals(1, invocations.get(), "Expected stage '$stage' to be executed")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - afterEvaluate based stage executes queue in order`() {
|
||||
val action1Invocations = AtomicInteger(0)
|
||||
val action2Invocations = AtomicInteger(0)
|
||||
val action3Invocations = AtomicInteger(0)
|
||||
|
||||
lifecycle.enqueue(Finalised) action3@{
|
||||
assertEquals(1, action1Invocations.get(), "Expected action1 to be executed before action3")
|
||||
assertEquals(1, action2Invocations.get(), "Expected action2 to be executed before action3")
|
||||
assertEquals(1, action3Invocations.incrementAndGet())
|
||||
}
|
||||
|
||||
lifecycle.enqueue(AfterEvaluate) action1@{
|
||||
assertEquals(0, action2Invocations.get(), "Expected action1 to be executed before action2")
|
||||
assertEquals(0, action3Invocations.get(), "Expected action1 to be executed before action3")
|
||||
assertEquals(1, action1Invocations.incrementAndGet())
|
||||
}
|
||||
|
||||
lifecycle.enqueue(AfterEvaluate) action2@{
|
||||
assertEquals(1, action1Invocations.get(), "Expected action1 to be executed before action2")
|
||||
assertEquals(0, action3Invocations.get(), "Expected action2 to be executed before action3")
|
||||
assertEquals(1, action2Invocations.incrementAndGet())
|
||||
}
|
||||
|
||||
assertEquals(0, action1Invocations.get())
|
||||
assertEquals(0, action2Invocations.get())
|
||||
assertEquals(0, action3Invocations.get())
|
||||
|
||||
project.evaluate()
|
||||
|
||||
assertEquals(1, action1Invocations.get())
|
||||
assertEquals(1, action2Invocations.get())
|
||||
assertEquals(1, action3Invocations.get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - afterEvaluate based stage - allows enqueue in current stage`() {
|
||||
val outerInvocations = AtomicInteger(0)
|
||||
val nestedAInvocations = AtomicInteger(0)
|
||||
val nestedBInvocations = AtomicInteger(0)
|
||||
val nestedCInvocations = AtomicInteger(0)
|
||||
lifecycle.enqueue(AfterEvaluate) {
|
||||
assertEquals(1, outerInvocations.incrementAndGet())
|
||||
|
||||
lifecycle.enqueue(AfterEvaluate) nestedA@{
|
||||
assertEquals(0, nestedBInvocations.get(), "Expected nestedA to be executed before nestedB")
|
||||
assertEquals(1, nestedAInvocations.incrementAndGet())
|
||||
|
||||
lifecycle.enqueue(AfterEvaluate) nestedC@{
|
||||
assertEquals(1, nestedBInvocations.get(), "Expected nestedB to be executed before nestedC")
|
||||
assertEquals(1, nestedCInvocations.incrementAndGet())
|
||||
}
|
||||
}
|
||||
|
||||
lifecycle.enqueue(AfterEvaluate) nestedB@{
|
||||
assertEquals(1, nestedAInvocations.get(), "Expected nestedA to be executed before nestedB")
|
||||
assertEquals(0, nestedCInvocations.get(), "Expected nestedB to be executed before nestedC")
|
||||
assertEquals(1, nestedBInvocations.incrementAndGet())
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(0, outerInvocations.get())
|
||||
assertEquals(0, nestedAInvocations.get())
|
||||
assertEquals(0, nestedBInvocations.get())
|
||||
assertEquals(0, nestedCInvocations.get())
|
||||
|
||||
project.evaluate()
|
||||
|
||||
assertEquals(1, outerInvocations.get())
|
||||
assertEquals(1, nestedAInvocations.get())
|
||||
assertEquals(1, nestedBInvocations.get())
|
||||
assertEquals(1, nestedCInvocations.get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - enqueue of already executed stage - throws exception`() {
|
||||
val executed = AtomicBoolean(false)
|
||||
lifecycle.enqueue(Finalised) {
|
||||
assertFailsWith<KotlinMultiplatformPluginLifecycle.IllegalLifecycleException> {
|
||||
lifecycle.enqueue(AfterEvaluate) { fail("This code shall not be executed!") }
|
||||
}
|
||||
assertFalse(executed.getAndSet(true))
|
||||
}
|
||||
|
||||
project.evaluate()
|
||||
assertTrue(executed.get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - stage property is correct`() {
|
||||
KotlinMultiplatformPluginLifecycle.Stage.values().forEach { stage ->
|
||||
lifecycle.enqueue(stage) {
|
||||
assertEquals(lifecycle.stage, stage)
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - invoke configure twice`() {
|
||||
val action1Invocations = AtomicInteger(0)
|
||||
val action2Invocations = AtomicInteger(0)
|
||||
val action3Invocations = AtomicInteger(0)
|
||||
|
||||
lifecycle.enqueue(Configure) action1@{
|
||||
assertEquals(0, action2Invocations.get())
|
||||
assertEquals(0, action3Invocations.get())
|
||||
assertEquals(1, action1Invocations.incrementAndGet())
|
||||
}
|
||||
|
||||
lifecycle.enqueue(Configure) action2@{
|
||||
lifecycle.enqueue(Configure) action3@{
|
||||
assertEquals(1, action1Invocations.get())
|
||||
assertEquals(1, action2Invocations.get())
|
||||
assertEquals(1, action3Invocations.incrementAndGet())
|
||||
}
|
||||
|
||||
assertEquals(1, action1Invocations.get())
|
||||
assertEquals(0, action3Invocations.get())
|
||||
assertEquals(1, action2Invocations.incrementAndGet())
|
||||
}
|
||||
|
||||
assertEquals(1, action1Invocations.get())
|
||||
assertEquals(1, action2Invocations.get())
|
||||
assertEquals(1, action3Invocations.get())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user