diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01dd33a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Xcode's user settings +xcuserdata/ diff --git a/Sources/tart/Commands/Create.swift b/Sources/tart/Commands/Create.swift new file mode 100644 index 0000000..d179af4 --- /dev/null +++ b/Sources/tart/Commands/Create.swift @@ -0,0 +1,41 @@ +import ArgumentParser +import Dispatch +import SwiftUI +import Foundation + +struct Create: ParsableCommand { + static var configuration = CommandConfiguration(abstract: "Create a VM") + + @Argument(help: "VM name") + var name: String + + @Option(help: ArgumentHelp("Path to the IPSW file (or \"latest\") to fetch the latest appropriate IPSW", valueName: "path")) var fromIPSW: String? + + func validate() throws { + if fromIPSW == nil { + throw ValidationError("Please specify a --from-ipsw option!") + } + } + + func run() throws { + Task { + do { + let vmDir = try VMStorage().create(name) + + if fromIPSW! == "latest" { + _ = try await VM(vmDir: vmDir, ipswURL: nil) + } else { + _ = try await VM(vmDir: vmDir, ipswURL: URL(fileURLWithPath: fromIPSW!)) + } + + Foundation.exit(0) + } catch { + print(error) + + Foundation.exit(1) + } + } + + dispatchMain() + } +} diff --git a/Sources/tart/Commands/Delete.swift b/Sources/tart/Commands/Delete.swift new file mode 100644 index 0000000..7b6fe6d --- /dev/null +++ b/Sources/tart/Commands/Delete.swift @@ -0,0 +1,26 @@ +import ArgumentParser +import Dispatch +import SwiftUI + +struct Delete: ParsableCommand { + static var configuration = CommandConfiguration(abstract: "Delete a VM") + + @Argument(help: "VM name") + var name: String + + func run() throws { + Task { + do { + try VMStorage().delete(name) + + Foundation.exit(0) + } catch { + print(error) + + Foundation.exit(1) + } + } + + dispatchMain() + } +} diff --git a/Sources/tart/Commands/List.swift b/Sources/tart/Commands/List.swift new file mode 100644 index 0000000..ab7c634 --- /dev/null +++ b/Sources/tart/Commands/List.swift @@ -0,0 +1,25 @@ +import ArgumentParser +import Dispatch +import SwiftUI + +struct List: ParsableCommand { + static var configuration = CommandConfiguration(abstract: "List created VMs") + + func run() throws { + Task { + do { + for vmURL in try VMStorage().list() { + print(vmURL) + } + + Foundation.exit(0) + } catch { + print(error) + + Foundation.exit(1) + } + } + + dispatchMain() + } +} diff --git a/Sources/tart/Commands/Root.swift b/Sources/tart/Commands/Root.swift new file mode 100644 index 0000000..897614b --- /dev/null +++ b/Sources/tart/Commands/Root.swift @@ -0,0 +1,7 @@ +import ArgumentParser + +struct Root: ParsableCommand { + static var configuration = CommandConfiguration( + commandName: "tart", + subcommands: [Create.self, Run.self, List.self, Delete.self]) +} diff --git a/Sources/tart/Commands/Run.swift b/Sources/tart/Commands/Run.swift new file mode 100644 index 0000000..7340384 --- /dev/null +++ b/Sources/tart/Commands/Run.swift @@ -0,0 +1,65 @@ +import ArgumentParser +import Dispatch +import SwiftUI +import Virtualization + +var vm: VM? + +struct Run: ParsableCommand { + static var configuration = CommandConfiguration(abstract: "Run a VM") + + @Argument(help: "VM name") + var name: String + + @Flag var noGraphics: Bool = false + + func run() throws { + let vmDir = try VMStorage().read(name) + vm = try VM(vmDir: vmDir) + + Task { + do { + try await vm!.run() + + Foundation.exit(0) + } catch { + print(error) + + Foundation.exit(1) + } + } + + if noGraphics { + dispatchMain() + } else { + // UI mumbo-jumbo + let nsApp = NSApplication.shared + nsApp.setActivationPolicy(.regular) + nsApp.activate(ignoringOtherApps: true) + + struct MainApp : App { + var body: some Scene { + WindowGroup { + VMView(vm: vm!) + } + } + } + + MainApp.main() + } + } +} + +struct VMView: NSViewRepresentable { + typealias NSViewType = VZVirtualMachineView + + @ObservedObject var vm: VM + + func makeNSView(context: Context) -> NSViewType { + VZVirtualMachineView() + } + + func updateNSView(_ nsView: NSViewType, context: Context) { + nsView.virtualMachine = vm.virtualMachine + } +} diff --git a/Sources/tart/VM.swift b/Sources/tart/VM.swift new file mode 100644 index 0000000..bb50cc8 --- /dev/null +++ b/Sources/tart/VM.swift @@ -0,0 +1,185 @@ +import Foundation +import Virtualization + +struct UnsupportedRestoreImageError: Error {} +struct NoMainScreenFoundError: Error {} + +class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { + // Virtualization.Framework's virtual machine + @Published var virtualMachine: VZVirtualMachine + + // Semaphore used to communicate with the VZVirtualMachineDelegate + var sema = DispatchSemaphore(value: 0) + + // VM's config + var vmConfig: VMConfig + + init(vmDir: VMDirectory) throws { + let auxStorage = VZMacAuxiliaryStorage(contentsOf: vmDir.nvramURL) + + self.vmConfig = try VMConfig.init(fromURL: vmDir.configURL) + + let configuration = try VM.craftConfiguration( + diskURL: vmDir.diskURL, + ecid: vmConfig.ecid, + auxStorage: auxStorage, + hardwareModel: vmConfig.hardwareModel, + cpuCount: vmConfig.cpuCount, + memorySize: vmConfig.memorySize + ) + + self.virtualMachine = VZVirtualMachine(configuration: configuration) + + super.init() + + self.virtualMachine.delegate = self + } + + static func retrieveLatestIPSW() async throws -> URL { + let image = try await withCheckedThrowingContinuation { continuation in + VZMacOSRestoreImage.fetchLatestSupported() { result in continuation.resume(with: result) } + } + + let (downloadedImageURL, _) = try await URLSession.shared.download(from: image.url, delegate: nil) + + return downloadedImageURL + } + + init(vmDir: VMDirectory, ipswURL: URL?, diskSize: UInt64 = 32 * 1024 * 1024 * 1024) async throws { + let ipswURL = ipswURL != nil ? ipswURL! : try await VM.retrieveLatestIPSW(); + + // 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 { throw UnsupportedRestoreImageError() } + + // Create NVRAM + let auxStorage = try VZMacAuxiliaryStorage(creatingStorageAt: vmDir.nvramURL, hardwareModel: requirements.hardwareModel) + + // Create disk + FileManager.default.createFile(atPath: vmDir.diskURL.path, contents: nil, attributes: nil) + let diskFileHandle = try FileHandle.init(forWritingTo: vmDir.diskURL) + try diskFileHandle.truncate(atOffset: diskSize) + try diskFileHandle.close() + + // Create config + self.vmConfig = VMConfig( + hardwareModel: requirements.hardwareModel, + cpuCount: requirements.minimumSupportedCPUCount, + memorySize: requirements.minimumSupportedMemorySize + ) + try self.vmConfig.save(toURL: vmDir.configURL) + + // Initialize the virtual machine and its configuration + let configuration = try VM.craftConfiguration( + diskURL: vmDir.diskURL, + ecid: self.vmConfig.ecid, + auxStorage: auxStorage, + hardwareModel: requirements.hardwareModel, + cpuCount: self.vmConfig.cpuCount, + memorySize: self.vmConfig.memorySize + ) + self.virtualMachine = VZVirtualMachine(configuration: configuration) + + super.init() + + self.virtualMachine.delegate = self + + // Run automated installation + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + DispatchQueue.main.async { + let installer = VZMacOSInstaller(virtualMachine: self.virtualMachine, restoringFromImageAt: ipswURL) + + installer.install { result in continuation.resume(with: result) } + } + } + } + + func run() async throws { + try await withCheckedThrowingContinuation { continuation in + DispatchQueue.main.async { + self.virtualMachine.start(completionHandler: { result in + continuation.resume(with: result) + }) + } + } + + sema.wait() + } + + static func craftConfiguration( + diskURL: URL, + ecid: VZMacMachineIdentifier, + auxStorage: VZMacAuxiliaryStorage, + hardwareModel: VZMacHardwareModel, + cpuCount: Int, + memorySize: UInt64 + ) throws -> VZVirtualMachineConfiguration { + let configuration = VZVirtualMachineConfiguration() + + // Boot loader + configuration.bootLoader = VZMacOSBootLoader() + + // CPU and memory + configuration.cpuCount = cpuCount + configuration.memorySize = memorySize + + // Platform + let platform = VZMacPlatformConfiguration() + + platform.machineIdentifier = ecid + platform.auxiliaryStorage = auxStorage + platform.hardwareModel = hardwareModel + + configuration.platform = platform + + // Display + let graphicsDeviceConfiguration = VZMacGraphicsDeviceConfiguration() + guard let mainScreen = NSScreen.main else { + throw NoMainScreenFoundError() + } + graphicsDeviceConfiguration.displays = [ + VZMacGraphicsDisplayConfiguration(for: mainScreen, sizeInPoints: mainScreen.frame.size) + ] + configuration.graphicsDevices = [graphicsDeviceConfiguration] + + // Keyboard and mouse + configuration.keyboards = [VZUSBKeyboardConfiguration()] + configuration.pointingDevices = [VZUSBScreenCoordinatePointingDeviceConfiguration()] + + // Networking + let vio = VZVirtioNetworkDeviceConfiguration() + vio.attachment = VZNATNetworkDeviceAttachment() + configuration.networkDevices = [vio] + + // Storage + let attachment = try VZDiskImageStorageDeviceAttachment(url: diskURL, readOnly: false) + let storage = VZVirtioBlockDeviceConfiguration(attachment: attachment) + configuration.storageDevices = [storage] + + // Entropy + configuration.entropyDevices = [VZVirtioEntropyDeviceConfiguration()] + + try configuration.validate() + + return configuration + } + + func guestDidStop(_ virtualMachine: VZVirtualMachine) { + print("guest has stopped the virtual machine") + sema.signal() + } + + func virtualMachine(_ virtualMachine: VZVirtualMachine, didStopWithError error: Error) { + print("guest has stopped the virtual machine due to error") + sema.signal() + } + + func virtualMachine(_ virtualMachine: VZVirtualMachine, networkDevice: VZNetworkDevice, attachmentWasDisconnectedWithError error: Error) { + print("virtual machine's network attachment has been disconnected") + sema.signal() + } +} diff --git a/Sources/tart/VMConfig.swift b/Sources/tart/VMConfig.swift new file mode 100644 index 0000000..a1cef27 --- /dev/null +++ b/Sources/tart/VMConfig.swift @@ -0,0 +1,77 @@ +import Virtualization + +enum CodingKeys: String, CodingKey { + case version + case ecid + case hardwareModel + case cpuCount + case memorySize +} + +struct VMConfig: Encodable, Decodable { + var version: Int = 0 + var ecid: VZMacMachineIdentifier + var hardwareModel: VZMacHardwareModel + var cpuCount: Int + var memorySize: UInt64 + + init(ecid: VZMacMachineIdentifier = VZMacMachineIdentifier(), hardwareModel: VZMacHardwareModel, cpuCount: Int, memorySize: UInt64) { + self.ecid = ecid + self.hardwareModel = hardwareModel + self.cpuCount = cpuCount + self.memorySize = memorySize + } + + init(fromURL: URL) throws { + let jsonConfigData = try FileHandle.init(forReadingFrom: fromURL).readToEnd()! + self = try JSONDecoder().decode(VMConfig.self, from: jsonConfigData) + } + + func save(toURL: URL) throws { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + try encoder.encode(self).write(to: toURL) + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.version = try container.decode(Int.self, forKey: .version) + + 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 + + self.cpuCount = try container.decode(Int.self, forKey: .cpuCount) + + self.memorySize = try container.decode(UInt64.self, forKey: .memorySize) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.version, forKey: .version) + try container.encode(self.ecid.dataRepresentation.base64EncodedString(), forKey: .ecid) + try container.encode(self.hardwareModel.dataRepresentation.base64EncodedString(), forKey: .hardwareModel) + try container.encode(self.cpuCount, forKey: .cpuCount) + try container.encode(self.memorySize, forKey: .memorySize) + } +} diff --git a/Sources/tart/VMDirectory.swift b/Sources/tart/VMDirectory.swift new file mode 100644 index 0000000..ff9de0f --- /dev/null +++ b/Sources/tart/VMDirectory.swift @@ -0,0 +1,32 @@ +import Foundation + +struct UninitializedVMDirectoryError: Error {} +struct AlreadyInitializedVMDirectoryError: Error {} + +struct VMDirectory { + var baseURL: URL + + var configURL: URL { self.baseURL.appendingPathComponent("config.json") } + var diskURL: URL { self.baseURL.appendingPathComponent("disk.bin") } + var nvramURL: URL { self.baseURL.appendingPathComponent("nvram.bin") } + + var initialized: Bool { + FileManager.default.fileExists(atPath: configURL.path) && + FileManager.default.fileExists(atPath: diskURL.path) && + FileManager.default.fileExists(atPath: nvramURL.path) + } + + func initialize() throws { + if initialized { + throw AlreadyInitializedVMDirectoryError() + } + + try FileManager.default.createDirectory(at: baseURL, withIntermediateDirectories: true, attributes: nil) + } + + func validate() throws { + if !initialized { + throw UninitializedVMDirectoryError() + } + } +} diff --git a/Sources/tart/VMStorage.swift b/Sources/tart/VMStorage.swift new file mode 100644 index 0000000..d11d19c --- /dev/null +++ b/Sources/tart/VMStorage.swift @@ -0,0 +1,57 @@ +import Foundation + +struct VMStorage { + var homeDir: URL + var tartDir: URL + var vmsDir: URL + + init() { + homeDir = FileManager.default.homeDirectoryForCurrentUser + tartDir = homeDir.appendingPathComponent(".tart", isDirectory: true) + vmsDir = tartDir.appendingPathComponent("vms", isDirectory: true) + } + + func create(_ name: String) throws -> VMDirectory { + let vmDir = VMDirectory(baseURL: vmURL(name)) + + try vmDir.initialize() + + return vmDir + } + + func read(_ name: String) throws -> VMDirectory { + let vmDir = VMDirectory(baseURL: vmURL(name)) + + try vmDir.validate() + + return vmDir + } + + func delete(_ name: String) throws { + try FileManager.default.removeItem(at: vmURL(name)) + } + + func list() throws -> [URL] { + do { + return try FileManager.default.contentsOfDirectory(at: vmsDir, + includingPropertiesForKeys: [.isDirectoryKey], + options: .skipsSubdirectoryDescendants) + } catch { + if error.isFileNotFound() { + return [] + } + + throw error + } + } + + private func vmURL(_ name: String) -> URL { + return URL.init(fileURLWithPath: name, isDirectory: true, relativeTo: vmsDir) + } +} + +extension Error { + func isFileNotFound() -> Bool { + return (self as NSError).code == NSFileReadNoSuchFileError + } +} diff --git a/Sources/tart/main.swift b/Sources/tart/main.swift new file mode 100644 index 0000000..480b247 --- /dev/null +++ b/Sources/tart/main.swift @@ -0,0 +1,3 @@ +import SwiftUI + +Root.main() diff --git a/tart.xcodeproj/project.pbxproj b/tart.xcodeproj/project.pbxproj new file mode 100644 index 0000000..2f95bcb --- /dev/null +++ b/tart.xcodeproj/project.pbxproj @@ -0,0 +1,452 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 440478FB27B134E10028EFB8 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 440478FA27B134E10028EFB8 /* ArgumentParser */; }; + 440478FD27B1352C0028EFB8 /* Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440478FC27B1352C0028EFB8 /* Create.swift */; }; + 440478FF27B13D590028EFB8 /* Run.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440478FE27B13D590028EFB8 /* Run.swift */; }; + 4473E1E527A94E28000850C3 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4473E1E427A94E28000850C3 /* main.swift */; }; + 44FDBB3427B4177C005A201B /* VMStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FDBB3327B4177C005A201B /* VMStorage.swift */; }; + 44FDBB4227B43E6D005A201B /* VM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FDBB4127B43E6D005A201B /* VM.swift */; }; + 44FDBB4427B4445E005A201B /* Root.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FDBB4327B4445E005A201B /* Root.swift */; }; + 44FDBB4627B44B35005A201B /* VMConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FDBB4527B44B35005A201B /* VMConfig.swift */; }; + 44FDBB4827B45EA1005A201B /* Delete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FDBB4727B45EA1005A201B /* Delete.swift */; }; + 44FDBB4A27B45F6F005A201B /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FDBB4927B45F6F005A201B /* List.swift */; }; + 44FDBB4C27B69515005A201B /* VMDirectory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FDBB4B27B69515005A201B /* VMDirectory.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 4473E1DF27A94E27000850C3 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 440478FC27B1352C0028EFB8 /* Create.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Create.swift; sourceTree = ""; }; + 440478FE27B13D590028EFB8 /* Run.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Run.swift; sourceTree = ""; }; + 4473E1E127A94E27000850C3 /* tart */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = tart; sourceTree = BUILT_PRODUCTS_DIR; }; + 4473E1E427A94E28000850C3 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 44FDBB3327B4177C005A201B /* VMStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMStorage.swift; sourceTree = ""; }; + 44FDBB3927B43CCF005A201B /* tart-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "tart-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 44FDBB4127B43E6D005A201B /* VM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VM.swift; sourceTree = ""; }; + 44FDBB4327B4445E005A201B /* Root.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Root.swift; sourceTree = ""; }; + 44FDBB4527B44B35005A201B /* VMConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMConfig.swift; sourceTree = ""; }; + 44FDBB4727B45EA1005A201B /* Delete.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delete.swift; sourceTree = ""; }; + 44FDBB4927B45F6F005A201B /* List.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = List.swift; sourceTree = ""; }; + 44FDBB4B27B69515005A201B /* VMDirectory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDirectory.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4473E1DE27A94E27000850C3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 440478FB27B134E10028EFB8 /* ArgumentParser in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 44FDBB3627B43CCF005A201B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4473E1D827A94E27000850C3 = { + isa = PBXGroup; + children = ( + 4473E1E327A94E27000850C3 /* Sources */, + 4473E1E227A94E27000850C3 /* Products */, + ); + sourceTree = ""; + }; + 4473E1E227A94E27000850C3 /* Products */ = { + isa = PBXGroup; + children = ( + 4473E1E127A94E27000850C3 /* tart */, + 44FDBB3927B43CCF005A201B /* tart-tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 4473E1E327A94E27000850C3 /* Sources */ = { + isa = PBXGroup; + children = ( + 44FDBB4F27B6A4B6005A201B /* tart */, + ); + path = Sources; + sourceTree = ""; + }; + 44FDBB4027B43DCB005A201B /* Commands */ = { + isa = PBXGroup; + children = ( + 440478FC27B1352C0028EFB8 /* Create.swift */, + 440478FE27B13D590028EFB8 /* Run.swift */, + 44FDBB4327B4445E005A201B /* Root.swift */, + 44FDBB4727B45EA1005A201B /* Delete.swift */, + 44FDBB4927B45F6F005A201B /* List.swift */, + ); + path = Commands; + sourceTree = ""; + }; + 44FDBB4F27B6A4B6005A201B /* tart */ = { + isa = PBXGroup; + children = ( + 44FDBB4027B43DCB005A201B /* Commands */, + 4473E1E427A94E28000850C3 /* main.swift */, + 44FDBB3327B4177C005A201B /* VMStorage.swift */, + 44FDBB4127B43E6D005A201B /* VM.swift */, + 44FDBB4527B44B35005A201B /* VMConfig.swift */, + 44FDBB4B27B69515005A201B /* VMDirectory.swift */, + ); + path = tart; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 4473E1E027A94E27000850C3 /* tart */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4473E1E827A94E28000850C3 /* Build configuration list for PBXNativeTarget "tart" */; + buildPhases = ( + 4473E1DD27A94E27000850C3 /* Sources */, + 4473E1DE27A94E27000850C3 /* Frameworks */, + 4473E1DF27A94E27000850C3 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = tart; + packageProductDependencies = ( + 440478FA27B134E10028EFB8 /* ArgumentParser */, + ); + productName = tart; + productReference = 4473E1E127A94E27000850C3 /* tart */; + productType = "com.apple.product-type.tool"; + }; + 44FDBB3827B43CCF005A201B /* tart-tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 44FDBB3D27B43CCF005A201B /* Build configuration list for PBXNativeTarget "tart-tests" */; + buildPhases = ( + 44FDBB3527B43CCF005A201B /* Sources */, + 44FDBB3627B43CCF005A201B /* Frameworks */, + 44FDBB3727B43CCF005A201B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "tart-tests"; + productName = "tart-tests"; + productReference = 44FDBB3927B43CCF005A201B /* tart-tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4473E1D927A94E27000850C3 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1320; + LastUpgradeCheck = 1320; + TargetAttributes = { + 4473E1E027A94E27000850C3 = { + CreatedOnToolsVersion = 13.2.1; + }; + 44FDBB3827B43CCF005A201B = { + CreatedOnToolsVersion = 13.2.1; + }; + }; + }; + buildConfigurationList = 4473E1DC27A94E27000850C3 /* Build configuration list for PBXProject "tart" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 4473E1D827A94E27000850C3; + packageReferences = ( + 440478F927B134E10028EFB8 /* XCRemoteSwiftPackageReference "swift-argument-parser" */, + ); + productRefGroup = 4473E1E227A94E27000850C3 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4473E1E027A94E27000850C3 /* tart */, + 44FDBB3827B43CCF005A201B /* tart-tests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 44FDBB3727B43CCF005A201B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4473E1DD27A94E27000850C3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 440478FF27B13D590028EFB8 /* Run.swift in Sources */, + 4473E1E527A94E28000850C3 /* main.swift in Sources */, + 44FDBB4827B45EA1005A201B /* Delete.swift in Sources */, + 44FDBB3427B4177C005A201B /* VMStorage.swift in Sources */, + 44FDBB4627B44B35005A201B /* VMConfig.swift in Sources */, + 44FDBB4227B43E6D005A201B /* VM.swift in Sources */, + 44FDBB4C27B69515005A201B /* VMDirectory.swift in Sources */, + 440478FD27B1352C0028EFB8 /* Create.swift in Sources */, + 44FDBB4427B4445E005A201B /* Root.swift in Sources */, + 44FDBB4A27B45F6F005A201B /* List.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 44FDBB3527B43CCF005A201B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 4473E1E627A94E28000850C3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 4473E1E727A94E28000850C3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 4473E1E927A94E28000850C3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = arm64; + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 4473E1EA27A94E28000850C3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = arm64; + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 44FDBB3E27B43CCF005A201B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "org.cirruslabs.tart-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 44FDBB3F27B43CCF005A201B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "org.cirruslabs.tart-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4473E1DC27A94E27000850C3 /* Build configuration list for PBXProject "tart" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4473E1E627A94E28000850C3 /* Debug */, + 4473E1E727A94E28000850C3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4473E1E827A94E28000850C3 /* Build configuration list for PBXNativeTarget "tart" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4473E1E927A94E28000850C3 /* Debug */, + 4473E1EA27A94E28000850C3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 44FDBB3D27B43CCF005A201B /* Build configuration list for PBXNativeTarget "tart-tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 44FDBB3E27B43CCF005A201B /* Debug */, + 44FDBB3F27B43CCF005A201B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 440478F927B134E10028EFB8 /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-argument-parser.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 440478FA27B134E10028EFB8 /* ArgumentParser */ = { + isa = XCSwiftPackageProductDependency; + package = 440478F927B134E10028EFB8 /* XCRemoteSwiftPackageReference "swift-argument-parser" */; + productName = ArgumentParser; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 4473E1D927A94E27000850C3 /* Project object */; +} diff --git a/tart.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/tart.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/tart.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/tart.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/tart.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/tart.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/tart.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/tart.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..9004fb5 --- /dev/null +++ b/tart.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser.git", + "state": { + "branch": null, + "revision": "e394bf350e38cb100b6bc4172834770ede1b7232", + "version": "1.0.3" + } + } + ] + }, + "version": 1 +}