From af649cbb444dd00dd6dedf7798d68d53d4a64f1c Mon Sep 17 00:00:00 2001 From: Artem Daugel-Dauge Date: Mon, 16 Jan 2023 17:21:41 +0100 Subject: [PATCH] [Gradle] Pass through errors from Gradle to Xcode ^KT-55650 Verification Pending --- .../kotlin/gradle/native/AppleFrameworkIT.kt | 139 ++++++++++++++++++ .../kotlin/gradle/native/CocoaPodsIT.kt | 71 +++++++++ .../kotlin/compilerRunner/KotlinToolRunner.kt | 3 +- .../compilerRunner/nativeToolRunners.kt | 12 +- .../gradle/plugin/PropertiesProvider.kt | 8 + .../plugin/mpp/KotlinMultiplatformPlugin.kt | 3 + .../plugin/mpp/apple/AppleXcodeTasks.kt | 7 +- .../plugin/mpp/apple/XcodeMessageReporting.kt | 53 +++++++ 8 files changed, 291 insertions(+), 5 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XcodeMessageReporting.kt diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt index ed9fcbc69db..feb562f59bf 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt @@ -227,4 +227,143 @@ class AppleFrameworkIT : BaseGradleIT() { } } } + + + @Test + fun `configuration errors reported to Xcode when embedAndSign task requested`() { + with(Project("sharedAppleFramework")) { + setupWorkingDir() + projectDir.resolve("shared/build.gradle.kts").appendText( + """ + kotlin { + sourceSets["commonMain"].dependencies { + implementation("com.example.unknown:dependency:0.0.1") + } + } + """.trimIndent() + ) + + build( + ":shared:embedAndSignAppleFrameworkForXcode", + options = defaultBuildOptions().copy( + customEnvironmentVariables = mapOf( + "CONFIGURATION" to "debug", + "SDK_NAME" to "iphoneos123", + "ARCHS" to "arm64", + "TARGET_BUILD_DIR" to "no use", + "FRAMEWORKS_FOLDER_PATH" to "no use" + ) + ) + ) { + assertFailed() + assertContains("error: Could not find com.example.unknown:dependency:0.0.1.") + } + } + } + + @Test + fun `compilation errors reported to Xcode when embedAndSign task requested`() { + with(Project("sharedAppleFramework")) { + setupWorkingDir() + projectDir.resolve("shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt") + .appendText("this can't be compiled") + + build( + ":shared:embedAndSignAppleFrameworkForXcode", + options = defaultBuildOptions().copy( + customEnvironmentVariables = mapOf( + "CONFIGURATION" to "debug", + "SDK_NAME" to "iphoneos123", + "ARCHS" to "arm64", + "TARGET_BUILD_DIR" to "no use", + "FRAMEWORKS_FOLDER_PATH" to "no use" + ) + ) + ) { + assertFailed() + assertContains("/sharedAppleFramework/shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt:7:2: error: Expecting a top level declaration") + assertContains("error: Compilation finished with errors") + } + } + } + + + @Test + fun `compilation errors printed with Gradle-style when any other task requested`() { + with(Project("sharedAppleFramework")) { + setupWorkingDir() + projectDir.resolve("shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt") + .appendText("this can't be compiled") + + build( + ":shared:assembleDebugAppleFrameworkForXcodeIosArm64", + options = defaultBuildOptions().copy( + customEnvironmentVariables = mapOf( + "CONFIGURATION" to "debug", + "SDK_NAME" to "iphoneos123", + "ARCHS" to "arm64", + "TARGET_BUILD_DIR" to "no use", + "FRAMEWORKS_FOLDER_PATH" to "no use" + ) + ) + ) { + assertFailed() + assertContains("e: file:///") + assertNotContains("error: Compilation finished with errors") + } + } + } + + + @Test + fun `compilation errors printed with Xcode-style with explicit option`() { + with(Project("sharedAppleFramework")) { + setupWorkingDir() + projectDir.resolve("shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt") + .appendText("this can't be compiled") + + build( + ":shared:assembleDebugAppleFrameworkForXcodeIosArm64", "-Pkotlin.native.useXcodeMessageStyle=true", + options = defaultBuildOptions().copy( + customEnvironmentVariables = mapOf( + "CONFIGURATION" to "debug", + "SDK_NAME" to "iphoneos123", + "ARCHS" to "arm64", + "TARGET_BUILD_DIR" to "no use", + "FRAMEWORKS_FOLDER_PATH" to "no use" + ) + ) + ) { + assertFailed() + assertContains("/sharedAppleFramework/shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt:7:2: error: Expecting a top level declaration") + assertContains("error: Compilation finished with errors") + } + } + } + + @Test + fun `compilation errors reported to Xcode when embedAndSign task requested and compiler runs in a separate process`() { + with(Project("sharedAppleFramework")) { + setupWorkingDir() + projectDir.resolve("shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt") + .appendText("this can't be compiled") + + build( + ":shared:embedAndSignAppleFrameworkForXcode", "-Pkotlin.native.disableCompilerDaemon=true", + options = defaultBuildOptions().copy( + customEnvironmentVariables = mapOf( + "CONFIGURATION" to "debug", + "SDK_NAME" to "iphoneos123", + "ARCHS" to "arm64", + "TARGET_BUILD_DIR" to "no use", + "FRAMEWORKS_FOLDER_PATH" to "no use" + ) + ) + ) { + assertFailed() + assertContains("/sharedAppleFramework/shared/src/commonMain/kotlin/com/github/jetbrains/myapplication/Greeting.kt:7:2: error: Expecting a top level declaration") + } + } + } + } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt index 5dd41634d3d..66c0f6d3a9c 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt @@ -875,6 +875,77 @@ class CocoaPodsIT : BaseGradleIT() { } } + + @Test + fun testSyncFrameworkUseXcodeStyleErrorsWhenConfigurationFailed() { + with(project) { + gradleBuildScript().appendText( + """ + kotlin { + sourceSets["commonMain"].dependencies { + implementation("com.example.unknown:dependency:0.0.1") + } + } + """.trimIndent() + ) + + build( + "syncFramework", + "-Pkotlin.native.cocoapods.platform=iphonesimulator", + "-Pkotlin.native.cocoapods.archs=x86_64", + "-Pkotlin.native.cocoapods.configuration=Debug" + ) { + assertFailed() + assertContains("error: Could not find com.example.unknown:dependency:0.0.1.") + } + } + } + + @Test + fun testSyncFrameworkUseXcodeStyleErrorsWhenCompilationFailed() { + with(project) { + projectDir.resolve("src/commonMain/kotlin/A.kt").appendText("this can't be compiled") + + build( + "syncFramework", + "-Pkotlin.native.cocoapods.platform=iphonesimulator", + "-Pkotlin.native.cocoapods.archs=x86_64", + "-Pkotlin.native.cocoapods.configuration=Debug", + ) { + assertFailed() + assertContains("/native-cocoapods-template/src/commonMain/kotlin/A.kt:5:2: error: Expecting a top level declaration") + assertContains("error: Compilation finished with errors") + } + } + } + + @Test + fun testOtherTasksUseGradleStyleErrorsWhenCompilationFailed() { + with(project) { + projectDir.resolve("src/commonMain/kotlin/A.kt").appendText("this can't be compiled") + + build("linkPodDebugFrameworkIOS") { + assertFailed() + assertContains("e: file:///") + assertContains("/native-cocoapods-template/src/commonMain/kotlin/A.kt:5:2 Expecting a top level declaration") + assertNotContains("error: Compilation finished with errors") + } + } + } + + @Test + fun testOtherTasksUseXcodeStyleErrorsWhenCompilationFailedAndOptionEnabled() { + with(project) { + projectDir.resolve("src/commonMain/kotlin/A.kt").appendText("this can't be compiled") + + build("linkPodDebugFrameworkIOS", "-Pkotlin.native.useXcodeMessageStyle=true") { + assertFailed() + assertContains("/native-cocoapods-template/src/commonMain/kotlin/A.kt:5:2: error: Expecting a top level declaration") + assertContains("error: Compilation finished with errors") + } + } + } + @Test fun testPodDependencyInUnitTests() = getProjectByName(cocoapodsTestsProjectName).testWithWrapper(":iosX64Test") diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/KotlinToolRunner.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/KotlinToolRunner.kt index c0bd3402000..473edc74fcc 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/KotlinToolRunner.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/KotlinToolRunner.kt @@ -186,7 +186,8 @@ abstract class KotlinToolRunner( try { val mainClass = isolatedClassLoader.loadClass(mainClass) - val entryPoint = mainClass.methods.single { it.name == daemonEntryPoint } + val entryPoint = mainClass.methods + .singleOrNull { it.name == daemonEntryPoint } ?: error("Couldn't find daemon entry point '$daemonEntryPoint'") entryPoint.invoke(null, transformedArgs.toTypedArray()) } catch (t: InvocationTargetException) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/nativeToolRunners.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/nativeToolRunners.kt index d292fb9a590..540054c0f12 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/nativeToolRunners.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/nativeToolRunners.kt @@ -7,11 +7,11 @@ package org.jetbrains.kotlin.compilerRunner import org.gradle.api.Project import org.gradle.api.file.FileCollection -import org.gradle.api.tasks.* import org.jetbrains.kotlin.cli.common.messages.MessageRenderer import org.jetbrains.kotlin.gradle.dsl.NativeCacheKind import org.jetbrains.kotlin.gradle.dsl.NativeCacheOrchestration import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.useXcodeMessageStyle import org.jetbrains.kotlin.gradle.plugin.mpp.isAtLeast import org.jetbrains.kotlin.gradle.plugin.mpp.nativeUseEmbeddableCompilerJar import org.jetbrains.kotlin.gradle.targets.native.KonanPropertiesBuildService @@ -59,6 +59,7 @@ private val Project.kotlinNativeCompilerJar: String else "$konanHome/konan/lib/kotlin-native.jar" + internal abstract class KotlinNativeToolRunner( protected val toolName: String, private val settings: Settings, @@ -69,6 +70,7 @@ internal abstract class KotlinNativeToolRunner( val konanVersion: CompilerVersion, val konanHome: String, val konanPropertiesFile: File, + val useXcodeMessageStyle: Boolean, val jvmArgs: List, val classpath: FileCollection ) { @@ -77,6 +79,7 @@ internal abstract class KotlinNativeToolRunner( konanVersion = project.konanVersion, konanHome = project.konanHome, konanPropertiesFile = project.file("${project.konanHome}/konan/konan.properties"), + useXcodeMessageStyle = project.useXcodeMessageStyle, jvmArgs = project.jvmArgs, classpath = project.files(project.kotlinNativeCompilerJar, "${project.konanHome}/konan/lib/trove4j.jar") ) @@ -86,7 +89,8 @@ internal abstract class KotlinNativeToolRunner( final override val displayName get() = toolName final override val mainClass get() = "org.jetbrains.kotlin.cli.utilities.MainKt" - final override val daemonEntryPoint get() = "daemonMain" + final override val daemonEntryPoint + get() = if (!settings.useXcodeMessageStyle) "daemonMain" else "daemonMainWithXcodeRenderer" // We need to unset some environment variables which are set by XCode and may potentially affect the tool executed. final override val execEnvironmentBlacklist: Set by lazy { @@ -102,9 +106,11 @@ internal abstract class KotlinNativeToolRunner( val konanHomeRequired = !settings.konanVersion.isAtLeast(1, 4, 0) || settings.konanVersion.toString(showMeta = false, showBuild = false) in listOf("1.4-M1", "1.4-M2") + val messageRenderer = if (settings.useXcodeMessageStyle) MessageRenderer.XCODE_STYLE else MessageRenderer.GRADLE_STYLE + listOfNotNull( if (konanHomeRequired) "konan.home" to settings.konanHome else null, - MessageRenderer.PROPERTY_KEY to MessageRenderer.GRADLE_STYLE.name + MessageRenderer.PROPERTY_KEY to messageRenderer.name ).toMap() } 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 38235a27211..c6c88d5799c 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 @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLI import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_IMPORT_ENABLE_SLOW_SOURCES_JAR_RESOLVER import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_NATIVE_DEPENDENCY_PROPAGATION +import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_NATIVE_USE_XCODE_MESSAGE_STYLE import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_STDLIB_DEFAULT_DEPENDENCY import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_STDLIB_JDK_VARIANTS_VERSION_ALIGNMENT import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService @@ -361,6 +362,12 @@ internal class PropertiesProvider private constructor(private val project: Proje key.removePrefix(KOTLIN_NATIVE_BINARY_OPTION_PREFIX) } + /** + * Forces K/N compiler to print messages which could be parsed by Xcode + */ + val nativeUseXcodeMessageStyle: Boolean? + get() = booleanProperty(KOTLIN_NATIVE_USE_XCODE_MESSAGE_STYLE) + /** * Allows a user to specify additional arguments of a JVM executing KLIB commonizer. */ @@ -548,6 +555,7 @@ internal class PropertiesProvider private constructor(private val project: Proje const val KOTLIN_BUILD_REPORT_SINGLE_FILE = "kotlin.build.report.single_file" const val KOTLIN_BUILD_REPORT_HTTP_URL = "kotlin.build.report.http.url" const val KOTLIN_OPTIONS_SUPPRESS_FREEARGS_MODIFICATION_WARNING = "kotlin.options.suppressFreeCompilerArgsModificationWarning" + const val KOTLIN_NATIVE_USE_XCODE_MESSAGE_STYLE = "kotlin.native.useXcodeMessageStyle" } companion object { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt index c63bab91842..01f137c34d0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.ide.kotlinIdeMultiplatformImport import org.jetbrains.kotlin.gradle.plugin.ide.locateOrRegisterIdeResolveDependenciesTask import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin.Companion.sourceSetFreeCompilerArgsPropertyName +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.addBuildListenerForXcode import org.jetbrains.kotlin.gradle.plugin.mpp.internal.checkAndReportDeprecatedNativeTargets import org.jetbrains.kotlin.gradle.plugin.mpp.internal.handleHierarchicalStructureFlagsMigration import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.* @@ -81,6 +82,8 @@ class KotlinMultiplatformPlugin : Plugin { // Ensure that the instance is created and configured during apply project.kotlinIdeMultiplatformImport project.locateOrRegisterIdeResolveDependenciesTask() + + project.addBuildListenerForXcode() } private fun exportProjectStructureMetadataForOtherBuilds( diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/AppleXcodeTasks.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/AppleXcodeTasks.kt index 583619a338a..259e1b845d0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/AppleXcodeTasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/AppleXcodeTasks.kt @@ -31,6 +31,11 @@ import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly import java.io.File +internal object AppleXcodeTasks { + const val embedAndSignTaskPrefix = "embedAndSign" + const val embedAndSignTaskPostfix = "AppleFrameworkForXcode" +} + private object XcodeEnvironment { val buildType: NativeBuildType? get() { @@ -144,7 +149,7 @@ internal fun Project.registerEmbedAndSignAppleFrameworkTask(framework: Framework val envFrameworkSearchDir = XcodeEnvironment.frameworkSearchDir val envSign = XcodeEnvironment.sign - val frameworkTaskName = lowerCamelCaseName("embedAndSign", framework.namePrefix, "AppleFrameworkForXcode") + val frameworkTaskName = lowerCamelCaseName(AppleXcodeTasks.embedAndSignTaskPrefix, framework.namePrefix, AppleXcodeTasks.embedAndSignTaskPostfix) if (envBuildType == null || envTargets.isEmpty() || envEmbeddedFrameworksDir == null || envFrameworkSearchDir == null) { locateOrRegisterTask(frameworkTaskName) { task -> diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XcodeMessageReporting.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XcodeMessageReporting.kt new file mode 100644 index 00000000000..09b3b79628e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XcodeMessageReporting.kt @@ -0,0 +1,53 @@ +/* + * 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.mpp.apple + +import org.gradle.BuildAdapter +import org.gradle.BuildResult +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider +import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_NATIVE_USE_XCODE_MESSAGE_STYLE +import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin +import org.jetbrains.kotlin.gradle.utils.getOrPutRootProjectProperty +import org.jetbrains.kotlin.gradle.utils.isConfigurationCacheAvailable + +internal val Project.useXcodeMessageStyle: Boolean + get() = getOrPutRootProjectProperty("$KOTLIN_NATIVE_USE_XCODE_MESSAGE_STYLE.extra") { + PropertiesProvider(this).nativeUseXcodeMessageStyle ?: isXcodeTasksRequested + } + +private val Project.isXcodeTasksRequested: Boolean + get() = gradle.startParameter.taskNames.any { requestedTask -> + val name = requestedTask.substringAfterLast(':') + val isSyncTask = name == KotlinCocoapodsPlugin.SYNC_TASK_NAME + val isEmbedAndSignTask = name.startsWith(AppleXcodeTasks.embedAndSignTaskPrefix) && name.endsWith(AppleXcodeTasks.embedAndSignTaskPostfix) + isSyncTask || isEmbedAndSignTask + } + +internal fun Project.addBuildListenerForXcode() { + if (!useXcodeMessageStyle) { + return + } + + if (isConfigurationCacheAvailable(gradle)) { + // TODO https://youtrack.jetbrains.com/issue/KT-55832 + // Configuration cache case will be supported later + return + } + + gradle.addBuildListener(XcodeBuildErrorListener) +} + +private object XcodeBuildErrorListener : BuildAdapter() { + @Suppress("OVERRIDE_DEPRECATION") // Listener is added only when configuration cache is disabled + override fun buildFinished(result: BuildResult) { + if (result.failure != null) { + val rootCause = generateSequence(result.failure) { it.cause }.last() + val message = rootCause.message ?: rootCause.toString() + System.err.println("error: ${message.lineSequence().first()}") + } + } +} \ No newline at end of file