Skip to content

Commit dae1841

Browse files
committed
Make swift driver friendlier to relative paths
Add handling for relative paths in output file map path, resonse file paths, recorded input modification dates.
1 parent c835d02 commit dae1841

File tree

6 files changed

+152
-28
lines changed

6 files changed

+152
-28
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -252,17 +252,15 @@ public struct Driver {
252252
self.inputFiles = inputFiles
253253
self.recordedInputModificationDates = .init(uniqueKeysWithValues:
254254
Set(inputFiles).compactMap {
255-
if case .absolute(let absolutePath) = $0.file,
256-
let modTime = try? localFileSystem.getFileInfo(absolutePath).modTime {
257-
return ($0, modTime)
258-
}
259-
return nil
255+
guard let modTime = try? localFileSystem
256+
.getFileInfo($0.file).modTime else { return nil }
257+
return ($0, modTime)
260258
})
261259

262260
let outputFileMap: OutputFileMap?
263261
// Initialize an empty output file map, which will be populated when we start creating jobs.
264262
if let outputFileMapArg = parsedOptions.getLastArgument(.outputFileMap)?.asSingle {
265-
let path = try AbsolutePath(validating: outputFileMapArg)
263+
let path = try VirtualPath(path: outputFileMapArg)
266264
outputFileMap = try .load(file: path, diagnosticEngine: diagnosticEngine)
267265
} else {
268266
outputFileMap = nil
@@ -504,13 +502,13 @@ extension Driver {
504502
private static func expandResponseFiles(
505503
_ args: [String],
506504
diagnosticsEngine: DiagnosticsEngine,
507-
visitedResponseFiles: inout Set<AbsolutePath>
505+
visitedResponseFiles: inout Set<VirtualPath>
508506
) throws -> [String] {
509507
var result: [String] = []
510508

511509
// Go through each arg and add arguments from response files.
512510
for arg in args {
513-
if arg.first == "@", let responseFile = try? AbsolutePath(validating: String(arg.dropFirst())) {
511+
if arg.first == "@", let responseFile = try? VirtualPath(path: String(arg.dropFirst())) {
514512
// Guard against infinite parsing loop.
515513
guard visitedResponseFiles.insert(responseFile).inserted else {
516514
diagnosticsEngine.emit(.warn_recursive_response_file(responseFile))
@@ -536,7 +534,7 @@ extension Driver {
536534
_ args: [String],
537535
diagnosticsEngine: DiagnosticsEngine
538536
) throws -> [String] {
539-
var visitedResponseFiles = Set<AbsolutePath>()
537+
var visitedResponseFiles = Set<VirtualPath>()
540538
return try expandResponseFiles(args, diagnosticsEngine: diagnosticsEngine, visitedResponseFiles: &visitedResponseFiles)
541539
}
542540
}
@@ -694,7 +692,7 @@ extension Driver {
694692
}
695693

696694
extension Diagnostic.Message {
697-
static func warn_recursive_response_file(_ path: AbsolutePath) -> Diagnostic.Message {
695+
static func warn_recursive_response_file(_ path: VirtualPath) -> Diagnostic.Message {
698696
.warning("response file '\(path)' is recursively expanded")
699697
}
700698

@@ -837,16 +835,8 @@ extension Driver {
837835
}
838836

839837
// Resolve the input file.
840-
let file: VirtualPath
841-
let fileExtension: String
842-
if let absolute = try? AbsolutePath(validating: input) {
843-
file = .absolute(absolute)
844-
fileExtension = absolute.extension ?? ""
845-
} else {
846-
let relative = try RelativePath(validating: input)
847-
fileExtension = relative.extension ?? ""
848-
file = .relative(relative)
849-
}
838+
let file = try VirtualPath(path: input)
839+
let fileExtension = file.extension ?? ""
850840

851841
// Determine the type of the input file based on its extension.
852842
// If we don't recognize the extension, treat it as an object file.

Sources/SwiftDriver/Driver/OutputFileMap.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public struct OutputFileMap: Equatable {
6868

6969
/// Load the output file map at the given path.
7070
public static func load(
71-
file: AbsolutePath,
71+
file: VirtualPath,
7272
diagnosticEngine: DiagnosticsEngine
7373
) throws -> OutputFileMap {
7474
// Load and decode the file.

Sources/SwiftDriver/Incremental Compilation/IncrementalCompilation.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Foundation
1616
public struct IncrementalCompilation {
1717
public let showIncrementalBuildDecisions: Bool
1818
public let enableIncrementalBuild: Bool
19-
public let buildRecordPath: AbsolutePath?
19+
public let buildRecordPath: VirtualPath?
2020
public let outputBuildRecordForModuleOnlyBuild: Bool
2121
public let argsHash: String
2222
public let lastBuildTime: Date
@@ -118,14 +118,14 @@ public struct IncrementalCompilation {
118118
outputFileMap: OutputFileMap?,
119119
compilerOutputType: FileType?,
120120
diagnosticEngine: DiagnosticsEngine?
121-
) -> AbsolutePath? {
121+
) -> VirtualPath? {
122122
// FIXME: This should work without an output file map. We should have
123123
// another way to specify a build record and where to put intermediates.
124124
guard let ofm = outputFileMap else {
125125
diagnosticEngine.map { $0.emit(.warning_incremental_requires_output_file_map) }
126126
return nil
127127
}
128-
guard let partialBuildRecordPath = ofm.existingOutputForSingleInput(outputType: .swiftDeps)?.absolutePath
128+
guard let partialBuildRecordPath = ofm.existingOutputForSingleInput(outputType: .swiftDeps)
129129
else {
130130
diagnosticEngine.map { $0.emit(.warning_incremental_requires_build_record_entry) }
131131
return nil
@@ -134,7 +134,7 @@ public struct IncrementalCompilation {
134134
// '~moduleonly'. So that module-only mode doesn't mess up build-record
135135
// file for full compilation.
136136
return compilerOutputType == .swiftModule
137-
? AbsolutePath(partialBuildRecordPath.pathString + "~moduleonly")
137+
? partialBuildRecordPath.appendingToBaseName("~moduleonly")
138138
: partialBuildRecordPath
139139
}
140140

Sources/SwiftDriver/Incremental Compilation/InputIInfoMap.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public extension InputInfoMap {
130130
argsHash: String,
131131
lastBuildTime: Date,
132132
inputFiles: [TypedVirtualPath],
133-
buildRecordPath: AbsolutePath,
133+
buildRecordPath: VirtualPath,
134134
showIncrementalBuildDecisions: Bool,
135135
diagnosticEngine: DiagnosticsEngine
136136
) -> Self? {

Sources/SwiftDriver/Utilities/VirtualPath.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,23 @@ public enum VirtualPath: Hashable {
109109
return self
110110
}
111111
}
112+
113+
/// Returns the virtual path with an additional suffix appended to base name.
114+
///
115+
/// This should not be used with `.standardInput` or `.standardOutput`.
116+
public func appendingToBaseName(_ suffix: String) -> VirtualPath {
117+
switch self {
118+
case let .absolute(path):
119+
return .absolute(AbsolutePath(path.pathString + suffix))
120+
case let .relative(path):
121+
return .relative(RelativePath(path.pathString + suffix))
122+
case let .temporary(path):
123+
return .temporary(RelativePath(path.pathString + suffix))
124+
case .standardInput, .standardOutput:
125+
assertionFailure("Can't append path component to standard in/out")
126+
return self
127+
}
128+
}
112129
}
113130

114131
extension VirtualPath: Codable {
@@ -193,3 +210,41 @@ extension VirtualPath {
193210
return try VirtualPath(path: pathString.appendingFileTypeExtension(fileType))
194211
}
195212
}
213+
214+
enum FileSystemError: Swift.Error {
215+
case noCurrentWorkingDirectory
216+
case cannotResolveTempPath(RelativePath)
217+
case cannotResolveStandardInput
218+
case cannotResolveStandardOutput
219+
}
220+
221+
extension TSCBasic.FileSystem {
222+
private func resolvingVirtualPath<T>(
223+
_ path: VirtualPath,
224+
apply f: (AbsolutePath) throws -> T
225+
) throws -> T {
226+
switch path {
227+
case let .absolute(absPath):
228+
return try f(absPath)
229+
case let .relative(relPath):
230+
guard let cwd = currentWorkingDirectory else {
231+
throw FileSystemError.noCurrentWorkingDirectory
232+
}
233+
return try f(.init(cwd, relPath))
234+
case let .temporary(relPath):
235+
throw FileSystemError.cannotResolveTempPath(relPath)
236+
case .standardInput:
237+
throw FileSystemError.cannotResolveStandardInput
238+
case .standardOutput:
239+
throw FileSystemError.cannotResolveStandardOutput
240+
}
241+
}
242+
243+
func readFileContents(_ path: VirtualPath) throws -> ByteString {
244+
try resolvingVirtualPath(path, apply: readFileContents)
245+
}
246+
247+
func getFileInfo(_ path: VirtualPath) throws -> TSCBasic.FileInfo {
248+
try resolvingVirtualPath(path, apply: getFileInfo)
249+
}
250+
}

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,28 @@ final class SwiftDriverTests: XCTestCase {
207207
XCTAssertEqual(driver4.inputFiles, [ TypedVirtualPath(file: .standardInput, type: .swift )])
208208
}
209209

210+
func testRecordedInputModificationDates() throws {
211+
try withTemporaryDirectory { path in
212+
guard let cwd = localFileSystem
213+
.currentWorkingDirectory else { fatalError() }
214+
let main = path.appending(component: "main.swift")
215+
let util = path.appending(component: "util.swift")
216+
let utilRelative = util.relative(to: cwd)
217+
try localFileSystem.writeFileContents(main) { $0 <<< "print(hi)" }
218+
try localFileSystem.writeFileContents(util) { $0 <<< "let hi = \"hi\"" }
219+
220+
let mainMDate = try localFileSystem.getFileInfo(main).modTime
221+
let utilMDate = try localFileSystem.getFileInfo(util).modTime
222+
let driver = try Driver(args: [
223+
"swiftc", main.pathString, utilRelative.pathString,
224+
])
225+
XCTAssertEqual(driver.recordedInputModificationDates, [
226+
.init(file: .absolute(main), type: .swift) : mainMDate,
227+
.init(file: .relative(utilRelative), type: .swift) : utilMDate,
228+
])
229+
}
230+
}
231+
210232
func testPrimaryOutputKinds() throws {
211233
let driver1 = try Driver(args: ["swiftc", "foo.swift", "-emit-module"])
212234
XCTAssertEqual(driver1.compilerOutputType, .swiftModule)
@@ -453,7 +475,7 @@ final class SwiftDriverTests: XCTestCase {
453475
try withTemporaryFile { file in
454476
try assertNoDiagnostics { diags in
455477
try localFileSystem.writeFileContents(file.path) { $0 <<< contents }
456-
let outputFileMap = try OutputFileMap.load(file: file.path, diagnosticEngine: diags)
478+
let outputFileMap = try OutputFileMap.load(file: .absolute(file.path), diagnosticEngine: diags)
457479

458480
let object = try outputFileMap.getOutput(inputFile: .init(path: "/tmp/foo/Sources/foo/foo.swift"), outputType: .object)
459481
XCTAssertEqual(object.name, "/tmp/foo/.build/x86_64-apple-macosx/debug/foo.build/foo.swift.o")
@@ -489,7 +511,7 @@ final class SwiftDriverTests: XCTestCase {
489511
try sampleOutputFileMap.store(file: file.path, diagnosticEngine: DiagnosticsEngine())
490512
let contentsForDebugging = try localFileSystem.readFileContents(file.path).cString
491513
_ = contentsForDebugging
492-
let recoveredOutputFileMap = try OutputFileMap.load(file: file.path, diagnosticEngine: DiagnosticsEngine())
514+
let recoveredOutputFileMap = try OutputFileMap.load(file: .absolute(file.path), diagnosticEngine: DiagnosticsEngine())
493515
XCTAssertEqual(sampleOutputFileMap, recoveredOutputFileMap)
494516
}
495517
}
@@ -532,6 +554,38 @@ final class SwiftDriverTests: XCTestCase {
532554
XCTAssertEqual(expectedOutputFileMap, resolvedOutputFileMap)
533555
}
534556

557+
func testOutputFileMapRelativePathArg() throws {
558+
try withTemporaryDirectory { path in
559+
guard let cwd = localFileSystem
560+
.currentWorkingDirectory else { fatalError() }
561+
let outputFileMap = path.appending(component: "outputFileMap.json")
562+
try localFileSystem.writeFileContents(outputFileMap) {
563+
$0 <<< """
564+
{
565+
"": {
566+
"swift-dependencies": "build/master.swiftdeps"
567+
},
568+
"main.swift": {
569+
"object": "build/main.o",
570+
"dependencies": "build/main.o.d"
571+
},
572+
"util.swift": {
573+
"object": "build/util.o",
574+
"dependencies": "build/util.o.d"
575+
}
576+
}
577+
"""
578+
}
579+
let outputFileMapRelative = outputFileMap.relative(to: cwd).pathString
580+
// FIXME: Needs a better way to check that outputFileMap correctly loaded
581+
XCTAssertNoThrow(try Driver(args: [
582+
"swiftc",
583+
"--output-file-map", outputFileMapRelative,
584+
"main.swift", "util.swift",
585+
]))
586+
}
587+
}
588+
535589
func testResponseFileExpansion() throws {
536590
try withTemporaryDirectory { path in
537591
let diags = DiagnosticsEngine()
@@ -640,6 +694,31 @@ final class SwiftDriverTests: XCTestCase {
640694
}
641695
}
642696

697+
func testResponseFile() throws {
698+
try withTemporaryDirectory { path in
699+
guard let cwd = localFileSystem
700+
.currentWorkingDirectory else { fatalError() }
701+
let responseFile1 = path.appending(component: "fileList1.rsp")
702+
let responseFile2 = path.appending(component: "fileList2.rsp")
703+
try localFileSystem.writeFileContents(responseFile1) {
704+
$0 <<< "from1stList.swift"
705+
}
706+
try localFileSystem.writeFileContents(responseFile2) {
707+
$0 <<< "from2ndList.swift"
708+
}
709+
let responseFile2Relative = responseFile2.relative(to: cwd)
710+
let driver = try Driver(args: [
711+
"swiftc",
712+
"@\(responseFile1.pathString)",
713+
"@\(responseFile2Relative)",
714+
])
715+
XCTAssertEqual(driver.inputFiles, [
716+
.init(file: .relative(.init("from1stList.swift")), type: .swift),
717+
.init(file: .relative(.init("from2ndList.swift")), type: .swift),
718+
])
719+
}
720+
}
721+
643722
func testLinking() throws {
644723
var env = ProcessEnv.vars
645724
env["SWIFT_DRIVER_TESTS_ENABLE_EXEC_PATH_FALLBACK"] = "1"

0 commit comments

Comments
 (0)