Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions Sources/SwiftDriver/Execution/ArgsResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//

import class Foundation.NSLock
import struct Foundation.UUID

import func TSCBasic.withTemporaryDirectory
import protocol TSCBasic.FileSystem
Expand All @@ -25,6 +26,11 @@ public enum ResponseFileHandling {
case heuristic
}

public enum ResolvedCommandLine {
case plain([String])
case usingResponseFile(resolved: [String], responseFileContents: [String])
}

/// Resolver for a job's argument template.
public final class ArgsResolver {
/// The map of virtual path to the actual path.
Expand Down Expand Up @@ -75,6 +81,18 @@ public final class ArgsResolver {
return (arguments, usingResponseFile)
}

public func resolveArgumentList(for job: Job, useResponseFiles: ResponseFileHandling = .heuristic)
throws -> ResolvedCommandLine {
let tool = try resolve(.path(job.tool))
let resolvedArguments = [tool] + (try resolveArgumentList(for: job.commandLine))
var actualArguments = resolvedArguments
let usingResponseFile = try createResponseFileIfNeeded(for: job, resolvedArguments: &actualArguments,
useResponseFiles: useResponseFiles)
return usingResponseFile ? .usingResponseFile(resolved: actualArguments,
responseFileContents: resolvedArguments)
: .plain(actualArguments)
}

public func resolveArgumentList(for commandLine: [Job.ArgTemplate]) throws -> [String] {
return try commandLine.map { try resolve($0) }
}
Expand Down Expand Up @@ -189,14 +207,13 @@ public final class ArgsResolver {
(job.supportsResponseFiles && !commandLineFitsWithinSystemLimits(path: resolvedArguments[0], args: resolvedArguments)) {
assert(!forceResponseFiles || job.supportsResponseFiles,
"Platform does not support response files for job: \(job)")
// Match the integrated driver's behavior, which uses response file names of the form "arguments-[0-9a-zA-Z].resp".
let hash = SHA256().hash(resolvedArguments.joined(separator: " ")).hexadecimalRepresentation
let responseFilePath = temporaryDirectory.appending(component: "arguments-\(hash).resp")
let uuid = UUID().uuidString
let responseFilePath = temporaryDirectory.appending(component: "arguments-\(uuid).resp")

// FIXME: Need a way to support this for distributed build systems...
if let absPath = responseFilePath.absolutePath {
let argumentBytes = ByteString(resolvedArguments[2...].map { $0.spm_shellEscaped() }.joined(separator: "\n").utf8)
try fileSystem.writeFileContents(absPath, bytes: argumentBytes, atomically: true)
try fileSystem.writeFileContents(absPath, bytes: argumentBytes)
resolvedArguments = [resolvedArguments[0], resolvedArguments[1], "@\(absPath.pathString)"]
}

Expand Down
43 changes: 22 additions & 21 deletions Tests/SwiftDriverTests/SwiftDriverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1806,37 +1806,38 @@ final class SwiftDriverTests: XCTestCase {
}
}

// No response file
// Response file query with full command-line API
do {
var driver = try Driver(args: ["swift"] + ["foo.swift"])
let source = try AbsolutePath(validating: "/foo.swift")
var driver = try Driver(args: ["swift"] + [source.nativePathString(escaped: false)])
let jobs = try driver.planBuild()
XCTAssertEqual(jobs.count, 1)
XCTAssertEqual(jobs[0].kind, .interpret)
let interpretJob = jobs[0]
let resolver = try ArgsResolver(fileSystem: localFileSystem)
let resolvedArgs: [String] = try resolver.resolveArgumentList(for: interpretJob)
XCTAssertFalse(resolvedArgs.contains { $0.hasPrefix("@") })
let resolved: ResolvedCommandLine = try resolver.resolveArgumentList(for: interpretJob, useResponseFiles: .forced)
guard case .usingResponseFile(resolved: let resolvedArgs, responseFileContents: let contents) = resolved else {
XCTFail("Argument wasn't a response file")
return
}
XCTAssertEqual(resolvedArgs.count, 3)
XCTAssertEqual(resolvedArgs[1], "-frontend")
XCTAssertEqual(resolvedArgs[2].first, "@")

XCTAssertTrue(contents.contains(subsequence: ["-frontend", "-interpret"]))
XCTAssertTrue(contents.contains(subsequence: ["-module-name", "foo"]))
}
}

func testResponseFileDeterministicNaming() throws {
#if !os(macOS)
try XCTSkipIf(true, "Test assumes macOS response file quoting behavior")
#endif
// No response file
do {
let testJob = Job(moduleName: "Foo",
kind: .compile,
tool: .init(path: try AbsolutePath(validating: "/swiftc"), supportsResponseFiles: true),
commandLine: (1...20000).map { .flag("-DTEST_\($0)") },
inputs: [],
primaryInputs: [],
outputs: [])
var driver = try Driver(args: ["swift"] + ["foo.swift"])
let jobs = try driver.planBuild()
XCTAssertEqual(jobs.count, 1)
XCTAssertEqual(jobs[0].kind, .interpret)
let interpretJob = jobs[0]
let resolver = try ArgsResolver(fileSystem: localFileSystem)
let resolvedArgs: [String] = try resolver.resolveArgumentList(for: testJob)
XCTAssertEqual(resolvedArgs.count, 3)
XCTAssertEqual(resolvedArgs[2].first, "@")
let responseFilePath = try AbsolutePath(validating: String(resolvedArgs[2].dropFirst()))
XCTAssertEqual(responseFilePath.basename, "arguments-847d15e70d97df7c18033735497ca8dcc4441f461d5a9c2b764b127004524e81.resp")
let resolvedArgs: [String] = try resolver.resolveArgumentList(for: interpretJob)
XCTAssertFalse(resolvedArgs.contains { $0.hasPrefix("@") })
}
}

Expand Down