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 * Creates an ExecutorService depending on a test target -Ptest_target
*/ */
fun create(project: Project): ExecutorService { fun create(project: Project): ExecutorService {
val platformManager = project.platformManager
val testTarget = project.testTarget val testTarget = project.testTarget
val platform = platformManager.platform(testTarget) val configurables = project.testTargetConfigurables
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)
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) 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) 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 { private fun emulatorExecutor(project: Project, target: KonanTarget) = object : ExecutorService {
val platformManager = project.platformManager val configurables = project.testTargetConfigurables as? ConfigurablesWithEmulator
val configurables = platformManager.platform(target).configurables as? ConfigurablesWithEmulator
?: error("$target does not support emulation!") ?: error("$target does not support emulation!")
val absoluteTargetSysRoot = configurables.absoluteTargetSysRoot 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 fun simulator(project: Project): ExecutorService = object : ExecutorService {
private val target = project.testTarget 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 { private val simctl by lazy {
val sdk = when (target) { val sdk = Xcode.current.pathToPlatformSdk(configurables.platformName())
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 out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
val result = project.exec { val result = project.exec {
commandLine("/usr/bin/xcrun", "--find", "simctl", "--sdk", sdk) 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) { private val archSpecification = when (target.architecture) {
Architecture.X86 -> listOf("-a", "i386") 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.") else -> error("${target.architecture} can't be used in simulator.")
}.toTypedArray() }.toTypedArray()
@@ -178,26 +178,12 @@ open class FrameworkTest : DefaultTask(), KonanTestExecutable {
* test target. * test target.
*/ */
private fun getSwiftLibsPathForTestTarget(): String { private fun getSwiftLibsPathForTestTarget(): String {
val target = project.testTarget val configs = project.testTargetConfigurables as AppleConfigurables
val platform = project.platformManager.platform(target) val swiftPlatform = configs.platformName().toLowerCase()
val configs = platform.configurables as AppleConfigurables val simulatorPath = when (configs.targetTriple.isSimulator) {
val swiftPlatform = when (target) { true -> Xcode.current.getLatestSimulatorRuntimeFor(configs.target.family, configs.osVersionMin)
KonanTarget.IOS_X64 -> "iphonesimulator" ?.bundlePath
KonanTarget.IOS_ARM32, KonanTarget.IOS_ARM64 -> "iphoneos" ?.let { "$it/Contents/Resources/RuntimeRoot/usr/lib/swift" }
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"
}
else -> null else -> null
} }
// Use default path from toolchain if we cannot get `bundlePath` for target. // 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> { private fun buildEnvironment(): Map<String, String> {
val target = project.testTarget val configs = project.testTargetConfigurables
// Hopefully, lexicographical comparison will work. // Hopefully, lexicographical comparison will work.
val newMacos = System.getProperty("os.version").compareTo("10.14.4") >= 0 val newMacos = System.getProperty("os.version").compareTo("10.14.4") >= 0
val dyldLibraryPathKey = when (target) { val dyldLibraryPathKey = if (configs.targetTriple.isSimulator) {
KonanTarget.IOS_X64, "SIMCTL_CHILD_DYLD_LIBRARY_PATH"
KonanTarget.WATCHOS_X86, } else {
KonanTarget.WATCHOS_X64, "DYLD_LIBRARY_PATH"
KonanTarget.TVOS_X64 -> "SIMCTL_CHILD_DYLD_LIBRARY_PATH"
else -> "DYLD_LIBRARY_PATH"
} }
// TODO: macos_arm64? // 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() dyldLibraryPathKey to getSwiftLibsPathForTestTarget()
) )
} }
@@ -245,25 +229,14 @@ open class FrameworkTest : DefaultTask(), KonanTestExecutable {
if (!fullBitcode) { if (!fullBitcode) {
return return
} }
val testTarget = project.testTarget val configurables = project.testTargetConfigurables as AppleConfigurables
val configurables = project.platformManager.platform(testTarget).configurables as AppleConfigurables
val bitcodeBuildTool = "${configurables.absoluteAdditionalToolsDir}/bin/bitcode-build-tool" val bitcodeBuildTool = "${configurables.absoluteAdditionalToolsDir}/bin/bitcode-build-tool"
val toolPath = "${configurables.absoluteTargetToolchain}/usr/bin/" val toolPath = "${configurables.absoluteTargetToolchain}/usr/bin/"
val sdk = when (testTarget) { if (configurables.targetTriple.isSimulator) {
KonanTarget.IOS_X64, return // bitcode-build-tool doesn't support simulators.
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")
} }
val sdk = Xcode.current.pathToPlatformSdk(configurables.platformName())
val python3 = listOf("/usr/bin/python3", "/usr/local/bin/python3") val python3 = listOf("/usr/bin/python3", "/usr/local/bin/python3")
.map { Paths.get(it) }.firstOrNull { Files.exists(it) } .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 configs = platform.configurables as AppleConfigurables
val compiler = configs.absoluteTargetToolchain + "/usr/bin/swiftc" val compiler = configs.absoluteTargetToolchain + "/usr/bin/swiftc"
val swiftTarget = when (target) { val swiftTarget = configs.targetTriple.withOSVersion(configs.osVersionMin).toString()
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 args = listOf("-sdk", configs.absoluteTargetSysRoot, "-target", swiftTarget) + val args = listOf("-sdk", configs.absoluteTargetSysRoot, "-target", swiftTarget) +
options + "-o" + output.toString() + sources + options + "-o" + output.toString() + sources +
@@ -411,3 +400,10 @@ internal fun StringBuilder.appendln(o: Any?) {
append(o) append(o)
append('\n') 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 package org.jetbrains.kotlin
import com.google.gson.annotations.Expose 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 org.jetbrains.kotlin.konan.target.Xcode
import kotlin.math.min 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. * Returns first available simulator runtime for [target] with at least [osMinVersion] OS version.
* */ * */
fun Xcode.getLatestSimulatorRuntimeFor(target: KonanTarget, osMinVersion: String): SimulatorRuntimeDescriptor? { fun Xcode.getLatestSimulatorRuntimeFor(family: Family, osMinVersion: String): SimulatorRuntimeDescriptor? {
val osName = when (target) { val osName = when (family) {
KonanTarget.IOS_X64 -> "iOS" Family.IOS -> "iOS"
KonanTarget.WATCHOS_X64, KonanTarget.WATCHOS_X86 -> "watchOS" Family.WATCHOS -> "watchOS"
KonanTarget.TVOS_X64 -> "tvOS" Family.TVOS -> "tvOS"
else -> error("Unexpected simulator target: $target") else -> error("Unexpected simulator OS: $family")
} }
return getSimulatorRuntimeDescriptors().firstOrNull { return getSimulatorRuntimeDescriptors().firstOrNull {
it.checkAvailability() && it.name.startsWith(osName) && compareStringsAsVersions(it.version, osMinVersion) >= 0 it.checkAvailability() && it.name.startsWith(osName) && compareStringsAsVersions(it.version, osMinVersion) >= 0
@@ -32,16 +32,7 @@ class AppleConfigurablesImpl(
private val xcodeAddonDependency = this.additionalToolsDir!! private val xcodeAddonDependency = this.additionalToolsDir!!
override val absoluteTargetSysRoot: String get() = when (val provider = xcodePartsProvider) { override val absoluteTargetSysRoot: String get() = when (val provider = xcodePartsProvider) {
is XcodePartsProvider.Local -> when (target) { is XcodePartsProvider.Local -> provider.xcode.pathToPlatformSdk(platformName())
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)
}
XcodePartsProvider.InternalServer -> absolute(sdkDependency) XcodePartsProvider.InternalServer -> absolute(sdkDependency)
} }
@@ -101,3 +92,26 @@ class AppleConfigurablesImpl(
object InternalServer : XcodePartsProvider() 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" 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 additionalTools: String
val simulatorRuntimes: 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 { companion object {
val current: Xcode by lazy { val current: Xcode by lazy {
CurrentXcode CurrentXcode