[Gradle] Add new infrastructure for diagnostics in KGP
This commit is contained in:
committed by
Space Team
parent
5dd52f7d40
commit
4a88e01a8e
+25
-7
@@ -32,14 +32,13 @@ 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.internal.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.internal.BasePluginConfiguration
|
||||
import org.jetbrains.kotlin.gradle.plugin.internal.DefaultJavaSourceSetsAccessorVariantFactory
|
||||
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.utils.markResolvable
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSetFactory
|
||||
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
|
||||
import org.jetbrains.kotlin.gradle.report.BuildMetricsService
|
||||
@@ -53,10 +52,6 @@ import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool
|
||||
import org.jetbrains.kotlin.gradle.testing.internal.KotlinTestsRegistry
|
||||
import org.jetbrains.kotlin.gradle.tooling.registerBuildKotlinToolingMetadataTask
|
||||
import org.jetbrains.kotlin.gradle.utils.*
|
||||
import org.jetbrains.kotlin.gradle.utils.addGradlePluginMetadataAttributes
|
||||
import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility
|
||||
import org.jetbrains.kotlin.gradle.utils.getOrPut
|
||||
import org.jetbrains.kotlin.gradle.utils.runProjectConfigurationHealthCheck
|
||||
import org.jetbrains.kotlin.statistics.metrics.StringMetrics
|
||||
import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion
|
||||
import kotlin.reflect.KClass
|
||||
@@ -249,7 +244,10 @@ abstract class KotlinBasePluginWrapper : DefaultKotlinBasePlugin() {
|
||||
|
||||
project.registerBuildKotlinToolingMetadataTask()
|
||||
|
||||
project.scheduleDiagnosticChecksAndReporting()
|
||||
|
||||
project.startKotlinPluginLifecycle()
|
||||
|
||||
}
|
||||
|
||||
internal open fun createTestRegistry(project: Project) = KotlinTestsRegistry(project)
|
||||
@@ -257,6 +255,26 @@ abstract class KotlinBasePluginWrapper : DefaultKotlinBasePlugin() {
|
||||
internal abstract fun getPlugin(
|
||||
project: Project,
|
||||
): Plugin<Project>
|
||||
|
||||
private fun Project.scheduleDiagnosticChecksAndReporting() {
|
||||
launchInStage(KotlinPluginLifecycle.Stage.ReadyForExecution) {
|
||||
// Do not run checkers on projects which configuration finished with failure,
|
||||
// as the internal state can not be trusted at this point (e.g. not entire of the
|
||||
// user's buildscript could've been executed) and might produce bogus warnings
|
||||
runProjectConfigurationHealthCheck {
|
||||
project.runKotlinGradleProjectCheckers()
|
||||
}
|
||||
|
||||
// TODO: this should run even if the application of KGP has finished with errors,
|
||||
// because some diagnostics might indicate specifically the root cause of an
|
||||
// exception.
|
||||
renderReportedDiagnostics(
|
||||
project.kotlinToolingDiagnosticsCollector.getDiagnosticsForProject(project),
|
||||
project.logger,
|
||||
project.kotlinPropertiesProvider.internalVerboseDiagnostics
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractKotlinPluginWrapper(
|
||||
|
||||
+5
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLI
|
||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_COMPILER_USE_PRECISE_COMPILATION_RESULTS_BACKUP
|
||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_CREATE_DEFAULT_MULTIPLATFORM_PUBLICATIONS
|
||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_EXPERIMENTAL_TRY_K2
|
||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_INTERNAL_VERBOSE_DIAGNOSTICS
|
||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_JS_KARMA_BROWSERS
|
||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ALLOW_LEGACY_DEPENDENCIES
|
||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ANDROID_GRADLE_PLUGIN_COMPATIBILITY_NO_WARN
|
||||
@@ -526,6 +527,9 @@ internal class PropertiesProvider private constructor(private val project: Proje
|
||||
}
|
||||
.orElse(false)
|
||||
|
||||
val internalVerboseDiagnostics: Boolean
|
||||
get() = booleanProperty(KOTLIN_INTERNAL_VERBOSE_DIAGNOSTICS) ?: false
|
||||
|
||||
/**
|
||||
* Retrieves a comma-separated list of browsers to use when running karma tests for [target]
|
||||
* @see KOTLIN_JS_KARMA_BROWSERS
|
||||
@@ -615,6 +619,7 @@ internal class PropertiesProvider private constructor(private val project: Proje
|
||||
const val KOTLIN_RUN_COMPILER_VIA_BUILD_TOOLS_API = "kotlin.compiler.runViaBuildToolsApi"
|
||||
const val KOTLIN_MPP_ALLOW_LEGACY_DEPENDENCIES = "kotlin.mpp.allow.legacy.dependencies"
|
||||
const val KOTLIN_EXPERIMENTAL_TRY_K2 = "kotlin.experimental.tryK2"
|
||||
const val KOTLIN_INTERNAL_VERBOSE_DIAGNOSTICS = "kotlin.internal.verboseDiagnostics"
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.dsl.KotlinMultiplatformExtension
|
||||
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
|
||||
import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.MissingNativeStdlibChecker
|
||||
import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.UnusedSourceSetsChecker
|
||||
|
||||
/**
|
||||
* Interface for generic checks of a Gradle Project with Kotlin Plugin applied
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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 fun interface KotlinGradleProjectChecker {
|
||||
fun KotlinGradleProjectCheckerContext.runChecks(collector: KotlinToolingDiagnosticsCollector)
|
||||
|
||||
companion object {
|
||||
val ALL_CHECKERS: List<KotlinGradleProjectChecker> = listOf(
|
||||
CommonMainWithDependsOnChecker,
|
||||
DeprecatedKotlinNativeTargetsChecker,
|
||||
MissingNativeStdlibChecker,
|
||||
UnusedSourceSetsChecker,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal open class KotlinGradleProjectCheckerContext(
|
||||
val project: Project,
|
||||
val kotlinPropertiesProvider: PropertiesProvider,
|
||||
val multiplatformExtension: KotlinMultiplatformExtension?,
|
||||
)
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.dsl.multiplatformExtensionOrNull
|
||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
|
||||
import org.jetbrains.kotlin.gradle.plugin.extraProperties
|
||||
|
||||
internal fun Project.runKotlinGradleProjectCheckers() {
|
||||
val checkers = kotlinGradleProjectCheckersOverride ?: KotlinGradleProjectChecker.ALL_CHECKERS
|
||||
|
||||
val context = KotlinGradleProjectCheckerContext(
|
||||
project,
|
||||
project.kotlinPropertiesProvider,
|
||||
project.multiplatformExtensionOrNull
|
||||
)
|
||||
val collector = project.kotlinToolingDiagnosticsCollector
|
||||
|
||||
for (checker in checkers) {
|
||||
with(checker) { context.runChecks(collector) }
|
||||
}
|
||||
}
|
||||
|
||||
internal val Project.kotlinGradleProjectCheckersOverride: Collection<KotlinGradleProjectChecker>?
|
||||
get() {
|
||||
return if (extraProperties.has(KOTLIN_GRADLE_PROJECT_CHECKERS_OVERRIDE))
|
||||
extraProperties.get(KOTLIN_GRADLE_PROJECT_CHECKERS_OVERRIDE) as Collection<KotlinGradleProjectChecker>?
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
|
||||
internal const val KOTLIN_GRADLE_PROJECT_CHECKERS_OVERRIDE = "kotlin.internal.override.checkers"
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.InvalidUserCodeException
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.services.BuildService
|
||||
import org.gradle.api.services.BuildServiceParameters
|
||||
import org.jetbrains.kotlin.gradle.utils.registerClassLoaderScopedBuildService
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
private typealias ToolingDiagnosticId = String
|
||||
private typealias GradleProjectPath = String
|
||||
|
||||
internal abstract class KotlinToolingDiagnosticsCollector : BuildService<BuildServiceParameters.None> {
|
||||
private val diagnosticsFromProject: MutableMap<GradleProjectPath, MutableList<ToolingDiagnostic>> = ConcurrentHashMap()
|
||||
private val reportedIds: MutableSet<ToolingDiagnosticId> = Collections.newSetFromMap(ConcurrentHashMap())
|
||||
|
||||
fun getDiagnosticsForProject(project: Project): Collection<ToolingDiagnostic> = diagnosticsFromProject[project.path].orEmpty()
|
||||
fun getAllDiagnostics(): Collection<ToolingDiagnostic> = diagnosticsFromProject.values.flatten()
|
||||
|
||||
fun report(project: Project, diagnostic: ToolingDiagnostic) {
|
||||
saveDiagnostic(project, diagnostic)
|
||||
}
|
||||
|
||||
fun reportOncePerGradleProject(fromProject: Project, diagnostic: ToolingDiagnostic, key: ToolingDiagnosticId = diagnostic.id) {
|
||||
if (reportedIds.add("${fromProject.path}#$key")) {
|
||||
saveDiagnostic(fromProject, diagnostic)
|
||||
}
|
||||
}
|
||||
|
||||
fun reportOncePerGradleBuild(fromProject: Project, diagnostic: ToolingDiagnostic, key: ToolingDiagnosticId = diagnostic.id) {
|
||||
if (reportedIds.add(":#$key")) {
|
||||
saveDiagnostic(fromProject, diagnostic)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveDiagnostic(project: Project, diagnostic: ToolingDiagnostic) {
|
||||
if (diagnostic.severity == ToolingDiagnostic.Severity.FATAL) {
|
||||
throw InvalidUserCodeException(diagnostic.message)
|
||||
}
|
||||
diagnosticsFromProject.compute(project.path) { _, previousListIfAny ->
|
||||
previousListIfAny?.apply { add(diagnostic) } ?: mutableListOf(diagnostic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val Project.kotlinToolingDiagnosticsCollector: KotlinToolingDiagnosticsCollector
|
||||
get() = gradle.registerClassLoaderScopedBuildService(KotlinToolingDiagnosticsCollector::class).get()
|
||||
|
||||
internal fun Project.reportDiagnostic(diagnostic: ToolingDiagnostic) {
|
||||
kotlinToolingDiagnosticsCollector.report(this, diagnostic)
|
||||
}
|
||||
|
||||
internal fun Project.reportDiagnosticOncePerProject(diagnostic: ToolingDiagnostic, key: ToolingDiagnosticId = diagnostic.id) {
|
||||
kotlinToolingDiagnosticsCollector.reportOncePerGradleProject(this, diagnostic, key)
|
||||
}
|
||||
|
||||
internal fun Project.reportDiagnosticOncePerBuild(diagnostic: ToolingDiagnostic, key: ToolingDiagnosticId = diagnostic.id) {
|
||||
kotlinToolingDiagnosticsCollector.reportOncePerGradleBuild(this, diagnostic, key)
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi
|
||||
|
||||
@InternalKotlinGradlePluginApi // used in integration tests
|
||||
class ToolingDiagnostic(val id: String, val message: String, val severity: Severity) {
|
||||
enum class Severity {
|
||||
WARNING,
|
||||
|
||||
/**
|
||||
* Stronger highlighting than WARNING, but doesn't prevent further actions (e.g. further
|
||||
* tasks) from being executed
|
||||
*/
|
||||
ERROR,
|
||||
|
||||
/**
|
||||
* Aborts the progress of the current process (Gradle build/Import/...).
|
||||
*
|
||||
* Please use *extremely* sparingly, as failing the current process can:
|
||||
* - 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)
|
||||
*/
|
||||
FATAL,
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "[$id | $severity] $message"
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi
|
||||
|
||||
@InternalKotlinGradlePluginApi // used in integration tests
|
||||
abstract class ToolingDiagnosticFactory(private val predefinedSeverity: ToolingDiagnostic.Severity?, customId: String?) {
|
||||
constructor(customId: String) : this(null, customId)
|
||||
constructor(predefinedSeverity: ToolingDiagnostic.Severity?) : this(predefinedSeverity, null)
|
||||
|
||||
open val id: String = customId ?: this::class.simpleName!!
|
||||
|
||||
protected fun build(message: String, severity: ToolingDiagnostic.Severity? = null): ToolingDiagnostic {
|
||||
if (severity == null && predefinedSeverity == null) {
|
||||
error(
|
||||
"Can't determine severity. " +
|
||||
"Either provide it in constructor of ToolingDiagnosticFactory, or in the 'build'-function invocation"
|
||||
)
|
||||
}
|
||||
if (severity != null && predefinedSeverity != null) {
|
||||
error(
|
||||
"Please provide severity either in ToolingDiagnosticFactory constructor, or as the 'build'-function parameter," +
|
||||
" but not both at once"
|
||||
)
|
||||
}
|
||||
return ToolingDiagnostic(id, message, severity ?: predefinedSeverity!!)
|
||||
}
|
||||
|
||||
protected fun String.onlyIf(condition: Boolean) = if (condition) this else ""
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.logging.Logger
|
||||
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")
|
||||
|
||||
ERROR -> logger.error("e: ${diagnostic.render(isVerbose)}\n")
|
||||
|
||||
FATAL ->
|
||||
error("Internal error: FATAL diagnostics throw an exception immediately in KotlinToolingDiagnosticsCollector")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ToolingDiagnostic.render(isVerbose: Boolean): String =
|
||||
if (isVerbose) toString() + "\n$DIAGNOSTIC_SEPARATOR" else message
|
||||
|
||||
internal const val DIAGNOSTIC_SEPARATOR = "#diagnostic-end"
|
||||
+2
-1
@@ -6,7 +6,7 @@
|
||||
package org.jetbrains.kotlin.gradle.internals
|
||||
|
||||
import org.jetbrains.kotlin.gradle.plugin.KOTLIN_12X_MPP_DEPRECATION_WARNING
|
||||
|
||||
import org.jetbrains.kotlin.gradle.plugin.diagnostics.DIAGNOSTIC_SEPARATOR
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.MULTIPLATFORM_PROJECT_METADATA_JSON_FILE_NAME
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.parseKotlinSourceSetMetadataFromJson
|
||||
@@ -27,3 +27,4 @@ const val NO_NATIVE_STDLIB_PROPERTY_WARNING = NO_NATIVE_STDLIB_PROPERTY_WARNING
|
||||
|
||||
val KOTLIN_12X_MPP_DEPRECATION_WARNING = KOTLIN_12X_MPP_DEPRECATION_WARNING
|
||||
|
||||
const val VERBOSE_DIAGNOSTIC_SEPARATOR = DIAGNOSTIC_SEPARATOR
|
||||
|
||||
Reference in New Issue
Block a user