[Gradle] Fail compileKotlin-tasks if ERROR-diagnostics were reported

KT-60237
This commit is contained in:
Dmitry Savvinov
2023-07-31 16:19:13 +02:00
committed by Space Team
parent c544d68a64
commit 6c88b92b88
4 changed files with 131 additions and 21 deletions
@@ -32,17 +32,21 @@ import org.jetbrains.kotlin.gradle.internal.KOTLIN_COMPILER_EMBEDDABLE
import org.jetbrains.kotlin.gradle.internal.KOTLIN_MODULE_GROUP
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.runMultiplatformAndroidGradlePluginCompatibilityHealthCheckWhenAndroidIsApplied
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.diagnostics.*
import org.jetbrains.kotlin.gradle.plugin.diagnostics.EnsureNoKotlinGradlePluginErrors
import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnosticRenderingOptions
import org.jetbrains.kotlin.gradle.plugin.diagnostics.UsesKotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.diagnostics.kotlinToolingDiagnosticsCollectorProvider
import org.jetbrains.kotlin.gradle.plugin.diagnostics.launchKotlinGradleProjectCheckers
import org.jetbrains.kotlin.gradle.plugin.internal.*
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20GradlePlugin
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSetFactory
import org.jetbrains.kotlin.gradle.plugin.statistics.BuildFlowService
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.report.BuildMetricsService
import org.jetbrains.kotlin.gradle.plugin.statistics.BuildFlowService
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsCompilerAttribute
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsPlugin
import org.jetbrains.kotlin.gradle.targets.js.KotlinWasmTargetAttribute
@@ -268,24 +272,30 @@ abstract class KotlinBasePluginWrapper : DefaultKotlinBasePlugin() {
}
private fun Project.setupDiagnosticsChecksAndReporting() {
val collectorProvider = kotlinToolingDiagnosticsCollectorProvider
val diagnosticRenderingOptions = ToolingDiagnosticRenderingOptions.forProject(this)
// Setup reporting from tasks
tasks.withType(UsesKotlinToolingDiagnostics::class.java).configureEach {
it.usesService(kotlinToolingDiagnosticsCollectorProvider)
it.toolingDiagnosticsCollector.value(kotlinToolingDiagnosticsCollectorProvider)
it.diagnosticRenderingOptions.set(ToolingDiagnosticRenderingOptions.forProject(this))
it.usesService(collectorProvider)
it.toolingDiagnosticsCollector.value(collectorProvider)
it.diagnosticRenderingOptions.set(diagnosticRenderingOptions)
}
// Launch checkers. Note that they are invoked eagerly to give them a fine-grained
// control over the lifecycle
project.launchKotlinGradleProjectCheckers()
launchKotlinGradleProjectCheckers()
// Setup a task that will abort the build if errors will be reported. This task should be the first in the taskgraph
project.locateOrRegisterEnsureNoKotlinGradlePluginErrors()
// Schedule diagnostics rendering
project.launch {
project.configurationResult.await()
launch {
configurationResult.await()
renderReportedDiagnostics(
project.kotlinToolingDiagnosticsCollector.getDiagnosticsForProject(project),
project.logger,
project.kotlinPropertiesProvider.internalVerboseDiagnostics
collectorProvider.get().getDiagnosticsForProject(project),
logger,
diagnosticRenderingOptions.isVerbose
)
}
@@ -293,7 +303,7 @@ private fun Project.setupDiagnosticsChecksAndReporting() {
// after projects are evaluated will be transparently rendered right away instead of being
// silently swallowed
gradle.projectsEvaluated {
kotlinToolingDiagnosticsCollector.switchToTransparentMode()
collectorProvider.get().switchToTransparentMode()
}
}
@@ -0,0 +1,79 @@
/*
* 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.diagnostics
import org.gradle.api.DefaultTask
import org.gradle.api.InvalidUserCodeException
import org.gradle.api.Project
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.gradle.work.DisableCachingByDefault
import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTool
import org.jetbrains.kotlin.gradle.tasks.withType
@DisableCachingByDefault(
because = "This task renders reported diagnostics; caching this task will hide diagnostics and obscure issues in the build"
)
internal abstract class EnsureNoKotlinGradlePluginErrors : DefaultTask() {
@get:Input
abstract val errorDiagnostics: ListProperty<ToolingDiagnostic>
@get:Internal
abstract val renderingOptions: Property<ToolingDiagnosticRenderingOptions>
@TaskAction
fun checkNoErrors() {
if (errorDiagnostics.get().isNotEmpty()) {
renderReportedDiagnostics(errorDiagnostics.get(), logger, renderingOptions.get().isVerbose)
throw InvalidUserCodeException("Kotlin Gradle Plugin reported errors. Check the log for details")
}
}
companion object {
internal const val TASK_NAME = "ensureNoKotlinGradlePluginErrors"
}
}
private const val DESCRIPTION =
"Checks that Kotlin Gradle Plugin hasn't reported project configuration errors, failing otherwise. " +
"This task always runs before compileKotlin* or similar tasks."
internal fun Project.locateOrRegisterEnsureNoKotlinGradlePluginErrors(): TaskProvider<EnsureNoKotlinGradlePluginErrors> {
val taskProvider = tasks.register(
EnsureNoKotlinGradlePluginErrors.TASK_NAME,
EnsureNoKotlinGradlePluginErrors::class.java
) { task ->
task.errorDiagnostics.set(
provider {
kotlinToolingDiagnosticsCollector
.getDiagnosticsForProject(this).asSequence()
.filter { it.severity == ToolingDiagnostic.Severity.ERROR }
.toList()
}
)
task.renderingOptions.set(ToolingDiagnosticRenderingOptions.forProject(this))
task.description = DESCRIPTION
task.group = LifecycleBasePlugin.VERIFICATION_GROUP
}
taskProvider.addDependsOnFromTasksThatShouldFailWhenErrorsReported(tasks)
return taskProvider
}
/**
* Adds dependsOn from some selection of the [tasks] to the [this]-task, effectively causing them to fail
* if the ERROR-diagnostics were reported.
*
* Currently, we're doing it conservatively and instrumenting only [KotlinCompileTool]-tasks.
* The intuition here is that if the build manages to do something useful for a user without compiling any .kt-sources,
* then it's OK for KGP to let that build pass even if it reported ERROR-diagnostics.
*/
private fun TaskProvider<EnsureNoKotlinGradlePluginErrors>.addDependsOnFromTasksThatShouldFailWhenErrorsReported(tasks: TaskContainer) {
tasks.withType<KotlinCompileTool>().configureEach { it.dependsOn(this) }
}
@@ -12,11 +12,24 @@ data class ToolingDiagnostic(
val id: String, val message: String, val severity: Severity, val throwable: Throwable? = null,
) {
enum class Severity {
/**
* More visible than most of the output (intuition: yellow-highlighting).
* Doesn't prevent the build from running.
*
* Use for non-critical misconfigurations with low rate of false-positives
*/
WARNING,
/**
* Stronger highlighting than WARNING, but doesn't prevent further actions (e.g. further
* tasks) from being executed
* Heavily emphasized in the output (intuition: bold red highlighting).
*
* ATTENTION. If a diagnostic with this severity is reported, Kotlin compiler
* will _not_ be invoked (build will appear failed, as with compilation error)
*
* However, Gradle IDE Sync and other tasks that are not connected with
* any of the Kotlin Compiler and tools (e.g. 'help', 'clean'), will run successfully.
*
* Use for critical misconfigurations that need immediate addressing
*/
ERROR,
@@ -27,8 +40,12 @@ data class ToolingDiagnostic(
* - mask further errors (forcing users to make multiple runs before fixing all issues)
*
* - lead to unpleasant UX in IDE (if the failure happens during import, then depending
* on when it happened users might not have even basic IDE assistance, which makes it
* hard to fix the issue)
* on when it happened users might not have even basic IDE assistance, which makes fixing
* the root cause very annoying)
*
* Use for irreconcilable misconfigurations / malformed input which prevent further
* configuration _and_ when the graceful degradation (allowing configuration phase to finish)
* is too expensive.
*/
FATAL,
}
@@ -24,9 +24,13 @@ internal class ToolingDiagnosticRenderingOptions(
}
}
internal fun Collection<ToolingDiagnostic>.withoutSuppressed(options: ToolingDiagnosticRenderingOptions): Collection<ToolingDiagnostic> =
filter { !it.isSuppressed(options) }
internal fun ToolingDiagnostic.isSuppressed(options: ToolingDiagnosticRenderingOptions): Boolean {
return when {
severity == ToolingDiagnostic.Severity.WARNING -> id in options.suppressedWarningIds
internal fun ToolingDiagnostic.isSuppressed(options: ToolingDiagnosticRenderingOptions): Boolean =
severity == ToolingDiagnostic.Severity.WARNING && id in options.suppressedWarningIds
|| severity == ToolingDiagnostic.Severity.ERROR && id in options.suppressedErrorIds
severity == ToolingDiagnostic.Severity.ERROR -> id in options.suppressedErrorIds
// NB: FATALs can not be suppressed
else -> false
}
}