Skip to content

Commit

Permalink
Merge pull request #6 from Alexander-Ignition/swift_concurrency
Browse files Browse the repository at this point in the history
Swift Concurrency
  • Loading branch information
Alexander-Ignition authored Sep 4, 2022
2 parents fa185bc + a3dbbb0 commit 24ae0bb
Show file tree
Hide file tree
Showing 26 changed files with 1,051 additions and 485 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "XSTooling",
platforms: [
.macOS(.v10_13),
.macOS(.v10_15),
],
products: [
.library(
Expand Down
52 changes: 0 additions & 52 deletions Playgrounds/README.playground/Contents.swift

This file was deleted.

4 changes: 0 additions & 4 deletions Playgrounds/README.playground/contents.xcplayground

This file was deleted.

104 changes: 92 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Supported tools:
To use the `XSTooling` library in a SwiftPM project, add the following line to the dependencies in your `Package.swift` file:

```swift
.package(url: "https://github.com/Alexander-Ignition/XSTooling", from: "0.0.1"),
.package(url: "https://github.com/Alexander-Ignition/XSTooling", from: "0.0.2"),
```

Include `"XSTooling"` as a dependency for your executable target:
Expand All @@ -34,25 +34,105 @@ Finally, add `import XSTooling` to your source code.
```swift
import XSTooling

let shell = Shell.sh
let sh = Shell.default
try await sh("swift build").run()
```

## Shell

Shell command can be `run` or `read`.

Read the shell command output.

```swift
let version = try await sh("xcodebuild -version").read().string
```

Run shell command with redirection to stdout and stderr.

```swift
try await sh("ls -al").run()
```

Redirection can be configured, for example, to write to a log file.

```swift
let url = URL(fileURLWithPath: "logs.txt", isDirectory: false)
FileManager.default.createFile(atPath: url.path, contents: nil)
let file = try FileHandle(forWritingTo: url)
defer {
try! file.close()
}
try await sh("swift build").run(.output(file).error(file))
```

`Shell` has predefined instances.

```swift
Shell.default
Shell.sh
Shell.bash
Shell.zsh
```

Conceptually, a `Shell` is a wrapper over a `ProcessCommand`.

let path = try shell("pwd").string
let files = try shell("ls").lines
- `sh.command` contains common parameters for all commands.
- `sh("ls")` each call to this method returned a copy of the `ProcessCommand` with additional arguments

```swift
sh = Shell.default
sh.command // ProcessCommand
sh.command.environment // [String: String]?
sh.command.currentDirectoryURL // URL?
sh("ls") // ProcessCommand
```

## ProcessCommand

The main component is `ProcessCommand`. Which can configure and run a subprocess. The `read` and `run` methods are called on the `ProcessCommand`.

```swift
let command = ProcessCommand(
path: "/usr/bin/xcodebuild",
arguments: ["-version"]
)
try await command.run()
```

The location of the executable file is not always known. To do this, there is a `find` method that searches for an executable file by name.

```swift
try await ProcessCommand
.find("xcodebuild")!
.appending(argument: "-version")
.run()
```

## simctl

Simulator control tool.
By analogy with Shell, you can make other wrappers over the `ProcessCommand`

`Simctl` (Simulator control tool) is an example of a complex such wrapper

Fetch Extract the list of devices and filter them by the prefix of the name "iPhone".
Using simctl, you can search for iPhone 12, turn it on and launch the application.

```swift
let xcrun = XCRun()
let simulator = try xcrun.simctl()
let simulator = xcrun.simctl

let list = try await simulator.list(.devices, "iPhone 12", available: true).json.decode()
let devices = list.devices.flatMap { $0.value }

for info in devices where info.state == "Booted" {
try await simulator.device(info.udid).shutdown.run()
}

let udid = devices.first!.udid
try await simulator.device(udid).boot.run()
try await simulator.device(udid).app("com.example.app").launch.run()
```

let devices = try simulator.list().devices(where: { device in
device.name.hasPrefix("iPhone")
})
## License

devices
```
MIT
55 changes: 55 additions & 0 deletions Sources/XSTooling/Core/AsyncProcess.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Foundation

extension Process {
var async: AsyncProcess {
AsyncProcess(process: self)
}
}

final class AsyncProcess {
private let _process: Process
private let _lock = NSLock()

fileprivate init(process: Process) {
self._process = process
}

/// Runs the process with the current environment.
func run() async throws {
try await withTaskCancellationHandler {
_terminate()
} operation: {
try Task.checkCancellation() // can be canceled before running
try await _run()
}
}

private func _run() async throws {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
var runningError: Error?
_lock.lock()
_process.terminationHandler = { _ in
continuation.resume()
}
do {
try _process.run() // waiting for the `terminationHandler` call
} catch {
// `terminationHandler` is not called if if an error occurred
runningError = error
}
_lock.unlock()

if let runningError = runningError {
continuation.resume(throwing: runningError)
}
}
}

private func _terminate() {
_lock.lock()
if _process.isRunning { // can be canceled without starting
_process.terminate() // crash if not running
}
_lock.unlock()
}
}
28 changes: 28 additions & 0 deletions Sources/XSTooling/Core/FileHandleReader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation

final class FileHandleReader {
fileprivate(set) var data = Data()

fileprivate init() {}
}

extension FileHandle {
func reader() -> FileHandleReader {
assert(self.readabilityHandler == nil)

let reader = FileHandleReader()

self.readabilityHandler = { fileHandle in
// invoke on serial queue
let data = fileHandle.availableData

if data.isEmpty {
// stop
fileHandle.readabilityHandler = nil
} else {
reader.data.append(data)
}
}
return reader
}
}
63 changes: 0 additions & 63 deletions Sources/XSTooling/Core/Kernel.swift

This file was deleted.

Loading

0 comments on commit 24ae0bb

Please sign in to comment.