Skip to content

Commit ba97bc7

Browse files
authored
Merge pull request #56 from DougGregor/unescape-response-files
Improve processing of response files.
2 parents 67845c6 + 99892f1 commit ba97bc7

File tree

5 files changed

+55
-12
lines changed

5 files changed

+55
-12
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ The driver has basic support for building and linking Swift code. There are a bu
7171
* [ ] Teach the `DarwinToolchain` to also handle iOS, tvOS, watchOS
7272
* [ ] Fill out the `GenericUnixToolchain` toolchain to get it working
7373
* [ ] Implement a `WindowsToolchain`
74+
* [ ] Implement proper tokenization for response files
7475
* Compilation modes
7576
* [ ] Batch mode
7677
* [ ] Whole-module-optimization mode

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ public struct Driver {
157157
) throws {
158158
// FIXME: Determine if we should run as subcommand.
159159

160-
let args = try Self.expandResponseFiles(args)
161160
self.diagnosticEngine = DiagnosticsEngine(handlers: [diagnosticsHandler])
161+
let args = try Self.expandResponseFiles(args, diagnosticsEngine: self.diagnosticEngine)
162162
self.driverKind = try Self.determineDriverKind(args: args)
163163
self.optionTable = OptionTable()
164164
self.parsedOptions = try optionTable.parse(Array(args.dropFirst()))
@@ -252,18 +252,39 @@ public struct Driver {
252252
self.swiftInterfacePath = try Self.computeSupplementaryOutputPath(&parsedOptions, type: .swiftInterface, isOutput: .emit_module_interface, outputPath: .emit_module_interface_path, compilerOutputType: compilerOutputType, moduleName: moduleName)
253253
self.optimizationRecordPath = try Self.computeSupplementaryOutputPath(&parsedOptions, type: .optimizationRecord, isOutput: .save_optimization_record, outputPath: .save_optimization_record_path, compilerOutputType: compilerOutputType, moduleName: moduleName)
254254
}
255+
}
255256

256-
/// Expand response files in the input arguments and return a new argument list.
257-
public static func expandResponseFiles(_ args: [String]) throws -> [String] {
257+
// Response files.
258+
extension Driver {
259+
/// Tokenize a single line in a response file
260+
private static func tokenizeResponseFileLine(_ line: String) -> String {
261+
// FIXME: This is wrong. We need to do proper shell escaping.
262+
return line.replacingOccurrences(of: "\\ ", with: " ")
263+
}
264+
265+
private static func expandResponseFiles(
266+
_ args: [String],
267+
diagnosticsEngine: DiagnosticsEngine,
268+
visitedResponseFiles: inout Set<AbsolutePath>
269+
) throws -> [String] {
258270
// FIXME: This is very very prelimary. Need to look at how Swift compiler expands response file.
259271

260272
var result: [String] = []
261273

262274
// Go through each arg and add arguments from response files.
263275
for arg in args {
264276
if arg.first == "@", let responseFile = try? AbsolutePath(validating: String(arg.dropFirst())) {
277+
guard visitedResponseFiles.insert(responseFile).inserted else {
278+
diagnosticsEngine.emit(.warn_recursive_response_file(responseFile))
279+
continue
280+
}
281+
defer {
282+
visitedResponseFiles.remove(responseFile)
283+
}
284+
265285
let contents = try localFileSystem.readFileContents(responseFile).cString
266-
result += contents.split(separator: "\n", omittingEmptySubsequences: true).map(String.init)
286+
let lines = contents.split(separator: "\n", omittingEmptySubsequences: true).map { tokenizeResponseFileLine(String($0)) }
287+
result.append(contentsOf: try expandResponseFiles(lines, diagnosticsEngine: diagnosticsEngine, visitedResponseFiles: &visitedResponseFiles))
267288
} else {
268289
result.append(arg)
269290
}
@@ -272,6 +293,17 @@ public struct Driver {
272293
return result
273294
}
274295

296+
/// Expand response files in the input arguments and return a new argument list.
297+
public static func expandResponseFiles(
298+
_ args: [String],
299+
diagnosticsEngine: DiagnosticsEngine
300+
) throws -> [String] {
301+
var visitedResponseFiles = Set<AbsolutePath>()
302+
return try expandResponseFiles(args, diagnosticsEngine: diagnosticsEngine, visitedResponseFiles: &visitedResponseFiles)
303+
}
304+
}
305+
306+
extension Driver {
275307
/// Determine the driver kind based on the command-line arguments.
276308
public static func determineDriverKind(
277309
args: [String],
@@ -364,6 +396,10 @@ public struct Driver {
364396
}
365397

366398
extension Diagnostic.Message {
399+
static func warn_recursive_response_file(_ path: AbsolutePath) -> Diagnostic.Message {
400+
.warning("response file '\(path)' is recursively expanded")
401+
}
402+
367403
static var error_no_swift_frontend: Diagnostic.Message {
368404
.error("-driver-use-frontend-path requires a Swift compiler executable argument")
369405
}

Sources/SwiftDriver/Driver/ToolExecutionDelegate.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ struct ToolExecutionDelegate: JobExecutorDelegate {
1616
case .regular:
1717
break
1818
case .verbose:
19-
// FIXME: Do we need to escape the arguments?
20-
stdoutStream <<< arguments.joined(separator: " ") <<< "\n"
19+
stdoutStream <<< arguments.map { $0.spm_shellEscaped() }.joined(separator: " ") <<< "\n"
2120
stdoutStream.flush()
2221
case .parsableOutput:
2322

Sources/SwiftDriver/Options/ParsedOptions.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ extension ParsedOptions {
9292
extension ParsedOptions: CustomStringConvertible {
9393
/// Pretty-printed version of all of the parsed options.
9494
public var description: String {
95-
// FIXME: Escape spaces?
9695
return parsedOptions.map { $0.description }.joined(separator: " ")
9796
}
9897
}

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,12 +213,20 @@ final class SwiftDriverTests: XCTestCase {
213213
}
214214

215215
func testResponseFileExpansion() throws {
216-
try withTemporaryFile { file in
217-
try localFileSystem.writeFileContents(file.path) {
218-
$0 <<< "hello\nbye\nbye"
216+
try withTemporaryDirectory { path in
217+
let diags = DiagnosticsEngine()
218+
let fooPath = path.appending(component: "foo.rsp")
219+
let barPath = path.appending(component: "bar.rsp")
220+
try localFileSystem.writeFileContents(fooPath) {
221+
$0 <<< "hello\nbye\nbye\\ to\\ you\n@\(barPath.pathString)"
222+
}
223+
try localFileSystem.writeFileContents(barPath) {
224+
$0 <<< "from\nbar\n@\(fooPath.pathString)"
219225
}
220-
let args = try Driver.expandResponseFiles(["swift", "compiler", "-Xlinker", "@loader_path", "@" + file.path.pathString, "something"])
221-
XCTAssertEqual(args, ["swift", "compiler", "-Xlinker", "@loader_path", "hello", "bye", "bye", "something"])
226+
let args = try Driver.expandResponseFiles(["swift", "compiler", "-Xlinker", "@loader_path", "@" + fooPath.pathString, "something"], diagnosticsEngine: diags)
227+
XCTAssertEqual(args, ["swift", "compiler", "-Xlinker", "@loader_path", "hello", "bye", "bye to you", "from", "bar", "something"])
228+
XCTAssertEqual(diags.diagnostics.count, 1)
229+
XCTAssert(diags.diagnostics.first!.description.contains("is recursively expanded"))
222230
}
223231
}
224232

0 commit comments

Comments
 (0)