From ebe0a84dee41073f910580f1540ec043aa369f0c Mon Sep 17 00:00:00 2001 From: Anton Lakotka Date: Fri, 3 Nov 2023 12:09:24 +0100 Subject: [PATCH] [Gradle] Fix GCC support for IOS specific Kotlin Native Test Tasks ^KT-63044 Verification Pending --- .../native/KotlinNativeTargetConfigurator.kt | 17 ----- .../native/KotlinNativeTestRunFactories.kt | 17 +++-- .../XcodeDefaultTestDevicesValueSource.kt | 63 +++++++++++++++++++ .../kotlin/gradle/utils/XcodeUtils.kt | 39 ------------ .../kotlin/gradle/utils/compatibility.kt | 26 +++++--- 5 files changed, 93 insertions(+), 69 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/XcodeDefaultTestDevicesValueSource.kt diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt index 9677eaa8ba9..f34f56eb379 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt @@ -6,38 +6,21 @@ @file:Suppress("PackageDirectoryMismatch") // Old package for compatibility package org.jetbrains.kotlin.gradle.plugin -import org.gradle.api.DefaultTask import org.gradle.api.NamedDomainObjectCollection import org.gradle.api.Project -import org.gradle.api.Task import org.gradle.api.artifacts.Dependency import org.gradle.api.attributes.Category import org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE -import org.gradle.api.plugins.BasePlugin -import org.gradle.api.tasks.Exec -import org.gradle.language.base.plugins.LifecycleBasePlugin import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPILATION_NAME import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.ReadyForExecution import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.KOTLIN_NATIVE_IGNORE_INCORRECT_DEPENDENCIES import org.jetbrains.kotlin.gradle.plugin.mpp.* -import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XcodeVersionTask import org.jetbrains.kotlin.gradle.plugin.mpp.apple.registerEmbedAndSignAppleFrameworkTask -import org.jetbrains.kotlin.gradle.plugin.mpp.apple.version import org.jetbrains.kotlin.gradle.artifacts.createKlibArtifact import org.jetbrains.kotlin.gradle.artifacts.klibOutputDirectory -import org.jetbrains.kotlin.gradle.targets.native.* import org.jetbrains.kotlin.gradle.targets.native.internal.* -import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeHostTest -import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest -import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest import org.jetbrains.kotlin.gradle.tasks.* -import org.jetbrains.kotlin.gradle.testing.internal.configureConventions -import org.jetbrains.kotlin.gradle.testing.internal.kotlinTestRegistry -import org.jetbrains.kotlin.gradle.testing.testTaskName -import org.jetbrains.kotlin.gradle.utils.whenEvaluated -import org.jetbrains.kotlin.gradle.utils.XcodeUtils import org.jetbrains.kotlin.gradle.utils.newInstance -import org.jetbrains.kotlin.konan.target.HostManager open class KotlinNativeTargetConfigurator : AbstractKotlinTargetConfigurator( createTestCompilation = true diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTestRunFactories.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTestRunFactories.kt index d6626709d80..c7c19ca4f99 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTestRunFactories.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTestRunFactories.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType import org.jetbrains.kotlin.gradle.plugin.mpp.isCurrentHost import org.jetbrains.kotlin.gradle.targets.KotlinTestRunFactory +import org.jetbrains.kotlin.gradle.targets.native.internal.XcodeDefaultTestDevicesValueSource import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest import org.jetbrains.kotlin.gradle.tasks.registerTask @@ -18,6 +19,7 @@ import org.jetbrains.kotlin.gradle.testing.internal.configureConventions import org.jetbrains.kotlin.gradle.testing.internal.kotlinTestRegistry import org.jetbrains.kotlin.gradle.testing.testTaskName import org.jetbrains.kotlin.gradle.utils.XcodeUtils +import org.jetbrains.kotlin.gradle.utils.valueSourceProviderCompat import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.KonanTarget @@ -65,10 +67,15 @@ private inline fun KotlinNativeTarget.registerNat private fun KotlinNativeSimulatorTest.configureDeviceId(konanTarget: KonanTarget) { if (!isEnabled) return - val deviceIdProvider = project.provider { - XcodeUtils.getDefaultTestDeviceId(konanTarget) ?: error( - "Xcode does not support simulator tests for ${konanTarget.name}. Check that requested SDK is installed." - ) + val deviceIdProvider = project.valueSourceProviderCompat(XcodeDefaultTestDevicesValueSource::class.java) + + // Extract primitive values to avoid [target] capture in lambda + val konanTargetFamily = konanTarget.family + val konanTargetName = konanTarget.name + + val defaultDevice = deviceIdProvider.map { + it[konanTargetFamily] + ?: error("Xcode does not support simulator tests for ${konanTargetName}. Check that requested SDK is installed.") } - device.convention(deviceIdProvider).finalizeValueOnRead() + device.convention(defaultDevice).finalizeValueOnRead() } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/XcodeDefaultTestDevicesValueSource.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/XcodeDefaultTestDevicesValueSource.kt new file mode 100644 index 00000000000..c91a26b3d44 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/XcodeDefaultTestDevicesValueSource.kt @@ -0,0 +1,63 @@ +/* + * 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.targets.native.internal + +import org.gradle.api.provider.ValueSource +import org.gradle.api.provider.ValueSourceParameters +import org.gradle.process.ExecOperations +import org.jetbrains.kotlin.konan.target.Family +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.util.capitalizeDecapitalize.toUpperCaseAsciiOnly +import java.io.ByteArrayOutputStream +import java.nio.charset.Charset +import javax.inject.Inject + +internal abstract class XcodeDefaultTestDevicesValueSource : ValueSource, ValueSourceParameters.None> { + + @get:Inject + abstract val execOperations: ExecOperations + + override fun obtain(): Map { + if (!HostManager.hostIsMac) { + return emptyMap() + } + + val osRegex = "-- .* --".toRegex() + val deviceRegex = """[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}""".toRegex() + + val output = ByteArrayOutputStream() + execOperations.exec { + it.commandLine(listOf("/usr/bin/xcrun", "simctl", "list", "devices", "available")) + it.standardOutput = output + } + val out = String(output.toByteArray(), Charset.defaultCharset()) + + val result = mutableMapOf() + var os: Family? = null + out.lines().forEach { s -> + val osFound = osRegex.find(s)?.value + if (osFound != null) { + val osName = osFound.split(" ")[1] + os = try { + Family.valueOf(osName.toUpperCaseAsciiOnly()) + } catch (e: Exception) { + null + } + } else { + val currentOs = os + if (currentOs != null) { + val deviceFound = deviceRegex.find(s)?.value + if (deviceFound != null) { + result[currentOs] = deviceFound + os = null + } + } + } + } + + return result + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/XcodeUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/XcodeUtils.kt index bf90c1cd763..165d2d5afce 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/XcodeUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/XcodeUtils.kt @@ -13,7 +13,6 @@ import org.jetbrains.kotlin.konan.target.* import org.jetbrains.kotlin.konan.target.Architecture.ARM32 import org.jetbrains.kotlin.konan.target.Architecture.ARM64 import org.jetbrains.kotlin.konan.target.Family.* -import org.jetbrains.kotlin.util.capitalizeDecapitalize.toUpperCaseAsciiOnly @InternalKotlinGradlePluginApi object XcodeUtils { @@ -41,44 +40,6 @@ object XcodeUtils { && target.family in listOf(IOS, WATCHOS, TVOS) && target.architecture in listOf(ARM32, ARM64) } - - fun getDefaultTestDeviceId(target: KonanTarget): String? = defaultTestDevices[target.family] - - private val defaultTestDevices: Map by lazy { - if (!HostManager.hostIsMac) { - return@lazy emptyMap() - } - - val osRegex = "-- .* --".toRegex() - val deviceRegex = """[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}""".toRegex() - - val out = runCommand(listOf("/usr/bin/xcrun", "simctl", "list", "devices", "available")) - - val result = mutableMapOf() - var os: Family? = null - out.lines().forEach { s -> - val osFound = osRegex.find(s)?.value - if (osFound != null) { - val osName = osFound.split(" ")[1] - os = try { - Family.valueOf(osName.toUpperCaseAsciiOnly()) - } catch (e: Exception) { - null - } - } else { - val currentOs = os - if (currentOs != null) { - val deviceFound = deviceRegex.find(s)?.value - if (deviceFound != null) { - result[currentOs] = deviceFound - os = null - } - } - } - } - result - } - } internal fun XcodeVersion.Companion.parse(file: Provider): XcodeVersion { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/compatibility.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/compatibility.kt index 256a723bb7e..e2408ba9b50 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/compatibility.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/compatibility.kt @@ -21,20 +21,15 @@ import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.ArtifactCollection import org.gradle.api.artifacts.result.ResolvedArtifactResult -import org.gradle.api.file.ArchiveOperations -import org.gradle.api.file.CopySpec -import org.gradle.api.file.FileSystemOperations -import org.gradle.api.file.FileTree -import org.gradle.api.internal.project.ProjectInternal import org.gradle.api.internal.tasks.testing.TestDescriptorInternal import org.gradle.api.provider.Provider +import org.gradle.api.provider.ValueSource +import org.gradle.api.provider.ValueSourceParameters import org.gradle.api.specs.Spec -import org.gradle.api.tasks.WorkResult import org.gradle.api.tasks.bundling.AbstractArchiveTask import org.gradle.api.tasks.testing.TestDescriptor import org.gradle.util.GradleVersion import java.io.File -import java.io.Serializable const val minSupportedGradleVersion = "6.8.3" @@ -99,4 +94,19 @@ internal fun ArtifactCollection.getResolvedArtifactsCompat(project: Project): Pr resolvedArtifacts } else { project.provider { artifacts } - } \ No newline at end of file + } + +/** + * ValueSources are not well-supported with Configuration Cache in early Gradle versions + * Gradle 8.1 is discovered experimentally + */ +internal fun Project.valueSourceProviderCompat( + clazz: Class> +): Provider { + return if (GradleVersion.current() < GradleVersion.version("8.1")) { + val vs = project.objects.newInstance(clazz) + project.provider { vs.obtain() } + } else { + providers.of(clazz) { } + } +} \ No newline at end of file