From 04cf52d23a363baf3a54331a7aa6f14c65b9dda6 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Fri, 8 Sep 2023 09:46:35 -0700 Subject: [PATCH] Support cancellation of subcommands in `swift-syntax-dev-utils` using Ctrl-C MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s really quite unfortunate that we need to do all this handling ourselves… --- .../SwiftSyntaxDevUtils.swift | 14 +++++++++++ .../common/ProcessRunner.swift | 25 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/SwiftSyntaxDevUtils/Sources/swift-syntax-dev-utils/SwiftSyntaxDevUtils.swift b/SwiftSyntaxDevUtils/Sources/swift-syntax-dev-utils/SwiftSyntaxDevUtils.swift index 85209c9bfad..c1988f67cde 100644 --- a/SwiftSyntaxDevUtils/Sources/swift-syntax-dev-utils/SwiftSyntaxDevUtils.swift +++ b/SwiftSyntaxDevUtils/Sources/swift-syntax-dev-utils/SwiftSyntaxDevUtils.swift @@ -31,4 +31,18 @@ struct SwiftSyntaxDevUtils: ParsableCommand { VerifySourceCode.self, ] ) + + public static func main() { + SigIntListener.registerSigIntSubprocessTerminationHandler() + do { + var command = try parseAsRoot(nil) + try command.run() + } catch { + if !SigIntListener.hasReceivedSigInt { + // No point printing an error message if the user requested the termination + // of the script. + exit(withError: error) + } + } + } } diff --git a/SwiftSyntaxDevUtils/Sources/swift-syntax-dev-utils/common/ProcessRunner.swift b/SwiftSyntaxDevUtils/Sources/swift-syntax-dev-utils/common/ProcessRunner.swift index e20b600997e..247f4150a0f 100644 --- a/SwiftSyntaxDevUtils/Sources/swift-syntax-dev-utils/common/ProcessRunner.swift +++ b/SwiftSyntaxDevUtils/Sources/swift-syntax-dev-utils/common/ProcessRunner.swift @@ -12,6 +12,29 @@ import Foundation +/// Keeps track of subprocesses spawned by this script and forwards SIGINT +/// signals to them. +class SigIntListener { + /// The subprocesses spawned by this script that are currently running. + static var runningSubprocesses: Set = [] + + /// Whether a `SIGINT` signal has been received by this script. + static var hasReceivedSigInt: Bool = false + + /// Registers a `SIGINT` signal handler that forwards `SIGINT` to all + /// subprocesses that are registered in `runningSubprocesses` + static func registerSigIntSubprocessTerminationHandler() { + #if canImport(Darwin) || canImport(Glibc) + signal(SIGINT) { _ in + SigIntListener.hasReceivedSigInt = true + for process in SigIntListener.runningSubprocesses { + process.interrupt() + } + } + #endif + } +} + /// Provides convenience APIs for launching and gathering output from a subprocess public class ProcessRunner { private static let serialQueue = DispatchQueue(label: "\(ProcessRunner.self)") @@ -56,7 +79,9 @@ public class ProcessRunner { } try process.run() + SigIntListener.runningSubprocesses.insert(process) process.waitUntilExit() + SigIntListener.runningSubprocesses.remove(process) if captureStdout || captureStderr { // Make sure we've received all stdout/stderr group.wait()