Skip to content

Commit b77cbb2

Browse files
committed
Add subcommand handling based on the original driver
1 parent 0f431c1 commit b77cbb2

File tree

3 files changed

+84
-2
lines changed

3 files changed

+84
-2
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,30 @@ public struct Driver {
193193
diagnosticsHandler: @escaping DiagnosticsEngine.DiagnosticsHandler = Driver.stderrDiagnosticsHandler
194194
) throws {
195195
self.env = env
196-
197-
// FIXME: Determine if we should run as subcommand.
198196

199197
self.diagnosticEngine = DiagnosticsEngine(handlers: [diagnosticsHandler])
200198
var args = try Self.expandResponseFiles(args, diagnosticsEngine: self.diagnosticEngine)[...]
199+
200+
switch try Self.invocationRunMode(forArgs: Array(args)) {
201+
case .normal(let isRepl):
202+
if isRepl {
203+
args.remove(at: 1)
204+
}
205+
case .subcommand(let subcommand):
206+
207+
args.remove(at: 1)
208+
209+
// We are running as a subcommand, try to find the subcommand
210+
// adjacent to the executable we are running as.
211+
let subcommandPath = Process.findExecutable(args[0])?.parentDirectory.appending(component: subcommand)
212+
// If we didn't find the tool there, let the OS search for it.
213+
// If there was an error, ignore it and just let the exec fail.
214+
?? Process.findExecutable(subcommand)
215+
216+
// Execute the subcommand.
217+
try exec(path: subcommandPath?.pathString ?? "", args: Array(args))
218+
}
219+
201220
self.driverKind = try Self.determineDriverKind(args: &args)
202221
self.optionTable = OptionTable()
203222
self.parsedOptions = try optionTable.parse(Array(args))
@@ -444,6 +463,41 @@ extension Driver {
444463
}
445464
}
446465

466+
extension Driver {
467+
468+
public enum InvocationRunMode: Equatable {
469+
case normal(isRepl: Bool)
470+
case subcommand(String)
471+
}
472+
473+
public static func invocationRunMode(forArgs args: [String]) throws -> InvocationRunMode {
474+
475+
assert(!args.isEmpty)
476+
477+
let execName = try VirtualPath(path: args[0]).basenameWithoutExt
478+
479+
// If we are not run as 'swift' or there are no program arguments, always invoke as normal.
480+
guard execName == "swift", args.count > 1 else { return .normal(isRepl: false) }
481+
482+
// Otherwise, we have a program argument.
483+
let firstArg = args[1]
484+
485+
// If it looks like an option or a path, then invoke in interactive mode with the arguments as given.
486+
if firstArg.hasPrefix("-") || firstArg.hasPrefix("/") || firstArg.contains(".") {
487+
return .normal(isRepl: false)
488+
}
489+
490+
// Otherwise, we should have some sort of subcommand.
491+
492+
// If it is the "built-in" 'repl', then use the normal driver.
493+
if firstArg == "repl" {
494+
return .normal(isRepl: true)
495+
}
496+
497+
return .subcommand("swift-\(firstArg)")
498+
}
499+
}
500+
447501
extension Driver {
448502
/// Determine the driver kind based on the command-line arguments, consuming the arguments
449503
/// conveying this information.

Sources/SwiftDriver/Utilities/VirtualPath.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,18 @@ public enum VirtualPath: Hashable {
7979
default: return nil
8080
}
8181
}
82+
83+
/// Retrieve the basename of the path without the extension.
84+
public var basenameWithoutExt: String {
85+
switch self {
86+
case .absolute(let path):
87+
return path.basenameWithoutExt
88+
case .relative(let path), .temporary(let path):
89+
return path.basenameWithoutExt
90+
case .standardInput, .standardOutput:
91+
return ""
92+
}
93+
}
8294
}
8395

8496
extension VirtualPath: Codable {

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,22 @@ final class SwiftDriverTests: XCTestCase {
4747
}
4848
}
4949

50+
func testInvocationRunModes() throws {
51+
XCTAssertEqual(try Driver.invocationRunMode(forArgs: ["swift"]), .normal(isRepl: false))
52+
XCTAssertEqual(try Driver.invocationRunMode(forArgs: ["swift", "-buzz"]), .normal(isRepl: false))
53+
XCTAssertEqual(try Driver.invocationRunMode(forArgs: ["swift", "/"]), .normal(isRepl: false))
54+
XCTAssertEqual(try Driver.invocationRunMode(forArgs: ["swift", "./foo"]), .normal(isRepl: false))
55+
XCTAssertEqual(try Driver.invocationRunMode(forArgs: ["swift", "repl"]), .normal(isRepl: true))
56+
XCTAssertEqual(try Driver.invocationRunMode(forArgs: ["swift", "foo", "bar"]), .subcommand("swift-foo"))
57+
58+
XCTAssertEqual(try Driver.invocationRunMode(forArgs: ["/path/to/swift"]), .normal(isRepl: false))
59+
XCTAssertEqual(try Driver.invocationRunMode(forArgs: ["/path/to/swift", "-buzz"]), .normal(isRepl: false))
60+
XCTAssertEqual(try Driver.invocationRunMode(forArgs: ["/path/to/swift", "/"]), .normal(isRepl: false))
61+
XCTAssertEqual(try Driver.invocationRunMode(forArgs: ["/path/to/swift", "./foo"]), .normal(isRepl: false))
62+
XCTAssertEqual(try Driver.invocationRunMode(forArgs: ["/path/to/swift", "repl"]), .normal(isRepl: true))
63+
XCTAssertEqual(try Driver.invocationRunMode(forArgs: ["/path/to/swift", "foo", "bar"]), .subcommand("swift-foo"))
64+
}
65+
5066
func testDriverKindParsing() throws {
5167
func assertArgs(
5268
_ args: String...,

0 commit comments

Comments
 (0)