Skip to content

Commit 0567383

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

File tree

5 files changed

+118
-14
lines changed

5 files changed

+118
-14
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,15 @@ 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])
198+
199+
if case .subcommand = try Self.invocationRunMode(forArgs: args) {
200+
diagnosticEngine.emit(.error_subcommand_in_driver)
201+
}
202+
200203
var args = try Self.expandResponseFiles(args, diagnosticsEngine: self.diagnosticEngine)[...]
204+
201205
self.driverKind = try Self.determineDriverKind(args: &args)
202206
self.optionTable = OptionTable()
203207
self.parsedOptions = try optionTable.parse(Array(args))
@@ -351,6 +355,48 @@ public struct Driver {
351355
}
352356
}
353357

358+
extension Driver {
359+
360+
public enum InvocationRunMode: Equatable {
361+
case normal(isRepl: Bool)
362+
case subcommand(String)
363+
}
364+
365+
public static func invocationRunMode(forArgs args: [String]) throws -> InvocationRunMode {
366+
367+
assert(!args.isEmpty)
368+
369+
let execName = try VirtualPath(path: args[0]).basenameWithoutExt
370+
371+
// If we are not run as 'swift' or there are no program arguments, always invoke as normal.
372+
guard execName == "swift", args.count > 1 else { return .normal(isRepl: false) }
373+
374+
// Otherwise, we have a program argument.
375+
let firstArg = args[1]
376+
377+
// If it looks like an option or a path, then invoke in interactive mode with the arguments as given.
378+
if firstArg.hasPrefix("-") || firstArg.hasPrefix("/") || firstArg.contains(".") {
379+
return .normal(isRepl: false)
380+
}
381+
382+
// Otherwise, we should have some sort of subcommand.
383+
384+
// If it is the "built-in" 'repl', then use the normal driver.
385+
if firstArg == "repl" {
386+
return .normal(isRepl: true)
387+
}
388+
389+
return .subcommand("swift-\(firstArg)")
390+
}
391+
}
392+
393+
extension Diagnostic.Message {
394+
395+
static var error_subcommand_in_driver: Self {
396+
.error("subcommands must be handled before driver is initialized")
397+
}
398+
}
399+
354400
// MARK: - Response files.
355401
extension Driver {
356402
/// Tokenize a single line in a response file.

Sources/SwiftDriver/Driver/OutputFileMap.swift

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,12 @@ public struct OutputFileMap: Equatable {
3131
return output
3232
}
3333

34-
// Create a temporary file
35-
let baseName: String
36-
switch inputFile {
37-
case .absolute(let path):
38-
baseName = path.basenameWithoutExt
39-
case .relative(let path), .temporary(let path):
40-
baseName = path.basenameWithoutExt
41-
case .standardInput:
42-
baseName = ""
43-
case .standardOutput:
34+
if inputFile == .standardOutput {
4435
fatalError("Standard output cannot be an input file")
4536
}
4637

4738
// Form the virtual path.
48-
return .temporary(RelativePath(baseName.appendingFileTypeExtension(outputType)))
39+
return .temporary(RelativePath(inputFile.basenameWithoutExt.appendingFileTypeExtension(outputType)))
4940
}
5041

5142
public func existingOutput(inputFile: VirtualPath, outputType: FileType) -> VirtualPath? {

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 {

Sources/swift-driver/main.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,34 @@ do {
2323
processSet.terminate()
2424
}
2525

26-
var driver = try Driver(args: CommandLine.arguments)
26+
var arguments = CommandLine.arguments
27+
28+
switch try Driver.invocationRunMode(forArgs: arguments) {
29+
case .normal(let isRepl):
30+
if isRepl {
31+
arguments.remove(at: 1)
32+
}
33+
case .subcommand(let subcommand):
34+
35+
arguments.remove(at: 1)
36+
37+
// We are running as a subcommand, try to find the subcommand adjacent to the executable we are running as.
38+
// If we didn't find the tool there, let the OS search for it.
39+
let subcommandPath = Process.findExecutable(arguments[0])?.parentDirectory.appending(component: subcommand)
40+
?? Process.findExecutable(subcommand)
41+
42+
if subcommandPath == nil || !localFileSystem.exists(subcommandPath!) {
43+
fatalError("cannot find subcommand executable '\(subcommand)'")
44+
}
45+
46+
// Remove "swift" from arguments
47+
arguments.removeFirst()
48+
49+
// Execute the subcommand.
50+
try exec(path: subcommandPath?.pathString ?? "", args: arguments)
51+
}
52+
53+
var driver = try Driver(args: arguments)
2754
let resolver = try ArgsResolver()
2855
try driver.run(resolver: resolver, processSet: processSet)
2956

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,34 @@ 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+
66+
func testSubcommandsHandling() throws {
67+
68+
try assertNoDriverDiagnostics(args: "swift")
69+
try assertNoDriverDiagnostics(args: "swift", "-I=foo")
70+
try assertNoDriverDiagnostics(args: "swift", ".foo")
71+
try assertNoDriverDiagnostics(args: "swift", "/foo")
72+
73+
try assertDriverDiagnostics(args: "swift", "foo") {
74+
$1.expect(.error("subcommands must be handled before driver is initialized"))
75+
}
76+
}
77+
5078
func testDriverKindParsing() throws {
5179
func assertArgs(
5280
_ args: String...,

0 commit comments

Comments
 (0)