[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 @file:Suppress("PackageDirectoryMismatch") // Old package for compatibility
package org.jetbrains.kotlin.gradle.plugin package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.DefaultTask
import org.gradle.api.NamedDomainObjectCollection import org.gradle.api.NamedDomainObjectCollection
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.Dependency
import org.gradle.api.attributes.Category import org.gradle.api.attributes.Category
import org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE 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.KotlinCompilation.Companion.TEST_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.ReadyForExecution 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.PropertiesProvider.Companion.KOTLIN_NATIVE_IGNORE_INCORRECT_DEPENDENCIES
import org.jetbrains.kotlin.gradle.plugin.mpp.* 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.registerEmbedAndSignAppleFrameworkTask
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.version
import org.jetbrains.kotlin.gradle.artifacts.createKlibArtifact import org.jetbrains.kotlin.gradle.artifacts.createKlibArtifact
import org.jetbrains.kotlin.gradle.artifacts.klibOutputDirectory 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.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.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.gradle.utils.newInstance
import org.jetbrains.kotlin.konan.target.HostManager
open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotlinTargetConfigurator<T>( open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotlinTargetConfigurator<T>(
createTestCompilation = true 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.NativeBuildType
import org.jetbrains.kotlin.gradle.plugin.mpp.isCurrentHost import org.jetbrains.kotlin.gradle.plugin.mpp.isCurrentHost
import org.jetbrains.kotlin.gradle.targets.KotlinTestRunFactory 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.KotlinNativeSimulatorTest
import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest
import org.jetbrains.kotlin.gradle.tasks.registerTask 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.internal.kotlinTestRegistry
import org.jetbrains.kotlin.gradle.testing.testTaskName import org.jetbrains.kotlin.gradle.testing.testTaskName
import org.jetbrains.kotlin.gradle.utils.XcodeUtils 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.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget 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) { private fun KotlinNativeSimulatorTest.configureDeviceId(konanTarget: KonanTarget) {
if (!isEnabled) return if (!isEnabled) return
val deviceIdProvider = project.provider { val deviceIdProvider = project.valueSourceProviderCompat(XcodeDefaultTestDevicesValueSource::class.java)
XcodeUtils.getDefaultTestDeviceId(konanTarget) ?: error(
"Xcode does not support simulator tests for ${konanTarget.name}. Check that requested SDK is installed." // 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.ARM32
import org.jetbrains.kotlin.konan.target.Architecture.ARM64 import org.jetbrains.kotlin.konan.target.Architecture.ARM64
import org.jetbrains.kotlin.konan.target.Family.* import org.jetbrains.kotlin.konan.target.Family.*
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toUpperCaseAsciiOnly
@InternalKotlinGradlePluginApi @InternalKotlinGradlePluginApi
object XcodeUtils { object XcodeUtils {
@@ -41,44 +40,6 @@ object XcodeUtils {
&& target.family in listOf(IOS, WATCHOS, TVOS) && target.family in listOf(IOS, WATCHOS, TVOS)
&& target.architecture in listOf(ARM32, ARM64) && 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 { 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.Task
import org.gradle.api.artifacts.ArtifactCollection import org.gradle.api.artifacts.ArtifactCollection
import org.gradle.api.artifacts.result.ResolvedArtifactResult 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.internal.tasks.testing.TestDescriptorInternal
import org.gradle.api.provider.Provider 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.specs.Spec
import org.gradle.api.tasks.WorkResult
import org.gradle.api.tasks.bundling.AbstractArchiveTask import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.api.tasks.testing.TestDescriptor import org.gradle.api.tasks.testing.TestDescriptor
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import java.io.File import java.io.File
import java.io.Serializable
const val minSupportedGradleVersion = "6.8.3" const val minSupportedGradleVersion = "6.8.3"
@@ -99,4 +94,19 @@ internal fun ArtifactCollection.getResolvedArtifactsCompat(project: Project): Pr
resolvedArtifacts resolvedArtifacts
} else { } else {
project.provider { artifacts } 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) { }
}
}