Skip to content

Commit 85059ce

Browse files
sellmairSpace Team
authored and
Space Team
committed
[Gradle] Split KotlinPluginLifecycle and KotlinPluginLifecycleImpl
^KT-58255 Verification Pending
1 parent 7910e0d commit 85059ce

File tree

3 files changed

+248
-231
lines changed

3 files changed

+248
-231
lines changed

libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginLifecycle.kt

Lines changed: 2 additions & 231 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,8 @@ package org.jetbrains.kotlin.gradle.plugin
88
import org.gradle.api.Project
99
import org.gradle.api.provider.Property
1010
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.*
11-
import org.jetbrains.kotlin.gradle.utils.CompletableFuture
1211
import org.jetbrains.kotlin.gradle.utils.Future
13-
import org.jetbrains.kotlin.gradle.utils.failures
1412
import org.jetbrains.kotlin.gradle.utils.getOrPut
15-
import java.util.*
16-
import java.util.concurrent.atomic.AtomicBoolean
17-
import kotlin.collections.ArrayDeque
1813
import kotlin.coroutines.*
1914

2015
/*
@@ -156,11 +151,7 @@ internal val Project.kotlinPluginLifecycle: KotlinPluginLifecycle
156151
* some later data to be available. In this case, the Future still will only return 'sane' data.
157152
*/
158153
internal val Project.configurationResult: Future<ProjectConfigurationResult>
159-
get() = configurationResultImpl
160-
161-
162-
private val Project.configurationResultImpl: CompletableFuture<ProjectConfigurationResult>
163-
get() = extraProperties.getOrPut("org.jetbrains.kotlin.gradle.plugin.configurationResult") { CompletableFuture() }
154+
get() = (kotlinPluginLifecycle as KotlinPluginLifecycleImpl).configurationResult
164155

165156

166157
/**
@@ -175,8 +166,7 @@ internal fun Project.startKotlinPluginLifecycle() {
175166
* the currently running coroutine. Throws if this coroutine was not started using a [KotlinPluginLifecycle]
176167
*/
177168
internal suspend fun currentKotlinPluginLifecycle(): KotlinPluginLifecycle {
178-
return coroutineContext[KotlinPluginLifecycleCoroutineContextElement]?.lifecycle
179-
?: error("Missing $KotlinPluginLifecycleCoroutineContextElement in currentCoroutineContext")
169+
return coroutineContext.kotlinPluginLifecycle
180170
}
181171

182172
/**
@@ -224,33 +214,6 @@ internal suspend fun <T> requireCurrentStage(block: suspend () -> T): T {
224214
return requiredStage(currentKotlinPluginLifecycle().stage, block)
225215
}
226216

227-
/**
228-
* Will ensure that the given [block] cannot leave the specified allowed stages [allowed]
229-
* e.g.
230-
*
231-
* ```kotlin
232-
* project.launchInStage(Stage.BeforeFinaliseDsl) {
233-
* withRestrictedStages(Stage.upTo(Stage.FinaliseDsl)) {
234-
* await(Stage.FinaliseDsl) // <- OK, since still in allowed stages
235-
* await(Stage.AfterFinaliseDsl) // <- fails, since not in allowed stages!
236-
* }
237-
* }
238-
* ```
239-
*/
240-
internal suspend fun <T> withRestrictedStages(allowed: Set<Stage>, block: suspend () -> T): T {
241-
val newCoroutineContext = coroutineContext + RestrictedLifecycleStages(currentKotlinPluginLifecycle(), allowed)
242-
return suspendCoroutine { continuation ->
243-
val newContinuation = object : Continuation<T> {
244-
override val context: CoroutineContext
245-
get() = newCoroutineContext
246-
247-
override fun resumeWith(result: Result<T>) {
248-
continuation.resumeWith(result)
249-
}
250-
}
251-
block.startCoroutine(newContinuation)
252-
}
253-
}
254217

