[Gradle] Print diagnostics stacktrace only under --stacktrace or higher

Most of the changes in commit are test-related, as our tests launched
with '-full-stacktrace' by defayult + asserting full stacktrace is
flaky and unreliable

^KT-59774 Fixed
This commit is contained in:
Dmitry Savvinov
2023-07-27 14:46:00 +02:00
committed by Space Team
parent e334523fd8
commit 027fdb86a5
19 changed files with 260 additions and 58 deletions
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle
import com.intellij.testFramework.TestDataFile import com.intellij.testFramework.TestDataFile
import org.gradle.api.logging.LogLevel import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.configuration.WarningMode import org.gradle.api.logging.configuration.WarningMode
import org.gradle.internal.logging.LoggingConfigurationBuildOptions.StacktraceOption
import org.gradle.tooling.GradleConnector import org.gradle.tooling.GradleConnector
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties.COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM import org.jetbrains.kotlin.cli.common.CompilerSystemProperties.COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM
@@ -281,6 +282,8 @@ abstract class BaseGradleIT {
val enableKpmModelMapping: Boolean? = null, val enableKpmModelMapping: Boolean? = null,
val useDaemonFallbackStrategy: Boolean = false, val useDaemonFallbackStrategy: Boolean = false,
val useParsableDiagnosticsFormatting: Boolean = true, val useParsableDiagnosticsFormatting: Boolean = true,
val showDiagnosticsStacktrace: Boolean? = false, // false by default to not clutter the testdata + stacktraces change often
val stacktraceMode: String? = StacktraceOption.FULL_STACKTRACE_LONG_OPTION,
) { ) {
val safeAndroidGradlePluginVersion: AGPVersion val safeAndroidGradlePluginVersion: AGPVersion
get() = androidGradlePluginVersion ?: error("AGP version is expected to be set") get() = androidGradlePluginVersion ?: error("AGP version is expected to be set")
@@ -856,7 +859,6 @@ abstract class BaseGradleIT {
private fun Project.createGradleTailParameters(options: BuildOptions, params: Array<out String> = arrayOf()): List<String> = private fun Project.createGradleTailParameters(options: BuildOptions, params: Array<out String> = arrayOf()): List<String> =
params.toMutableList().apply { params.toMutableList().apply {
add("--stacktrace")
when (minLogLevel) { when (minLogLevel) {
// Do not allow to configure Gradle project with `ERROR` log level (error logs visible on all log levels) // Do not allow to configure Gradle project with `ERROR` log level (error logs visible on all log levels)
LogLevel.ERROR -> error("Log level ERROR is not supported by Gradle command-line") LogLevel.ERROR -> error("Log level ERROR is not supported by Gradle command-line")
@@ -941,6 +943,14 @@ abstract class BaseGradleIT {
add("-Pkotlin.internal.diagnostics.useParsableFormatting=true") add("-Pkotlin.internal.diagnostics.useParsableFormatting=true")
} }
if (options.showDiagnosticsStacktrace != null) {
add("-Pkotlin.internal.diagnostics.showStacktrace=${options.showDiagnosticsStacktrace}")
}
if (options.stacktraceMode != null) {
add("--${options.stacktraceMode}")
}
// Workaround: override a console type set in the user machine gradle.properties (since Gradle 4.3): // Workaround: override a console type set in the user machine gradle.properties (since Gradle 4.3):
add("--console=plain") add("--console=plain")
//The feature of failing the build on deprecation warnings is introduced in gradle 5.6 //The feature of failing the build on deprecation warnings is introduced in gradle 5.6
@@ -168,6 +168,26 @@ class MppDiagnosticsIt : KGPBaseTest() {
} }
} }
@GradleTest
fun testDiagnosticsRenderingWithStacktraceOption(gradleVersion: GradleVersion) {
project("diagnosticsRenderingWithStacktraceOption", gradleVersion) {
// KGP sets showDiagnosticsStacktrace=false and --full-stacktrace by default in tests,
// need to override that to mimic real-life scenarios
val options = buildOptions.copy(showDiagnosticsStacktrace = null, stacktraceMode = null)
build("help", buildOptions = options) {
assertEqualsToFile(expectedOutputFile("without-stacktrace"), extractProjectsAndTheirDiagnostics())
}
build("help", "--stacktrace", buildOptions = options) {
assertEqualsToFile(expectedOutputFile("with-stacktrace"), extractProjectsAndTheirDiagnostics())
}
build("help", "--full-stacktrace", buildOptions = options) {
assertEqualsToFile(expectedOutputFile("with-full-stacktrace"), extractProjectsAndTheirDiagnostics())
}
}
}
private fun TestProject.expectedOutputFile(suffix: String? = null): File { private fun TestProject.expectedOutputFile(suffix: String? = null): File {
val suffixIfAny = if (suffix != null) "-$suffix" else "" val suffixIfAny = if (suffix != null) "-$suffix" else ""
return projectPath.resolve("expectedOutput$suffixIfAny.txt").toFile() return projectPath.resolve("expectedOutput$suffixIfAny.txt").toFile()
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.gradle.testbase
import org.gradle.api.logging.LogLevel import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.configuration.WarningMode import org.gradle.api.logging.configuration.WarningMode
import org.gradle.internal.logging.LoggingConfigurationBuildOptions.StacktraceOption
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties.COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM import org.jetbrains.kotlin.cli.common.CompilerSystemProperties.COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM
import org.jetbrains.kotlin.gradle.BaseGradleIT import org.jetbrains.kotlin.gradle.BaseGradleIT
@@ -18,6 +19,7 @@ import java.util.*
data class BuildOptions( data class BuildOptions(
val logLevel: LogLevel = LogLevel.INFO, val logLevel: LogLevel = LogLevel.INFO,
val stacktraceMode: String? = StacktraceOption.FULL_STACKTRACE_LONG_OPTION,
val kotlinVersion: String = TestVersions.Kotlin.CURRENT, val kotlinVersion: String = TestVersions.Kotlin.CURRENT,
val warningMode: WarningMode = WarningMode.Fail, val warningMode: WarningMode = WarningMode.Fail,
val configurationCache: Boolean = false, val configurationCache: Boolean = false,
@@ -44,6 +46,7 @@ data class BuildOptions(
val keepIncrementalCompilationCachesInMemory: Boolean? = null, val keepIncrementalCompilationCachesInMemory: Boolean? = null,
val useDaemonFallbackStrategy: Boolean = false, val useDaemonFallbackStrategy: Boolean = false,
val useParsableDiagnosticsFormatting: Boolean = true, val useParsableDiagnosticsFormatting: Boolean = true,
val showDiagnosticsStacktrace: Boolean? = false, // false by default to not clutter the testdata + stacktraces change often
val nativeOptions: NativeOptions = NativeOptions(), val nativeOptions: NativeOptions = NativeOptions(),
val compilerExecutionStrategy: KotlinCompilerExecutionStrategy? = null, val compilerExecutionStrategy: KotlinCompilerExecutionStrategy? = null,
val runViaBuildToolsApi: Boolean? = null, val runViaBuildToolsApi: Boolean? = null,
@@ -187,6 +190,14 @@ data class BuildOptions(
arguments.add("-Pkotlin.compiler.runViaBuildToolsApi=$runViaBuildToolsApi") arguments.add("-Pkotlin.compiler.runViaBuildToolsApi=$runViaBuildToolsApi")
} }
if (showDiagnosticsStacktrace != null) {
arguments.add("-Pkotlin.internal.diagnostics.showStacktrace=$showDiagnosticsStacktrace")
}
if (stacktraceMode != null) {
arguments.add("--$stacktraceMode")
}
arguments.addAll(freeArgs) arguments.addAll(freeArgs)
return arguments.toList() return arguments.toList()
@@ -7,8 +7,7 @@ package org.jetbrains.kotlin.gradle.testbase
import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.BuildResult
import org.jetbrains.kotlin.gradle.BaseGradleIT import org.jetbrains.kotlin.gradle.BaseGradleIT
import org.jetbrains.kotlin.gradle.internals.ENSURE_NO_KOTLIN_GRADLE_PLUGIN_ERRORS_TASK_NAME import org.jetbrains.kotlin.gradle.internals.*
import org.jetbrains.kotlin.gradle.internals.VERBOSE_DIAGNOSTIC_SEPARATOR
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnosticFactory import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnosticFactory
import kotlin.test.assertNull import kotlin.test.assertNull
@@ -31,7 +30,7 @@ fun BuildResult.assertNoDiagnostic(diagnosticFactory: ToolingDiagnosticFactory,
} }
fun String.assertHasDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, withSubstring: String? = null) { fun String.assertHasDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, withSubstring: String? = null) {
val diagnosticsMessages = extractVerboselyRenderedDiagnostics(diagnosticFactory, this) val diagnosticsMessages = extractRenderedDiagnostics(diagnosticFactory, this)
assertTrue(diagnosticsMessages.isNotEmpty(), "Diagnostic with id=${diagnosticFactory.id} not found. Full text output:\n\n" + this) assertTrue(diagnosticsMessages.isNotEmpty(), "Diagnostic with id=${diagnosticFactory.id} not found. Full text output:\n\n" + this)
if (withSubstring != null) { if (withSubstring != null) {
assertTrue( assertTrue(
@@ -46,7 +45,7 @@ fun String.assertHasDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, with
} }
fun String.assertNoDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, withSubstring: String? = null) { fun String.assertNoDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, withSubstring: String? = null) {
val diagnosticMessages = extractVerboselyRenderedDiagnostics(diagnosticFactory, this) val diagnosticMessages = extractRenderedDiagnostics(diagnosticFactory, this)
if (withSubstring != null) { if (withSubstring != null) {
val matchedWithSubstring = diagnosticMessages.find { withSubstring in it } val matchedWithSubstring = diagnosticMessages.find { withSubstring in it }
assertNull( assertNull(
@@ -76,6 +75,7 @@ fun String.assertNoDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, withS
*/ */
fun BuildResult.extractProjectsAndTheirDiagnostics(): String = buildString { fun BuildResult.extractProjectsAndTheirDiagnostics(): String = buildString {
var diagnosticStarted = false var diagnosticStarted = false
var stacktraceStarted = false
val currentDiagnostic = mutableListOf<String>() val currentDiagnostic = mutableListOf<String>()
fun startDiagnostic(line: String, lineIndex: Int) { fun startDiagnostic(line: String, lineIndex: Int) {
@@ -89,7 +89,21 @@ fun BuildResult.extractProjectsAndTheirDiagnostics(): String = buildString {
} }
fun continueDiagnostic(line: String) { fun continueDiagnostic(line: String) {
currentDiagnostic += line when {
line == KOTLIN_DIAGNOSTIC_STACKTRACE_START -> {
stacktraceStarted = true
currentDiagnostic += line
currentDiagnostic += DIAGNOSTIC_STACKTRACE_REPLACEMENT_STUB
}
line == KOTLIN_DIAGNOSTIC_STACKTRACE_END_SEPARATOR -> {
stacktraceStarted = false
}
stacktraceStarted -> return // Omit stacktrace lines for tests stability
else -> currentDiagnostic += line
}
} }
fun endDiagnostic(line: String, lineIndex: Int) { fun endDiagnostic(line: String, lineIndex: Int) {
@@ -100,12 +114,10 @@ fun BuildResult.extractProjectsAndTheirDiagnostics(): String = buildString {
currentDiagnostic += line currentDiagnostic += line
// Suppress InternalKotlinGradlePluginProperties, but only if the single property it complains about is if (KotlinToolingDiagnostics.InternalKotlinGradlePluginPropertiesUsed.id in currentDiagnostic.first()) {
// 'kotlin.internal.diagnostics.useParsableFormatting' val cleanedDiagnostic = filterKgpUtilityPropertiesFromDiagnostic(currentDiagnostic)
val offendingProperties = currentDiagnostic.asSequence().filter { it.startsWith("kotlin.internal.") } if (cleanedDiagnostic.isNotEmpty()) appendLine(cleanedDiagnostic.joinToString(separator = "\n", postfix = "\n"))
if (KotlinToolingDiagnostics.InternalKotlinGradlePluginPropertiesUsed.id !in currentDiagnostic.first() || } else {
offendingProperties.singleOrNull() != "kotlin.internal.diagnostics.useParsableFormatting"
) {
appendLine(currentDiagnostic.joinToString(separator = "\n", postfix = "\n")) appendLine(currentDiagnostic.joinToString(separator = "\n", postfix = "\n"))
} }
@@ -131,6 +143,27 @@ fun BuildResult.extractProjectsAndTheirDiagnostics(): String = buildString {
} }
}.trim() }.trim()
/**
* Filters from the report all internal utility-properties that KGP uses in tests.
* If no properties aside from utility-properties were reported, the whole report is hidden
*/
private fun filterKgpUtilityPropertiesFromDiagnostic(diagnosticLines: List<String>): List<String> {
val diagnosticWithUtilityPropertiesFiltered = diagnosticLines.filter { line ->
!utilityInternalProperties.any { line.startsWith(it) }
}
return if (diagnosticWithUtilityPropertiesFiltered.none { it.startsWith("kotlin.internal") }) {
// all reported properties were utility-properties, so don't render this diagnostic at all
emptyList()
} else {
diagnosticWithUtilityPropertiesFiltered
}
}
private val utilityInternalProperties = listOf(
KOTLIN_INTERNAL_DIAGNOSTICS_USE_PARSABLE_FORMATTING,
KOTLIN_INTERNAL_DIAGNOSTICS_SHOW_STACKTRACE,
)
/* /*
Expected format Expected format
w: [DIAGNOSTIC_ID | WARNING] first line of diagnostic's text w: [DIAGNOSTIC_ID | WARNING] first line of diagnostic's text
@@ -158,18 +191,18 @@ private val DIAGNOSTIC_START_REGEX = """\s*([we]:)?\s*\[\w+ \| \w+].*""".toRegex
Multiline Multiline
Text" Text"
*/ */
private fun extractVerboselyRenderedDiagnostics(diagnostic: ToolingDiagnosticFactory, fromText: String): List<String> { private fun extractRenderedDiagnostics(diagnostic: ToolingDiagnosticFactory, fromText: String): List<String> {
var parsedPrefix = 0 var parsedPrefix = 0
return generateSequence { return generateSequence {
extractNextVerboselyRenderedDiagnosticAndIndex(diagnostic, fromText, startIndex = parsedPrefix) extractNextDiagnosticAndIndex(diagnostic, fromText, startIndex = parsedPrefix)
?.also { (_, newPrefix) -> parsedPrefix = newPrefix } ?.also { (_, newPrefix) -> parsedPrefix = newPrefix }
?.first ?.first
}.toList() }.toList()
} }
// Returns diagnostic substring + index of the first symbol after the diagnostic message // Returns diagnostic substring + index of the first symbol after the diagnostic message
private fun extractNextVerboselyRenderedDiagnosticAndIndex( private fun extractNextDiagnosticAndIndex(
diagnostic: ToolingDiagnosticFactory, diagnostic: ToolingDiagnosticFactory,
fromText: String, fromText: String,
startIndex: Int, startIndex: Int,
@@ -184,9 +217,12 @@ private fun extractNextVerboselyRenderedDiagnosticAndIndex(
// NB: substring's endIndex is exclusive, which gives us exactly the message // NB: substring's endIndex is exclusive, which gives us exactly the message
val diagnosticMessage = fromText.substring(diagnosticMessageStart, diagnosticSeparatorStartIndex) val diagnosticMessage = fromText.substring(diagnosticMessageStart, diagnosticSeparatorStartIndex)
.trim { it.isWhitespace() || it == '\n' } .trim { it.isWhitespace() || it == '\n' }
val diagnosticMessageSanitized = diagnosticMessage.replace(DIAGNOSTIC_STACKTRACE_REGEX, DIAGNOSTIC_STACKTRACE_REPLACEMENT_STUB)
val indexOfFirstSymbolAfterSeparator = diagnosticSeparatorStartIndex + VERBOSE_DIAGNOSTIC_SEPARATOR.length val indexOfFirstSymbolAfterSeparator = diagnosticSeparatorStartIndex + VERBOSE_DIAGNOSTIC_SEPARATOR.length
return diagnosticMessage to indexOfFirstSymbolAfterSeparator return diagnosticMessageSanitized to indexOfFirstSymbolAfterSeparator
} }
private const val CONFIGURE_PROJECT_PREFIX = "> Configure project" private const val CONFIGURE_PROJECT_PREFIX = "> Configure project"
private const val TASK_EXECUTION_PREFIX = "> Task" private const val TASK_EXECUTION_PREFIX = "> Task"
private val DIAGNOSTIC_STACKTRACE_REGEX = Regex("""$KOTLIN_DIAGNOSTIC_STACKTRACE_START[^#]*$KOTLIN_DIAGNOSTIC_STACKTRACE_END_SEPARATOR""")
private val DIAGNOSTIC_STACKTRACE_REPLACEMENT_STUB = """ <... stackframes are omitted for test stability ...>"""
@@ -428,7 +428,6 @@ private fun commonBuildSetup(
// Required toolchains should be pre-installed via repo. Tests should not download any JDKs // Required toolchains should be pre-installed via repo. Tests should not download any JDKs
"-Porg.gradle.java.installations.auto-download=false", "-Porg.gradle.java.installations.auto-download=false",
"-Porg.gradle.java.installations.paths=$jdkLocations", "-Porg.gradle.java.installations.paths=$jdkLocations",
"--full-stacktrace",
if (enableBuildCacheDebug) "-Dorg.gradle.caching.debug=true" else null, if (enableBuildCacheDebug) "-Dorg.gradle.caching.debug=true" else null,
if (enableBuildScan) "--scan" else null, if (enableBuildScan) "--scan" else null,
kotlinDaemonDebugPort?.let { kotlinDaemonDebugPort?.let {
@@ -0,0 +1,18 @@
plugins {
kotlin("multiplatform")
}
repositories {
mavenLocal()
mavenCentral()
}
kotlin {
jvm("customName")
sourceSets {
jvmMain { // should provoke PlatformSourceSetConventionUsedWithCustomTargetName
}
}
}
@@ -0,0 +1,22 @@
> Configure project :
w: [PlatformSourceSetConventionUsedWithCustomTargetName | WARNING] Accessed 'source set jvmMain', but jvm target used a custom name 'customName' (expected 'jvm'):
Replace:
kotlin {
jvm("customName") /* <- custom name used */
}
With:
kotlin {
jvm()
}
Stacktrace:
<... stackframes are omitted for test stability ...>
#diagnostic-end
w: [UnusedSourceSetsWarning | WARNING] The Kotlin source set jvmMain was configured but not added to any Kotlin compilation.
You can add a source set to a target's compilation by connecting it with the compilation's default source set using 'dependsOn'.
See https://kotl.in/connecting-source-sets
#diagnostic-end
@@ -0,0 +1,22 @@
> Configure project :
w: [PlatformSourceSetConventionUsedWithCustomTargetName | WARNING] Accessed 'source set jvmMain', but jvm target used a custom name 'customName' (expected 'jvm'):
Replace:
kotlin {
jvm("customName") /* <- custom name used */
}
With:
kotlin {
jvm()
}
Stacktrace:
<... stackframes are omitted for test stability ...>
#diagnostic-end
w: [UnusedSourceSetsWarning | WARNING] The Kotlin source set jvmMain was configured but not added to any Kotlin compilation.
You can add a source set to a target's compilation by connecting it with the compilation's default source set using 'dependsOn'.
See https://kotl.in/connecting-source-sets
#diagnostic-end
@@ -0,0 +1,19 @@
> Configure project :
w: [PlatformSourceSetConventionUsedWithCustomTargetName | WARNING] Accessed 'source set jvmMain', but jvm target used a custom name 'customName' (expected 'jvm'):
Replace:
kotlin {
jvm("customName") /* <- custom name used */
}
With:
kotlin {
jvm()
}
#diagnostic-end
w: [UnusedSourceSetsWarning | WARNING] The Kotlin source set jvmMain was configured but not added to any Kotlin compilation.
You can add a source set to a target's compilation by connecting it with the compilation's default source set using 'dependsOn'.
See https://kotl.in/connecting-source-sets
#diagnostic-end
@@ -2,9 +2,8 @@
w: [InternalKotlinGradlePluginPropertiesUsed | WARNING] ATTENTION! This build uses the following Kotlin Gradle Plugin properties: w: [InternalKotlinGradlePluginPropertiesUsed | WARNING] ATTENTION! This build uses the following Kotlin Gradle Plugin properties:
kotlin.internal.suppressGradlePluginErrors kotlin.internal.suppressGradlePluginErrors
kotlin.internal.diagnostics.useParsableFormatting
kotlin.internal-properties are not recommended for production use. Internal properties are not recommended for production use.
Stability and future compatibility of the build is not guaranteed. Stability and future compatibility of the build is not guaranteed.
#diagnostic-end #diagnostic-end
@@ -294,7 +294,7 @@ private fun Project.setupDiagnosticsChecksAndReporting() {
renderReportedDiagnostics( renderReportedDiagnostics(
collectorProvider.get().getDiagnosticsForProject(project), collectorProvider.get().getDiagnosticsForProject(project),
logger, logger,
diagnosticRenderingOptions.useParsableFormat diagnosticRenderingOptions
) )
} }
@@ -20,6 +20,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_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_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_EXPERIMENTAL_TRY_K2
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_INTERNAL_DIAGNOSTICS_SHOW_STACKTRACE
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_INTERNAL_DIAGNOSTICS_USE_PARSABLE_FORMATTING import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_INTERNAL_DIAGNOSTICS_USE_PARSABLE_FORMATTING
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_JS_KARMA_BROWSERS import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_JS_KARMA_BROWSERS
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_JS_STDLIB_DOM_API_INCLUDED import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_JS_STDLIB_DOM_API_INCLUDED
@@ -524,9 +525,22 @@ internal class PropertiesProvider private constructor(private val project: Proje
} }
.orElse(false) .orElse(false)
/**
* Outputs diagnostics in a more verbose format with synthetic delimiters, helping to parse
* diagnostics from unstructured input (e.g. build log).
* This mode is meant to be at least somewhat human-readable (as it's used in our tests),
* but is not meant to be pretty (users shouldn't see it)
*/
val internalDiagnosticsUseParsableFormat: Boolean val internalDiagnosticsUseParsableFormat: Boolean
get() = booleanProperty(KOTLIN_INTERNAL_DIAGNOSTICS_USE_PARSABLE_FORMATTING) ?: false get() = booleanProperty(KOTLIN_INTERNAL_DIAGNOSTICS_USE_PARSABLE_FORMATTING) ?: false
/**
* *Overrides* the default option for rendering a stacktrace from which a diagnostic has been reported.
* Because it's an override, 'null' is meaningful here and signifies "prefer the choice made without this property"
*/
val internalDiagnosticsShowStacktrace: Boolean?
get() = booleanProperty(KOTLIN_INTERNAL_DIAGNOSTICS_SHOW_STACKTRACE)
val suppressedGradlePluginWarnings: List<String> val suppressedGradlePluginWarnings: List<String>
get() = property(PropertyNames.KOTLIN_SUPPRESS_GRADLE_PLUGIN_WARNINGS)?.split(",").orEmpty() get() = property(PropertyNames.KOTLIN_SUPPRESS_GRADLE_PLUGIN_WARNINGS)?.split(",").orEmpty()
@@ -651,6 +665,7 @@ internal class PropertiesProvider private constructor(private val project: Proje
val KOTLIN_MPP_HIERARCHICAL_STRUCTURE_BY_DEFAULT = property("$KOTLIN_INTERNAL_NAMESPACE.mpp.hierarchicalStructureByDefault") val KOTLIN_MPP_HIERARCHICAL_STRUCTURE_BY_DEFAULT = property("$KOTLIN_INTERNAL_NAMESPACE.mpp.hierarchicalStructureByDefault")
val KOTLIN_CREATE_DEFAULT_MULTIPLATFORM_PUBLICATIONS = property("$KOTLIN_INTERNAL_NAMESPACE.mpp.createDefaultMultiplatformPublications") val KOTLIN_CREATE_DEFAULT_MULTIPLATFORM_PUBLICATIONS = property("$KOTLIN_INTERNAL_NAMESPACE.mpp.createDefaultMultiplatformPublications")
val KOTLIN_INTERNAL_DIAGNOSTICS_USE_PARSABLE_FORMATTING = property("$KOTLIN_INTERNAL_NAMESPACE.diagnostics.useParsableFormatting") val KOTLIN_INTERNAL_DIAGNOSTICS_USE_PARSABLE_FORMATTING = property("$KOTLIN_INTERNAL_NAMESPACE.diagnostics.useParsableFormatting")
val KOTLIN_INTERNAL_DIAGNOSTICS_SHOW_STACKTRACE = property("$KOTLIN_INTERNAL_NAMESPACE.diagnostics.showStacktrace")
val KOTLIN_SUPPRESS_GRADLE_PLUGIN_ERRORS = property("$KOTLIN_INTERNAL_NAMESPACE.suppressGradlePluginErrors") val KOTLIN_SUPPRESS_GRADLE_PLUGIN_ERRORS = property("$KOTLIN_INTERNAL_NAMESPACE.suppressGradlePluginErrors")
val MPP_13X_FLAGS_SET_BY_PLUGIN = property("$KOTLIN_INTERNAL_NAMESPACE.mpp.13X.flags.setByPlugin") val MPP_13X_FLAGS_SET_BY_PLUGIN = property("$KOTLIN_INTERNAL_NAMESPACE.mpp.13X.flags.setByPlugin")
} }
@@ -29,7 +29,7 @@ internal abstract class CheckKotlinGradlePluginConfigurationErrors : DefaultTask
@TaskAction @TaskAction
fun checkNoErrors() { fun checkNoErrors() {
if (errorDiagnostics.get().isNotEmpty()) { if (errorDiagnostics.get().isNotEmpty()) {
renderReportedDiagnostics(errorDiagnostics.get(), logger, renderingOptions.get().useParsableFormat) renderReportedDiagnostics(errorDiagnostics.get(), logger, renderingOptions.get())
throw InvalidUserCodeException("Kotlin Gradle Plugin reported errors. Check the log for details") throw InvalidUserCodeException("Kotlin Gradle Plugin reported errors. Check the log for details")
} }
} }
@@ -610,7 +610,7 @@ object KotlinToolingDiagnostics {
| |
|${propertiesUsed.joinToString(separator = "\n")} |${propertiesUsed.joinToString(separator = "\n")}
| |
|kotlin.internal-properties are not recommended for production use. |Internal properties are not recommended for production use.
|Stability and future compatibility of the build is not guaranteed. |Stability and future compatibility of the build is not guaranteed.
""".trimMargin() """.trimMargin()
) )
@@ -9,7 +9,6 @@ import org.gradle.api.Project
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.services.BuildService import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters import org.gradle.api.services.BuildServiceParameters
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.utils.registerClassLoaderScopedBuildService import org.jetbrains.kotlin.gradle.utils.registerClassLoaderScopedBuildService
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@@ -39,7 +38,7 @@ internal abstract class KotlinToolingDiagnosticsCollector : BuildService<BuildSe
fun report(task: UsesKotlinToolingDiagnostics, diagnostic: ToolingDiagnostic) { fun report(task: UsesKotlinToolingDiagnostics, diagnostic: ToolingDiagnostic) {
val options = task.diagnosticRenderingOptions.get() val options = task.diagnosticRenderingOptions.get()
if (!diagnostic.isSuppressed(options)) { if (!diagnostic.isSuppressed(options)) {
renderReportedDiagnostic(diagnostic, task.logger, options.useParsableFormat) renderReportedDiagnostic(diagnostic, task.logger, options)
} }
} }
@@ -60,12 +59,11 @@ internal abstract class KotlinToolingDiagnosticsCollector : BuildService<BuildSe
} }
private fun handleDiagnostic(project: Project, diagnostic: ToolingDiagnostic) { private fun handleDiagnostic(project: Project, diagnostic: ToolingDiagnostic) {
if (diagnostic.isSuppressed(ToolingDiagnosticRenderingOptions.forProject(project))) return val options = ToolingDiagnosticRenderingOptions.forProject(project)
if (diagnostic.isSuppressed(options)) return
val useParsableFormat = project.kotlinPropertiesProvider.internalDiagnosticsUseParsableFormat
if (isTransparent) { if (isTransparent) {
renderReportedDiagnostic(diagnostic, project.logger, useParsableFormat) renderReportedDiagnostic(diagnostic, project.logger, options)
return return
} }
@@ -74,7 +72,7 @@ internal abstract class KotlinToolingDiagnosticsCollector : BuildService<BuildSe
} }
if (diagnostic.severity == ToolingDiagnostic.Severity.FATAL) { if (diagnostic.severity == ToolingDiagnostic.Severity.FATAL) {
throw diagnostic.createAnExceptionForFatalDiagnostic(isVerbose) throw diagnostic.createAnExceptionForFatalDiagnostic(options)
} }
} }
} }
@@ -6,20 +6,28 @@
package org.jetbrains.kotlin.gradle.plugin.diagnostics package org.jetbrains.kotlin.gradle.plugin.diagnostics
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.logging.configuration.ShowStacktrace
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
internal class ToolingDiagnosticRenderingOptions( internal class ToolingDiagnosticRenderingOptions(
val useParsableFormat: Boolean, val useParsableFormat: Boolean,
val suppressedWarningIds: List<String>, val suppressedWarningIds: List<String>,
val suppressedErrorIds: List<String> val suppressedErrorIds: List<String>,
val showStacktrace: Boolean
) { ) {
companion object { companion object {
fun forProject(project: Project): ToolingDiagnosticRenderingOptions = with(project.kotlinPropertiesProvider) { fun forProject(project: Project): ToolingDiagnosticRenderingOptions {
ToolingDiagnosticRenderingOptions( return with(project.kotlinPropertiesProvider) {
internalDiagnosticsUseParsableFormat, val showStacktrace = internalDiagnosticsShowStacktrace
suppressedGradlePluginWarnings, ?: (project.gradle.startParameter.showStacktrace > ShowStacktrace.INTERNAL_EXCEPTIONS)
suppressedGradlePluginErrors
) ToolingDiagnosticRenderingOptions(
internalDiagnosticsUseParsableFormat,
suppressedGradlePluginWarnings,
suppressedGradlePluginErrors,
showStacktrace
)
}
} }
} }
} }
@@ -9,44 +9,60 @@ import org.gradle.api.InvalidUserCodeException
import org.gradle.api.logging.Logger import org.gradle.api.logging.Logger
import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnostic.Severity.* import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnostic.Severity.*
internal fun renderReportedDiagnostics(diagnostics: Collection<ToolingDiagnostic>, logger: Logger, useParsableFormat: Boolean) { internal fun renderReportedDiagnostics(
diagnostics: Collection<ToolingDiagnostic>,
logger: Logger,
renderingOptions: ToolingDiagnosticRenderingOptions,
) {
for (diagnostic in diagnostics) { for (diagnostic in diagnostics) {
renderReportedDiagnostic(diagnostic, logger, useParsableFormat) renderReportedDiagnostic(diagnostic, logger, renderingOptions)
} }
} }
internal fun renderReportedDiagnostic( internal fun renderReportedDiagnostic(
diagnostic: ToolingDiagnostic, diagnostic: ToolingDiagnostic,
logger: Logger, logger: Logger,
useParsableFormat: Boolean, renderingOptions: ToolingDiagnosticRenderingOptions
) { ) {
when (diagnostic.severity) { when (diagnostic.severity) {
WARNING -> logger.warn("w: ${diagnostic.render(useParsableFormat)}\n") WARNING -> logger.warn("w: ${diagnostic.render(renderingOptions.useParsableFormat, renderingOptions.showStacktrace)}\n")
ERROR -> logger.error("e: ${diagnostic.render(useParsableFormat)}\n") ERROR -> logger.error("e: ${diagnostic.render(renderingOptions.useParsableFormat, renderingOptions.showStacktrace)}\n")
FATAL -> throw diagnostic.createAnExceptionForFatalDiagnostic(useParsableFormat) FATAL -> throw diagnostic.createAnExceptionForFatalDiagnostic(renderingOptions)
} }
} }
internal fun ToolingDiagnostic.createAnExceptionForFatalDiagnostic(useParsableFormat: Boolean): InvalidUserCodeException = internal fun ToolingDiagnostic.createAnExceptionForFatalDiagnostic(
renderingOptions: ToolingDiagnosticRenderingOptions
): InvalidUserCodeException {
// NB: override showStacktrace to false, because it will be shown as 'cause' anyways
val message = render(renderingOptions.useParsableFormat, showStacktrace = false)
if (throwable != null) if (throwable != null)
InvalidUserCodeException(render(useParsableFormat), throwable) throw InvalidUserCodeException(message, throwable)
else else
InvalidUserCodeException(render(useParsableFormat)) throw InvalidUserCodeException(message)
}
private fun ToolingDiagnostic.render(useParsableFormat: Boolean): String = buildString { private fun ToolingDiagnostic.render(useParsableFormatting: Boolean, showStacktrace: Boolean): String = buildString {
if (useParsableFormat) { // Main message
appendLine(this@render) if (useParsableFormatting) appendLine(this@render) else append(message)
append(DIAGNOSTIC_SEPARATOR)
} else { // Additional stacktrace, if requested
append(message) if (showStacktrace) renderStacktrace(this@render.throwable, useParsableFormatting)
if (throwable != null) {
appendLine() // Separator, if in verbose mode
appendLine("Stacktrace:") if (useParsableFormatting) appendLine(DIAGNOSTIC_SEPARATOR)
append(throwable.stackTraceToString().prependIndent(" ")) }
}
} private fun StringBuilder.renderStacktrace(throwable: Throwable?, useParsableFormatting: Boolean) {
if (throwable == null) return
appendLine()
appendLine(DIAGNOSTIC_STACKTRACE_START)
appendLine(throwable.stackTraceToString().trim().prependIndent(" "))
if (useParsableFormatting) appendLine(DIAGNOSTIC_STACKTRACE_END_SEPARATOR)
} }
internal const val DIAGNOSTIC_SEPARATOR = "#diagnostic-end" internal const val DIAGNOSTIC_SEPARATOR = "#diagnostic-end"
internal const val DIAGNOSTIC_STACKTRACE_START = "Stacktrace:"
internal const val DIAGNOSTIC_STACKTRACE_END_SEPARATOR = "#stacktrace-end"
@@ -2,5 +2,5 @@
kotlin.internal.suppressGradlePluginErrors kotlin.internal.suppressGradlePluginErrors
kotlin.internal-properties are not recommended for production use. Internal properties are not recommended for production use.
Stability and future compatibility of the build is not guaranteed. Stability and future compatibility of the build is not guaranteed.
@@ -7,8 +7,12 @@ package org.jetbrains.kotlin.gradle.internals
import org.jetbrains.kotlin.compilerRunner.asFinishLogMessage import org.jetbrains.kotlin.compilerRunner.asFinishLogMessage
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_NATIVE_IGNORE_DISABLED_TARGETS import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_NATIVE_IGNORE_DISABLED_TARGETS
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_INTERNAL_DIAGNOSTICS_USE_PARSABLE_FORMATTING
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_INTERNAL_DIAGNOSTICS_SHOW_STACKTRACE
import org.jetbrains.kotlin.gradle.plugin.diagnostics.DIAGNOSTIC_SEPARATOR import org.jetbrains.kotlin.gradle.plugin.diagnostics.DIAGNOSTIC_SEPARATOR
import org.jetbrains.kotlin.gradle.plugin.diagnostics.CheckKotlinGradlePluginConfigurationErrors import org.jetbrains.kotlin.gradle.plugin.diagnostics.CheckKotlinGradlePluginConfigurationErrors
import org.jetbrains.kotlin.gradle.plugin.diagnostics.DIAGNOSTIC_STACKTRACE_END_SEPARATOR
import org.jetbrains.kotlin.gradle.plugin.diagnostics.DIAGNOSTIC_STACKTRACE_START
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata 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.MULTIPLATFORM_PROJECT_METADATA_JSON_FILE_NAME
import org.jetbrains.kotlin.gradle.plugin.mpp.parseKotlinSourceSetMetadataFromJson import org.jetbrains.kotlin.gradle.plugin.mpp.parseKotlinSourceSetMetadataFromJson
@@ -26,3 +30,8 @@ const val ENSURE_NO_KOTLIN_GRADLE_PLUGIN_ERRORS_TASK_NAME = CheckKotlinGradlePlu
val KotlinCompilerExecutionStrategy.asFinishLogMessage: String val KotlinCompilerExecutionStrategy.asFinishLogMessage: String
get() = this.asFinishLogMessage get() = this.asFinishLogMessage
val KOTLIN_INTERNAL_DIAGNOSTICS_USE_PARSABLE_FORMATTING = KOTLIN_INTERNAL_DIAGNOSTICS_USE_PARSABLE_FORMATTING
val KOTLIN_INTERNAL_DIAGNOSTICS_SHOW_STACKTRACE = KOTLIN_INTERNAL_DIAGNOSTICS_SHOW_STACKTRACE
val KOTLIN_DIAGNOSTIC_STACKTRACE_START = DIAGNOSTIC_STACKTRACE_START
val KOTLIN_DIAGNOSTIC_STACKTRACE_END_SEPARATOR = DIAGNOSTIC_STACKTRACE_END_SEPARATOR