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 d5d1662ddf8..08b57ee6c7e 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 @@ -53,7 +53,7 @@ private fun Executor.service(project: Project) = object: ExecutorService { val execSpec = project.objects.newInstance().apply { action.execute(this) } - val request = executeRequest( + val request = ExecuteRequest( executableAbsolutePath = execSpec.executable, args = execSpec.args, ).apply { @@ -105,6 +105,7 @@ fun create(project: Project): ExecutorService { } }.service(project) supportsRunningTestsOnDevice(testTarget) -> deviceLauncher(project) + configurables is AppleConfigurables && RosettaExecutor.availableFor(configurables) -> RosettaExecutor(configurables).service(project) else -> HostExecutor().service(project) } } diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/cpp/RunGTest.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/cpp/RunGTest.kt index c5236c0b078..24eadae0cc8 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/cpp/RunGTest.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/cpp/RunGTest.kt @@ -40,6 +40,7 @@ private abstract class RunGTestJob : WorkAction { when { target == hostTarget -> HostExecutor() configurables is AppleConfigurables && configurables.targetTriple.isSimulator -> XcodeSimulatorExecutor(configurables) + configurables is AppleConfigurables && RosettaExecutor.availableFor(configurables) -> RosettaExecutor(configurables) else -> error("Cannot run for target $target") } } @@ -50,7 +51,7 @@ private abstract class RunGTestJob : WorkAction { with(parameters) { reportFileUnprocessed.asFile.get().parentFile.mkdirs() - executor.execute(executeRequest(this@with.executable.asFile.get().absolutePath).apply { + executor.execute(ExecuteRequest(this@with.executable.asFile.get().absolutePath).apply { this.args.add("--gtest_output=xml:${reportFileUnprocessed.asFile.get().absolutePath}") filter.orNull?.also { this.args.add("--gtest_filter=${it}") diff --git a/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/Executor.kt b/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/Executor.kt index dbc9329c310..12102bb2034 100644 --- a/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/Executor.kt +++ b/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/Executor.kt @@ -3,8 +3,6 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -@file:OptIn(ExperimentalTime::class) - package org.jetbrains.kotlin.native.executors import java.io.* @@ -62,13 +60,6 @@ data class ExecuteRequest( inline fun copying(block: ExecuteRequest.() -> Unit): ExecuteRequest = copy().apply(block) } -/** - * Helper for [ExecuteRequest] creation. - * Allows to create it without opting in [kotlin.time.ExperimentalTime] for API < 1.6. - */ -fun executeRequest(executableAbsolutePath: String, args: MutableList = mutableListOf()): ExecuteRequest = - ExecuteRequest(executableAbsolutePath, args) - data class ExecuteResponse( /** * Process exit code if it exited by itself, or `null` if it was killed by a timeout. diff --git a/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/RosettaExecutor.kt b/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/RosettaExecutor.kt new file mode 100644 index 00000000000..7504ca4387e --- /dev/null +++ b/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/RosettaExecutor.kt @@ -0,0 +1,61 @@ +/* + * 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.native.executors + +import org.jetbrains.kotlin.konan.target.AppleConfigurables +import org.jetbrains.kotlin.konan.target.Configurables +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.target.KonanTarget + +/** + * [Executor] that runs the process using Apple's Rosetta 2. + * + * @param configurables [Configurables] for target + */ +class RosettaExecutor( + private val configurables: AppleConfigurables, +) : Executor { + companion object { + /** + * Returns `true` if running via Rosetta 2 can be made available for given [configurables]. + * + * This does not check that Rosetta 2 is installed. + */ + fun availableFor(configurables: AppleConfigurables): Boolean { + return HostManager.host is KonanTarget.MACOS_ARM64 && configurables.target is KonanTarget.MACOS_X64 + } + + /** + * Return `true` if Rosetta 2 is installed. + * + * @param [hostExecutor] executor in which to run the check. By default [HostExecutor]. + */ + fun checkIsInstalled(hostExecutor: Executor = HostExecutor()): Boolean { + if (HostManager.host !is KonanTarget.MACOS_ARM64) { + return false + } + return hostExecutor.execute(ExecuteRequest("/usr/bin/arch").apply { + this.args.addAll(listOf("-x86_64", "/usr/bin/true")) + }).exitCode == 0 + } + } + + private val hostExecutor: Executor = HostExecutor() + + private val target by configurables::target + + init { + require(availableFor(configurables)) { + "$target cannot be run under Rosetta 2 from host ${HostManager.host}" + } + require(checkIsInstalled(hostExecutor)) { + "Rosetta 2 is not installed on host ${HostManager.host}" + } + } + + // When Rosetta 2 is installed, all x64 binaries are automatically run through it. + override fun execute(request: ExecuteRequest): ExecuteResponse = hostExecutor.execute(request) +} \ No newline at end of file diff --git a/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/XcodeSimulatorExecutor.kt b/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/XcodeSimulatorExecutor.kt index 370e597aa16..fbab4981706 100644 --- a/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/XcodeSimulatorExecutor.kt +++ b/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/XcodeSimulatorExecutor.kt @@ -19,7 +19,7 @@ private fun defaultDeviceId(target: KonanTarget) = when (target.family) { } private fun Executor.run(executableAbsolutePath: String, vararg args: String) = ByteArrayOutputStream().let { - this.execute(executeRequest(executableAbsolutePath).apply { + this.execute(ExecuteRequest(executableAbsolutePath).apply { this.args.addAll(args) stdout = it workingDirectory = File("").absoluteFile diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunners.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunners.kt index a95472377ee..55301d5ca1f 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunners.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunners.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.native.executors.Executor import org.jetbrains.kotlin.native.executors.EmulatorExecutor import org.jetbrains.kotlin.native.executors.XcodeSimulatorExecutor import org.jetbrains.kotlin.konan.target.* +import org.jetbrains.kotlin.native.executors.RosettaExecutor import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail import java.util.concurrent.ConcurrentHashMap @@ -35,6 +36,7 @@ internal object TestRunners { configurables is ConfigurablesWithEmulator -> EmulatorExecutor(configurables) configurables is AppleConfigurables && configurables.targetTriple.isSimulator -> XcodeSimulatorExecutor(configurables) + configurables is AppleConfigurables && RosettaExecutor.availableFor(configurables) -> RosettaExecutor(configurables) else -> runningOnUnsupportedTarget() } )