Build x86 binary

To support Linux VMs on Intel aka x86_64
This commit is contained in:
fedor
2024-01-22 14:29:39 -05:00
parent 3e4bc73ff0
commit a5572bd3da
8 changed files with 97 additions and 48 deletions
+9 -5
View File
@@ -1,7 +1,7 @@
use_compute_credits: true
env:
XCODE_TAG: 15
XCODE_TAG: 15.2
task:
name: Test on Sonoma
@@ -51,14 +51,18 @@ task:
task:
only_if: $CIRRUS_TAG == ''
name: Build
env:
matrix:
BUILD_ARCH: arm64
BUILD_ARCH: x86_64
name: Build ($BUILD_ARCH)
alias: build
macos_instance:
image: ghcr.io/cirruslabs/macos-sonoma-xcode:$XCODE_TAG
build_script: swift build --product tart
sign_script: codesign --sign - --entitlements Resources/tart-dev.entitlements --force .build/debug/tart
build_script: swift build --arch $BUILD_ARCH --product tart
sign_script: codesign --sign - --entitlements Resources/tart-dev.entitlements --force .build/$BUILD_ARCH-apple-macosx/tart
binary_artifacts:
path: .build/debug/tart
path: .build/$BUILD_ARCH-apple-macosx/tart
task:
only_if: $CIRRUS_TAG == '' && ($CIRRUS_USER_PERMISSION == 'write' || $CIRRUS_USER_PERMISSION == 'admin')
+13 -12
View File
@@ -3,10 +3,9 @@ project_name: tart
before:
hooks:
- .ci/set-version.sh
- swift build -c release --product tart
- swift build --arch x86_64 -c release --product tart
- swift build --arch arm64 -c release --product tart
- gon gon.hcl
- mkdir -p tart.app/Contents/MacOS
- cp .build/arm64-apple-macosx/release/tart tart.app/Contents/MacOS/
builds:
- builder: prebuilt
@@ -16,10 +15,18 @@ builds:
- arm64
binary: tart.app/Contents/MacOS/tart
prebuilt:
path: tart.app/Contents/MacOS/tart
path: .build/arm64-apple-macosx/release/tart
- builder: prebuilt
goos:
- darwin
goarch:
- amd64
binary: tart.app/Contents/MacOS/tart
prebuilt:
path: .build/x86_64-apple-macosx/release/tart
archives:
- name_template: "{{ .ProjectName }}"
- name_template: "{{ .ProjectName }}-{{ .Arch }}"
files:
- src: Resources/embedded.provisionprofile
dst: tart.app/Contents
@@ -37,7 +44,7 @@ brews:
caveats: See the GitHub repository for more information
homepage: https://github.com/cirruslabs/tart
license: "Fair Source"
description: Run macOS VMs on Apple Silicon
description: Run macOS and Linux VMs on Apple Hardware
skip_upload: auto
dependencies:
- "cirruslabs/cli/softnet"
@@ -46,9 +53,3 @@ brews:
bin.write_exec_script "#{libexec}/tart.app/Contents/MacOS/tart"
custom_block: |
depends_on :macos => :ventura
on_macos do
unless Hardware::CPU.arm?
odie "Tart only works on Apple Silicon!"
end
end
+18 -2
View File
@@ -1,7 +1,8 @@
import ArgumentParser
import Dispatch
import SwiftUI
import Foundation
import SwiftUI
import Virtualization
struct Create: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Create a VM")
@@ -22,6 +23,11 @@ struct Create: AsyncParsableCommand {
if fromIPSW == nil && !linux {
throw ValidationError("Please specify either a --from-ipsw or --linux option!")
}
#if arch(x86_64)
if fromIPSW != nil {
throw ValidationError("Only Linux VMs are supported on Intel!")
}
#endif
}
func run() async throws {
@@ -32,11 +38,20 @@ struct Create: AsyncParsableCommand {
try tmpVMDirLock.lock()
try await withTaskCancellationHandler(operation: {
#if arch(arm64)
if let fromIPSW = fromIPSW {
let ipswURL: URL
if fromIPSW == "latest" {
ipswURL = try await VM.latestIPSWURL()
defaultLogger.appendNewLine("Looking up the latest supported IPSW...")
let image = try await withCheckedThrowingContinuation { continuation in
VZMacOSRestoreImage.fetchLatestSupported() { result in
continuation.resume(with: result)
}
}
ipswURL = image.url
} else if fromIPSW.starts(with: "http://") || fromIPSW.starts(with: "https://") {
ipswURL = URL(string: fromIPSW)!
} else {
@@ -45,6 +60,7 @@ struct Create: AsyncParsableCommand {
_ = try await VM(vmDir: tmpVMDir, ipswURL: ipswURL, diskSizeGB: diskSize)
}
#endif
if linux {
_ = try await VM.linux(vmDir: tmpVMDir, diskSizeGB: diskSize)
+36 -16
View File
@@ -36,19 +36,25 @@ struct Run: AsyncParsableCommand {
@Flag(help: "Force open a UI window, even when VNC is enabled.")
var graphics: Bool = false
#if arch(arm64)
@Flag(help: "Boot into recovery mode")
#endif
var recovery: Bool = false
#if arch(arm64)
@Flag(help: ArgumentHelp(
"Use screen sharing instead of the built-in UI.",
discussion: "Useful since Screen Sharing supports copy/paste, drag and drop, etc.\n"
+ "Note that Remote Login option should be enabled inside the VM."))
#endif
var vnc: Bool = false
#if arch(arm64)
@Flag(help: ArgumentHelp(
"Use Virtualization.Framework's VNC server instead of the build-in UI.",
discussion: "Useful since this type of VNC is available in recovery mode and in macOS installation.\n"
+ "Note that this feature is experimental and there may be bugs present when using VNC."))
#endif
var vncExperimental: Bool = false
@Option(help: ArgumentHelp("""
@@ -66,6 +72,7 @@ struct Run: AsyncParsableCommand {
""", valueName: "path[:ro]"))
var disk: [String] = []
#if arch(arm64)
@Option(name: [.customLong("rosetta")], help: ArgumentHelp(
"Attaches a Rosetta share to the guest Linux VM with a specific tag (e.g. --rosetta=\"rosetta\")",
discussion: """
@@ -78,6 +85,7 @@ struct Run: AsyncParsableCommand {
""",
valueName: "tag"
))
#endif
var rosettaTag: String?
@Option(help: ArgumentHelp("""
@@ -105,11 +113,15 @@ struct Run: AsyncParsableCommand {
discussion: "Learn how to configure Softnet for use with Tart here: https://github.com/cirruslabs/softnet"))
var netSoftnet: Bool = false
#if arch(arm64)
@Flag(help: ArgumentHelp("Disables audio and entropy devices and switches to only Mac-specific input devices.", discussion: "Useful for running a VM that can be suspended via \"tart suspend\"."))
#endif
var suspendable: Bool = false
#if arch(arm64)
@Flag(help: ArgumentHelp("Whether system hot keys should be sent to the guest instead of the host",
discussion: "If enabled then system hot keys like Cmd+Tab will be sent to the guest instead of the host."))
#endif
var captureSystemKeys: Bool = false
mutating func validate() throws {
@@ -236,6 +248,7 @@ struct Run: AsyncParsableCommand {
do {
var resume = false
#if arch(arm64)
if #available(macOS 14, *) {
if FileManager.default.fileExists(atPath: vmDir.stateURL.path) {
print("restoring VM state from a snapshot...")
@@ -245,6 +258,7 @@ struct Run: AsyncParsableCommand {
print("resuming VM...")
}
}
#endif
try await vm!.start(recovery: recovery, resume: resume)
@@ -290,6 +304,7 @@ struct Run: AsyncParsableCommand {
sigusr1Src.setEventHandler {
Task {
do {
#if arch(arm64)
if #available(macOS 14, *) {
try vm!.configuration.validateSaveRestoreSupport()
@@ -307,6 +322,7 @@ struct Run: AsyncParsableCommand {
Foundation.exit(1)
}
#endif
} catch (let e) {
print(RuntimeError.SuspendFailed(e.localizedDescription))
@@ -464,25 +480,29 @@ struct Run: AsyncParsableCommand {
guard let rosettaTag = rosettaTag else {
return []
}
#if arch(arm64)
guard #available(macOS 13, *) else {
throw UnsupportedOSError("Rosetta directory share", "is")
}
guard #available(macOS 13, *) else {
throw UnsupportedOSError("Rosetta directory share", "is")
}
switch VZLinuxRosettaDirectoryShare.availability {
case .notInstalled:
throw UnsupportedOSError("Rosetta directory share", "is", "that have Rosetta installed")
case .notSupported:
throw UnsupportedOSError("Rosetta directory share", "is", "running Apple silicon")
default:
break
}
switch VZLinuxRosettaDirectoryShare.availability {
case .notInstalled:
throw UnsupportedOSError("Rosetta directory share", "is", "that have Rosetta installed")
case .notSupported:
throw UnsupportedOSError("Rosetta directory share", "is", "running Apple silicon")
default:
break
}
try VZVirtioFileSystemDeviceConfiguration.validateTag(rosettaTag)
let device = VZVirtioFileSystemDeviceConfiguration(tag: rosettaTag)
device.share = try VZLinuxRosettaDirectoryShare()
try VZVirtioFileSystemDeviceConfiguration.validateTag(rosettaTag)
let device = VZVirtioFileSystemDeviceConfiguration(tag: rosettaTag)
device.share = try VZLinuxRosettaDirectoryShare()
return [device]
return [device]
#elseif arch(x86_64)
// there is no Rosetta on Intel
return []
#endif
}
private func runUI(_ suspendable: Bool, _ captureSystemKeys: Bool) {
+4
View File
@@ -6,6 +6,8 @@ struct UnsupportedHostOSError: Error, CustomStringConvertible {
}
}
#if arch(arm64)
struct Darwin: PlatformSuspendable {
var ecid: VZMacMachineIdentifier
var hardwareModel: VZMacHardwareModel
@@ -130,3 +132,5 @@ struct Darwin: PlatformSuspendable {
}
}
}
#endif
+6 -12
View File
@@ -116,18 +116,6 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
return try FileManager.default.replaceItemAt(finalLocation, withItemAt: temporaryLocation)!
}
static func latestIPSWURL() async throws -> URL {
defaultLogger.appendNewLine("Looking up the latest supported IPSW...")
let image = try await withCheckedThrowingContinuation { continuation in
VZMacOSRestoreImage.fetchLatestSupported() { result in
continuation.resume(with: result)
}
}
return image.url
}
var inFinalState: Bool {
get {
virtualMachine.state == VZVirtualMachine.State.stopped ||
@@ -137,6 +125,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
}
}
#if arch(arm64)
init(
vmDir: VMDirectory,
ipswURL: URL,
@@ -213,6 +202,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
}
}
}
#endif
@available(macOS 13, *)
static func linux(vmDir: VMDirectory, diskSizeGB: UInt16) async throws -> VM {
@@ -257,9 +247,13 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
@MainActor
private func start(_ recovery: Bool) async throws {
#if arch(arm64)
let startOptions = VZMacOSVirtualMachineStartOptions()
startOptions.startUpFromMacOSRecovery = recovery
try await virtualMachine.start(options: startOptions)
#else
try await virtualMachine.start()
#endif
}
@MainActor
+7
View File
@@ -95,7 +95,14 @@ struct VMConfig: Codable {
arch = try container.decodeIfPresent(Architecture.self, forKey: .arch) ?? .arm64
switch os {
case .darwin:
#if arch(arm64)
platform = try Darwin(from: decoder)
#else
throw DecodingError.dataCorruptedError(
forKey: .os,
in: container,
debugDescription: "Darwin VMs are only supported on Apple Silicon hosts")
#endif
case .linux:
platform = try Linux(from: decoder)
}
+4 -1
View File
@@ -1,4 +1,7 @@
source = [".build/arm64-apple-macosx/release/tart"]
source = [
".build/x86_64-apple-macosx/release/tart",
".build/arm64-apple-macosx/release/tart"
]
bundle_id = "com.github.cirruslabs.tart"
apple_id {