[Gradle] Pass through errors from Gradle to Xcode

^KT-55650 Verification Pending
This commit is contained in:
Artem Daugel-Dauge
2023-01-16 17:21:41 +01:00
committed by Space Team
parent 7914d3dd42
commit af649cbb44
8 changed files with 291 additions and 5 deletions
@@ -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")
}
}
}
}
@@ -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")
@@ -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) {
@@ -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<String>,
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<String> 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()
}
@@ -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 {
@@ -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<Project> {
// Ensure that the instance is created and configured during apply
project.kotlinIdeMultiplatformImport
project.locateOrRegisterIdeResolveDependenciesTask()
project.addBuildListenerForXcode()
}
private fun exportProjectStructureMetadataForOtherBuilds(
@@ -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<DefaultTask>(frameworkTaskName) { task ->
@@ -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()}")
}
}
}