Skip to content
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
5 changes: 5 additions & 0 deletions Sources/SwiftDriver/Execution/ArgsResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ public final class ArgsResolver {
case let .joinedOptionAndPath(option, path):
return option + (try resolve(.path(path)))

case let .commaJoinedOptionAndPaths(option, paths):
return try option + paths.map {
try resolve(.path($0))
}.joined(separator: ",")

case let .squashedArgumentList(option: option, args: args):
return try option + args.map {
try resolve($0)
Expand Down
4 changes: 4 additions & 0 deletions Sources/SwiftDriver/Jobs/CommandLineArguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ extension Array where Element == Job.ArgTemplate {
return "@\(path.name.spm_shellEscaped())"
case let .joinedOptionAndPath(option, path):
return option.spm_shellEscaped() + path.name.spm_shellEscaped()
case let .commaJoinedOptionAndPaths(option, paths):
return (option + paths.map(\.name).joined(separator: ",")).spm_shellEscaped()
case let .squashedArgumentList(option, args):
return (option + args.joinedUnresolvedArguments).spm_shellEscaped()
}
Expand All @@ -207,6 +209,8 @@ extension Array where Element == Job.ArgTemplate {
return "@\(path.name)"
case let .joinedOptionAndPath(option, path):
return option + path.name
case let .commaJoinedOptionAndPaths(option, paths):
return option + paths.map(\.name).joined(separator: ",")
case let .squashedArgumentList(option, args):
return option + args.joinedUnresolvedArguments
}
Expand Down
37 changes: 25 additions & 12 deletions Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,11 @@ extension Driver {
try commandLine.appendLast(.RpassMissedEQ, from: &parsedOptions)
try commandLine.appendLast(.suppressWarnings, from: &parsedOptions)
try commandLine.appendLast(.profileGenerate, from: &parsedOptions)
try commandLine.appendLast(.profileUse, from: &parsedOptions)
try addLastArgumentWithPath(.profileUse, to: &commandLine, remap: jobNeedPathRemap)
try commandLine.appendLast(.profileCoverageMapping, from: &parsedOptions)
try commandLine.appendLast(.debugInfoForProfiling, from: &parsedOptions)
if parsedOptions.hasArgument(.profileSampleUse) {
try commandLine.appendLast(.profileSampleUse, from: &parsedOptions)
try addLastArgumentWithPath(.profileSampleUse, to: &commandLine, remap: jobNeedPathRemap)
// Use LLVM's "profi" to infer missing sample data from the profile.
commandLine.appendFlag(.Xllvm)
commandLine.appendFlag("-sample-profile-use-profi")
Expand Down Expand Up @@ -1249,15 +1249,23 @@ extension Driver {
}

public mutating func addPathOption(_ option: ParsedOption, to commandLine: inout [Job.ArgTemplate], remap: Bool = true) throws {
let path = try VirtualPath(path: option.argument.asSingle)
try addPathOption(option: option.option, path: path, to: &commandLine, remap: remap)
if option.option.kind == .commaJoined || option.option.kind == .multiArg {
let paths = try option.argument.asMultiple.map { try VirtualPath(path: $0) }
try addPathOptions(option: option.option, paths: paths, to: &commandLine, remap: remap)
} else {
let path = try VirtualPath(path: option.argument.asSingle)
try addPathOption(option: option.option, path: path, to: &commandLine, remap: remap)
}
}

private mutating func needsPathRemapping(for option: Option, remap: Bool) -> Bool {
remap && isCachingEnabled && option.attributes.contains(.argumentIsPath) &&
!option.attributes.contains(.cacheInvariant)
}

public mutating func addPathOption(option: Option, path: VirtualPath, to commandLine: inout [Job.ArgTemplate], remap: Bool = true) throws {
assert(option.kind != .commaJoined && option.kind != .multiArg)
let needRemap = remap && isCachingEnabled && option.attributes.contains(.argumentIsPath) &&
!option.attributes.contains(.cacheInvariant)
let commandPath = needRemap ? remapPath(path) : path
let commandPath = needsPathRemapping(for: option, remap: remap) ? remapPath(path) : path
if option.kind == .joined {
commandLine.append(.joinedOptionAndPath(option.spelling, commandPath))
} else {
Expand All @@ -1269,12 +1277,17 @@ extension Driver {

public mutating func addPathOptions(option: Option, paths: [VirtualPath], to commandLine: inout [Job.ArgTemplate], remap: Bool = true) throws {
assert(option.kind == .commaJoined || option.kind == .multiArg)
let commandPaths = paths.map {
let needRemap = remap && isCachingEnabled && option.attributes.contains(.argumentIsPath) &&
!option.attributes.contains(.cacheInvariant)
return needRemap ? remapPath($0).name : $0.name
let needRemap = needsPathRemapping(for: option, remap: remap)
if option.kind == .commaJoined {
let commandPaths = paths.map { needRemap ? remapPath($0) : $0 }
commandLine.append(.commaJoinedOptionAndPaths(option.spelling, commandPaths))
} else {
commandLine.appendFlag(option)
for path in paths {
let commandPath = needRemap ? remapPath(path) : path
commandLine.appendPath(commandPath)
}
}
commandLine.appendFlag(option.spelling + commandPaths.joined(separator: ","))
}

/// Helper function to add last argument with path to command-line.
Expand Down
21 changes: 20 additions & 1 deletion Sources/SwiftDriver/Jobs/Job.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public struct Job: Codable, Equatable, Hashable {
/// Represents a joined option+path combo.
case joinedOptionAndPath(String, VirtualPath)

/// Represents a comma-joined option with multiple paths (e.g., `-option/path1,/path2`).
case commaJoinedOptionAndPaths(String, [VirtualPath])

/// Represents a list of arguments squashed together and passed as a single argument.
case squashedArgumentList(option: String, args: [ArgTemplate])
}
Expand Down Expand Up @@ -321,12 +324,16 @@ extension Job.Kind {

extension Job.ArgTemplate: Codable {
private enum CodingKeys: String, CodingKey {
case flag, path, responseFilePath, joinedOptionAndPath, squashedArgumentList
case flag, path, responseFilePath, joinedOptionAndPath, commaJoinedOptionAndPaths, squashedArgumentList

enum JoinedOptionAndPathCodingKeys: String, CodingKey {
case option, path
}

enum CommaJoinedOptionAndPathsCodingKeys: String, CodingKey {
case option, paths
}

enum SquashedArgumentListCodingKeys: String, CodingKey {
case option, args
}
Expand All @@ -350,6 +357,12 @@ extension Job.ArgTemplate: Codable {
forKey: .joinedOptionAndPath)
try keyedContainer.encode(option, forKey: .option)
try keyedContainer.encode(path, forKey: .path)
case let .commaJoinedOptionAndPaths(option, paths):
var keyedContainer = container.nestedContainer(
keyedBy: CodingKeys.CommaJoinedOptionAndPathsCodingKeys.self,
forKey: .commaJoinedOptionAndPaths)
try keyedContainer.encode(option, forKey: .option)
try keyedContainer.encode(paths, forKey: .paths)
case .squashedArgumentList(option: let option, args: let args):
var keyedContainer = container.nestedContainer(
keyedBy: CodingKeys.SquashedArgumentListCodingKeys.self,
Expand Down Expand Up @@ -383,6 +396,12 @@ extension Job.ArgTemplate: Codable {
forKey: .joinedOptionAndPath)
self = .joinedOptionAndPath(try keyedValues.decode(String.self, forKey: .option),
try keyedValues.decode(VirtualPath.self, forKey: .path))
case .commaJoinedOptionAndPaths:
let keyedValues = try values.nestedContainer(
keyedBy: CodingKeys.CommaJoinedOptionAndPathsCodingKeys.self,
forKey: .commaJoinedOptionAndPaths)
self = .commaJoinedOptionAndPaths(try keyedValues.decode(String.self, forKey: .option),
try keyedValues.decode([VirtualPath].self, forKey: .paths))
case .squashedArgumentList:
let keyedValues = try values.nestedContainer(
keyedBy: CodingKeys.SquashedArgumentListCodingKeys.self,
Expand Down
73 changes: 73 additions & 0 deletions Tests/SwiftDriverTests/CachingBuildTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,79 @@ final class CachingBuildTests: XCTestCase {
}
}

func testCommaJoinedPathRemapping() throws {
#if os(Windows)
try XCTSkipIf(true, "Skipping due to improper path mapping handling.")
#endif

try withTemporaryDirectory { path in
let main = path.appending(component: "testCommaJoinedPathRemapping.swift")
try localFileSystem.writeFileContents(main) {
$0.send("import C;")
$0.send("import E;")
$0.send("import G;")
}

// Create dummy profdata files so the driver doesn't emit missing data errors.
let profdata1 = path.appending(component: "prof1.profdata")
let profdata2 = path.appending(component: "prof2.profdata")
try localFileSystem.writeFileContents(profdata1, bytes: .init())
try localFileSystem.writeFileContents(profdata2, bytes: .init())

let cHeadersPath: AbsolutePath =
try testInputsPath.appending(component: "ExplicitModuleBuilds")
.appending(component: "CHeaders")
let swiftModuleInterfacesPath: AbsolutePath =
try testInputsPath.appending(component: "ExplicitModuleBuilds")
.appending(component: "Swift")
let casPath = path.appending(component: "cas")
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
let dependencyOracle = InterModuleDependencyOracle()
var driver = try Driver(args: ["swiftc",
"-I", cHeadersPath.nativePathString(escaped: false),
"-I", swiftModuleInterfacesPath.nativePathString(escaped: false),
"-g", "-explicit-module-build",
"-cache-compile-job", "-cas-path", casPath.nativePathString(escaped: false),
"-working-directory", path.nativePathString(escaped: false),
"-disable-clang-target",
"-scanner-prefix-map-paths", path.nativePathString(escaped: false), "/^tmp",
"-profile-use=" + profdata1.nativePathString(escaped: false) + "," + profdata2.nativePathString(escaped: false),
main.nativePathString(escaped: false)] + sdkArgumentsForTesting,
interModuleDependencyOracle: dependencyOracle)
guard driver.isFrontendArgSupported(.scannerPrefixMapPaths) else {
throw XCTSkip("frontend doesn't support prefix map")
}
let scanLibPath = try XCTUnwrap(driver.getSwiftScanLibPath())
try dependencyOracle.verifyOrCreateScannerInstance(swiftScanLibPath: scanLibPath)
let resolver = try ArgsResolver(fileSystem: localFileSystem)

let jobs = try driver.planBuild()
for job in jobs {
if !job.kind.supportCaching {
continue
}
let command = try job.commandLine.map { try resolver.resolve($0) }
// Check that -profile-use= paths are remapped and don't contain the original temp path.
for arg in command {
if arg.hasPrefix("-profile-use=") {
let paths = String(arg.dropFirst("-profile-use=".count))
for profilePath in paths.split(separator: ",") {
XCTAssertTrue(profilePath.starts(with: "/^tmp"),
"Expected remapped profile path, got: \(profilePath)")
}
}
}
// Verify no unremapped temp paths appear (except -cas-path).
for i in 0..<command.count {
if i >= 2 && command[i - 2] == "-cache-replay-prefix-map" { continue }
XCTAssertFalse(command[i] != casPath.description && command[i].starts(with: path.description),
"Found unremapped path: \(command[i])")
}
}
XCTAssertFalse(driver.diagnosticEngine.hasErrors)
}
}

func testCacheIncrementalBuildPlan() throws {
try withTemporaryDirectory { path in
try localFileSystem.changeCurrentWorkingDirectory(to: path)
Expand Down
2 changes: 1 addition & 1 deletion Tests/SwiftDriverTests/SwiftDriverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9467,7 +9467,7 @@ private extension Array where Element == Job.ArgTemplate {
switch $0 {
case let .path(path):
return path.basename == basename
case .flag, .responseFilePath, .joinedOptionAndPath, .squashedArgumentList:
case .flag, .responseFilePath, .joinedOptionAndPath, .commaJoinedOptionAndPaths, .squashedArgumentList:
return false
}
}
Expand Down
Loading