Завершить подпроцессы инструмента командной строки macOS в Swift
Я пишу инструмент командной строки macOS в swift, который выполняет команды оболочки:
let process = Process()
process.launchPath = "/bin/sleep"
process.arguments = ["100"]
process.launch()
process.waitUntilExit()
Однако, если прерывание (CTRL-C
) или в мою программу посылается сигнал завершения, эти команды оболочки не завершаются и просто продолжают свое выполнение.
Есть ли способ автоматически прекратить их, если моя программа неожиданно завершает работу?
0 ответов
Вот что мы сделали, чтобы реагировать на прерывание (CTRL-C
) при использовании двух конвейерных подпроцессов.
Идея позади: блокировка waitUntilExit()
вызов заменен на асинхронный terminationHandler
, Бесконечный цикл dispatchMain()
используется для обслуживания событий отправки. При получении Interrupt
сигнал мы звоним interrupt()
на подпроцессах.
Пример класса, который инкапсулирует логику запуска и прерывания подпроцесса:
class AppTester: Builder {
private var processes: [Process] = [] // Keeps references to launched processes.
func test(completion: @escaping (Int32) -> Void) {
let xcodebuildProcess = Process(executableName: "xcodebuild", arguments: ...)
let xcprettyProcess = Process(executableName: "xcpretty", arguments: ...)
// Organising pipe between processes. Like `xcodebuild ... | xcpretty` in shell
let pipe = Pipe()
xcodebuildProcess.standardOutput = pipe
xcprettyProcess.standardInput = pipe
// Assigning `terminationHandler` for needed subprocess.
processes.append(xcodebuildProcess)
xcodebuildProcess.terminationHandler = { process in
completion(process.terminationStatus)
}
xcodebuildProcess.launch()
xcprettyProcess.launch()
// Note. We should not use blocking `waitUntilExit()` call.
}
func interrupt() {
// Interrupting running processes (if any).
processes.filter { $0.isRunning }.forEach { $0.interrupt() }
}
}
Использование (т.е. main.swift
):
let tester = AppTester(...)
tester.test(....) {
if $0 == EXIT_SUCCESS {
// Do some other work.
} else {
exit($0)
}
}
// Making Interrupt signal listener.
let source = DispatchSource.makeSignalSource(signal: SIGINT)
source.setEventHandler {
tester.interrupt() // Will interrupt running processes (if any).
exit(SIGINT)
}
source.resume()
dispatchMain() // Starting dispatch loop. This function never returns.
Пример вывода в оболочке:
...
▸ Running script 'Run Script: Verify Sources'
▸ Processing Framework-Info.plist
▸ Running script 'Run Script: Verify Sources'
▸ Linking AppTestability
^C** BUILD INTERRUPTED **