Move all Process-specific code to its own file.
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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 */,
|
||||
|
||||
Reference in New Issue
Block a user