Завершить подпроцессы инструмента командной строки macOS в Swift - PullRequest
0 голосов
/ 12 октября 2018

Я пишу инструмент командной строки macOS в swift, который выполняет команды оболочки:

let process = Process()
process.launchPath = "/bin/sleep"
process.arguments = ["100"]
process.launch()
process.waitUntilExit()

Однако, если в мою программу посылается сигнал прерывания (CTRL-C) или завершения, эти оболочкиКоманды не прерываются и просто продолжают их выполнение.

Есть ли способ автоматически прервать их, если моя программа неожиданно прервется?

1 Ответ

0 голосов
/ 25 января 2019

Вот что мы сделали, чтобы реагировать на прерывание (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 **
...