Skip to content

Make swift driver friendlier to relative paths #73

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 6, 2020
Merged
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
22 changes: 6 additions & 16 deletions Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -252,17 +252,15 @@ public struct Driver {
self.inputFiles = inputFiles
self.recordedInputModificationDates = .init(uniqueKeysWithValues:
Set(inputFiles).compactMap {
if case .absolute(let absolutePath) = $0.file,
let modTime = try? localFileSystem.getFileInfo(absolutePath).modTime {
return ($0, modTime)
}
return nil
guard let modTime = try? localFileSystem
.getFileInfo($0.file).modTime else { return nil }
return ($0, modTime)
})

let outputFileMap: OutputFileMap?
// Initialize an empty output file map, which will be populated when we start creating jobs.
if let outputFileMapArg = parsedOptions.getLastArgument(.outputFileMap)?.asSingle {
let path = try AbsolutePath(validating: outputFileMapArg)
let path = try VirtualPath(path: outputFileMapArg)
outputFileMap = try .load(file: path, diagnosticEngine: diagnosticEngine)
} else {
outputFileMap = nil
Expand Down Expand Up @@ -837,16 +835,8 @@ extension Driver {
}

// Resolve the input file.
let file: VirtualPath
let fileExtension: String
if let absolute = try? AbsolutePath(validating: input) {
file = .absolute(absolute)
fileExtension = absolute.extension ?? ""
} else {
let relative = try RelativePath(validating: input)
fileExtension = relative.extension ?? ""
file = .relative(relative)
}
let file = try VirtualPath(path: input)
let fileExtension = file.extension ?? ""

// Determine the type of the input file based on its extension.
// If we don't recognize the extension, treat it as an object file.
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftDriver/Driver/OutputFileMap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public struct OutputFileMap: Equatable {

/// Load the output file map at the given path.
public static func load(
file: AbsolutePath,
file: VirtualPath,
diagnosticEngine: DiagnosticsEngine
) throws -> OutputFileMap {
// Load and decode the file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Foundation
public struct IncrementalCompilation {
public let showIncrementalBuildDecisions: Bool
public let enableIncrementalBuild: Bool
public let buildRecordPath: AbsolutePath?
public let buildRecordPath: VirtualPath?
public let outputBuildRecordForModuleOnlyBuild: Bool
public let argsHash: String
public let lastBuildTime: Date
Expand Down Expand Up @@ -118,14 +118,14 @@ public struct IncrementalCompilation {
outputFileMap: OutputFileMap?,
compilerOutputType: FileType?,
diagnosticEngine: DiagnosticsEngine?
) -> AbsolutePath? {
) -> VirtualPath? {
// FIXME: This should work without an output file map. We should have
// another way to specify a build record and where to put intermediates.
guard let ofm = outputFileMap else {
diagnosticEngine.map { $0.emit(.warning_incremental_requires_output_file_map) }
return nil
}
guard let partialBuildRecordPath = ofm.existingOutputForSingleInput(outputType: .swiftDeps)?.absolutePath
guard let partialBuildRecordPath = ofm.existingOutputForSingleInput(outputType: .swiftDeps)
else {
diagnosticEngine.map { $0.emit(.warning_incremental_requires_build_record_entry) }
return nil
Expand All @@ -134,7 +134,7 @@ public struct IncrementalCompilation {
// '~moduleonly'. So that module-only mode doesn't mess up build-record
// file for full compilation.
return compilerOutputType == .swiftModule
? AbsolutePath(partialBuildRecordPath.pathString + "~moduleonly")
? partialBuildRecordPath.appendingToBaseName("~moduleonly")
: partialBuildRecordPath
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public extension InputInfoMap {
argsHash: String,
lastBuildTime: Date,
inputFiles: [TypedVirtualPath],
buildRecordPath: AbsolutePath,
buildRecordPath: VirtualPath,
showIncrementalBuildDecisions: Bool,
diagnosticEngine: DiagnosticsEngine
) -> Self? {
Expand Down
55 changes: 55 additions & 0 deletions Sources/SwiftDriver/Utilities/VirtualPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,23 @@ public enum VirtualPath: Hashable {
return self
}
}

/// Returns the virtual path with an additional suffix appended to base name.
///
/// This should not be used with `.standardInput` or `.standardOutput`.
public func appendingToBaseName(_ suffix: String) -> VirtualPath {
switch self {
case let .absolute(path):
return .absolute(AbsolutePath(path.pathString + suffix))
case let .relative(path):
return .relative(RelativePath(path.pathString + suffix))
case let .temporary(path):
return .temporary(RelativePath(path.pathString + suffix))
case .standardInput, .standardOutput:
assertionFailure("Can't append path component to standard in/out")
return self
}
}
}

extension VirtualPath: Codable {
Expand Down Expand Up @@ -193,3 +210,41 @@ extension VirtualPath {
return try VirtualPath(path: pathString.appendingFileTypeExtension(fileType))
}
}

enum FileSystemError: Swift.Error {
case noCurrentWorkingDirectory
case cannotResolveTempPath(RelativePath)
case cannotResolveStandardInput
case cannotResolveStandardOutput
}

extension TSCBasic.FileSystem {
private func resolvingVirtualPath<T>(
_ path: VirtualPath,
apply f: (AbsolutePath) throws -> T
) throws -> T {
switch path {
case let .absolute(absPath):
return try f(absPath)
case let .relative(relPath):
guard let cwd = currentWorkingDirectory else {
throw FileSystemError.noCurrentWorkingDirectory
}
return try f(.init(cwd, relPath))
case let .temporary(relPath):
throw FileSystemError.cannotResolveTempPath(relPath)
case .standardInput:
throw FileSystemError.cannotResolveStandardInput
case .standardOutput:
throw FileSystemError.cannotResolveStandardOutput
}
}

func readFileContents(_ path: VirtualPath) throws -> ByteString {
try resolvingVirtualPath(path, apply: readFileContents)
}

func getFileInfo(_ path: VirtualPath) throws -> TSCBasic.FileInfo {
try resolvingVirtualPath(path, apply: getFileInfo)
}
}
58 changes: 56 additions & 2 deletions Tests/SwiftDriverTests/SwiftDriverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,28 @@ final class SwiftDriverTests: XCTestCase {
XCTAssertEqual(driver4.inputFiles, [ TypedVirtualPath(file: .standardInput, type: .swift )])
}

func testRecordedInputModificationDates() throws {
try withTemporaryDirectory { path in
guard let cwd = localFileSystem
.currentWorkingDirectory else { fatalError() }
let main = path.appending(component: "main.swift")
let util = path.appending(component: "util.swift")
let utilRelative = util.relative(to: cwd)
try localFileSystem.writeFileContents(main) { $0 <<< "print(hi)" }
try localFileSystem.writeFileContents(util) { $0 <<< "let hi = \"hi\"" }

let mainMDate = try localFileSystem.getFileInfo(main).modTime
let utilMDate = try localFileSystem.getFileInfo(util).modTime
let driver = try Driver(args: [
"swiftc", main.pathString, utilRelative.pathString,
])
XCTAssertEqual(driver.recordedInputModificationDates, [
.init(file: .absolute(main), type: .swift) : mainMDate,
.init(file: .relative(utilRelative), type: .swift) : utilMDate,
])
}
}

func testPrimaryOutputKinds() throws {
let driver1 = try Driver(args: ["swiftc", "foo.swift", "-emit-module"])
XCTAssertEqual(driver1.compilerOutputType, .swiftModule)
Expand Down Expand Up @@ -453,7 +475,7 @@ final class SwiftDriverTests: XCTestCase {
try withTemporaryFile { file in
try assertNoDiagnostics { diags in
try localFileSystem.writeFileContents(file.path) { $0 <<< contents }
let outputFileMap = try OutputFileMap.load(file: file.path, diagnosticEngine: diags)
let outputFileMap = try OutputFileMap.load(file: .absolute(file.path), diagnosticEngine: diags)

let object = try outputFileMap.getOutput(inputFile: .init(path: "/tmp/foo/Sources/foo/foo.swift"), outputType: .object)
XCTAssertEqual(object.name, "/tmp/foo/.build/x86_64-apple-macosx/debug/foo.build/foo.swift.o")
Expand Down Expand Up @@ -489,7 +511,7 @@ final class SwiftDriverTests: XCTestCase {
try sampleOutputFileMap.store(file: file.path, diagnosticEngine: DiagnosticsEngine())
let contentsForDebugging = try localFileSystem.readFileContents(file.path).cString
_ = contentsForDebugging
let recoveredOutputFileMap = try OutputFileMap.load(file: file.path, diagnosticEngine: DiagnosticsEngine())
let recoveredOutputFileMap = try OutputFileMap.load(file: .absolute(file.path), diagnosticEngine: DiagnosticsEngine())
XCTAssertEqual(sampleOutputFileMap, recoveredOutputFileMap)
}
}
Expand Down Expand Up @@ -532,6 +554,38 @@ final class SwiftDriverTests: XCTestCase {
XCTAssertEqual(expectedOutputFileMap, resolvedOutputFileMap)
}

func testOutputFileMapRelativePathArg() throws {
try withTemporaryDirectory { path in
guard let cwd = localFileSystem
.currentWorkingDirectory else { fatalError() }
let outputFileMap = path.appending(component: "outputFileMap.json")
try localFileSystem.writeFileContents(outputFileMap) {
$0 <<< """
{
"": {
"swift-dependencies": "build/master.swiftdeps"
},
"main.swift": {
"object": "build/main.o",
"dependencies": "build/main.o.d"
},
"util.swift": {
"object": "build/util.o",
"dependencies": "build/util.o.d"
}
}
"""
}
let outputFileMapRelative = outputFileMap.relative(to: cwd).pathString
// FIXME: Needs a better way to check that outputFileMap correctly loaded
XCTAssertNoThrow(try Driver(args: [
"swiftc",
"--output-file-map", outputFileMapRelative,
"main.swift", "util.swift",
]))
}
}

func testResponseFileExpansion() throws {
try withTemporaryDirectory { path in
let diags = DiagnosticsEngine()
Expand Down