Move all Process-specific code to its own file.

This commit is contained in:
Kare Morstol
2018-04-06 21:41:45 +02:00
parent 5e3e1b0c66
commit eedbd2ab73
3 changed files with 134 additions and 137 deletions
+2 -137
View File
@@ -10,18 +10,6 @@
import Foundation
import Dispatch
#if os(Linux)
import Glibc
#endif
#if !(os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !swift(>=3.1)
typealias Process = Task
extension Task {
var isRunning: Bool { return running }
}
#endif
// MARK: exit
/**
@@ -136,43 +124,6 @@ public func == (e1: CommandError, e2: CommandError) -> Bool {
}
}
// MARK: Process
extension Process {
/**
Launch process.
- throws: CommandError.inAccessibleExecutable if command could not be executed.
*/
public func launchThrowably() throws {
guard Files.isExecutableFile(atPath: self.launchPath!) else {
throw CommandError.inAccessibleExecutable(path: self.launchPath!)
}
launch()
}
/**
Wait until process is finished.
- throws: `CommandError.returnedErrorCode(command: String, errorcode: Int)` if the exit code is anything but 0.
*/
public func finish() throws {
self.waitUntilExit()
guard self.terminationStatus == 0 else {
throw CommandError.returnedErrorCode(command: commandAsString()!, errorcode: Int(self.terminationStatus))
}
}
/** The full path to the executable + all arguments, each one quoted if it contains a space. */
func commandAsString() -> String? {
guard let path = self.launchPath else { return nil }
return self.arguments?.reduce(path) { (acc: String, arg: String) in
return acc + " " + ( arg.contains(" ") ? ("\"" + arg + "\"") : arg )
}
}
}
// MARK: run
/// Output from a `run` command.
@@ -312,92 +263,7 @@ public final class AsyncCommand {
/// Is the command still running?
public var isRunning: Bool { return process.isRunning }
#if os(Linux)
fileprivate enum ProcessAttribute: String {
case blockedSignals = "blocked"
case ignoredSignals = "ignored"
}
/**
Gets a specified process attribute using the `ps` command installed on all
Linux systems
- parameter attr: Which specific attribute to return
- returns: A String containing the hexadecimal representation of the mask,
or nil if there is no stdout output
*/
fileprivate func getProcessInfo(_ attr: ProcessAttribute) -> String? {
let attribute = run(bash: "ps --no-headers -q \(process.processIdentifier) -o \(attr.rawValue)").stdout
return attribute.isEmpty ? nil : attribute
}
/// Determines whether the running process is blocking the specified signal
fileprivate func isBlockingSignal(_ signum: Int32) -> Bool {
// If there is no mask, then the signal isn't blocked
guard let blockedMask = getProcessInfo(.blockedSignals) else { return false }
// If the output isn't in proper hexadecimal (like it should be), then
// it could be ignored, but we can't be sure. Return true, just to be safe
guard let blocked = Int(blockedMask, radix: 16) else { return true }
// Checks if the signals bit in the mask is 1 (1 == blocked)
return blocked & (1 << signum) == 1
}
/// Determines whether the running process is ignoring the specified signal
fileprivate func isIgnoringSignal(_ signum: Int32) -> Bool {
// If there is no mask, then the signal isn't ignored
guard let ignoredMask = getProcessInfo(.ignoredSignals) else { return false }
// If the output isn't in proper hexadecimal (like it should be), then
// it could be ignored, but we can't be sure. Return true, just to be safe
guard let ignored = Int(ignoredMask, radix: 16) else { return true }
// Checks if the signals bit in the mask is 1 (1 == ignored)
return ignored & (1 << signum) == 1
}
/// Sends the specified signal to the currently running process
@discardableResult fileprivate func signal(_ signum: Int32) -> Int32 {
return kill(process.processIdentifier, signum)
}
/// Terminates the command by sending the SIGTERM signal
public func stop() {
// If the SIGTERM signal is being blocked or ignored by the process,
// then don't bother sending it
guard !(isBlockingSignal(SIGTERM) || isIgnoringSignal(SIGTERM)) else { return }
signal(SIGTERM)
}
/// Interrupts the command by sending the SIGINT signal
public func interrupt() {
// If the SIGINT signal is being blocked or ignored by the process,
// then don't bother sending it
guard !(isBlockingSignal(SIGINT) || isIgnoringSignal(SIGINT)) else { return }
signal(SIGINT)
}
/**
Temporarily suspends a command. Call resume() to resume a suspended command
- returns: true if the command was successfully suspended
*/
@discardableResult public func suspend() -> Bool {
return signal(SIGTSTP) == 0
}
/**
Resumes a command previously suspended with suspend().
- returns: true if the command was successfully resumed
*/
@discardableResult public func resume() -> Bool {
return signal(SIGCONT) == 0
}
#else
/// Terminates the command by sending the SIGTERM signal
public func stop() {
process.terminate()
@@ -427,7 +293,6 @@ public final class AsyncCommand {
@discardableResult public func resume() -> Bool {
return process.resume()
}
#endif
/**
Wait for this command to finish.
@@ -448,9 +313,9 @@ public final class AsyncCommand {
}
/**
Wait for the command to finish, then return why the command terminated
Wait for the command to finish, then return why the command terminated.
- returns: .exited if the command exited normally, otherwise it's .uncaughtSignal
- returns: `.exited` if the command exited normally, otherwise `.uncaughtSignal`.
*/
public func terminationReason() -> Process.TerminationReason {
process.waitUntilExit()
+128
View File
@@ -0,0 +1,128 @@
//
// Process.swift
// SwiftShell
//
// Created by Kåre Morstøl on 06/04/2018.
//
import Foundation
// MARK: Process
extension Process {
/// Launch process.
///
/// - throws: CommandError.inAccessibleExecutable if command could not be executed.
public func launchThrowably() throws {
guard Files.isExecutableFile(atPath: self.launchPath!) else {
throw CommandError.inAccessibleExecutable(path: self.launchPath!)
}
launch()
}
/// Wait until process is finished.
///
/// - throws: `CommandError.returnedErrorCode(command: String, errorcode: Int)`
/// if the exit code is anything but 0.
public func finish() throws {
self.waitUntilExit()
guard self.terminationStatus == 0 else {
throw CommandError.returnedErrorCode(command: commandAsString()!, errorcode: Int(self.terminationStatus))
}
}
/// The full path to the executable + all arguments, each one quoted if it contains a space.
func commandAsString() -> String? {
guard let path = self.launchPath else { return nil }
return self.arguments?.reduce(path) { (acc: String, arg: String) in
return acc + " " + ( arg.contains(" ") ? ("\"" + arg + "\"") : arg )
}
}
}
// MARK: Process extensions for Linux
#if os(Linux)
import Glibc
extension Process {
fileprivate enum ProcessAttribute: String {
case blockedSignals = "blocked"
case ignoredSignals = "ignored"
}
/// Gets a specified process attribute using the `ps` command installed on all Linux systems
///
/// - parameter attr: Which specific attribute to return
/// - returns: A String containing the hexadecimal representation of the mask,
/// or nil if there is no stdout output
fileprivate func getProcessInfo(_ attr: ProcessAttribute) -> String? {
let attribute = run(bash: "ps --no-headers -q \(self.processIdentifier) -o \(attr.rawValue)").stdout
return attribute.isEmpty ? nil : attribute
}
/// Determines whether the running process is blocking the specified signal
fileprivate func isBlockingSignal(_ signum: Int32) -> Bool {
// If there is no mask, then the signal isn't blocked
guard let blockedMask = getProcessInfo(.blockedSignals) else { return false }
// If the output isn't in proper hexadecimal (like it should be), then
// it could be ignored, but we can't be sure. Return true, just to be safe
guard let blocked = Int(blockedMask, radix: 16) else { return true }
// Checks if the signals bit in the mask is 1 (1 == blocked)
return blocked & (1 << signum) == 1
}
/// Determines whether the running process is ignoring the specified signal
fileprivate func isIgnoringSignal(_ signum: Int32) -> Bool {
// If there is no mask, then the signal isn't ignored
guard let ignoredMask = getProcessInfo(.ignoredSignals) else { return false }
// If the output isn't in proper hexadecimal (like it should be), then
// it could be ignored, but we can't be sure. Return true, just to be safe
guard let ignored = Int(ignoredMask, radix: 16) else { return true }
// Checks if the signals bit in the mask is 1 (1 == ignored)
return ignored & (1 << signum) == 1
}
/// Sends the specified signal to the currently running process
@discardableResult fileprivate func signal(_ signum: Int32) -> Int32 {
return kill(self.processIdentifier, signum)
}
/// Terminates the command by sending the SIGTERM signal
public func terminate() {
// If the SIGTERM signal is being blocked or ignored by the process,
// then don't bother sending it
guard !(isBlockingSignal(SIGTERM) || isIgnoringSignal(SIGTERM)) else { return }
signal(SIGTERM)
}
/// Interrupts the command by sending the SIGINT signal
public func interrupt() {
// If the SIGINT signal is being blocked or ignored by the process,
// then don't bother sending it
guard !(isBlockingSignal(SIGINT) || isIgnoringSignal(SIGINT)) else { return }
signal(SIGINT)
}
/// Temporarily suspends a command. Call resume() to resume a suspended command
///
/// - returns: true if the command was successfully suspended
@discardableResult public func suspend() -> Bool {
return signal(SIGTSTP) == 0
}
/// Resumes a command previously suspended with suspend().
///
/// - returns: true if the command was successfully resumed.
@discardableResult public func resume() -> Bool {
return signal(SIGCONT) == 0
}
}
#endif
+4
View File
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
BA930AEB2077EC5C003E5703 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA930AEA2077EC5C003E5703 /* Process.swift */; };
OBJ_45 /* Bash.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* Bash.swift */; };
OBJ_46 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* Command.swift */; };
OBJ_47 /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* Context.swift */; };
@@ -53,6 +54,7 @@
/* Begin PBXFileReference section */
BA50D28D1E8DB923002BA4A5 /* LinuxMain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinuxMain.swift; sourceTree = "<group>"; };
BA930AEA2077EC5C003E5703 /* Process.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Process.swift; sourceTree = "<group>"; };
OBJ_10 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Command.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
OBJ_11 /* Context.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Context.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
OBJ_12 /* FileHandle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = FileHandle.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
@@ -209,6 +211,7 @@
OBJ_10 /* Command.swift */,
OBJ_11 /* Context.swift */,
OBJ_12 /* FileHandle.swift */,
BA930AEA2077EC5C003E5703 /* Process.swift */,
OBJ_13 /* Files.swift */,
OBJ_14 /* String.swift */,
OBJ_15 /* General */,
@@ -354,6 +357,7 @@
buildActionMask = 0;
files = (
OBJ_45 /* Bash.swift in Sources */,
BA930AEB2077EC5C003E5703 /* Process.swift in Sources */,
OBJ_46 /* Command.swift in Sources */,
OBJ_47 /* Context.swift in Sources */,
OBJ_48 /* FileHandle.swift in Sources */,