From 437a0aa450c88b424298251c681173d0b5b0a2e4 Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Fri, 2 Apr 2021 16:29:26 +0700 Subject: [PATCH] Prepare test infrastructure for Apple Silicon simulators. * Get rid of explicit target enumerations by using TargetTriple. * Remove some code duplication with `project.testTargetConfigurables` --- .../org/jetbrains/kotlin/ExecutorService.kt | 74 +++++++++---------- .../org/jetbrains/kotlin/FrameworkTest.kt | 59 ++++----------- .../main/kotlin/org/jetbrains/kotlin/Utils.kt | 20 ++--- .../org/jetbrains/kotlin/XcRunRuntimeUtils.kt | 14 ++-- .../jetbrains/kotlin/konan/target/Apple.kt | 34 ++++++--- .../kotlin/konan/target/TargetTriple.kt | 16 +++- .../jetbrains/kotlin/konan/target/Xcode.kt | 11 +++ 7 files changed, 115 insertions(+), 113 deletions(-) diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt index f28a875820e..c34b97137af 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt @@ -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): 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): 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() diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt index 96f811fff70..147f5a96c7e 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt @@ -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 { - 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) } diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/Utils.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/Utils.kt index cbc77039874..ac8885fdd38 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/Utils.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/Utils.kt @@ -292,18 +292,7 @@ fun compileSwift(project: Project, target: KonanTarget, sources: List, 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 + } diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/XcRunRuntimeUtils.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/XcRunRuntimeUtils.kt index 49ce5d00033..852d360665b 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/XcRunRuntimeUtils.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/XcRunRuntimeUtils.kt @@ -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 "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 diff --git a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Apple.kt b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Apple.kt index 6137c5ae17d..5ff6f838ad1 100644 --- a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Apple.kt +++ b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Apple.kt @@ -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") +} diff --git a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/TargetTriple.kt b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/TargetTriple.kt index 2b7698385b1..a2a04d67156 100644 --- a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/TargetTriple.kt +++ b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/TargetTriple.kt @@ -50,4 +50,18 @@ data class TargetTriple( ?: "" return "$architecture-$vendor-$os$envSuffix" } -} \ No newline at end of file +} + +/** + * 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}") \ No newline at end of file diff --git a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Xcode.kt b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Xcode.kt index d2508aba603..0ad58562865 100644 --- a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Xcode.kt +++ b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Xcode.kt @@ -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