Skip to content

Only make input paths absolute when -working-directory is passed #1708

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
Oct 10, 2024
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
23 changes: 13 additions & 10 deletions Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,8 @@ public struct Driver {
case .absolute(let path):
return path
case .relative(let path):
return workingDirectory.map { AbsolutePath($0, path) }
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory
return cwd.map { AbsolutePath($0, path) }
case .standardInput, .standardOutput, .temporary, .temporaryWithKnownContents, .fileList:
fatalError("Frontend target information will never include a path of this type.")
}
Expand Down Expand Up @@ -688,14 +689,14 @@ public struct Driver {
compilerMode: compilerMode)

// Compute the working directory.
let cwd = fileSystem.currentWorkingDirectory
workingDirectory = try parsedOptions.getLastArgument(.workingDirectory).map { workingDirectoryArg in
self.workingDirectory = try parsedOptions.getLastArgument(.workingDirectory).map { workingDirectoryArg in
let cwd = fileSystem.currentWorkingDirectory
return try cwd.map{ try AbsolutePath(validating: workingDirectoryArg.asSingle, relativeTo: $0) } ?? AbsolutePath(validating: workingDirectoryArg.asSingle)
} ?? cwd

// Apply the working directory to the parsed options.
if let workingDirectory = self.workingDirectory {
try Self.applyWorkingDirectory(workingDirectory, to: &self.parsedOptions)
}
if let specifiedWorkingDir = self.workingDirectory {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the code will be cleaner (save all the ?? fileSystem.currentWorkingDirectory) if self.workingDirectory is always set regardless of if -working-directory is passed but only calls applyWorkingDirectory if the option is passed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that initially, but then the output paths (and profile data paths) end up absolute. I didn't track down where that was happening, but there seems to be multiple places in the code that would end up prefixing paths if workingDirectory was set.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe driver will think that working-directory is passed via command-line so it is passing working directory (in abs path) frontend flag plus some other flags derived from working directory?

Sounds fine with me.

// Apply the working directory to the parsed options if passed explicitly.
try Self.applyWorkingDirectory(specifiedWorkingDir, to: &self.parsedOptions)
}

let staticExecutable = parsedOptions.hasFlag(positive: .staticExecutable,
Expand Down Expand Up @@ -776,6 +777,8 @@ public struct Driver {
}

if let workingDirectory = self.workingDirectory {
// Input paths are prefixed with the working directory when specified,
// apply the same logic to the output file map keys.
self.outputFileMap = outputFileMap?.resolveRelativePaths(relativeTo: workingDirectory)
} else {
self.outputFileMap = outputFileMap
Expand Down Expand Up @@ -854,7 +857,7 @@ public struct Driver {
self.buildRecordInfo = BuildRecordInfo(
actualSwiftVersion: self.frontendTargetInfo.compilerVersion,
compilerOutputType: compilerOutputType,
workingDirectory: self.workingDirectory,
workingDirectory: self.workingDirectory ?? fileSystem.currentWorkingDirectory,
diagnosticEngine: diagnosticEngine,
fileSystem: fileSystem,
moduleOutputInfo: moduleOutputInfo,
Expand Down Expand Up @@ -3099,7 +3102,7 @@ extension Driver {
}

if let profileArgs = parsedOptions.getLastArgument(.profileUse)?.asMultiple,
let workingDirectory = workingDirectory {
let workingDirectory = workingDirectory ?? fileSystem.currentWorkingDirectory {
for profilingData in profileArgs {
if let path = try? AbsolutePath(validating: profilingData,
relativeTo: workingDirectory) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ public extension Driver {

if supportInProcessSwiftScanQueries {
var scanDiagnostics: [ScannerDiagnosticPayload] = []
guard let cwd = workingDirectory else {
guard let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory else {
throw DependencyScanningError.dependencyScanFailed("cannot determine working directory")
}
var command = try Self.itemizedJobCommand(of: preScanJob,
Expand Down Expand Up @@ -260,7 +260,7 @@ public extension Driver {

if supportInProcessSwiftScanQueries {
var scanDiagnostics: [ScannerDiagnosticPayload] = []
guard let cwd = workingDirectory else {
guard let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory else {
throw DependencyScanningError.dependencyScanFailed("cannot determine working directory")
}
var command = try Self.itemizedJobCommand(of: scannerJob,
Expand Down Expand Up @@ -298,7 +298,7 @@ public extension Driver {

if supportInProcessSwiftScanQueries {
var scanDiagnostics: [ScannerDiagnosticPayload] = []
guard let cwd = workingDirectory else {
guard let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory else {
throw DependencyScanningError.dependencyScanFailed("cannot determine working directory")
}
var command = try Self.itemizedJobCommand(of: batchScanningJob,
Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ extension Driver {
if let compilationDir = parsedOptions.getLastArgument(.fileCompilationDir)?.asSingle {
let compDirPath = try VirtualPath.intern(path: compilationDir)
try addPathArgument(VirtualPath.lookup(compDirPath), to:&commandLine, remap: jobNeedPathRemap)
} else if let cwd = workingDirectory {
} else if let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory {
let compDirPath = VirtualPath.absolute(cwd)
try addPathArgument(compDirPath, to:&commandLine, remap: jobNeedPathRemap)
}
Expand Down Expand Up @@ -837,7 +837,7 @@ extension Driver {

extension Driver {
private func getAbsolutePathFromVirtualPath(_ path: VirtualPath) -> AbsolutePath? {
guard let cwd = workingDirectory else {
guard let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory else {
return nil
}
return path.resolvedRelativePath(base: cwd).absolutePath
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftDriver/Jobs/PrintTargetInfoJob.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ extension Driver {
fileSystem: FileSystem,
workingDirectory: AbsolutePath?,
invocationCommand: [String]) throws -> FrontendTargetInfo {
let cwd = try workingDirectory ?? fileSystem.tempDirectory
let cwd = try workingDirectory ?? fileSystem.currentWorkingDirectory ?? fileSystem.tempDirectory
let compilerExecutablePath = try toolchain.resolvedTool(.swiftCompiler).path
let targetInfoData =
try libSwiftScanInstance.queryTargetInfoJSON(workingDirectory: cwd,
Expand Down
108 changes: 93 additions & 15 deletions Tests/SwiftDriverTests/SwiftDriverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,15 @@ private var testInputsPath: AbsolutePath {
}
}

func toPath(_ path: String, base: AbsolutePath = localFileSystem.currentWorkingDirectory!) throws -> VirtualPath {
return try VirtualPath(path: path).resolvedRelativePath(base: base)
func toPath(_ path: String, isRelative: Bool = true) throws -> VirtualPath {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am slightly prefer leaving the tests in the new state but add -working-directory to the driver command.

Also, would be nice to add a test to check the behavior of path arguments with/without -working-directory

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am slightly prefer leaving the tests in the new state but add -working-directory to the driver command.

For the changed tests? For testDOTFileEmission that would work, for the others it would require adding isRelative: false to all the toPath() calls, which would be a bigger change.

Also, would be nice to add a test to check the behavior of path arguments with/without -working-directory

I added testRelativeInputs on L7239 for this

if isRelative {
return VirtualPath.relative(try .init(validating: path))
}
return try VirtualPath(path: path).resolvedRelativePath(base: localFileSystem.currentWorkingDirectory!)
}

func toPathOption(_ path: String, base: AbsolutePath = localFileSystem.currentWorkingDirectory!) throws -> Job.ArgTemplate {
return .path(try toPath(path, base: base))
func toPathOption(_ path: String, isRelative: Bool = true) throws -> Job.ArgTemplate {
return .path(try toPath(path, isRelative: isRelative))
}

final class SwiftDriverTests: XCTestCase {
Expand Down Expand Up @@ -351,7 +354,7 @@ final class SwiftDriverTests: XCTestCase {
])
XCTAssertEqual(driver.recordedInputModificationDates, [
.init(file: VirtualPath.absolute(main).intern(), type: .swift) : mainMDate,
.init(file: VirtualPath.absolute(util).intern(), type: .swift) : utilMDate,
.init(file: VirtualPath.relative(utilRelative).intern(), type: .swift) : utilMDate,
])
}
}
Expand Down Expand Up @@ -1295,12 +1298,11 @@ final class SwiftDriverTests: XCTestCase {
let plannedJobs = try driver.planBuild().removingAutolinkExtractJobs()
XCTAssertEqual(plannedJobs.count, 3)
XCTAssertEqual(plannedJobs[0].kind, .compile)
XCTAssertTrue(plannedJobs[0].primaryInputs.map{ $0.file.description }.elementsEqual([rebase("foo.swift"),
rebase("bar.swift")]))
XCTAssertTrue(plannedJobs[0].primaryInputs.map{ $0.file.description }.elementsEqual(["foo.swift", "bar.swift"]))
XCTAssertTrue(plannedJobs[0].commandLine.contains("-emit-const-values-path"))
XCTAssertEqual(plannedJobs[0].outputs.filter({ $0.type == .swiftConstValues }).count, 2)
XCTAssertEqual(plannedJobs[1].kind, .compile)
XCTAssertTrue(plannedJobs[1].primaryInputs.map{ $0.file.description }.elementsEqual([rebase("baz.swift")]))
XCTAssertTrue(plannedJobs[1].primaryInputs.map{ $0.file.description }.elementsEqual(["baz.swift"]))
XCTAssertTrue(plannedJobs[1].commandLine.contains("-emit-const-values-path"))
XCTAssertEqual(plannedJobs[1].outputs.filter({ $0.type == .swiftConstValues }).count, 1)
XCTAssertEqual(plannedJobs[2].kind, .link)
Expand Down Expand Up @@ -1331,13 +1333,12 @@ final class SwiftDriverTests: XCTestCase {
let plannedJobs = try driver.planBuild().removingAutolinkExtractJobs()
XCTAssertEqual(plannedJobs.count, 3)
XCTAssertEqual(plannedJobs[0].kind, .compile)
XCTAssertTrue(plannedJobs[0].primaryInputs.map{ $0.file.description }.elementsEqual([rebase("foo.swift"),
rebase("bar.swift")]))
XCTAssertTrue(plannedJobs[0].primaryInputs.map{ $0.file.description }.elementsEqual(["foo.swift", "bar.swift"]))
XCTAssertTrue(plannedJobs[0].commandLine.contains(subsequence: [.flag("-emit-const-values-path"), .path(.absolute(try .init(validating: "/tmp/foo.build/foo.swiftconstvalues")))]))
XCTAssertTrue(plannedJobs[0].commandLine.contains(subsequence: [.flag("-emit-const-values-path"), .path(.absolute(try .init(validating: "/tmp/foo.build/bar.swiftconstvalues")))]))
XCTAssertEqual(plannedJobs[0].outputs.filter({ $0.type == .swiftConstValues }).count, 2)
XCTAssertEqual(plannedJobs[1].kind, .compile)
XCTAssertTrue(plannedJobs[1].primaryInputs.map{ $0.file.description }.elementsEqual([rebase("baz.swift")]))
XCTAssertTrue(plannedJobs[1].primaryInputs.map{ $0.file.description }.elementsEqual(["baz.swift"]))
XCTAssertTrue(plannedJobs[1].commandLine.contains("-emit-const-values-path"))
XCTAssertEqual(plannedJobs[1].outputs.filter({ $0.type == .swiftConstValues }).count, 1)
XCTAssertTrue(plannedJobs[1].commandLine.contains(subsequence: [.flag("-emit-const-values-path"), .path(.absolute(try .init(validating: "/tmp/foo.build/baz.swiftconstvalues")))]))
Expand Down Expand Up @@ -3039,10 +3040,10 @@ final class SwiftDriverTests: XCTestCase {
XCTAssertEqual(emitModuleJob.outputs[0].file, try toPath("Test.swiftmodule"))
XCTAssertEqual(emitModuleJob.outputs[1].file, try toPath("Test.swiftdoc"))
XCTAssertEqual(emitModuleJob.outputs[2].file, try toPath("Test.swiftsourceinfo"))
XCTAssertEqual(emitModuleJob.outputs[3].file, try toPath("Test.swiftinterface"))
XCTAssertEqual(emitModuleJob.outputs[3].file, try VirtualPath(path: "./Test.swiftinterface"))
XCTAssertEqual(emitModuleJob.outputs[4].file, try toPath("Test.private.swiftinterface"))
XCTAssertEqual(emitModuleJob.outputs[5].file, try toPath("Test-Swift.h"))
XCTAssertEqual(emitModuleJob.outputs[6].file, try toPath("Test.tbd"))
XCTAssertEqual(emitModuleJob.outputs[6].file, try VirtualPath(path: "./Test.tbd"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rmaz do you recall why this change was needed for only these specific outputs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't, but changing it to toPath() gives us:

XCTAssertEqual failed: ("./Test.swiftinterface") is not equal to ("Test.swiftinterface")

So somewhere a CWD of . is being prefixed only on the swiftinterface path and the tbd path.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some code wandering, it is because the path is unspecified in the compiler args, so falls back to this codepath to determine it:

return path
.parentDirectory
.appending(component: "\(moduleName).\(type.rawValue)")
.intern()

This ends up calling dirname on a relative path with no folder components, which ends up here:
https://github.com/swiftlang/swift-tools-support-core/blob/b464fcd8d884e599e3202d9bd1eee29a9e504069/Sources/TSCBasic/Path.swift#L598-L605

This ends up calculating the parent as . and generates the filename as modulename.swiftinterface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

if driver1.targetTriple.isDarwin {
XCTAssertEqual(emitModuleJob.outputs[7].file, try toPath("Test.abi.json"))
}
Expand Down Expand Up @@ -5188,7 +5189,7 @@ final class SwiftDriverTests: XCTestCase {
// Reset the temporary store to ensure predictable results.
VirtualPath.resetTemporaryFileStore()
var driver = try Driver(args: [
"swiftc", "-emit-executable", "test.swift", "-emit-module", "-avoid-emit-module-source-info", "-experimental-emit-module-separately"
"swiftc", "-emit-executable", "test.swift", "-emit-module", "-avoid-emit-module-source-info", "-experimental-emit-module-separately", "-working-directory", localFileSystem.currentWorkingDirectory!.description
])
let plannedJobs = try driver.planBuild()

Expand Down Expand Up @@ -7234,6 +7235,83 @@ final class SwiftDriverTests: XCTestCase {
})
}
}

func testRelativeInputs() throws {
do {
// Inputs with relative paths with no -working-directory flag should remain relative
var driver = try Driver(args: ["swiftc",
"-target", "arm64-apple-ios13.1",
"foo.swift"])
let plannedJobs = try driver.planBuild()
let compileJob = plannedJobs[0]
XCTAssertEqual(compileJob.kind, .compile)
XCTAssertTrue(compileJob.commandLine.contains(subsequence: ["-primary-file", try toPathOption("foo.swift", isRelative: true)]))
}

do {
// Inputs with relative paths with -working-directory flag should prefix all inputs
var driver = try Driver(args: ["swiftc",
"-target", "arm64-apple-ios13.1",
"foo.swift",
"-working-directory", "/foo/bar"])
let plannedJobs = try driver.planBuild()
let compileJob = plannedJobs[0]
XCTAssertEqual(compileJob.kind, .compile)
XCTAssertTrue(compileJob.commandLine.contains(subsequence: ["-primary-file", try toPathOption("/foo/bar/foo.swift", isRelative: false)]))
}

try withTemporaryFile { fileMapFile in
let outputMapContents: ByteString = """
{
"": {
"diagnostics": "/tmp/foo/.build/x86_64-apple-macosx/debug/foo.build/master.dia",
"emit-module-diagnostics": "/tmp/foo/.build/x86_64-apple-macosx/debug/foo.build/master.emit-module.dia"
},
"foo.swift": {
"object": "/tmp/foo/.build/x86_64-apple-macosx/debug/foo.build/foo.o"
}
}
"""
try localFileSystem.writeFileContents(fileMapFile.path, bytes: outputMapContents)

// Inputs with relative paths should be found in output file maps
var driver = try Driver(args: ["swiftc",
"-target", "arm64-apple-ios13.1",
"foo.swift",
"-output-file-map", fileMapFile.path.description])
let plannedJobs = try driver.planBuild()
let compileJob = plannedJobs[0]
XCTAssertEqual(compileJob.kind, .compile)
XCTAssertTrue(compileJob.commandLine.contains(subsequence: ["-o", try toPathOption("/tmp/foo/.build/x86_64-apple-macosx/debug/foo.build/foo.o", isRelative: false)]))
}

try withTemporaryFile { fileMapFile in
let outputMapContents: ByteString = """
{
"": {
"diagnostics": "/tmp/foo/.build/x86_64-apple-macosx/debug/foo.build/master.dia",
"emit-module-diagnostics": "/tmp/foo/.build/x86_64-apple-macosx/debug/foo.build/master.emit-module.dia"
},
"/some/workingdir/foo.swift": {
"object": "/tmp/foo/.build/x86_64-apple-macosx/debug/foo.build/foo.o"
}
}
"""
try localFileSystem.writeFileContents(fileMapFile.path, bytes: outputMapContents)

// Inputs with relative paths and working-dir should use absolute paths in output file maps
var driver = try Driver(args: ["swiftc",
"-target", "arm64-apple-ios13.1",
"foo.swift",
"-working-directory", "/some/workingdir",
"-output-file-map", fileMapFile.path.description])
let plannedJobs = try driver.planBuild()
let compileJob = plannedJobs[0]
XCTAssertEqual(compileJob.kind, .compile)
XCTAssertTrue(compileJob.commandLine.contains(subsequence: ["-o", try toPathOption("/tmp/foo/.build/x86_64-apple-macosx/debug/foo.build/foo.o", isRelative: false)]))
}

}

func testRelativeResourceDir() throws {
do {
Expand Down Expand Up @@ -7274,7 +7352,7 @@ final class SwiftDriverTests: XCTestCase {
XCTAssertEqual(compileJob.kind, .compile)
let linkJob = plannedJobs[1]
XCTAssertEqual(linkJob.kind, .link)
XCTAssertTrue(linkJob.commandLine.contains(try toPathOption(sdkRoot.pathString + "/usr/lib/swift/linux/x86_64/swiftrt.o")))
XCTAssertTrue(linkJob.commandLine.contains(try toPathOption(sdkRoot.pathString + "/usr/lib/swift/linux/x86_64/swiftrt.o", isRelative: false)))
}
}

Expand Down