[Gradle] Pass through errors from Gradle to Xcode

^KT-55650 Verification Pending
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 8b25d86..7f1855b 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
@@ -220,4 +220,143 @@
             }
         }
     }
+
+
+    @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 5dd4163..66c0f6d 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 @@
         }
     }
 
+
+    @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 c0bd340..473edc7 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 @@
 
         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 d292fb9..540054c 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 @@
 
 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 @@
     else
         "$konanHome/konan/lib/kotlin-native.jar"
 
+
 internal abstract class KotlinNativeToolRunner(
     protected val toolName: String,
     private val settings: Settings,
@@ -69,6 +70,7 @@
         val konanVersion: CompilerVersion,
         val konanHome: String,
         val konanPropertiesFile: File,
+        val useXcodeMessageStyle: Boolean,
         val jvmArgs: List<String>,
         val classpath: FileCollection
     ) {
@@ -77,6 +79,7 @@
                 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 @@
     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<String> by lazy {
@@ -102,9 +106,11 @@
         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 38235a2..c6c88d5 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.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
@@ -362,6 +363,12 @@
         }
 
     /**
+     * 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.
      */
     val commonizerJvmArgs: List<String>
@@ -548,6 +555,7 @@
         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 c63bab9..01f137c 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.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 @@
         // 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 583619a..259e1b8 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.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 @@
     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<DefaultTask>(frameworkTaskName) { task ->