From 82abfcd5070546421ca1f9ef602912b84488930e Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Wed, 19 Nov 2025 19:51:14 +0300 Subject: [PATCH] chore: avoid printing project stactraces multiple times Previously, each subproject printed failure, thus it ended up in multiple messages for the same failure --- .../gradle/BuildFailurePrintFlowAction.kt | 47 +++++++++++++++ .../vlsi/gradle/BuildFailurePrintService.kt | 27 +++++++++ .../github/vlsi/gradle/FlowScopedServices.kt | 29 +++++++++ .../vlsi/gradle/ProjectExtensionsPlugin.kt | 34 ++++++++--- .../github/vlsi/gradle/ReportBuildFailures.kt | 59 +++++++++++-------- .../github/vlsi/gradle/ThrowablePrinter.kt | 10 ++++ 6 files changed, 176 insertions(+), 30 deletions(-) create mode 100644 plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/BuildFailurePrintFlowAction.kt create mode 100644 plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/BuildFailurePrintService.kt create mode 100644 plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/FlowScopedServices.kt diff --git a/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/BuildFailurePrintFlowAction.kt b/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/BuildFailurePrintFlowAction.kt new file mode 100644 index 00000000..ebebee09 --- /dev/null +++ b/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/BuildFailurePrintFlowAction.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2019 Vladimir Sitnikov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.github.vlsi.gradle + +import org.gradle.api.flow.BuildWorkResult +import org.gradle.api.flow.FlowAction +import org.gradle.api.flow.FlowParameters +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input + +interface BuildFailurePrintFlowParameters: FlowParameters { + @get:Input + val enableStyle: Property + @get:Input + val fullTrace: Property + @get:Input + val buildWorkResult: Property +} + +abstract class BuildFailurePrintFlowAction: FlowAction { + override fun execute(parameters: BuildFailurePrintFlowParameters) { + val buildWorkResult = parameters.buildWorkResult.get() + buildWorkResult.failure.ifPresent { failure -> + printBuildFailures( + failure, + "Build", + enableStyle = parameters.enableStyle.get(), + fullTrace = parameters.fullTrace.get() + ) + } + } +} diff --git a/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/BuildFailurePrintService.kt b/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/BuildFailurePrintService.kt new file mode 100644 index 00000000..892d7c33 --- /dev/null +++ b/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/BuildFailurePrintService.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2019 Vladimir Sitnikov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.github.vlsi.gradle + +import org.gradle.api.services.BuildService +import org.gradle.api.services.BuildServiceParameters + +interface BuildFailurePrintServiceParameters: BuildServiceParameters { +} + +abstract class BuildFailurePrintService: BuildService { +} diff --git a/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/FlowScopedServices.kt b/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/FlowScopedServices.kt new file mode 100644 index 00000000..196ce463 --- /dev/null +++ b/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/FlowScopedServices.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2019 Vladimir Sitnikov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.github.vlsi.gradle + +import org.gradle.api.flow.FlowProviders +import org.gradle.api.flow.FlowScope +import javax.inject.Inject + +interface FlowScopedServices { + @get:Inject + val flowScope: FlowScope + @get:Inject + val flowProviders: FlowProviders +} diff --git a/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/ProjectExtensionsPlugin.kt b/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/ProjectExtensionsPlugin.kt index 2ea3fc7b..4692c28a 100644 --- a/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/ProjectExtensionsPlugin.kt +++ b/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/ProjectExtensionsPlugin.kt @@ -26,6 +26,8 @@ import org.gradle.api.invocation.Gradle import org.gradle.api.tasks.testing.AbstractTestTask import org.gradle.api.tasks.testing.logging.TestLogEvent import org.gradle.build.event.BuildEventsListenerRegistry +import org.gradle.kotlin.dsl.always +import org.gradle.kotlin.dsl.newInstance import org.gradle.kotlin.dsl.registerIfAbsent import org.gradle.kotlin.dsl.support.serviceOf import org.gradle.kotlin.dsl.withType @@ -50,16 +52,23 @@ class ProjectExtensionsPlugin : Plugin { default = System.getProperty("os.name").contains("windows", ignoreCase = true) ) val fullTrace = target.props.bool("fulltrace") - if (!target.gradle.configurationCacheEnabled) { - target.gradle.addBuildListener( - ReportBuildFailures( - enableStyle = enableStyle, - fullTrace = fullTrace - ) + val buildServiceId = "BuildFailurePrintService.sharedService" + val sharedServices = target.gradle.sharedServices + if (sharedServices.registrations.findByName(buildServiceId) == null) { + sharedServices.registerIfAbsent( + buildServiceId, + BuildFailurePrintService::class, ) + if (GradleVersion.current() >= GradleVersion.version("8.1")) { + reportBuildFailure(target, enableStyle, fullTrace) + } else if (!target.gradle.configurationCacheEnabled) { + target.gradle.addBuildListener( + ReportBuildFailures(enableStyle, fullTrace) + ) + } } if (GitHubActionsLogger.isEnabled) { - val gitHubMarkers = target.gradle.sharedServices.registerIfAbsent( + val gitHubMarkers = sharedServices.registerIfAbsent( "PrintGitHubActionsMarkersForFailingTasks", PrintGitHubActionsMarkersForFailingTasks::class ) { @@ -81,6 +90,17 @@ class ProjectExtensionsPlugin : Plugin { printTestResults() } } + + private fun reportBuildFailure(target: Project, enableStyle: Boolean, fullTrace: Boolean) { + val flowScopeServices = target.objects.newInstance() + flowScopeServices.flowScope.always(BuildFailurePrintFlowAction::class) { + parameters { + this.enableStyle.set(enableStyle) + this.fullTrace.set(fullTrace) + this.buildWorkResult.set(flowScopeServices.flowProviders.buildWorkResult) + } + } + } } internal fun createThrowablePrinter(fullTrace: Boolean) = ThrowablePrinter().apply { diff --git a/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/ReportBuildFailures.kt b/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/ReportBuildFailures.kt index bb4c3454..1f7dbabb 100644 --- a/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/ReportBuildFailures.kt +++ b/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/ReportBuildFailures.kt @@ -21,33 +21,46 @@ import com.github.vlsi.gradle.styledtext.Style import com.github.vlsi.gradle.styledtext.StyledTextBuilder import org.gradle.BuildAdapter import org.gradle.BuildResult +import java.util.concurrent.atomic.AtomicBoolean class ReportBuildFailures( - val enableStyle: Boolean, - val fullTrace: Boolean, -) : BuildAdapter() { + private val enableStyle: Boolean, + private val fullTrace: Boolean +): BuildAdapter() { + companion object { + private val buildCompleted = AtomicBoolean() + } override fun buildFinished(result: BuildResult) { - val failure = result.failure ?: return - val gradle = result.gradle - val sb = StyledTextBuilder(enableStyle = enableStyle) - val throwablePrinter = createThrowablePrinter(fullTrace = fullTrace) - throwablePrinter.indent = " " - sb.appendPlatformLine() - sb.append(result.action).append(" ") - sb.withStyle(Style.BOLD) { - append(gradle?.rootProject?.name ?: "unknown rootProject") - sb.append(" ") - sb.withStyle( - StandardColor.RED.foreground) { - append("FAILURE") - } + if (!buildCompleted.compareAndSet(false, true)) { + return } - // Sometimes the message interferes with Gradle's progress bar. - // So we print extra spaces so the garbage after "reason" is wiped out. - sb.appendPlatformLine(" reason: ") - throwablePrinter.print(failure, sb) - if (throwablePrinter.interestingCases > 0 || throwablePrinter.classExcludes.isEmpty()) { - println(sb.toString()) + printBuildFailures( + result.failure ?: return, + action = result.action, + enableStyle = enableStyle, + fullTrace = fullTrace + ) + } +} + +fun printBuildFailures(failure: Throwable, action: String = "Build", enableStyle: Boolean, fullTrace: Boolean) { + val sb = StyledTextBuilder(enableStyle = enableStyle) + val throwablePrinter = createThrowablePrinter(fullTrace = fullTrace) + throwablePrinter.indent = " " + sb.appendPlatformLine() + sb.append(action).append(" ") + sb.withStyle(Style.BOLD) { + sb.append(" ") + sb.withStyle( + StandardColor.RED.foreground) { + append("FAILURE") } } + // Sometimes the message interferes with Gradle's progress bar. + // So we print extra spaces so the garbage after "reason" is wiped out. + sb.appendPlatformLine(" reason: ") + throwablePrinter.print(failure, sb) + if (throwablePrinter.interestingCases > 0 || throwablePrinter.classExcludes.isEmpty()) { + println(sb.toString()) + } } diff --git a/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/ThrowablePrinter.kt b/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/ThrowablePrinter.kt index aacace94..ca757eec 100644 --- a/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/ThrowablePrinter.kt +++ b/plugins/gradle-extensions-plugin/src/main/kotlin/com/github/vlsi/gradle/ThrowablePrinter.kt @@ -20,9 +20,11 @@ import com.github.vlsi.gradle.styledtext.StandardColor import com.github.vlsi.gradle.styledtext.Style import com.github.vlsi.gradle.styledtext.StyledTextBuilder import org.gradle.api.GradleException +import org.gradle.api.InvalidUserDataException import org.gradle.api.UncheckedIOException import org.gradle.api.internal.tasks.TaskDependencyResolveException import org.gradle.api.tasks.TaskExecutionException +import org.gradle.execution.MultipleBuildFailures import org.gradle.execution.TaskSelectionException import org.gradle.execution.commandline.TaskConfigurationException import org.gradle.internal.UncheckedException @@ -126,6 +128,14 @@ class ThrowablePrinter { { it.message?.startsWith("The following files have format violations") == true }, + { + it is MultipleBuildFailures + }, + { + it is InvalidUserDataException && + it.message?.startsWith("Cannot perform signing task") == true && + it.message?.endsWith("because it has no configured signatory") == true + }, { it.javaClass.name == "org.opentest4j.MultipleFailuresError" },