255218
/*
256219
Definition of the Lifecycle and its stages
@@ -339,195 +302,3 @@ internal interface KotlinPluginLifecycle {
339302

340303
class IllegalLifecycleException(message: String) : IllegalStateException(message)
341304
}
342-
343-
344-
/*
345-
Implementation
346-
*/
347-
348-
internal class KotlinPluginLifecycleImpl(override val project: Project) : KotlinPluginLifecycle {
349-
private val enqueuedActions: Map<Stage, ArrayDeque<KotlinPluginLifecycle.() -> Unit>> =
350-
Stage.values().associateWith { ArrayDeque() }
351-
352-
private val loopRunning = AtomicBoolean(false)
353-
private val isStarted = AtomicBoolean(false)
354-
private val isFinishedSuccessfully = AtomicBoolean(false)
355-
private val isFinishedWithFailures = AtomicBoolean(false)
356-
357-
override var stage: Stage = Stage.values.first()
358-
359-
fun start() {
360-
check(!isStarted.getAndSet(true)) {
361-
"${KotlinPluginLifecycle::class.java.name} already started"
362-
}
363-
364-
check(!project.state.executed) {
365-
"${KotlinPluginLifecycle::class.java.name} cannot be started in ProjectState '${project.state}'"
366-
}
367-
368-
loopIfNecessary()
369-
370-
project.whenEvaluated {
371-
/* Check for failures happening during buildscript evaluation */
372-
project.failures.let { failures ->
373-
if (failures.isNotEmpty()) {
374-
finishWithFailures(failures)
375-
return@whenEvaluated
376-
}
377-
}
378-
379-
assert(enqueuedActions.getValue(stage).isEmpty()) { "Expected empty queue from '$stage'" }
380-
stage = stage.nextOrThrow
381-
executeCurrentStageAndScheduleNext()
382-
}
383-
}
384-
385-
private fun executeCurrentStageAndScheduleNext() {
386-
stage.previousOrNull?.let { previousStage ->
387-
assert(enqueuedActions.getValue(previousStage).isEmpty()) {
388-
"Actions from previous stage '$previousStage' have not been executed (stage: '$stage')"
389-
}
390-
}
391-
392-
val failures = project.failures
393-
if (failures.isNotEmpty()) {
394-
finishWithFailures(failures)
395-
return
396-
}
397-
398-
try {
399-
loopIfNecessary()
400-
} catch (t: Throwable) {
401-
finishWithFailures(listOf(t))
402-
throw t
403-
}
404-
405-
stage = stage.nextOrNull ?: run {
406-
finishSuccessfully()
407-
return
408-
}
409-
410-
project.afterEvaluate {
411-
executeCurrentStageAndScheduleNext()
412-
}
413-
}
414-
415-
private fun loopIfNecessary() {
416-
if (loopRunning.getAndSet(true)) return
417-
try {
418-
val queue = enqueuedActions.getValue(stage)
419-
do {
420-
project.state.rethrowFailure()
421-
val action = queue.removeFirstOrNull()
422-
action?.invoke(this)
423-
} while (action != null)
424-
} finally {
425-
loopRunning.set(false)
426-
}
427-
}
428-
429-
private fun finishWithFailures(failures: List<Throwable>) {
430-
assert(failures.isNotEmpty())
431-
assert(isStarted.get())
432-
assert(!isFinishedWithFailures.getAndSet(true))
433-
project.configurationResultImpl.complete(ProjectConfigurationResult.Failure(failures))
434-
}
435-
436-
private fun finishSuccessfully() {
437-
assert(isStarted.get())
438-
assert(!isFinishedSuccessfully.getAndSet(true))
439-
project.configurationResultImpl.complete(ProjectConfigurationResult.Success)
440-
}
441-
442-
fun enqueue(stage: Stage, action: KotlinPluginLifecycle.() -> Unit) {
443-
if (stage < this.stage) {
444-
throw IllegalLifecycleException("Cannot enqueue Action for stage '$stage' in current stage '${this.stage}'")
445-
}
446-
447-
/*
448-
Lifecycle finished: action shall not be enqueued, but just executed right away.
449-
This is desirable, so that .enqueue (and .launch) functions that are scheduled in execution phase
450-
will be executed right away (no suspend necessary or wanted)
451-
*/
452-
if (isFinishedSuccessfully.get()) {
453-
return action()
454-
}
455-
456-
/*
457-
Lifecycle finished, but some exceptions have been thrown.
458-
In this case, an enqueue for future Stages is not allowed, since those will not be executed anymore.
459-
Any enqueue in the current stage will be executed right away (no suspend necessary or wanted).
460-
*/
461-
if (isFinishedWithFailures.get()) {
462-
return if (stage == this.stage) action()
463-
else Unit
464-
}
465-
466-
enqueuedActions.getValue(stage).addLast(action)
467-
468-
if (stage == Stage.EvaluateBuildscript && isStarted.get()) {
469-
loopIfNecessary()
470-
}
471-
}
472-
473-
override fun launch(block: suspend KotlinPluginLifecycle.() -> Unit) {
474-
val lifecycle = this
475-
476-
val coroutine = block.createCoroutine(this, object : Continuation<Unit> {
477-
override val context: CoroutineContext = EmptyCoroutineContext +
478-
KotlinPluginLifecycleCoroutineContextElement(lifecycle)
479-
480-
override fun resumeWith(result: Result<Unit>) = result.getOrThrow()
481-
})
482-
483-
enqueue(stage) {
484-
coroutine.resume(Unit)
485-
}
486-
}
487-
488-
override suspend fun await(stage: Stage) {
489-
if (this.stage > stage) return
490-
suspendCoroutine<Unit> { continuation ->
491-
enqueue(stage) {
492-
continuation.resume(Unit)
493-
}
494-
}
495-
}
496-
}
497-
498-
private class KotlinPluginLifecycleCoroutineContextElement(
499-
val lifecycle: KotlinPluginLifecycle,
500-
) : CoroutineContext.Element {
501-
companion object Key : CoroutineContext.Key<KotlinPluginLifecycleCoroutineContextElement>
502-
503-
override val key: CoroutineContext.Key<KotlinPluginLifecycleCoroutineContextElement> = Key
504-
}
505-
506-
private class RestrictedLifecycleStages(
507-
private val lifecycle: KotlinPluginLifecycle,
508-
private val allowedStages: Set<Stage>,
509-
) : CoroutineContext.Element, ContinuationInterceptor {
510-
511-
override val key: CoroutineContext.Key<*> = ContinuationInterceptor
512-
513-
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = object : Continuation<T> {
514-
override val context: CoroutineContext
515-
get() = continuation.context
516-
517-
override fun resumeWith(result: Result<T>) = when {
518-
result.isFailure -> continuation.resumeWith(result)
519-
lifecycle.stage !in allowedStages -> continuation.resumeWithException(
520-
IllegalLifecycleException(
521-
"Required stage in '$allowedStages', but lifecycle switched to '${lifecycle.stage}'"
522-
)
523-
)
524-
else -> continuation.resumeWith(result)
525-
}
526-
}
527-
528-
init {
529-
if (lifecycle.stage !in allowedStages) {
530-
throw IllegalLifecycleException("Required stage in '${allowedStages}' but lifecycle is currently in '${lifecycle.stage}'")
531-
}
532-
}
533-
}

0 commit comments

Comments
 (0)