[Gradle] Support rendering diagnostics reported in execution phase

- Introduce 'UsesKotlinDiagnostics' marker-interface for tasks that
want to report diagnostics during their execution

- Introduce 'transparent mode' to collector. When all projects are
evaluated, Collector switches to it and will render all received
diagnostics right away. This allows reporting and rendering diagnostics
that are reported not from tasks, but after evaluation phase (e.g.
diagnostics on resolved dependencies)

^KT-58353 Fixed
This commit is contained in:
Dmitry Savvinov
2023-05-02 16:41:09 +01:00
parent 37e791a7d3
commit 1ab1dccea1
5 changed files with 114 additions and 9 deletions
@@ -257,7 +257,7 @@ abstract class KotlinBasePluginWrapper : DefaultKotlinBasePlugin() {
project.registerBuildKotlinToolingMetadataTask()
project.launchDiagnosticChecksAndReporting()
project.setupDiagnosticsChecksAndReporting()
}
internal open fun createTestRegistry(project: Project) = KotlinTestsRegistry(project)
@@ -267,8 +267,19 @@ abstract class KotlinBasePluginWrapper : DefaultKotlinBasePlugin() {
): Plugin<Project>
}
private fun Project.launchDiagnosticChecksAndReporting() {
private fun Project.setupDiagnosticsChecksAndReporting() {
// Setup reporting from tasks
tasks.withType(UsesKotlinToolingDiagnostics::class.java).configureEach {
it.usesService(kotlinToolingDiagnosticsCollectorProvider)
it.toolingDiagnosticsCollector.value(kotlinToolingDiagnosticsCollectorProvider)
it.diagnosticRenderingOptions.set(ToolingDiagnosticRenderingOptions.forProject(this))
}
// Launch checkers. Note that they are invoked eagerly to give them a fine-grained
// control over the lifecycle
project.launchKotlinGradleProjectCheckers()
// Schedule diagnostics rendering
project.launch {
project.configurationResult.await()
renderReportedDiagnostics(
@@ -277,6 +288,13 @@ private fun Project.launchDiagnosticChecksAndReporting() {
project.kotlinPropertiesProvider.internalVerboseDiagnostics
)
}
// Schedule switching of Collector to transparent mode, so that any diagnostics reported
// after projects are evaluated will be transparently rendered right away instead of being
// silently swallowed
gradle.projectsEvaluated {
kotlinToolingDiagnosticsCollector.switchToTransparentMode()
}
}
abstract class AbstractKotlinPluginWrapper(
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.gradle.plugin.diagnostics
import org.gradle.api.InvalidUserCodeException
import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
@@ -20,6 +21,13 @@ private typealias ToolingDiagnosticId = String
private typealias GradleProjectPath = String
internal abstract class KotlinToolingDiagnosticsCollector : BuildService<BuildServiceParameters.None> {
/**
* When collector is in transparent mode, any diagnostics received will be immediately rendered
* instead of collected
*/
@Volatile
private var isTransparent: Boolean = false
private val rawDiagnosticsFromProject: MutableMap<GradleProjectPath, MutableList<ToolingDiagnostic>> = ConcurrentHashMap()
private val reportedIds: MutableSet<ToolingDiagnosticId> = Collections.newSetFromMap(ConcurrentHashMap())
@@ -39,6 +47,10 @@ internal abstract class KotlinToolingDiagnosticsCollector : BuildService<BuildSe
saveDiagnostic(project, diagnostic)
}
fun report(task: UsesKotlinToolingDiagnostics, diagnostic: ToolingDiagnostic) {
renderReportedDiagnostic(diagnostic, task.logger, isVerbose = false) // TODO: wire suppression/verbosity
}
fun reportOncePerGradleProject(fromProject: Project, diagnostic: ToolingDiagnostic, key: ToolingDiagnosticId = diagnostic.id) {
if (reportedIds.add("${fromProject.path}#$key")) {
saveDiagnostic(fromProject, diagnostic)
@@ -51,7 +63,16 @@ internal abstract class KotlinToolingDiagnosticsCollector : BuildService<BuildSe
}
}
fun switchToTransparentMode() {
isTransparent = true
}
private fun saveDiagnostic(project: Project, diagnostic: ToolingDiagnostic) {
if (isTransparent) {
renderReportedDiagnostic(diagnostic, project.logger, project.kotlinPropertiesProvider.internalVerboseDiagnostics)
return
}
if (diagnostic.severity == ToolingDiagnostic.Severity.FATAL) {
throw InvalidUserCodeException(diagnostic.message)
}
@@ -61,8 +82,12 @@ internal abstract class KotlinToolingDiagnosticsCollector : BuildService<BuildSe
}
}
internal val Project.kotlinToolingDiagnosticsCollectorProvider: Provider<KotlinToolingDiagnosticsCollector>
get() = gradle.registerClassLoaderScopedBuildService(KotlinToolingDiagnosticsCollector::class)
internal val Project.kotlinToolingDiagnosticsCollector: KotlinToolingDiagnosticsCollector
get() = gradle.registerClassLoaderScopedBuildService(KotlinToolingDiagnosticsCollector::class).get()
get() = kotlinToolingDiagnosticsCollectorProvider.get()
internal fun Project.reportDiagnostic(diagnostic: ToolingDiagnostic) {
kotlinToolingDiagnosticsCollector.report(this, diagnostic)
@@ -0,0 +1,32 @@
/*
* 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.Project
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
internal class ToolingDiagnosticRenderingOptions(
val isVerbose: Boolean,
val suppressedWarningIds: List<String>,
val suppressedErrorIds: List<String>
) {
companion object {
fun forProject(project: Project): ToolingDiagnosticRenderingOptions = with(project.kotlinPropertiesProvider) {
ToolingDiagnosticRenderingOptions(
internalVerboseDiagnostics,
suppressedGradlePluginWarnings,
suppressedGradlePluginErrors
)
}
}
}
internal fun Collection<ToolingDiagnostic>.withoutSuppressed(options: ToolingDiagnosticRenderingOptions): Collection<ToolingDiagnostic> =
filter { !it.isSuppressed(options) }
internal fun ToolingDiagnostic.isSuppressed(options: ToolingDiagnosticRenderingOptions): Boolean =
severity == ToolingDiagnostic.Severity.WARNING && id in options.suppressedWarningIds
|| severity == ToolingDiagnostic.Severity.ERROR && id in options.suppressedErrorIds
@@ -0,0 +1,22 @@
/*
* 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.Task
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Internal
internal interface UsesKotlinToolingDiagnostics : Task {
@get:Internal
val toolingDiagnosticsCollector: Property<KotlinToolingDiagnosticsCollector>
@get:Internal
val diagnosticRenderingOptions: Property<ToolingDiagnosticRenderingOptions>
fun reportDiagnostic(diagnostic: ToolingDiagnostic) {
toolingDiagnosticsCollector.get().report(this, diagnostic)
}
}
@@ -10,14 +10,22 @@ import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnostic.Severity
internal fun renderReportedDiagnostics(diagnostics: Collection<ToolingDiagnostic>, logger: Logger, isVerbose: Boolean) {
for (diagnostic in diagnostics) {
when (diagnostic.severity) {
WARNING -> logger.warn("w: ${diagnostic.render(isVerbose)}\n")
renderReportedDiagnostic(diagnostic, logger, isVerbose)
}
}
ERROR -> logger.error("e: ${diagnostic.render(isVerbose)}\n")
internal fun renderReportedDiagnostic(
diagnostic: ToolingDiagnostic,
logger: Logger,
isVerbose: Boolean
) {
when (diagnostic.severity) {
WARNING -> logger.warn("w: ${diagnostic.render(isVerbose)}\n")
FATAL ->
error("Internal error: FATAL diagnostics throw an exception immediately in KotlinToolingDiagnosticsCollector")
}
ERROR -> logger.error("e: ${diagnostic.render(isVerbose)}\n")
FATAL ->
error("Internal error: FATAL diagnostics throw an exception immediately in KotlinToolingDiagnosticsCollector")
}
}