From 08055b6188c6f946928db178a598029acf2a5c58 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Wed, 3 May 2023 18:54:20 +0200 Subject: [PATCH] [Gradle] KGP Diagnostics: Update KotlinGradleProjectChecker documentation KT-58275 --- .../diagnostics/KotlinGradleProjectChecker.kt | 81 +++++++++++++++++-- .../KotlinGradleProjectCheckersRunner.kt | 1 + 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectChecker.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectChecker.kt index 058fadfed8f..1b6283c4a7b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectChecker.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectChecker.kt @@ -5,8 +5,10 @@ package org.jetbrains.kotlin.gradle.plugin.diagnostics +import org.gradle.api.Plugin import org.gradle.api.Project import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.CommonMainWithDependsOnChecker import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.DeprecatedKotlinNativeTargetsChecker @@ -14,14 +16,81 @@ import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.MissingNativeStdl import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.UnusedSourceSetsChecker /** - * Interface for generic checks of a Gradle Project with Kotlin Plugin applied + * Interface for generic checks of a Gradle Project with any Kotlin Gradle Plugin applied. + * Checkers are launched within the [KotlinPluginLifecycle] during the [Plugin.apply] function. + * The respective [runChecks] method is marked as suspending and should await for necessary data to be finally available. * - * All checkers are guaranteed to be launched in [org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.ReadyForExecution], - * meaning that all changes to DSL are expected to be received, and all structures of KGP are finalised their state. + * This ensures that the checker can emit the warning as soon as possible, while still only running on healthy projects + * (removing noisy induced errors). + * + * #### Why prefer suspend functions over generic `KotlinPluginLifecycle.Stage.ReadyForExecution.await()`? + * Fine-grained suspension calls will improve the checker by enabling the checker to emit the Diagnostic as soon as it really becomes + * relevant. Let's consider the example where an exception is thrown during [KotlinPluginLifecycle.Stage.FinaliseRefinesEdges]. + * (this might happen on ill-defined projects having ill-defined target hierarchies declared). The checker in the example however, + * only verifies the user input, so it only requires some properties final value: + * + *```kotlin + * + * // correct implementation + * override suspend fun KotlinGradleProjectCheckerContext.runChecks(collector: KotlinToolingDiagnosticsCollector) { + * ↪ val myFinalValue = getUserProperty().awaitFinalValue() + * if(!myFinalValue.isAllowed) { + * // emit diagnostic + * } + * } + * + * // non-ideal / lazy implementation + * override suspend fun KotlinGradleProjectCheckerContext.runChecks(collector: KotlinToolingDiagnosticsCollector) { + * ↪ Stage.ReadyForExecution.await() + * val myFinalValue = getUserProperty().orNull + * if(!myFinalValue.isAllowed) { + * // emit diagnostic + * } + * } + * ``` + * + * In this example the diagnostic will not be emitted in the non-ideal implementation as the exception thrown in previous Stage + * will never cause the 'isAllowed' check to never execute. Maybe the illegal property value was causing the exception? User will never know! + * + * However, the correct implementation can unsuspend as soon as the relevant data is available. This will run before the exception, + * the diagnostic will be emitted and user can be warned about the issue! + * + * #### Example: Checker requiring all source sets to be there: + * ```kotlin + * override suspend fun KotlinGradleProjectCheckerContext.runChecks(collector: KotlinToolingDiagnosticsCollector) { + * ↪ project.multiplatformExtension.awaitSourceSets() // <- instead of .sourceSets | suspending until finally available + * } + * ``` + * + * #### Example: Checker that requires platform compilations of a given SourceSet + * ```kotlin + * override suspend fun KotlinGradleProjectCheckerContext.runChecks(collector: KotlinToolingDiagnosticsCollector) { + * ↪ project.multiplatformExtension.awaitSourceSets().getByName("commonMain") // suspending until all source sets available + * ↪ .internal.awaitPlatformCompilations() // suspending until all platform compilations are finally known + * } + * ``` + * + * #### Example: Checker that just wants to run in a given [KotlinPluginLifecycle.Stage] + * ```kotlin + * override suspend fun KotlinGradleProjectCheckerContext.runChecks(collector: KotlinToolingDiagnosticsCollector) { + * ↪ KotlinPluginLifecycle.Stage.FinaliseDsl.await() // suspending until FinaliseDsl stage is reached + * } + * + * ``` + * + * #### Example: Very simple checker without fine-grained of data requirements + * If building a simple project checker which requires a fully successfully configured project, but the data requirements + * cannot (yet) be implemented by calling into respective suspend functions, then awaiting the [KotlinPluginLifecycle.Stage.ReadyForExecution] + * stage will be sufficient. + * ```kotlin + * override suspend fun KotlinGradleProjectCheckerContext.runChecks(collector: KotlinToolingDiagnosticsCollector) { + * ↪ KotlinPluginLifecycle.Stage.ReadyForExecution.await() + * // ... run my check on fully configured project + * } + * ``` + * However, this approach is discouraged and shall only be used if there are no suspend functions available to express the + * requirements of this checker. * - * Checkers **will not** be launched if the project's configuration has finished with failure (e.g. some plugin or a build script - * threw an exception). If your check aims to prevent some exception, then code that check as soon as possible in the `apply`-block - * of the [org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper] or appropriate inheritor */ internal interface KotlinGradleProjectChecker { suspend fun KotlinGradleProjectCheckerContext.runChecks(collector: KotlinToolingDiagnosticsCollector) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectCheckersRunner.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectCheckersRunner.kt index a73973931cb..53f375a1690 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectCheckersRunner.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectCheckersRunner.kt @@ -29,6 +29,7 @@ internal fun Project.launchKotlinGradleProjectCheckers() { internal val Project.kotlinGradleProjectCheckersOverride: Collection? get() { return if (extraProperties.has(KOTLIN_GRADLE_PROJECT_CHECKERS_OVERRIDE)) + @Suppress("unchecked_cast") extraProperties.get(KOTLIN_GRADLE_PROJECT_CHECKERS_OVERRIDE) as Collection? else null