Skip to content

Commit 0aa2178

Browse files
authored
Merge pull request #73 from zienag/relative_paths_handling
Make swift driver friendlier to relative paths
2 parents 49c5998 + eef0bfd commit 0aa2178

File tree

6 files changed

+123
-24
lines changed

6 files changed

+123
-24
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -274,17 +274,15 @@ public struct Driver {
274274
self.inputFiles = inputFiles
275275
self.recordedInputModificationDates = .init(uniqueKeysWithValues:
276276
Set(inputFiles).compactMap {
277-
if case .absolute(let absolutePath) = $0.file,
278-
let modTime = try? localFileSystem.getFileInfo(absolutePath).modTime {
279-
return ($0, modTime)
280-
}
281-
return nil
277+
guard let modTime = try? localFileSystem
278+
.getFileInfo($0.file).modTime else { return nil }
279+
return ($0, modTime)
282280
})
283281

284282
let outputFileMap: OutputFileMap?
285283
// Initialize an empty output file map, which will be populated when we start creating jobs.
286284
if let outputFileMapArg = parsedOptions.getLastArgument(.outputFileMap)?.asSingle {
287-
let path = try AbsolutePath(validating: outputFileMapArg)
285+
let path = try VirtualPath(path: outputFileMapArg)
288286
outputFileMap = try .load(file: path, diagnosticEngine: diagnosticEngine)
289287
} else {
290288
outputFileMap = nil
@@ -859,16 +857,8 @@ extension Driver {
859857
}
860858

861859
// Resolve the input file.
862-
let file: VirtualPath
863-
let fileExtension: String
864-
if let absolute = try? AbsolutePath(validating: input) {
865-
file = .absolute(absolute)
866-
fileExtension = absolute.extension ?? ""
867-
} else {
868-
let relative = try RelativePath(validating: input)
869-
fileExtension = relative.extension ?? ""
870-
file = .relative(relative)
871-
}
860+
let file = try VirtualPath(path: input)
861+
let fileExtension = file.extension ?? ""
872862

873863
// Determine the type of the input file based on its extension.
874864
// 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
@@ -17,7 +17,7 @@ import SwiftOptions
1717
public struct IncrementalCompilation {
1818
public let showIncrementalBuildDecisions: Bool
1919
public let enableIncrementalBuild: Bool
20-
public let buildRecordPath: AbsolutePath?
20+
public let buildRecordPath: VirtualPath?
2121
public let outputBuildRecordForModuleOnlyBuild: Bool
2222
public let argsHash: String
2323
public let lastBuildTime: Date
@@ -119,14 +119,14 @@ public struct IncrementalCompilation {
119119
outputFileMap: OutputFileMap?,
120120
compilerOutputType: FileType?,
121121
diagnosticEngine: DiagnosticsEngine?
122-
) -> AbsolutePath? {
122+
) -> VirtualPath? {
123123
// FIXME: This should work without an output file map. We should have
124124
// another way to specify a build record and where to put intermediates.
125125
guard let ofm = outputFileMap else {
126126
diagnosticEngine.map { $0.emit(.warning_incremental_requires_output_file_map) }
127127
return nil
128128
}
129-
guard let partialBuildRecordPath = ofm.existingOutputForSingleInput(outputType: .swiftDeps)?.absolutePath
129+
guard let partialBuildRecordPath = ofm.existingOutputForSingleInput(outputType: .swiftDeps)
130130
else {
131131
diagnosticEngine.map { $0.emit(.warning_incremental_requires_build_record_entry) }
132132
return nil
@@ -135,7 +135,7 @@ public struct IncrementalCompilation {
135135
// '~moduleonly'. So that module-only mode doesn't mess up build-record
136136
// file for full compilation.
137137
return compilerOutputType == .swiftModule
138-
? AbsolutePath(partialBuildRecordPath.pathString + "~moduleonly")
138+
? partialBuildRecordPath.appendingToBaseName("~moduleonly")
139139
: partialBuildRecordPath
140140
}
141141

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: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,28 @@ final class SwiftDriverTests: XCTestCase {
198198
XCTAssertEqual(driver4.inputFiles, [ TypedVirtualPath(file: .standardInput, type: .swift )])
199199
}
200200

201+
func testRecordedInputModificationDates() throws {
202+
try withTemporaryDirectory { path in
203+
guard let cwd = localFileSystem
204+
.currentWorkingDirectory else { fatalError() }
205+
let main = path.appending(component: "main.swift")
206+
let util = path.appending(component: "util.swift")
207+
let utilRelative = util.relative(to: cwd)
208+
try localFileSystem.writeFileContents(main) { $0 <<< "print(hi)" }
209+
try localFileSystem.writeFileContents(util) { $0 <<< "let hi = \"hi\"" }
210+
211+
let mainMDate = try localFileSystem.getFileInfo(main).modTime
212+
let utilMDate = try localFileSystem.getFileInfo(util).modTime
213+
let driver = try Driver(args: [
214+
"swiftc", main.pathString, utilRelative.pathString,
215+
])
216+
XCTAssertEqual(driver.recordedInputModificationDates, [
217+
.init(file: .absolute(main), type: .swift) : mainMDate,
218+
.init(file: .relative(utilRelative), type: .swift) : utilMDate,
219+
])
220+
}
221+
}
222+
201223
func testPrimaryOutputKinds() throws {
202224
let driver1 = try Driver(args: ["swiftc", "foo.swift", "-emit-module"])
203225
XCTAssertEqual(driver1.compilerOutputType, .swiftModule)
@@ -444,7 +466,7 @@ final class SwiftDriverTests: XCTestCase {
444466
try withTemporaryFile { file in
445467
try assertNoDiagnostics { diags in
446468
try localFileSystem.writeFileContents(file.path) { $0 <<< contents }
447-
let outputFileMap = try OutputFileMap.load(file: file.path, diagnosticEngine: diags)
469+
let outputFileMap = try OutputFileMap.load(file: .absolute(file.path), diagnosticEngine: diags)
448470

449471
let object = try outputFileMap.getOutput(inputFile: .init(path: "/tmp/foo/Sources/foo/foo.swift"), outputType: .object)
450472
XCTAssertEqual(object.name, "/tmp/foo/.build/x86_64-apple-macosx/debug/foo.build/foo.swift.o")
@@ -480,7 +502,7 @@ final class SwiftDriverTests: XCTestCase {
480502
try sampleOutputFileMap.store(file: file.path, diagnosticEngine: DiagnosticsEngine())
481503
let contentsForDebugging = try localFileSystem.readFileContents(file.path).cString
482504
_ = contentsForDebugging
483-
let recoveredOutputFileMap = try OutputFileMap.load(file: file.path, diagnosticEngine: DiagnosticsEngine())
505+
let recoveredOutputFileMap = try OutputFileMap.load(file: .absolute(file.path), diagnosticEngine: DiagnosticsEngine())
484506
XCTAssertEqual(sampleOutputFileMap, recoveredOutputFileMap)
485507
}
486508
}
@@ -523,6 +545,38 @@ final class SwiftDriverTests: XCTestCase {
523545
XCTAssertEqual(expectedOutputFileMap, resolvedOutputFileMap)
524546
}
525547

548+
func testOutputFileMapRelativePathArg() throws {
549+
try withTemporaryDirectory { path in
550+
guard let cwd = localFileSystem
551+
.currentWorkingDirectory else { fatalError() }
552+
let outputFileMap = path.appending(component: "outputFileMap.json")
553+
try localFileSystem.writeFileContents(outputFileMap) {
554+
$0 <<< """
555+
{
556+
"": {
557+
"swift-dependencies": "build/master.swiftdeps"
558+
},
559+
"main.swift": {
560+
"object": "build/main.o",
561+
"dependencies": "build/main.o.d"
562+
},
563+
"util.swift": {
564+
"object": "build/util.o",
565+
"dependencies": "build/util.o.d"
566+
}
567+
}
568+
"""
569+
}
570+
let outputFileMapRelative = outputFileMap.relative(to: cwd).pathString
571+
// FIXME: Needs a better way to check that outputFileMap correctly loaded
572+
XCTAssertNoThrow(try Driver(args: [
573+
"swiftc",
574+
"--output-file-map", outputFileMapRelative,
575+
"main.swift", "util.swift",
576+
]))
577+
}
578+
}
579+
526580
func testResponseFileExpansion() throws {
527581
try withTemporaryDirectory { path in
528582
let diags = DiagnosticsEngine()

0 commit comments

Comments
 (0)