mirror of
https://github.com/cirruslabs/tart.git
synced 2026-10-20 17:24:20 +02:00
Fixed paths and formatting
This commit is contained in:
+2
-2
@@ -60,9 +60,9 @@ task:
|
|||||||
macos_instance:
|
macos_instance:
|
||||||
image: ghcr.io/cirruslabs/macos-sonoma-xcode:$XCODE_TAG
|
image: ghcr.io/cirruslabs/macos-sonoma-xcode:$XCODE_TAG
|
||||||
build_script: swift build --arch $BUILD_ARCH --product 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
|
sign_script: codesign --sign - --entitlements Resources/tart-dev.entitlements --force .build/$BUILD_ARCH-apple-macosx/debug/tart
|
||||||
binary_artifacts:
|
binary_artifacts:
|
||||||
path: .build/$BUILD_ARCH-apple-macosx/tart
|
path: .build/$BUILD_ARCH-apple-macosx/debug/tart
|
||||||
|
|
||||||
task:
|
task:
|
||||||
only_if: $CIRRUS_TAG == '' && ($CIRRUS_USER_PERMISSION == 'write' || $CIRRUS_USER_PERMISSION == 'admin')
|
only_if: $CIRRUS_TAG == '' && ($CIRRUS_USER_PERMISSION == 'write' || $CIRRUS_USER_PERMISSION == 'admin')
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ struct Create: AsyncParsableCommand {
|
|||||||
throw ValidationError("Please specify either a --from-ipsw or --linux option!")
|
throw ValidationError("Please specify either a --from-ipsw or --linux option!")
|
||||||
}
|
}
|
||||||
#if arch(x86_64)
|
#if arch(x86_64)
|
||||||
if fromIPSW != nil {
|
if fromIPSW != nil {
|
||||||
throw ValidationError("Only Linux VMs are supported on Intel!")
|
throw ValidationError("Only Linux VMs are supported on Intel!")
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,27 +39,27 @@ struct Create: AsyncParsableCommand {
|
|||||||
|
|
||||||
try await withTaskCancellationHandler(operation: {
|
try await withTaskCancellationHandler(operation: {
|
||||||
#if arch(arm64)
|
#if arch(arm64)
|
||||||
if let fromIPSW = fromIPSW {
|
if let fromIPSW = fromIPSW {
|
||||||
let ipswURL: URL
|
let ipswURL: URL
|
||||||
|
|
||||||
if fromIPSW == "latest" {
|
if fromIPSW == "latest" {
|
||||||
defaultLogger.appendNewLine("Looking up the latest supported IPSW...")
|
defaultLogger.appendNewLine("Looking up the latest supported IPSW...")
|
||||||
|
|
||||||
let image = try await withCheckedThrowingContinuation { continuation in
|
let image = try await withCheckedThrowingContinuation { continuation in
|
||||||
VZMacOSRestoreImage.fetchLatestSupported() { result in
|
VZMacOSRestoreImage.fetchLatestSupported() { result in
|
||||||
continuation.resume(with: result)
|
continuation.resume(with: result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipswURL = image.url
|
||||||
|
} else if fromIPSW.starts(with: "http://") || fromIPSW.starts(with: "https://") {
|
||||||
|
ipswURL = URL(string: fromIPSW)!
|
||||||
|
} else {
|
||||||
|
ipswURL = URL(fileURLWithPath: NSString(string: fromIPSW).expandingTildeInPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
ipswURL = image.url
|
_ = try await VM(vmDir: tmpVMDir, ipswURL: ipswURL, diskSizeGB: diskSize)
|
||||||
} else if fromIPSW.starts(with: "http://") || fromIPSW.starts(with: "https://") {
|
|
||||||
ipswURL = URL(string: fromIPSW)!
|
|
||||||
} else {
|
|
||||||
ipswURL = URL(fileURLWithPath: NSString(string: fromIPSW).expandingTildeInPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = try await VM(vmDir: tmpVMDir, ipswURL: ipswURL, diskSizeGB: diskSize)
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if linux {
|
if linux {
|
||||||
|
|||||||
@@ -37,23 +37,23 @@ struct Run: AsyncParsableCommand {
|
|||||||
var graphics: Bool = false
|
var graphics: Bool = false
|
||||||
|
|
||||||
#if arch(arm64)
|
#if arch(arm64)
|
||||||
@Flag(help: "Boot into recovery mode")
|
@Flag(help: "Boot into recovery mode")
|
||||||
#endif
|
#endif
|
||||||
var recovery: Bool = false
|
var recovery: Bool = false
|
||||||
|
|
||||||
#if arch(arm64)
|
#if arch(arm64)
|
||||||
@Flag(help: ArgumentHelp(
|
@Flag(help: ArgumentHelp(
|
||||||
"Use screen sharing instead of the built-in UI.",
|
"Use screen sharing instead of the built-in UI.",
|
||||||
discussion: "Useful since Screen Sharing supports copy/paste, drag and drop, etc.\n"
|
discussion: "Useful since Screen Sharing supports copy/paste, drag and drop, etc.\n"
|
||||||
+ "Note that Remote Login option should be enabled inside the VM."))
|
+ "Note that Remote Login option should be enabled inside the VM."))
|
||||||
#endif
|
#endif
|
||||||
var vnc: Bool = false
|
var vnc: Bool = false
|
||||||
|
|
||||||
#if arch(arm64)
|
#if arch(arm64)
|
||||||
@Flag(help: ArgumentHelp(
|
@Flag(help: ArgumentHelp(
|
||||||
"Use Virtualization.Framework's VNC server instead of the build-in UI.",
|
"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"
|
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."))
|
+ "Note that this feature is experimental and there may be bugs present when using VNC."))
|
||||||
#endif
|
#endif
|
||||||
var vncExperimental: Bool = false
|
var vncExperimental: Bool = false
|
||||||
|
|
||||||
@@ -73,18 +73,18 @@ struct Run: AsyncParsableCommand {
|
|||||||
var disk: [String] = []
|
var disk: [String] = []
|
||||||
|
|
||||||
#if arch(arm64)
|
#if arch(arm64)
|
||||||
@Option(name: [.customLong("rosetta")], help: ArgumentHelp(
|
@Option(name: [.customLong("rosetta")], help: ArgumentHelp(
|
||||||
"Attaches a Rosetta share to the guest Linux VM with a specific tag (e.g. --rosetta=\"rosetta\")",
|
"Attaches a Rosetta share to the guest Linux VM with a specific tag (e.g. --rosetta=\"rosetta\")",
|
||||||
discussion: """
|
discussion: """
|
||||||
Requires host to be macOS 13.0 (Ventura) with Rosetta installed. The latter can be done
|
Requires host to be macOS 13.0 (Ventura) with Rosetta installed. The latter can be done
|
||||||
by running "softwareupdate --install-rosetta" (without quotes) in the Terminal.app.
|
by running "softwareupdate --install-rosetta" (without quotes) in the Terminal.app.
|
||||||
|
|
||||||
Note that you also have to configure Rosetta in the guest Linux VM by following the
|
Note that you also have to configure Rosetta in the guest Linux VM by following the
|
||||||
steps from "Mount the Shared Directory and Register Rosetta" section here:
|
steps from "Mount the Shared Directory and Register Rosetta" section here:
|
||||||
https://developer.apple.com/documentation/virtualization/running_intel_binaries_in_linux_vms_with_rosetta#3978496
|
https://developer.apple.com/documentation/virtualization/running_intel_binaries_in_linux_vms_with_rosetta#3978496
|
||||||
""",
|
""",
|
||||||
valueName: "tag"
|
valueName: "tag"
|
||||||
))
|
))
|
||||||
#endif
|
#endif
|
||||||
var rosettaTag: String?
|
var rosettaTag: String?
|
||||||
|
|
||||||
@@ -114,13 +114,13 @@ struct Run: AsyncParsableCommand {
|
|||||||
var netSoftnet: Bool = false
|
var netSoftnet: Bool = false
|
||||||
|
|
||||||
#if arch(arm64)
|
#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\"."))
|
@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
|
#endif
|
||||||
var suspendable: Bool = false
|
var suspendable: Bool = false
|
||||||
|
|
||||||
#if arch(arm64)
|
#if arch(arm64)
|
||||||
@Flag(help: ArgumentHelp("Whether system hot keys should be sent to the guest instead of the host",
|
@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."))
|
discussion: "If enabled then system hot keys like Cmd+Tab will be sent to the guest instead of the host."))
|
||||||
#endif
|
#endif
|
||||||
var captureSystemKeys: Bool = false
|
var captureSystemKeys: Bool = false
|
||||||
|
|
||||||
@@ -249,15 +249,15 @@ struct Run: AsyncParsableCommand {
|
|||||||
var resume = false
|
var resume = false
|
||||||
|
|
||||||
#if arch(arm64)
|
#if arch(arm64)
|
||||||
if #available(macOS 14, *) {
|
if #available(macOS 14, *) {
|
||||||
if FileManager.default.fileExists(atPath: vmDir.stateURL.path) {
|
if FileManager.default.fileExists(atPath: vmDir.stateURL.path) {
|
||||||
print("restoring VM state from a snapshot...")
|
print("restoring VM state from a snapshot...")
|
||||||
try await vm!.virtualMachine.restoreMachineStateFrom(url: vmDir.stateURL)
|
try await vm!.virtualMachine.restoreMachineStateFrom(url: vmDir.stateURL)
|
||||||
try FileManager.default.removeItem(at: vmDir.stateURL)
|
try FileManager.default.removeItem(at: vmDir.stateURL)
|
||||||
resume = true
|
resume = true
|
||||||
print("resuming VM...")
|
print("resuming VM...")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
try await vm!.start(recovery: recovery, resume: resume)
|
try await vm!.start(recovery: recovery, resume: resume)
|
||||||
@@ -305,23 +305,23 @@ struct Run: AsyncParsableCommand {
|
|||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
#if arch(arm64)
|
#if arch(arm64)
|
||||||
if #available(macOS 14, *) {
|
if #available(macOS 14, *) {
|
||||||
try vm!.configuration.validateSaveRestoreSupport()
|
try vm!.configuration.validateSaveRestoreSupport()
|
||||||
|
|
||||||
print("pausing VM to take a snapshot...")
|
print("pausing VM to take a snapshot...")
|
||||||
try await vm!.virtualMachine.pause()
|
try await vm!.virtualMachine.pause()
|
||||||
|
|
||||||
print("creating a snapshot...")
|
print("creating a snapshot...")
|
||||||
try await vm!.virtualMachine.saveMachineStateTo(url: vmDir.stateURL)
|
try await vm!.virtualMachine.saveMachineStateTo(url: vmDir.stateURL)
|
||||||
|
|
||||||
print("snapshot created successfully! shutting down the VM...")
|
print("snapshot created successfully! shutting down the VM...")
|
||||||
|
|
||||||
task.cancel()
|
task.cancel()
|
||||||
} else {
|
} else {
|
||||||
print(RuntimeError.SuspendFailed("this functionality is only supported on macOS 14 (Sonoma) or newer"))
|
print(RuntimeError.SuspendFailed("this functionality is only supported on macOS 14 (Sonoma) or newer"))
|
||||||
|
|
||||||
Foundation.exit(1)
|
Foundation.exit(1)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
} catch (let e) {
|
} catch (let e) {
|
||||||
print(RuntimeError.SuspendFailed(e.localizedDescription))
|
print(RuntimeError.SuspendFailed(e.localizedDescription))
|
||||||
|
|||||||
+108
-108
@@ -8,129 +8,129 @@ struct UnsupportedHostOSError: Error, CustomStringConvertible {
|
|||||||
|
|
||||||
#if arch(arm64)
|
#if arch(arm64)
|
||||||
|
|
||||||
struct Darwin: PlatformSuspendable {
|
struct Darwin: PlatformSuspendable {
|
||||||
var ecid: VZMacMachineIdentifier
|
var ecid: VZMacMachineIdentifier
|
||||||
var hardwareModel: VZMacHardwareModel
|
var hardwareModel: VZMacHardwareModel
|
||||||
|
|
||||||
init(ecid: VZMacMachineIdentifier, hardwareModel: VZMacHardwareModel) {
|
init(ecid: VZMacMachineIdentifier, hardwareModel: VZMacHardwareModel) {
|
||||||
self.ecid = ecid
|
self.ecid = ecid
|
||||||
self.hardwareModel = hardwareModel
|
self.hardwareModel = hardwareModel
|
||||||
}
|
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
|
|
||||||
let encodedECID = try container.decode(String.self, forKey: .ecid)
|
|
||||||
guard let data = Data.init(base64Encoded: encodedECID) else {
|
|
||||||
throw DecodingError.dataCorruptedError(forKey: .ecid,
|
|
||||||
in: container,
|
|
||||||
debugDescription: "failed to initialize Data using the provided value")
|
|
||||||
}
|
|
||||||
guard let ecid = VZMacMachineIdentifier.init(dataRepresentation: data) else {
|
|
||||||
throw DecodingError.dataCorruptedError(forKey: .ecid,
|
|
||||||
in: container,
|
|
||||||
debugDescription: "failed to initialize VZMacMachineIdentifier using the provided value")
|
|
||||||
}
|
|
||||||
self.ecid = ecid
|
|
||||||
|
|
||||||
let encodedHardwareModel = try container.decode(String.self, forKey: .hardwareModel)
|
|
||||||
guard let data = Data.init(base64Encoded: encodedHardwareModel) else {
|
|
||||||
throw DecodingError.dataCorruptedError(forKey: .hardwareModel, in: container, debugDescription: "")
|
|
||||||
}
|
|
||||||
guard let hardwareModel = VZMacHardwareModel.init(dataRepresentation: data) else {
|
|
||||||
throw DecodingError.dataCorruptedError(forKey: .hardwareModel, in: container, debugDescription: "")
|
|
||||||
}
|
|
||||||
self.hardwareModel = hardwareModel
|
|
||||||
}
|
|
||||||
|
|
||||||
func encode(to encoder: Encoder) throws {
|
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
||||||
|
|
||||||
try container.encode(ecid.dataRepresentation.base64EncodedString(), forKey: .ecid)
|
|
||||||
try container.encode(hardwareModel.dataRepresentation.base64EncodedString(), forKey: .hardwareModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func os() -> OS {
|
|
||||||
.darwin
|
|
||||||
}
|
|
||||||
|
|
||||||
func bootLoader(nvramURL: URL) throws -> VZBootLoader {
|
|
||||||
VZMacOSBootLoader()
|
|
||||||
}
|
|
||||||
|
|
||||||
func platform(nvramURL: URL) throws -> VZPlatformConfiguration {
|
|
||||||
let result = VZMacPlatformConfiguration()
|
|
||||||
|
|
||||||
result.machineIdentifier = ecid
|
|
||||||
result.auxiliaryStorage = VZMacAuxiliaryStorage(url: nvramURL)
|
|
||||||
|
|
||||||
if !hardwareModel.isSupported {
|
|
||||||
// At the moment support of M1 chip is not yet dropped in any macOS version
|
|
||||||
// This mean that host software is not supporting this hardware model and should be updated
|
|
||||||
throw UnsupportedHostOSError()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.hardwareModel = hardwareModel
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
return result
|
let encodedECID = try container.decode(String.self, forKey: .ecid)
|
||||||
}
|
guard let data = Data.init(base64Encoded: encodedECID) else {
|
||||||
|
throw DecodingError.dataCorruptedError(forKey: .ecid,
|
||||||
|
in: container,
|
||||||
|
debugDescription: "failed to initialize Data using the provided value")
|
||||||
|
}
|
||||||
|
guard let ecid = VZMacMachineIdentifier.init(dataRepresentation: data) else {
|
||||||
|
throw DecodingError.dataCorruptedError(forKey: .ecid,
|
||||||
|
in: container,
|
||||||
|
debugDescription: "failed to initialize VZMacMachineIdentifier using the provided value")
|
||||||
|
}
|
||||||
|
self.ecid = ecid
|
||||||
|
|
||||||
func graphicsDevice(vmConfig: VMConfig) -> VZGraphicsDeviceConfiguration {
|
let encodedHardwareModel = try container.decode(String.self, forKey: .hardwareModel)
|
||||||
let result = VZMacGraphicsDeviceConfiguration()
|
guard let data = Data.init(base64Encoded: encodedHardwareModel) else {
|
||||||
|
throw DecodingError.dataCorruptedError(forKey: .hardwareModel, in: container, debugDescription: "")
|
||||||
|
}
|
||||||
|
guard let hardwareModel = VZMacHardwareModel.init(dataRepresentation: data) else {
|
||||||
|
throw DecodingError.dataCorruptedError(forKey: .hardwareModel, in: container, debugDescription: "")
|
||||||
|
}
|
||||||
|
self.hardwareModel = hardwareModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
try container.encode(ecid.dataRepresentation.base64EncodedString(), forKey: .ecid)
|
||||||
|
try container.encode(hardwareModel.dataRepresentation.base64EncodedString(), forKey: .hardwareModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func os() -> OS {
|
||||||
|
.darwin
|
||||||
|
}
|
||||||
|
|
||||||
|
func bootLoader(nvramURL: URL) throws -> VZBootLoader {
|
||||||
|
VZMacOSBootLoader()
|
||||||
|
}
|
||||||
|
|
||||||
|
func platform(nvramURL: URL) throws -> VZPlatformConfiguration {
|
||||||
|
let result = VZMacPlatformConfiguration()
|
||||||
|
|
||||||
|
result.machineIdentifier = ecid
|
||||||
|
result.auxiliaryStorage = VZMacAuxiliaryStorage(url: nvramURL)
|
||||||
|
|
||||||
|
if !hardwareModel.isSupported {
|
||||||
|
// At the moment support of M1 chip is not yet dropped in any macOS version
|
||||||
|
// This mean that host software is not supporting this hardware model and should be updated
|
||||||
|
throw UnsupportedHostOSError()
|
||||||
|
}
|
||||||
|
|
||||||
|
result.hardwareModel = hardwareModel
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func graphicsDevice(vmConfig: VMConfig) -> VZGraphicsDeviceConfiguration {
|
||||||
|
let result = VZMacGraphicsDeviceConfiguration()
|
||||||
|
|
||||||
|
if let hostMainScreen = NSScreen.main {
|
||||||
|
let vmScreenSize = NSSize(width: vmConfig.display.width, height: vmConfig.display.height)
|
||||||
|
result.displays = [
|
||||||
|
VZMacGraphicsDisplayConfiguration(for: hostMainScreen, sizeInPoints: vmScreenSize)
|
||||||
|
]
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
if let hostMainScreen = NSScreen.main {
|
|
||||||
let vmScreenSize = NSSize(width: vmConfig.display.width, height: vmConfig.display.height)
|
|
||||||
result.displays = [
|
result.displays = [
|
||||||
VZMacGraphicsDisplayConfiguration(for: hostMainScreen, sizeInPoints: vmScreenSize)
|
VZMacGraphicsDisplayConfiguration(
|
||||||
|
widthInPixels: vmConfig.display.width,
|
||||||
|
heightInPixels: vmConfig.display.height,
|
||||||
|
// A reasonable guess according to Apple's documentation[1]
|
||||||
|
// [1]: https://developer.apple.com/documentation/coregraphics/1456599-cgdisplayscreensize
|
||||||
|
pixelsPerInch: 72
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
result.displays = [
|
func keyboards() -> [VZKeyboardConfiguration] {
|
||||||
VZMacGraphicsDisplayConfiguration(
|
if #available(macOS 14, *) {
|
||||||
widthInPixels: vmConfig.display.width,
|
// Mac keyboard is only supported by guests starting with macOS Ventura
|
||||||
heightInPixels: vmConfig.display.height,
|
return [VZMacKeyboardConfiguration(), VZUSBKeyboardConfiguration()]
|
||||||
// A reasonable guess according to Apple's documentation[1]
|
} else {
|
||||||
// [1]: https://developer.apple.com/documentation/coregraphics/1456599-cgdisplayscreensize
|
return [VZUSBKeyboardConfiguration()]
|
||||||
pixelsPerInch: 72
|
}
|
||||||
)
|
}
|
||||||
]
|
|
||||||
|
|
||||||
return result
|
func keyboardsSuspendable() -> [VZKeyboardConfiguration] {
|
||||||
}
|
if #available(macOS 14, *) {
|
||||||
|
return [VZMacKeyboardConfiguration()]
|
||||||
|
} else {
|
||||||
|
// fallback to the regular configuration
|
||||||
|
return keyboards()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func keyboards() -> [VZKeyboardConfiguration] {
|
func pointingDevices() -> [VZPointingDeviceConfiguration] {
|
||||||
if #available(macOS 14, *) {
|
// Trackpad is only supported by guests starting with macOS Ventura
|
||||||
// Mac keyboard is only supported by guests starting with macOS Ventura
|
[VZMacTrackpadConfiguration(), VZUSBScreenCoordinatePointingDeviceConfiguration()]
|
||||||
return [VZMacKeyboardConfiguration(), VZUSBKeyboardConfiguration()]
|
}
|
||||||
} else {
|
|
||||||
return [VZUSBKeyboardConfiguration()]
|
func pointingDevicesSuspendable() -> [VZPointingDeviceConfiguration] {
|
||||||
|
if #available(macOS 14, *) {
|
||||||
|
return [VZMacTrackpadConfiguration()]
|
||||||
|
} else {
|
||||||
|
// fallback to the regular configuration
|
||||||
|
return pointingDevices()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyboardsSuspendable() -> [VZKeyboardConfiguration] {
|
|
||||||
if #available(macOS 14, *) {
|
|
||||||
return [VZMacKeyboardConfiguration()]
|
|
||||||
} else {
|
|
||||||
// fallback to the regular configuration
|
|
||||||
return keyboards()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pointingDevices() -> [VZPointingDeviceConfiguration] {
|
|
||||||
// Trackpad is only supported by guests starting with macOS Ventura
|
|
||||||
[VZMacTrackpadConfiguration(), VZUSBScreenCoordinatePointingDeviceConfiguration()]
|
|
||||||
}
|
|
||||||
|
|
||||||
func pointingDevicesSuspendable() -> [VZPointingDeviceConfiguration] {
|
|
||||||
if #available(macOS 14, *) {
|
|
||||||
return [VZMacTrackpadConfiguration()]
|
|
||||||
} else {
|
|
||||||
// fallback to the regular configuration
|
|
||||||
return pointingDevices()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+72
-72
@@ -126,82 +126,82 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if arch(arm64)
|
#if arch(arm64)
|
||||||
init(
|
init(
|
||||||
vmDir: VMDirectory,
|
vmDir: VMDirectory,
|
||||||
ipswURL: URL,
|
ipswURL: URL,
|
||||||
diskSizeGB: UInt16,
|
diskSizeGB: UInt16,
|
||||||
network: Network = NetworkShared(),
|
network: Network = NetworkShared(),
|
||||||
additionalStorageDevices: [VZStorageDeviceConfiguration] = [],
|
additionalStorageDevices: [VZStorageDeviceConfiguration] = [],
|
||||||
directorySharingDevices: [VZDirectorySharingDeviceConfiguration] = [],
|
directorySharingDevices: [VZDirectorySharingDeviceConfiguration] = [],
|
||||||
serialPorts: [VZSerialPortConfiguration] = []
|
serialPorts: [VZSerialPortConfiguration] = []
|
||||||
) async throws {
|
) async throws {
|
||||||
var ipswURL = ipswURL
|
var ipswURL = ipswURL
|
||||||
|
|
||||||
if !ipswURL.isFileURL {
|
if !ipswURL.isFileURL {
|
||||||
ipswURL = try await VM.retrieveIPSW(remoteURL: ipswURL)
|
ipswURL = try await VM.retrieveIPSW(remoteURL: ipswURL)
|
||||||
}
|
|
||||||
|
|
||||||
// We create a temporary TART_HOME directory in tests, which has its "cache" folder symlinked
|
|
||||||
// to the users Tart cache directory (~/.tart/cache). However, the Virtualization.Framework
|
|
||||||
// cannot deal with paths that contain symlinks, so expand them here first.
|
|
||||||
ipswURL.resolveSymlinksInPath()
|
|
||||||
|
|
||||||
// Load the restore image and try to get the requirements
|
|
||||||
// that match both the image and our platform
|
|
||||||
let image = try await withCheckedThrowingContinuation { continuation in
|
|
||||||
VZMacOSRestoreImage.load(from: ipswURL) { result in
|
|
||||||
continuation.resume(with: result)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
guard let requirements = image.mostFeaturefulSupportedConfiguration else {
|
// We create a temporary TART_HOME directory in tests, which has its "cache" folder symlinked
|
||||||
throw UnsupportedRestoreImageError()
|
// to the users Tart cache directory (~/.tart/cache). However, the Virtualization.Framework
|
||||||
}
|
// cannot deal with paths that contain symlinks, so expand them here first.
|
||||||
|
ipswURL.resolveSymlinksInPath()
|
||||||
|
|
||||||
// Create NVRAM
|
// Load the restore image and try to get the requirements
|
||||||
_ = try VZMacAuxiliaryStorage(creatingStorageAt: vmDir.nvramURL, hardwareModel: requirements.hardwareModel)
|
// that match both the image and our platform
|
||||||
|
let image = try await withCheckedThrowingContinuation { continuation in
|
||||||
// Create disk
|
VZMacOSRestoreImage.load(from: ipswURL) { result in
|
||||||
try vmDir.resizeDisk(diskSizeGB)
|
|
||||||
|
|
||||||
name = vmDir.name
|
|
||||||
// Create config
|
|
||||||
config = VMConfig(
|
|
||||||
platform: Darwin(ecid: VZMacMachineIdentifier(), hardwareModel: requirements.hardwareModel),
|
|
||||||
cpuCountMin: requirements.minimumSupportedCPUCount,
|
|
||||||
memorySizeMin: requirements.minimumSupportedMemorySize
|
|
||||||
)
|
|
||||||
// allocate at least 4 CPUs because otherwise VMs are frequently freezing
|
|
||||||
try config.setCPU(cpuCount: max(4, requirements.minimumSupportedCPUCount))
|
|
||||||
try config.save(toURL: vmDir.configURL)
|
|
||||||
|
|
||||||
// Initialize the virtual machine and its configuration
|
|
||||||
self.network = network
|
|
||||||
configuration = try Self.craftConfiguration(diskURL: vmDir.diskURL, nvramURL: vmDir.nvramURL,
|
|
||||||
vmConfig: config, network: network,
|
|
||||||
additionalStorageDevices: additionalStorageDevices,
|
|
||||||
directorySharingDevices: directorySharingDevices,
|
|
||||||
serialPorts: serialPorts
|
|
||||||
)
|
|
||||||
virtualMachine = VZVirtualMachine(configuration: configuration)
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
virtualMachine.delegate = self
|
|
||||||
|
|
||||||
// Run automated installation
|
|
||||||
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
|
||||||
DispatchQueue.main.async { [ipswURL] in
|
|
||||||
let installer = VZMacOSInstaller(virtualMachine: self.virtualMachine, restoringFromImageAt: ipswURL)
|
|
||||||
|
|
||||||
defaultLogger.appendNewLine("Installing OS...")
|
|
||||||
ProgressObserver(installer.progress).log(defaultLogger)
|
|
||||||
|
|
||||||
installer.install { result in
|
|
||||||
continuation.resume(with: result)
|
continuation.resume(with: result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard let requirements = image.mostFeaturefulSupportedConfiguration else {
|
||||||
|
throw UnsupportedRestoreImageError()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create NVRAM
|
||||||
|
_ = try VZMacAuxiliaryStorage(creatingStorageAt: vmDir.nvramURL, hardwareModel: requirements.hardwareModel)
|
||||||
|
|
||||||
|
// Create disk
|
||||||
|
try vmDir.resizeDisk(diskSizeGB)
|
||||||
|
|
||||||
|
name = vmDir.name
|
||||||
|
// Create config
|
||||||
|
config = VMConfig(
|
||||||
|
platform: Darwin(ecid: VZMacMachineIdentifier(), hardwareModel: requirements.hardwareModel),
|
||||||
|
cpuCountMin: requirements.minimumSupportedCPUCount,
|
||||||
|
memorySizeMin: requirements.minimumSupportedMemorySize
|
||||||
|
)
|
||||||
|
// allocate at least 4 CPUs because otherwise VMs are frequently freezing
|
||||||
|
try config.setCPU(cpuCount: max(4, requirements.minimumSupportedCPUCount))
|
||||||
|
try config.save(toURL: vmDir.configURL)
|
||||||
|
|
||||||
|
// Initialize the virtual machine and its configuration
|
||||||
|
self.network = network
|
||||||
|
configuration = try Self.craftConfiguration(diskURL: vmDir.diskURL, nvramURL: vmDir.nvramURL,
|
||||||
|
vmConfig: config, network: network,
|
||||||
|
additionalStorageDevices: additionalStorageDevices,
|
||||||
|
directorySharingDevices: directorySharingDevices,
|
||||||
|
serialPorts: serialPorts
|
||||||
|
)
|
||||||
|
virtualMachine = VZVirtualMachine(configuration: configuration)
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
virtualMachine.delegate = self
|
||||||
|
|
||||||
|
// Run automated installation
|
||||||
|
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
||||||
|
DispatchQueue.main.async { [ipswURL] in
|
||||||
|
let installer = VZMacOSInstaller(virtualMachine: self.virtualMachine, restoringFromImageAt: ipswURL)
|
||||||
|
|
||||||
|
defaultLogger.appendNewLine("Installing OS...")
|
||||||
|
ProgressObserver(installer.progress).log(defaultLogger)
|
||||||
|
|
||||||
|
installer.install { result in
|
||||||
|
continuation.resume(with: result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@available(macOS 13, *)
|
@available(macOS 13, *)
|
||||||
@@ -248,11 +248,11 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||||||
@MainActor
|
@MainActor
|
||||||
private func start(_ recovery: Bool) async throws {
|
private func start(_ recovery: Bool) async throws {
|
||||||
#if arch(arm64)
|
#if arch(arm64)
|
||||||
let startOptions = VZMacOSVirtualMachineStartOptions()
|
let startOptions = VZMacOSVirtualMachineStartOptions()
|
||||||
startOptions.startUpFromMacOSRecovery = recovery
|
startOptions.startUpFromMacOSRecovery = recovery
|
||||||
try await virtualMachine.start(options: startOptions)
|
try await virtualMachine.start(options: startOptions)
|
||||||
#else
|
#else
|
||||||
try await virtualMachine.start()
|
try await virtualMachine.start()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,12 +96,12 @@ struct VMConfig: Codable {
|
|||||||
switch os {
|
switch os {
|
||||||
case .darwin:
|
case .darwin:
|
||||||
#if arch(arm64)
|
#if arch(arm64)
|
||||||
platform = try Darwin(from: decoder)
|
platform = try Darwin(from: decoder)
|
||||||
#else
|
#else
|
||||||
throw DecodingError.dataCorruptedError(
|
throw DecodingError.dataCorruptedError(
|
||||||
forKey: .os,
|
forKey: .os,
|
||||||
in: container,
|
in: container,
|
||||||
debugDescription: "Darwin VMs are only supported on Apple Silicon hosts")
|
debugDescription: "Darwin VMs are only supported on Apple Silicon hosts")
|
||||||
#endif
|
#endif
|
||||||
case .linux:
|
case .linux:
|
||||||
platform = try Linux(from: decoder)
|
platform = try Linux(from: decoder)
|
||||||
|
|||||||
Reference in New Issue
Block a user