mirror of
https://github.com/cirruslabs/tart.git
synced 2026-10-09 15:35:31 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e35c13425e | ||
|
|
0debec1266 | ||
|
|
294c5fc5e5 | ||
|
|
99777b6740 | ||
|
|
a2972aa4d9 |
@@ -29,6 +29,7 @@ Table of Contents
|
|||||||
|
|
||||||
1. Code should follow camel case
|
1. Code should follow camel case
|
||||||
2. Code should follow [SwiftFormat](https://github.com/nicklockwood/SwiftFormat#swift-package-manager-plugin) guidelines. You can auto-format the code by running the following command:
|
2. Code should follow [SwiftFormat](https://github.com/nicklockwood/SwiftFormat#swift-package-manager-plugin) guidelines. You can auto-format the code by running the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
swift package plugin --allow-writing-to-package-directory swiftformat --cache ignore .
|
swift package plugin --allow-writing-to-package-directory swiftformat --cache ignore .
|
||||||
```
|
```
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "c5371137580239f6928cf64425e754e77680bf24430b50f02dad5558c23f68b0",
|
"originHash" : "668bad809d4882f75f097e66a12a6dbc8e61ec998f1800a7e09439c854fadda1",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "antlr4",
|
"identity" : "antlr4",
|
||||||
@@ -78,8 +78,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-argument-parser",
|
"location" : "https://github.com/apple/swift-argument-parser",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "41982a3656a71c768319979febd796c6fd111d5c",
|
"revision" : "309a47b2b1d9b5e991f36961c983ecec72275be3",
|
||||||
"version" : "1.5.0"
|
"version" : "1.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ let package = Package(
|
|||||||
.executable(name: "tart", targets: ["tart"])
|
.executable(name: "tart", targets: ["tart"])
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.1"),
|
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.6.1"),
|
||||||
.package(url: "https://github.com/mhdhejazi/Dynamic", branch: "master"),
|
.package(url: "https://github.com/mhdhejazi/Dynamic", branch: "master"),
|
||||||
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"),
|
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"),
|
||||||
.package(url: "https://github.com/malcommac/SwiftDate", from: "7.0.0"),
|
.package(url: "https://github.com/malcommac/SwiftDate", from: "7.0.0"),
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ Try running a Tart VM on your Apple Silicon device running macOS 13.0 (Ventura)
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install cirruslabs/cli/tart
|
brew install cirruslabs/cli/tart
|
||||||
tart clone ghcr.io/cirruslabs/macos-sequoia-base:latest sequoia-base
|
tart clone ghcr.io/cirruslabs/macos-tahoe-base:latest tahoe-base
|
||||||
tart run sequoia-base
|
tart run tahoe-base
|
||||||
```
|
```
|
||||||
|
|
||||||
Please check the [official documentation](https://tart.run) for more information and/or feel free to use [discussions](https://github.com/cirruslabs/tart/discussions)
|
Please check the [official documentation](https://tart.run) for more information and/or feel free to use [discussions](https://github.com/cirruslabs/tart/discussions)
|
||||||
|
|||||||
@@ -100,28 +100,61 @@ struct Exec: AsyncParsableCommand {
|
|||||||
try await withThrowingTaskGroup { group in
|
try await withThrowingTaskGroup { group in
|
||||||
// Stream host's standard input if interactive mode is enabled
|
// Stream host's standard input if interactive mode is enabled
|
||||||
if interactive {
|
if interactive {
|
||||||
let stdinStream = AsyncStream<Data> { continuation in
|
let stdinStream = AsyncThrowingStream<Data, Error> { continuation in
|
||||||
let handle = FileHandle.standardInput
|
let handle = FileHandle.standardInput
|
||||||
|
|
||||||
handle.readabilityHandler = { handle in
|
if isRegularFile(handle.fileDescriptor) {
|
||||||
let data = handle.availableData
|
// Standard input can be a regular file when input redirection (<) is used,
|
||||||
|
// in which case the handle won't receive any new readability events, so we
|
||||||
|
// just read the file normally here in chunks and consider done with it
|
||||||
|
//
|
||||||
|
// Ideally this is best handled by using non-blocking I/O, but Swift's
|
||||||
|
// standard library only offers inefficient bytes[1] property and SwiftNIO's
|
||||||
|
// NIOFileSystem doesn't seem to support opening raw file descriptors.
|
||||||
|
//
|
||||||
|
// [1]: https://developer.apple.com/documentation/foundation/filehandle/bytes
|
||||||
|
while true {
|
||||||
|
do {
|
||||||
|
let data = try handle.read(upToCount: 64 * 1024)
|
||||||
|
if let data = data {
|
||||||
|
continuation.yield(data)
|
||||||
|
} else {
|
||||||
|
continuation.finish()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (let error) {
|
||||||
|
continuation.finish(throwing: error)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handle.readabilityHandler = { handle in
|
||||||
|
let data = handle.availableData
|
||||||
|
|
||||||
continuation.yield(data)
|
if data.isEmpty {
|
||||||
|
continuation.finish()
|
||||||
if data.isEmpty {
|
} else {
|
||||||
continuation.finish()
|
continuation.yield(data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group.addTask {
|
group.addTask {
|
||||||
for await stdinData in stdinStream {
|
for try await stdinData in stdinStream {
|
||||||
try await execCall.requestStream.send(.with {
|
try await execCall.requestStream.send(.with {
|
||||||
$0.type = .standardInput(.with {
|
$0.type = .standardInput(.with {
|
||||||
$0.data = stdinData
|
$0.data = stdinData
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Signal EOF as we're done reading standard input
|
||||||
|
try await execCall.requestStream.send(.with {
|
||||||
|
$0.type = .standardInput(.with {
|
||||||
|
$0.data = Data()
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,3 +211,13 @@ struct Exec: AsyncParsableCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func isRegularFile(_ fileDescriptor: Int32) -> Bool {
|
||||||
|
var stat = stat()
|
||||||
|
|
||||||
|
if fstat(fileDescriptor, &stat) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return (stat.st_mode & S_IFMT) == S_IFREG
|
||||||
|
}
|
||||||
|
|||||||
@@ -116,10 +116,6 @@ struct Run: AsyncParsableCommand {
|
|||||||
#endif
|
#endif
|
||||||
var vncExperimental: Bool = false
|
var vncExperimental: Bool = false
|
||||||
|
|
||||||
// Note that the valueName here should really be "path[:options]" instead of just "path",
|
|
||||||
// see ArgumentParser issue[1] for more details.
|
|
||||||
//
|
|
||||||
// [1]: https://github.com/apple/swift-argument-parser/issues/761
|
|
||||||
@Option(help: ArgumentHelp("""
|
@Option(help: ArgumentHelp("""
|
||||||
Additional disk attachments with an optional read-only and synchronization options in the form of path[:options] (e.g. --disk="disk.bin", --disk="ubuntu.iso:ro", --disk="/dev/disk0", --disk "ghcr.io/cirruslabs/xcode:16.0:ro" or --disk="nbd://localhost:10809/myDisk:sync=none")
|
Additional disk attachments with an optional read-only and synchronization options in the form of path[:options] (e.g. --disk="disk.bin", --disk="ubuntu.iso:ro", --disk="/dev/disk0", --disk "ghcr.io/cirruslabs/xcode:16.0:ro" or --disk="nbd://localhost:10809/myDisk:sync=none")
|
||||||
""", discussion: """
|
""", discussion: """
|
||||||
@@ -143,7 +139,7 @@ struct Run: AsyncParsableCommand {
|
|||||||
To work around this pass TART_HOME explicitly:
|
To work around this pass TART_HOME explicitly:
|
||||||
|
|
||||||
sudo TART_HOME="$HOME/.tart" tart run sequoia --disk=/dev/disk0
|
sudo TART_HOME="$HOME/.tart" tart run sequoia --disk=/dev/disk0
|
||||||
""", valueName: "path"), completion: .file())
|
""", valueName: "path[:options]"), completion: .file())
|
||||||
var disk: [String] = []
|
var disk: [String] = []
|
||||||
|
|
||||||
#if arch(arm64)
|
#if arch(arm64)
|
||||||
@@ -162,10 +158,6 @@ struct Run: AsyncParsableCommand {
|
|||||||
#endif
|
#endif
|
||||||
var rosettaTag: String?
|
var rosettaTag: String?
|
||||||
|
|
||||||
// Note that the valueName here should really be "[name:]path[:options]" instead of just "path",
|
|
||||||
// see ArgumentParser issue[1] for more details.
|
|
||||||
//
|
|
||||||
// [1]: https://github.com/apple/swift-argument-parser/issues/761
|
|
||||||
@Option(help: ArgumentHelp("Additional directory shares with an optional read-only and mount tag options in the form of [name:]path[:options] (e.g. --dir=\"~/src/build\" or --dir=\"~/src/sources:ro\")", discussion: """
|
@Option(help: ArgumentHelp("Additional directory shares with an optional read-only and mount tag options in the form of [name:]path[:options] (e.g. --dir=\"~/src/build\" or --dir=\"~/src/sources:ro\")", discussion: """
|
||||||
Requires host to be macOS 13.0 (Ventura) or newer. macOS guests must be running macOS 13.0 (Ventura) or newer too.
|
Requires host to be macOS 13.0 (Ventura) or newer. macOS guests must be running macOS 13.0 (Ventura) or newer too.
|
||||||
|
|
||||||
@@ -178,7 +170,7 @@ struct Run: AsyncParsableCommand {
|
|||||||
Mount tag can be overridden by appending tag property to the directory share (e.g. --dir=\"~/src/build:tag=build\" or --dir=\"~/src/build:ro,tag=build\"). Then it can be mounted via "mount_virtiofs build ~/build" inside guest macOS and "mount -t virtiofs build ~/build" inside guest Linux.
|
Mount tag can be overridden by appending tag property to the directory share (e.g. --dir=\"~/src/build:tag=build\" or --dir=\"~/src/build:ro,tag=build\"). Then it can be mounted via "mount_virtiofs build ~/build" inside guest macOS and "mount -t virtiofs build ~/build" inside guest Linux.
|
||||||
|
|
||||||
In case of passing multiple directories per mount tag it is required to prefix them with names e.g. --dir=\"build:~/src/build\" --dir=\"sources:~/src/sources:ro\". These names will be used as directory names under the mounting point inside guests. For the example above it will be "/Volumes/My Shared Files/build" and "/Volumes/My Shared Files/sources" respectively.
|
In case of passing multiple directories per mount tag it is required to prefix them with names e.g. --dir=\"build:~/src/build\" --dir=\"sources:~/src/sources:ro\". These names will be used as directory names under the mounting point inside guests. For the example above it will be "/Volumes/My Shared Files/build" and "/Volumes/My Shared Files/sources" respectively.
|
||||||
""", valueName: "path"), completion: .directory)
|
""", valueName: "[name:]path[:options]"), completion: .directory)
|
||||||
var dir: [String] = []
|
var dir: [String] = []
|
||||||
|
|
||||||
@Flag(help: ArgumentHelp("Enable nested virtualization if possible"))
|
@Flag(help: ArgumentHelp("Enable nested virtualization if possible"))
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ fileprivate func normalizeName(_ name: String) -> String {
|
|||||||
return name.replacingOccurrences(of: ":", with: "\\:")
|
return name.replacingOccurrences(of: ":", with: "\\:")
|
||||||
}
|
}
|
||||||
|
|
||||||
func completeMachines(_ arguments: [String]) -> [String] {
|
func completeMachines(_ arguments: [String], _ argumentIdx: Int, _ argumentPrefix: String) -> [String] {
|
||||||
let localVMs = (try? VMStorageLocal().list().map { name, _ in
|
let localVMs = (try? VMStorageLocal().list().map { name, _ in
|
||||||
normalizeName(name)
|
normalizeName(name)
|
||||||
}) ?? []
|
}) ?? []
|
||||||
@@ -15,12 +15,12 @@ func completeMachines(_ arguments: [String]) -> [String] {
|
|||||||
return (localVMs + ociVMs)
|
return (localVMs + ociVMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func completeLocalMachines(_ arguments: [String]) -> [String] {
|
func completeLocalMachines(_ arguments: [String], _ argumentIdx: Int, _ argumentPrefix: String) -> [String] {
|
||||||
let localVMs = (try? VMStorageLocal().list()) ?? []
|
let localVMs = (try? VMStorageLocal().list()) ?? []
|
||||||
return localVMs.map { name, _ in normalizeName(name) }
|
return localVMs.map { name, _ in normalizeName(name) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func completeRunningMachines(_ arguments: [String]) -> [String] {
|
func completeRunningMachines(_ arguments: [String], _ argumentIdx: Int, _ argumentPrefix: String) -> [String] {
|
||||||
let localVMs = (try? VMStorageLocal().list()) ?? []
|
let localVMs = (try? VMStorageLocal().list()) ?? []
|
||||||
return localVMs
|
return localVMs
|
||||||
.filter { _, vmDir in (try? vmDir.state() == .Running) ?? false}
|
.filter { _, vmDir in (try? vmDir.state() == .Running) ?? false}
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ class VMStorageOCI: PrunableStorage {
|
|||||||
return digest
|
return digest
|
||||||
}
|
}
|
||||||
|
|
||||||
func open(_ name: RemoteName) throws -> VMDirectory {
|
func open(_ name: RemoteName, _ accessDate: Date = Date()) throws -> VMDirectory {
|
||||||
let vmDir = VMDirectory(baseURL: vmURL(name))
|
let vmDir = VMDirectory(baseURL: vmURL(name))
|
||||||
|
|
||||||
try vmDir.validate(userFriendlyName: name.description)
|
try vmDir.validate(userFriendlyName: name.description)
|
||||||
|
|
||||||
try vmDir.baseURL.updateAccessDate()
|
try vmDir.baseURL.updateAccessDate(accessDate)
|
||||||
|
|
||||||
return vmDir
|
return vmDir
|
||||||
}
|
}
|
||||||
@@ -180,6 +180,10 @@ class VMStorageOCI: PrunableStorage {
|
|||||||
let transaction = SentrySDK.startTransaction(name: name.description, operation: "pull", bindToScope: true)
|
let transaction = SentrySDK.startTransaction(name: name.description, operation: "pull", bindToScope: true)
|
||||||
let tmpVMDir = try VMDirectory.temporaryDeterministic(key: name.description)
|
let tmpVMDir = try VMDirectory.temporaryDeterministic(key: name.description)
|
||||||
|
|
||||||
|
// Open an existing VM directory corresponding to this name, if any,
|
||||||
|
// marking it as outdated to speed up the garbage collection process
|
||||||
|
_ = try? open(name, Date(timeIntervalSince1970: 0))
|
||||||
|
|
||||||
// Lock the temporary VM directory to prevent it's garbage collection
|
// Lock the temporary VM directory to prevent it's garbage collection
|
||||||
let tmpVMDirLock = try FileLock(lockURL: tmpVMDir.baseURL)
|
let tmpVMDirLock = try FileLock(lockURL: tmpVMDir.baseURL)
|
||||||
try tmpVMDirLock.lock()
|
try tmpVMDirLock.lock()
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ plugins:
|
|||||||
match_path: blog/posts/.*
|
match_path: blog/posts/.*
|
||||||
date_from_meta:
|
date_from_meta:
|
||||||
as_creation: date
|
as_creation: date
|
||||||
|
abstract_chars_count: -1
|
||||||
- social:
|
- social:
|
||||||
cards_layout_dir: docs/layouts
|
cards_layout_dir: docs/layouts
|
||||||
cards_layout: custom
|
cards_layout: custom
|
||||||
|
|||||||
Reference in New Issue
Block a user