From 027fdb86a55a2df8513ebe375e1df3335d3e1e71 Mon Sep 17 00:00:00 2001 From: Dmitry Savvinov Date: Thu, 27 Jul 2023 14:46:00 +0200 Subject: [PATCH] [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 --- .../jetbrains/kotlin/gradle/BaseGradleIT.kt | 12 +++- .../kotlin/gradle/mpp/MppDiagnosticsIt.kt | 20 ++++++ .../kotlin/gradle/testbase/BuildOptions.kt | 11 ++++ .../gradle/testbase/diagnosticsAssertions.kt | 66 ++++++++++++++----- .../kotlin/gradle/testbase/testDsl.kt | 1 - .../build.gradle.kts | 18 +++++ .../expectedOutput-with-full-stacktrace.txt | 22 +++++++ .../expectedOutput-with-stacktrace.txt | 22 +++++++ .../expectedOutput-without-stacktrace.txt | 19 ++++++ .../expectedOutput.txt | 3 +- .../gradle/plugin/KotlinPluginWrapper.kt | 2 +- .../gradle/plugin/PropertiesProvider.kt | 15 +++++ ...ckKotlinGradlePluginConfigurationErrors.kt | 2 +- .../diagnostics/KotlinToolingDiagnostics.kt | 2 +- .../KotlinToolingDiagnosticsCollector.kt | 12 ++-- .../ToolingDiagnosticRenderingOptions.kt | 22 +++++-- .../diagnostics/renderReportedDiagnostics.kt | 58 ++++++++++------ .../expectedDiagnostics/suppressedErrors.txt | 2 +- .../internals/ExposedForIntegrationTests.kt | 9 +++ 19 files changed, 260 insertions(+), 58 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/build.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/expectedOutput-with-full-stacktrace.txt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/expectedOutput-with-stacktrace.txt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/expectedOutput-without-stacktrace.txt diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt index 9561ce5fff1..0098d08fd61 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle import com.intellij.testFramework.TestDataFile import org.gradle.api.logging.LogLevel import org.gradle.api.logging.configuration.WarningMode +import org.gradle.internal.logging.LoggingConfigurationBuildOptions.StacktraceOption import org.gradle.tooling.GradleConnector import org.gradle.util.GradleVersion import org.jetbrains.kotlin.cli.common.CompilerSystemProperties.COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM @@ -281,6 +282,8 @@ abstract class BaseGradleIT { val enableKpmModelMapping: Boolean? = null, val useDaemonFallbackStrategy: Boolean = false, 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 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 = arrayOf()): List = params.toMutableList().apply { - add("--stacktrace") when (minLogLevel) { // 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") @@ -941,6 +943,14 @@ abstract class BaseGradleIT { 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): add("--console=plain") //The feature of failing the build on deprecation warnings is introduced in gradle 5.6 diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/MppDiagnosticsIt.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/MppDiagnosticsIt.kt index dae13eeca76..c700a78d19b 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/MppDiagnosticsIt.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/MppDiagnosticsIt.kt @@ -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 { val suffixIfAny = if (suffix != null) "-$suffix" else "" return projectPath.resolve("expectedOutput$suffixIfAny.txt").toFile() diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt index 1cfd8c3f538..86b13f96cfc 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.gradle.testbase import org.gradle.api.logging.LogLevel import org.gradle.api.logging.configuration.WarningMode +import org.gradle.internal.logging.LoggingConfigurationBuildOptions.StacktraceOption import org.gradle.util.GradleVersion import org.jetbrains.kotlin.cli.common.CompilerSystemProperties.COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM import org.jetbrains.kotlin.gradle.BaseGradleIT @@ -18,6 +19,7 @@ import java.util.* data class BuildOptions( val logLevel: LogLevel = LogLevel.INFO, + val stacktraceMode: String? = StacktraceOption.FULL_STACKTRACE_LONG_OPTION, val kotlinVersion: String = TestVersions.Kotlin.CURRENT, val warningMode: WarningMode = WarningMode.Fail, val configurationCache: Boolean = false, @@ -44,6 +46,7 @@ data class BuildOptions( val keepIncrementalCompilationCachesInMemory: Boolean? = null, val useDaemonFallbackStrategy: Boolean = false, val useParsableDiagnosticsFormatting: Boolean = true, + val showDiagnosticsStacktrace: Boolean? = false, // false by default to not clutter the testdata + stacktraces change often val nativeOptions: NativeOptions = NativeOptions(), val compilerExecutionStrategy: KotlinCompilerExecutionStrategy? = null, val runViaBuildToolsApi: Boolean? = null, @@ -187,6 +190,14 @@ data class BuildOptions( 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) return arguments.toList() diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/diagnosticsAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/diagnosticsAssertions.kt index 2acd7f24382..01b6c4d249d 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/diagnosticsAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/diagnosticsAssertions.kt @@ -7,8 +7,7 @@ package org.jetbrains.kotlin.gradle.testbase import org.gradle.testkit.runner.BuildResult 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.VERBOSE_DIAGNOSTIC_SEPARATOR +import org.jetbrains.kotlin.gradle.internals.* import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnosticFactory import kotlin.test.assertNull @@ -31,7 +30,7 @@ fun BuildResult.assertNoDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, } 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) if (withSubstring != null) { assertTrue( @@ -46,7 +45,7 @@ fun String.assertHasDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, with } fun String.assertNoDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, withSubstring: String? = null) { - val diagnosticMessages = extractVerboselyRenderedDiagnostics(diagnosticFactory, this) + val diagnosticMessages = extractRenderedDiagnostics(diagnosticFactory, this) if (withSubstring != null) { val matchedWithSubstring = diagnosticMessages.find { withSubstring in it } assertNull( @@ -76,6 +75,7 @@ fun String.assertNoDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, withS */ fun BuildResult.extractProjectsAndTheirDiagnostics(): String = buildString { var diagnosticStarted = false + var stacktraceStarted = false val currentDiagnostic = mutableListOf() fun startDiagnostic(line: String, lineIndex: Int) { @@ -89,7 +89,21 @@ fun BuildResult.extractProjectsAndTheirDiagnostics(): String = buildString { } 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) { @@ -100,12 +114,10 @@ fun BuildResult.extractProjectsAndTheirDiagnostics(): String = buildString { currentDiagnostic += line - // Suppress InternalKotlinGradlePluginProperties, but only if the single property it complains about is - // 'kotlin.internal.diagnostics.useParsableFormatting' - val offendingProperties = currentDiagnostic.asSequence().filter { it.startsWith("kotlin.internal.") } - if (KotlinToolingDiagnostics.InternalKotlinGradlePluginPropertiesUsed.id !in currentDiagnostic.first() || - offendingProperties.singleOrNull() != "kotlin.internal.diagnostics.useParsableFormatting" - ) { + if (KotlinToolingDiagnostics.InternalKotlinGradlePluginPropertiesUsed.id in currentDiagnostic.first()) { + val cleanedDiagnostic = filterKgpUtilityPropertiesFromDiagnostic(currentDiagnostic) + if (cleanedDiagnostic.isNotEmpty()) appendLine(cleanedDiagnostic.joinToString(separator = "\n", postfix = "\n")) + } else { appendLine(currentDiagnostic.joinToString(separator = "\n", postfix = "\n")) } @@ -131,6 +143,27 @@ fun BuildResult.extractProjectsAndTheirDiagnostics(): String = buildString { } }.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): List { + 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 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 Text" */ -private fun extractVerboselyRenderedDiagnostics(diagnostic: ToolingDiagnosticFactory, fromText: String): List { +private fun extractRenderedDiagnostics(diagnostic: ToolingDiagnosticFactory, fromText: String): List { var parsedPrefix = 0 return generateSequence { - extractNextVerboselyRenderedDiagnosticAndIndex(diagnostic, fromText, startIndex = parsedPrefix) + extractNextDiagnosticAndIndex(diagnostic, fromText, startIndex = parsedPrefix) ?.also { (_, newPrefix) -> parsedPrefix = newPrefix } ?.first }.toList() } // Returns diagnostic substring + index of the first symbol after the diagnostic message -private fun extractNextVerboselyRenderedDiagnosticAndIndex( +private fun extractNextDiagnosticAndIndex( diagnostic: ToolingDiagnosticFactory, fromText: String, startIndex: Int, @@ -184,9 +217,12 @@ private fun extractNextVerboselyRenderedDiagnosticAndIndex( // NB: substring's endIndex is exclusive, which gives us exactly the message val diagnosticMessage = fromText.substring(diagnosticMessageStart, diagnosticSeparatorStartIndex) .trim { it.isWhitespace() || it == '\n' } + val diagnosticMessageSanitized = diagnosticMessage.replace(DIAGNOSTIC_STACKTRACE_REGEX, DIAGNOSTIC_STACKTRACE_REPLACEMENT_STUB) 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 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 ...>""" diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt index 54640354cdb..1b5f0fe9347 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt @@ -428,7 +428,6 @@ private fun commonBuildSetup( // 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.paths=$jdkLocations", - "--full-stacktrace", if (enableBuildCacheDebug) "-Dorg.gradle.caching.debug=true" else null, if (enableBuildScan) "--scan" else null, kotlinDaemonDebugPort?.let { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/build.gradle.kts new file mode 100644 index 00000000000..4c493aca3aa --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + kotlin("multiplatform") +} + +repositories { + mavenLocal() + mavenCentral() +} + +kotlin { + jvm("customName") + + sourceSets { + jvmMain { // should provoke PlatformSourceSetConventionUsedWithCustomTargetName + + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/expectedOutput-with-full-stacktrace.txt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/expectedOutput-with-full-stacktrace.txt new file mode 100644 index 00000000000..7b8cbe68272 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/expectedOutput-with-full-stacktrace.txt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/expectedOutput-with-stacktrace.txt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/expectedOutput-with-stacktrace.txt new file mode 100644 index 00000000000..7b8cbe68272 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/expectedOutput-with-stacktrace.txt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/expectedOutput-without-stacktrace.txt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/expectedOutput-without-stacktrace.txt new file mode 100644 index 00000000000..15e90a89e47 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/diagnosticsRenderingWithStacktraceOption/expectedOutput-without-stacktrace.txt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/suppressGradlePluginErrors/expectedOutput.txt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/suppressGradlePluginErrors/expectedOutput.txt index 2a5b05a1f19..09887a74d67 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/suppressGradlePluginErrors/expectedOutput.txt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/suppressGradlePluginErrors/expectedOutput.txt @@ -2,9 +2,8 @@ w: [InternalKotlinGradlePluginPropertiesUsed | WARNING] ATTENTION! This build uses the following Kotlin Gradle Plugin properties: 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. #diagnostic-end diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt index c04f4a6ca06..6fff040aa02 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt @@ -294,7 +294,7 @@ private fun Project.setupDiagnosticsChecksAndReporting() { renderReportedDiagnostics( collectorProvider.get().getDiagnosticsForProject(project), logger, - diagnosticRenderingOptions.useParsableFormat + diagnosticRenderingOptions ) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt index eaa4175aeb4..8de0eb7aef9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt @@ -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_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_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_JS_KARMA_BROWSERS 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) + /** + * 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 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 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_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_SHOW_STACKTRACE = property("$KOTLIN_INTERNAL_NAMESPACE.diagnostics.showStacktrace") 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") } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/CheckKotlinGradlePluginConfigurationErrors.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/CheckKotlinGradlePluginConfigurationErrors.kt index fb4aa043e2c..3d9275c3547 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/CheckKotlinGradlePluginConfigurationErrors.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/CheckKotlinGradlePluginConfigurationErrors.kt @@ -29,7 +29,7 @@ internal abstract class CheckKotlinGradlePluginConfigurationErrors : DefaultTask @TaskAction fun checkNoErrors() { 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") } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt index 4a16103f638..49ed7100669 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt @@ -610,7 +610,7 @@ object KotlinToolingDiagnostics { | |${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. """.trimMargin() ) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnosticsCollector.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnosticsCollector.kt index 18390efba4c..7c50c26df76 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnosticsCollector.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnosticsCollector.kt @@ -9,7 +9,6 @@ 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 import org.jetbrains.kotlin.gradle.utils.registerClassLoaderScopedBuildService import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -39,7 +38,7 @@ internal abstract class KotlinToolingDiagnosticsCollector : BuildService, - val suppressedErrorIds: List + val suppressedErrorIds: List, + val showStacktrace: Boolean ) { companion object { - fun forProject(project: Project): ToolingDiagnosticRenderingOptions = with(project.kotlinPropertiesProvider) { - ToolingDiagnosticRenderingOptions( - internalDiagnosticsUseParsableFormat, - suppressedGradlePluginWarnings, - suppressedGradlePluginErrors - ) + fun forProject(project: Project): ToolingDiagnosticRenderingOptions { + return with(project.kotlinPropertiesProvider) { + val showStacktrace = internalDiagnosticsShowStacktrace + ?: (project.gradle.startParameter.showStacktrace > ShowStacktrace.INTERNAL_EXCEPTIONS) + + ToolingDiagnosticRenderingOptions( + internalDiagnosticsUseParsableFormat, + suppressedGradlePluginWarnings, + suppressedGradlePluginErrors, + showStacktrace + ) + } } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/renderReportedDiagnostics.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/renderReportedDiagnostics.kt index 8cc74c4db7e..23b6fca9589 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/renderReportedDiagnostics.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/renderReportedDiagnostics.kt @@ -9,44 +9,60 @@ import org.gradle.api.InvalidUserCodeException import org.gradle.api.logging.Logger import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnostic.Severity.* -internal fun renderReportedDiagnostics(diagnostics: Collection, logger: Logger, useParsableFormat: Boolean) { +internal fun renderReportedDiagnostics( + diagnostics: Collection, + logger: Logger, + renderingOptions: ToolingDiagnosticRenderingOptions, +) { for (diagnostic in diagnostics) { - renderReportedDiagnostic(diagnostic, logger, useParsableFormat) + renderReportedDiagnostic(diagnostic, logger, renderingOptions) } } internal fun renderReportedDiagnostic( diagnostic: ToolingDiagnostic, logger: Logger, - useParsableFormat: Boolean, + renderingOptions: ToolingDiagnosticRenderingOptions ) { 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) - InvalidUserCodeException(render(useParsableFormat), throwable) + throw InvalidUserCodeException(message, throwable) else - InvalidUserCodeException(render(useParsableFormat)) + throw InvalidUserCodeException(message) +} -private fun ToolingDiagnostic.render(useParsableFormat: Boolean): String = buildString { - if (useParsableFormat) { - appendLine(this@render) - append(DIAGNOSTIC_SEPARATOR) - } else { - append(message) - if (throwable != null) { - appendLine() - appendLine("Stacktrace:") - append(throwable.stackTraceToString().prependIndent(" ")) - } - } +private fun ToolingDiagnostic.render(useParsableFormatting: Boolean, showStacktrace: Boolean): String = buildString { + // Main message + if (useParsableFormatting) appendLine(this@render) else append(message) + + // Additional stacktrace, if requested + if (showStacktrace) renderStacktrace(this@render.throwable, useParsableFormatting) + + // Separator, if in verbose mode + if (useParsableFormatting) appendLine(DIAGNOSTIC_SEPARATOR) +} + +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_STACKTRACE_START = "Stacktrace:" +internal const val DIAGNOSTIC_STACKTRACE_END_SEPARATOR = "#stacktrace-end" diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/resources/expectedDiagnostics/suppressedErrors.txt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/resources/expectedDiagnostics/suppressedErrors.txt index 4fbc7bd6b79..fcf2b97231f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/resources/expectedDiagnostics/suppressedErrors.txt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/resources/expectedDiagnostics/suppressedErrors.txt @@ -2,5 +2,5 @@ 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. diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internals/ExposedForIntegrationTests.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internals/ExposedForIntegrationTests.kt index dc7044046d8..b4a0d7e24b7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internals/ExposedForIntegrationTests.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internals/ExposedForIntegrationTests.kt @@ -7,8 +7,12 @@ package org.jetbrains.kotlin.gradle.internals 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_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.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.MULTIPLATFORM_PROJECT_METADATA_JSON_FILE_NAME 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 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