[Gradle] Fix GCC support for IOS specific Kotlin Native Test Tasks

^KT-63044 Verification Pending
This commit is contained in:
Anton Lakotka
2023-11-03 12:09:24 +01:00
committed by Space Team
parent 704adc053a
commit ebe0a84dee
5 changed files with 93 additions and 69 deletions
@@ -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<T : KotlinNativeTarget> : AbstractKotlinTargetConfigurator<T>(
createTestCompilation = true
@@ -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 <reified T : KotlinNativeTest> 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()
}
@@ -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<Map<Family, String>, ValueSourceParameters.None> {
@get:Inject
abstract val execOperations: ExecOperations
override fun obtain(): Map<Family, String> {
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<Family, String>()
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
}
}
@@ -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<Family, String> 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<Family, String>()
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<RegularFile>): XcodeVersion {
@@ -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 }
}
}
/**
* ValueSources are not well-supported with Configuration Cache in early Gradle versions
* Gradle 8.1 is discovered experimentally
*/
internal fun <T> Project.valueSourceProviderCompat(
clazz: Class<out ValueSource<T, ValueSourceParameters.None>>
): Provider<T> {
return if (GradleVersion.current() < GradleVersion.version("8.1")) {
val vs = project.objects.newInstance(clazz)
project.provider { vs.obtain() }
} else {
providers.of(clazz) { }
}
}