Prepare test infrastructure for Apple Silicon simulators.

* Get rid of explicit target enumerations by using TargetTriple.
* Remove some code duplication with `project.testTargetConfigurables`
This commit is contained in:
Sergey Bogolepov
2021-04-02 16:29:26 +07:00
committed by TeamCityServer
parent cebf815fed
commit 437a0aa450
7 changed files with 115 additions and 113 deletions
@@ -46,42 +46,32 @@ interface ExecutorService {
* Creates an ExecutorService depending on a test target -Ptest_target
*/
fun create(project: Project): ExecutorService {
val platformManager = project.platformManager
val testTarget = project.testTarget
val platform = platformManager.platform(testTarget)
val absoluteTargetToolchain = platform.absoluteTargetToolchain
return if (project.hasProperty("remote")) {
sshExecutor(project)
} else when (testTarget) {
KonanTarget.WASM32 -> object : ExecutorService {
override fun execute(action: Action<in ExecSpec>): ExecResult? = project.exec {
action.execute(this)
val exe = executable
val d8 = "$absoluteTargetToolchain/bin/d8"
val launcherJs = "$executable.js"
this.executable = d8
this.args = listOf("--expose-wasm", launcherJs, "--", exe) + args
}
}
KonanTarget.LINUX_MIPS32,
KonanTarget.LINUX_MIPSEL32,
KonanTarget.LINUX_ARM32_HFP,
KonanTarget.LINUX_ARM64 -> emulatorExecutor(project, testTarget)
KonanTarget.IOS_X64,
KonanTarget.TVOS_X64,
KonanTarget.WATCHOS_X86,
KonanTarget.WATCHOS_X64 -> simulator(project)
KonanTarget.IOS_ARM32,
KonanTarget.IOS_ARM64 -> deviceLauncher(project)
val configurables = project.testTargetConfigurables
return when {
project.hasProperty("remote") -> sshExecutor(project)
configurables is WasmConfigurables -> wasmExecutor(project)
configurables is ConfigurablesWithEmulator && testTarget != HostManager.host -> emulatorExecutor(project, testTarget)
configurables.targetTriple.isSimulator -> simulator(project)
testTarget == KonanTarget.IOS_ARM32 || testTarget == KonanTarget.IOS_ARM64 -> deviceLauncher(project)
else -> localExecutorService(project)
}
}
private fun wasmExecutor(project: Project) = object : ExecutorService {
val absoluteTargetToolchain = project.testTargetConfigurables.absoluteTargetToolchain
override fun execute(action: Action<in ExecSpec>): ExecResult? = project.exec {
action.execute(this)
val exe = executable
val d8 = "$absoluteTargetToolchain/bin/d8"
val launcherJs = "$executable.js"
this.executable = d8
this.args = listOf("--expose-wasm", launcherJs, "--", exe) + args
}
}
data class ProcessOutput(var stdOut: String, var stdErr: String, var exitCode: Int)
/**
@@ -196,8 +186,7 @@ fun localExecutorService(project: Project): ExecutorService = object : ExecutorS
}
private fun emulatorExecutor(project: Project, target: KonanTarget) = object : ExecutorService {
val platformManager = project.platformManager
val configurables = platformManager.platform(target).configurables as? ConfigurablesWithEmulator
val configurables = project.testTargetConfigurables as? ConfigurablesWithEmulator
?: error("$target does not support emulation!")
val absoluteTargetSysRoot = configurables.absoluteTargetSysRoot
@@ -234,15 +223,19 @@ private fun emulatorExecutor(project: Project, target: KonanTarget) = object : E
private fun simulator(project: Project): ExecutorService = object : ExecutorService {
private val target = project.testTarget
val configurables = project.testTargetConfigurables as AppleConfigurables
init {
require(configurables.targetTriple.isSimulator) {
"${configurables.target} is not a simulator."
}
require(HostManager.host.architecture == configurables.target.architecture) {
"Can't run simulator with ${configurables.target.architecture} architecture."
}
}
private val simctl by lazy {
val sdk = when (target) {
KonanTarget.TVOS_X64 -> Xcode.current.appletvsimulatorSdk
KonanTarget.IOS_X64 -> Xcode.current.iphonesimulatorSdk
KonanTarget.WATCHOS_X64,
KonanTarget.WATCHOS_X86 -> Xcode.current.watchsimulatorSdk
else -> error("Unexpected simulation target: $target")
}
val sdk = Xcode.current.pathToPlatformSdk(configurables.platformName())
val out = ByteArrayOutputStream()
val result = project.exec {
commandLine("/usr/bin/xcrun", "--find", "simctl", "--sdk", sdk)
@@ -282,7 +275,8 @@ private fun simulator(project: Project): ExecutorService = object : ExecutorServ
private val archSpecification = when (target.architecture) {
Architecture.X86 -> listOf("-a", "i386")
Architecture.X64 -> listOf() // x86-64 is used by default.
Architecture.X64 -> listOf() // x86-64 is used by default on Intel Macs.
Architecture.ARM64 -> listOf() // arm64 is used by default on Apple Silicon.
else -> error("${target.architecture} can't be used in simulator.")
}.toTypedArray()
@@ -178,26 +178,12 @@ open class FrameworkTest : DefaultTask(), KonanTestExecutable {
* test target.
*/
private fun getSwiftLibsPathForTestTarget(): String {
val target = project.testTarget
val platform = project.platformManager.platform(target)
val configs = platform.configurables as AppleConfigurables
val swiftPlatform = when (target) {
KonanTarget.IOS_X64 -> "iphonesimulator"
KonanTarget.IOS_ARM32, KonanTarget.IOS_ARM64 -> "iphoneos"
KonanTarget.TVOS_X64 -> "appletvsimulator"
KonanTarget.TVOS_ARM64 -> "appletvos"
KonanTarget.MACOS_X64, KonanTarget.MACOS_ARM64 -> "macosx"
KonanTarget.WATCHOS_ARM64 -> "watchos"
KonanTarget.WATCHOS_X64, KonanTarget.WATCHOS_X86 -> "watchsimulator"
else -> throw IllegalStateException("Test target $target is not supported")
}
val simulatorPath = when (target) {
KonanTarget.TVOS_X64,
KonanTarget.IOS_X64,
KonanTarget.WATCHOS_X86,
KonanTarget.WATCHOS_X64 -> Xcode.current.getLatestSimulatorRuntimeFor(target, configs.osVersionMin)?.bundlePath?.let {
"$it/Contents/Resources/RuntimeRoot/usr/lib/swift"
}
val configs = project.testTargetConfigurables as AppleConfigurables
val swiftPlatform = configs.platformName().toLowerCase()
val simulatorPath = when (configs.targetTriple.isSimulator) {
true -> Xcode.current.getLatestSimulatorRuntimeFor(configs.target.family, configs.osVersionMin)
?.bundlePath
?.let { "$it/Contents/Resources/RuntimeRoot/usr/lib/swift" }
else -> null
}
// Use default path from toolchain if we cannot get `bundlePath` for target.
@@ -206,18 +192,16 @@ open class FrameworkTest : DefaultTask(), KonanTestExecutable {
}
private fun buildEnvironment(): Map<String, String> {
val target = project.testTarget
val configs = project.testTargetConfigurables
// Hopefully, lexicographical comparison will work.
val newMacos = System.getProperty("os.version").compareTo("10.14.4") >= 0
val dyldLibraryPathKey = when (target) {
KonanTarget.IOS_X64,
KonanTarget.WATCHOS_X86,
KonanTarget.WATCHOS_X64,
KonanTarget.TVOS_X64 -> "SIMCTL_CHILD_DYLD_LIBRARY_PATH"
else -> "DYLD_LIBRARY_PATH"
val dyldLibraryPathKey = if (configs.targetTriple.isSimulator) {
"SIMCTL_CHILD_DYLD_LIBRARY_PATH"
} else {
"DYLD_LIBRARY_PATH"
}
// TODO: macos_arm64?
return if (newMacos && target == KonanTarget.MACOS_X64) emptyMap() else mapOf(
return if (newMacos && configs.target == KonanTarget.MACOS_X64) emptyMap() else mapOf(
dyldLibraryPathKey to getSwiftLibsPathForTestTarget()
)
}
@@ -245,25 +229,14 @@ open class FrameworkTest : DefaultTask(), KonanTestExecutable {
if (!fullBitcode) {
return
}
val testTarget = project.testTarget
val configurables = project.platformManager.platform(testTarget).configurables as AppleConfigurables
val configurables = project.testTargetConfigurables as AppleConfigurables
val bitcodeBuildTool = "${configurables.absoluteAdditionalToolsDir}/bin/bitcode-build-tool"
val toolPath = "${configurables.absoluteTargetToolchain}/usr/bin/"
val sdk = when (testTarget) {
KonanTarget.IOS_X64,
KonanTarget.TVOS_X64,
KonanTarget.WATCHOS_X86,
KonanTarget.WATCHOS_X64 -> return // bitcode-build-tool doesn't support simulators.
KonanTarget.IOS_ARM64,
KonanTarget.IOS_ARM32 -> Xcode.current.iphoneosSdk
KonanTarget.MACOS_X64,
KonanTarget.MACOS_ARM64 -> Xcode.current.macosxSdk
KonanTarget.TVOS_ARM64 -> Xcode.current.appletvosSdk
KonanTarget.WATCHOS_ARM32,
KonanTarget.WATCHOS_ARM64 -> Xcode.current.watchosSdk
else -> error("Cannot validate bitcode for test target $testTarget")
if (configurables.targetTriple.isSimulator) {
return // bitcode-build-tool doesn't support simulators.
}
val sdk = Xcode.current.pathToPlatformSdk(configurables.platformName())
val python3 = listOf("/usr/bin/python3", "/usr/local/bin/python3")
.map { Paths.get(it) }.firstOrNull { Files.exists(it) }
@@ -292,18 +292,7 @@ fun compileSwift(project: Project, target: KonanTarget, sources: List<String>, o
val configs = platform.configurables as AppleConfigurables
val compiler = configs.absoluteTargetToolchain + "/usr/bin/swiftc"
val swiftTarget = when (target) {
KonanTarget.IOS_X64 -> "x86_64-apple-ios" + configs.osVersionMin
KonanTarget.IOS_ARM32 -> "armv7-apple-ios" + configs.osVersionMin
KonanTarget.IOS_ARM64 -> "arm64-apple-ios" + configs.osVersionMin
KonanTarget.TVOS_X64 -> "x86_64-apple-tvos" + configs.osVersionMin
KonanTarget.TVOS_ARM64 -> "arm64-apple-tvos" + configs.osVersionMin
KonanTarget.MACOS_X64 -> "x86_64-apple-macosx" + configs.osVersionMin
KonanTarget.MACOS_ARM64 -> "arm64-apple-macos" + configs.osVersionMin
KonanTarget.WATCHOS_X86 -> "i386-apple-watchos" + configs.osVersionMin
KonanTarget.WATCHOS_X64 -> "x86_64-apple-watchos" + configs.osVersionMin
else -> throw IllegalStateException("Test target $target is not supported")
}
val swiftTarget = configs.targetTriple.withOSVersion(configs.osVersionMin).toString()
val args = listOf("-sdk", configs.absoluteTargetSysRoot, "-target", swiftTarget) +
options + "-o" + output.toString() + sources +
@@ -411,3 +400,10 @@ internal fun StringBuilder.appendln(o: Any?) {
append(o)
append('\n')
}
internal val Project.testTargetConfigurables: Configurables
get() {
val platformManager = project.platformManager
val testTarget = project.testTarget
return platformManager.platform(testTarget).configurables
}
@@ -1,7 +1,7 @@
package org.jetbrains.kotlin
import com.google.gson.annotations.Expose
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.Xcode
import kotlin.math.min
@@ -28,12 +28,12 @@ private fun Xcode.getSimulatorRuntimeDescriptors(): List<SimulatorRuntimeDescrip
/**
* Returns first available simulator runtime for [target] with at least [osMinVersion] OS version.
* */
fun Xcode.getLatestSimulatorRuntimeFor(target: KonanTarget, osMinVersion: String): SimulatorRuntimeDescriptor? {
val osName = when (target) {
KonanTarget.IOS_X64 -> "iOS"
KonanTarget.WATCHOS_X64, KonanTarget.WATCHOS_X86 -> "watchOS"
KonanTarget.TVOS_X64 -> "tvOS"
else -> error("Unexpected simulator target: $target")
fun Xcode.getLatestSimulatorRuntimeFor(family: Family, osMinVersion: String): SimulatorRuntimeDescriptor? {
val osName = when (family) {
Family.IOS -> "iOS"
Family.WATCHOS -> "watchOS"
Family.TVOS -> "tvOS"
else -> error("Unexpected simulator OS: $family")
}
return getSimulatorRuntimeDescriptors().firstOrNull {
it.checkAvailability() && it.name.startsWith(osName) && compareStringsAsVersions(it.version, osMinVersion) >= 0
@@ -32,16 +32,7 @@ class AppleConfigurablesImpl(
private val xcodeAddonDependency = this.additionalToolsDir!!
override val absoluteTargetSysRoot: String get() = when (val provider = xcodePartsProvider) {
is XcodePartsProvider.Local -> when (target) {
KonanTarget.MACOS_X64, KonanTarget.MACOS_ARM64 -> provider.xcode.macosxSdk
KonanTarget.IOS_ARM32, KonanTarget.IOS_ARM64 -> provider.xcode.iphoneosSdk
KonanTarget.IOS_X64 -> provider.xcode.iphonesimulatorSdk
KonanTarget.TVOS_ARM64 -> provider.xcode.appletvosSdk
KonanTarget.TVOS_X64 -> provider.xcode.appletvsimulatorSdk
KonanTarget.WATCHOS_ARM64, KonanTarget.WATCHOS_ARM32 -> provider.xcode.watchosSdk
KonanTarget.WATCHOS_X64, KonanTarget.WATCHOS_X86 -> provider.xcode.watchsimulatorSdk
else -> error(target)
}
is XcodePartsProvider.Local -> provider.xcode.pathToPlatformSdk(platformName())
XcodePartsProvider.InternalServer -> absolute(sdkDependency)
}
@@ -101,3 +92,26 @@ class AppleConfigurablesImpl(
object InternalServer : XcodePartsProvider()
}
}
/**
* Name of an Apple platform as in Xcode.app/Contents/Developer/Platforms.
*/
fun AppleConfigurables.platformName(): String = when (target.family) {
Family.OSX -> "MacOSX"
Family.IOS -> if (targetTriple.isSimulator) {
"iPhoneSimulator"
} else {
"iPhoneOS"
}
Family.TVOS -> if (targetTriple.isSimulator) {
"AppleTVSimulator"
} else {
"AppleTVOS"
}
Family.WATCHOS -> if (targetTriple.isSimulator) {
"WatchSimulator"
} else {
"WatchOS"
}
else -> error("Not an Apple target: $target")
}
@@ -50,4 +50,18 @@ data class TargetTriple(
?: ""
return "$architecture-$vendor-$os$envSuffix"
}
}
}
/**
* Check that given target is Apple simulator.
*/
val TargetTriple.isSimulator: Boolean
get() = environment == "simulator"
/**
* Appends version to OS part of triple.
*
* Useful for precise target specification in Clang and Swift.
*/
fun TargetTriple.withOSVersion(osVersion: String): TargetTriple =
copy(os = "${os}${osVersion}")
@@ -35,6 +35,17 @@ interface Xcode {
val additionalTools: String
val simulatorRuntimes: String
fun pathToPlatformSdk(platformName: String): String = when (platformName.toLowerCase()) {
"macosx" -> macosxSdk
"iphoneos" -> iphoneosSdk
"iphonesimulator" -> iphonesimulatorSdk
"appletvos" -> appletvosSdk
"appletvsimulator" -> appletvsimulatorSdk
"watchos" -> watchosSdk
"watchsimulator" -> watchsimulatorSdk
else -> error("Unknown Apple platform: $platformName")
}
companion object {
val current: Xcode by lazy {
CurrentXcode