Skip to content

Commit a0c50b5

Browse files
committed
[SwiftTool] Add interrupt handler and process set to SwiftTool
1 parent a45c391 commit a0c50b5

File tree

2 files changed

+90
-6
lines changed

2 files changed

+90
-6
lines changed

Sources/Commands/SwiftTool.swift

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ import PackageLoading
1515
import PackageGraph
1616
import PackageModel
1717
import POSIX
18+
import SourceControl
1819
import Utility
1920
import Workspace
21+
import libc
2022

2123
private class ToolWorkspaceDelegate: WorkspaceDelegate {
2224
func fetchingMissingRepositories(_ urls: Set<String>) {
@@ -75,8 +77,16 @@ public class SwiftTool<Options: ToolOptions> {
7577
/// Path to the build directory.
7678
let buildPath: AbsolutePath
7779

80+
/// Reference to the argument parser.
7881
let parser: ArgumentParser
7982

83+
/// The process set to hold the launched processes. These will be terminated on any signal
84+
/// received by the swift tools.
85+
let processSet: ProcessSet
86+
87+
/// The interrupt handler.
88+
let interruptHandler: InterruptHandler
89+
8090
/// Create an instance of this tool.
8191
///
8292
/// - parameter args: The command line arguments to be passed to this tool.
@@ -150,8 +160,28 @@ public class SwiftTool<Options: ToolOptions> {
150160
if let dir = options.chdir {
151161
// FIXME: This should be an API which takes AbsolutePath and maybe
152162
// should be moved to file system APIs with currentWorkingDirectory.
153-
try chdir(dir.asString)
163+
try POSIX.chdir(dir.asString)
154164
}
165+
166+
let processSet = ProcessSet()
167+
interruptHandler = try InterruptHandler {
168+
// Terminate all processes on receiving an interrupt signal.
169+
processSet.terminate()
170+
171+
// Install the default signal handler.
172+
var action = sigaction()
173+
#if os(macOS)
174+
action.__sigaction_u.__sa_handler = SIG_DFL
175+
#else
176+
action.__sigaction_handler = unsafeBitCast(SIG_DFL, to: sigaction.__Unnamed_union___sigaction_handler.self)
177+
#endif
178+
sigaction(SIGINT, &action, nil)
179+
180+
// Die with sigint.
181+
kill(getpid(), SIGINT)
182+
}
183+
self.processSet = processSet
184+
155185
} catch {
156186
handle(error: error)
157187
}
@@ -182,12 +212,14 @@ public class SwiftTool<Options: ToolOptions> {
182212
}
183213
let delegate = ToolWorkspaceDelegate()
184214
let rootPackage = try getPackageRoot()
215+
let provider = GitRepositoryProvider(processSet: processSet)
185216
let workspace = try Workspace(
186217
dataPath: buildPath,
187218
editablesPath: rootPackage.appending(component: "Packages"),
188219
pinsFile: rootPackage.appending(component: "Package.pins"),
189220
manifestLoader: manifestLoader,
190-
delegate: delegate
221+
delegate: delegate,
222+
repositoryProvider: provider
191223
)
192224
workspace.registerPackage(at: rootPackage)
193225
_workspace = workspace
@@ -266,7 +298,7 @@ private func findPackageRoot() -> AbsolutePath? {
266298

267299
private func getEnvBuildPath() -> AbsolutePath? {
268300
// Don't rely on build path from env for SwiftPM's own tests.
269-
guard getenv("IS_SWIFTPM_TEST") == nil else { return nil }
270-
guard let env = getenv("SWIFT_BUILD_PATH") else { return nil }
301+
guard POSIX.getenv("IS_SWIFTPM_TEST") == nil else { return nil }
302+
guard let env = POSIX.getenv("SWIFT_BUILD_PATH") else { return nil }
271303
return AbsolutePath(env, relativeTo: currentWorkingDirectory)
272304
}

Tests/FunctionalTests/MiscellaneousTests.swift

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ import XCTest
1212
import TestSupport
1313
import Basic
1414
import PackageModel
15+
import Utility
16+
import libc
17+
import class Foundation.ProcessInfo
1518

16-
import class Utility.Git
17-
import func libc.sleep
1819
import enum POSIX.Error
1920
import func POSIX.popen
21+
typealias ProcessID = Utility.Process.ProcessID
2022

2123
class MiscellaneousTestCase: XCTestCase {
2224
func testPrintsSelectedDependencyVersion() {
@@ -437,6 +439,55 @@ class MiscellaneousTestCase: XCTestCase {
437439
}
438440
}
439441

442+
func testCanKillSubprocessOnSigInt() throws {
443+
fixture(name: "DependencyResolution/External/Simple") { prefix in
444+
445+
let fakeGit = prefix.appending(components: "bin", "git")
446+
let waitFile = prefix.appending(components: "waitfile")
447+
448+
try localFileSystem.createDirectory(fakeGit.parentDirectory)
449+
450+
// Write out fake git.
451+
let stream = BufferedOutputByteStream()
452+
stream <<< "#!/bin/sh" <<< "\n"
453+
stream <<< "set -e" <<< "\n"
454+
stream <<< "printf \"$$\" >> " <<< waitFile.asString <<< "\n"
455+
stream <<< "while true; do sleep 1; done" <<< "\n"
456+
try localFileSystem.writeFileContents(fakeGit, bytes: stream.bytes)
457+
458+
// Make it executable.
459+
_ = try Process.popen(args: "chmod", "+x", fakeGit.asString)
460+
461+
// Put fake git in PATH.
462+
var env = ProcessInfo.processInfo.environment
463+
let oldPath = env["PATH"]
464+
env["PATH"] = fakeGit.parentDirectory.asString
465+
if let oldPath = oldPath {
466+
env["PATH"] = env["PATH"]! + ":" + oldPath
467+
}
468+
469+
// Launch swift-build.
470+
let app = prefix.appending(component: "Bar")
471+
let process = Process(args: SwiftPMProduct.SwiftBuild.path.asString, "--chdir", app.asString, environment: env)
472+
try process.launch()
473+
474+
guard waitForFile(waitFile) else {
475+
return XCTFail("Couldn't launch the process")
476+
}
477+
// Interrupt the process.
478+
process.signal(SIGINT)
479+
let result = try process.waitUntilExit()
480+
481+
// We should not have exited with zero.
482+
XCTAssert(result.exitStatus != .terminated(code: 0))
483+
484+
// Process and subprocesses should be dead.
485+
let contents = try localFileSystem.readFileContents(waitFile).asString!
486+
XCTAssertFalse(try Process.running(process.processID))
487+
XCTAssertFalse(try Process.running(ProcessID(contents)!))
488+
}
489+
}
490+
440491
static var allTests = [
441492
("testExecutableAsBuildOrderDependency", testExecutableAsBuildOrderDependency),
442493
("testPrintsSelectedDependencyVersion", testPrintsSelectedDependencyVersion),
@@ -470,5 +521,6 @@ class MiscellaneousTestCase: XCTestCase {
470521
("testInitPackageNonc99Directory", testInitPackageNonc99Directory),
471522
("testOverridingSwiftcArguments", testOverridingSwiftcArguments),
472523
("testPkgConfigClangModules", testPkgConfigClangModules),
524+
("testCanKillSubprocessOnSigInt", testCanKillSubprocessOnSigInt),
473525
]
474526
}

0 commit comments

Comments
 (0)