diff --git a/Sources/SwiftDriver/Execution/ArgsResolver.swift b/Sources/SwiftDriver/Execution/ArgsResolver.swift index 0e5470988..e8086dbd8 100644 --- a/Sources/SwiftDriver/Execution/ArgsResolver.swift +++ b/Sources/SwiftDriver/Execution/ArgsResolver.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import class Foundation.NSLock +import struct Foundation.UUID import func TSCBasic.withTemporaryDirectory import protocol TSCBasic.FileSystem @@ -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. @@ -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) } } @@ -183,14 +201,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)"] } diff --git a/Tests/SwiftDriverTests/SwiftDriverTests.swift b/Tests/SwiftDriverTests/SwiftDriverTests.swift index 8b46640f7..ec7427571 100644 --- a/Tests/SwiftDriverTests/SwiftDriverTests.swift +++ b/Tests/SwiftDriverTests/SwiftDriverTests.swift @@ -1823,37 +1823,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("@") }) } }