Skip to content

Commit b6386b0

Browse files
[Caching] Path remapping support
Teach swift-driver to remap path when needed. This involves: * Infer SDK/toolchain related path that needs to remapped * Send path remap related arguments to swift-frontend for depscanning * Send reverse map to swift-frontend for diagnostics replay * Constructing the compilation command for main module using remapped path
1 parent 39151f6 commit b6386b0

File tree

8 files changed

+279
-38
lines changed

8 files changed

+279
-38
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,35 @@ public struct Driver {
272272
let enableCaching: Bool
273273
let useClangIncludeTree: Bool
274274

275+
/// Scanner prefix mapping.
276+
let scannerPrefixMap: [AbsolutePath: AbsolutePath]
277+
let scannerPrefixMapSDK: AbsolutePath?
278+
let scannerPrefixMapToolchain: AbsolutePath?
279+
lazy var prefixMapping: [(AbsolutePath, AbsolutePath)] = {
280+
var mapping: [(AbsolutePath, AbsolutePath)] = scannerPrefixMap.map {
281+
return ($0.key, $0.value)
282+
}
283+
do {
284+
guard isFrontendArgSupported(.scannerPrefixMap) else {
285+
return []
286+
}
287+
if let sdkMapping = scannerPrefixMapSDK,
288+
let sdkPath = absoluteSDKPath {
289+
mapping.append((sdkPath, sdkMapping))
290+
}
291+
if let toolchainMapping = scannerPrefixMapToolchain {
292+
let toolchainPath = try toolchain.executableDir.parentDirectory // usr
293+
.parentDirectory // toolchain
294+
mapping.append((toolchainPath, toolchainMapping))
295+
}
296+
// The mapping needs to be sorted so the mapping is determinisitic.
297+
// The sorting order is reversed so /tmp/tmp is preferred over /tmp in remapping.
298+
return mapping.sorted { $0.0 > $1.0 }
299+
} catch {
300+
return mapping.sorted { $0.0 > $1.0 }
301+
}
302+
}()
303+
275304
/// Code & data for incremental compilation. Nil if not running in incremental mode.
276305
/// Set during planning because needs the jobs to look at outputs.
277306
@_spi(Testing) public private(set) var incrementalCompilationState: IncrementalCompilationState? = nil
@@ -595,6 +624,17 @@ public struct Driver {
595624
let cachingEnableOverride = parsedOptions.hasArgument(.driverExplicitModuleBuild) && env.keys.contains("SWIFT_ENABLE_CACHING")
596625
self.enableCaching = parsedOptions.hasArgument(.cacheCompileJob) || cachingEnableOverride
597626
self.useClangIncludeTree = enableCaching && env.keys.contains("SWIFT_CACHING_USE_INCLUDE_TREE")
627+
self.scannerPrefixMap = try Self.computeScanningPrefixMapper(&parsedOptions)
628+
if let sdkMapping = parsedOptions.getLastArgument(.scannerPrefixMapSdk)?.asSingle {
629+
self.scannerPrefixMapSDK = try AbsolutePath(validating: sdkMapping)
630+
} else {
631+
self.scannerPrefixMapSDK = nil
632+
}
633+
if let toolchainMapping = parsedOptions.getLastArgument(.scannerPrefixMapToolchain)?.asSingle {
634+
self.scannerPrefixMapToolchain = try AbsolutePath(validating: toolchainMapping)
635+
} else {
636+
self.scannerPrefixMapToolchain = nil
637+
}
598638

599639
// Compute the working directory.
600640
workingDirectory = try parsedOptions.getLastArgument(.workingDirectory).map { workingDirectoryArg in
@@ -3490,4 +3530,18 @@ extension Driver {
34903530
}
34913531
return options
34923532
}
3533+
3534+
static func computeScanningPrefixMapper(_ parsedOptions: inout ParsedOptions) throws -> [AbsolutePath: AbsolutePath] {
3535+
var mapping: [AbsolutePath: AbsolutePath] = [:]
3536+
for opt in parsedOptions.arguments(for: .scannerPrefixMap) {
3537+
let pluginArg = opt.argument.asSingle.split(separator: "=", maxSplits: 1)
3538+
if pluginArg.count != 2 {
3539+
throw Error.invalidArgumentValue(Option.scannerPrefixMap.spelling, opt.argument.asSingle)
3540+
}
3541+
let key = try AbsolutePath(validating: String(pluginArg[0]))
3542+
let value = try AbsolutePath(validating: String(pluginArg[1]))
3543+
mapping[key] = value
3544+
}
3545+
return mapping
3546+
}
34933547
}

Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@ public extension Driver {
124124
try commandLine.appendLast(.clangScannerModuleCachePath, from: &parsedOptions)
125125
}
126126

127+
if isFrontendArgSupported(.scannerPrefixMap) {
128+
// construct `-scanner-prefix-mapper` for scanner.
129+
for (key, value) in prefixMapping {
130+
commandLine.appendFlag(.scannerPrefixMap)
131+
commandLine.appendFlag(key.pathString + "=" + value.pathString)
132+
}
133+
}
134+
127135
// Pass on the input files
128136
commandLine.append(contentsOf: inputFiles.filter { $0.type == .swift }.map { .path($0.file) })
129137
return (inputs, commandLine)

Sources/SwiftDriver/Jobs/CompileJob.swift

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -112,21 +112,21 @@ extension Driver {
112112
outputType: FileType?,
113113
commandLine: inout [Job.ArgTemplate])
114114
throws -> ([TypedVirtualPath], [TypedVirtualPath]) {
115-
let useInputFileList: Bool
116-
if let allSourcesFileList = allSourcesFileList {
117-
useInputFileList = true
115+
let useInputFileList = shouldUseInputFileList
116+
if shouldUseInputFileList {
117+
let swiftInputs = inputFiles.filter(\.type.isPartOfSwiftCompilation)
118+
let remappedSourcesFileList = try VirtualPath.createUniqueFilelist(RelativePath("sources"),
119+
.list(swiftInputs.map{ return remapPath($0.file) }))
118120
commandLine.appendFlag(.filelist)
119-
commandLine.appendPath(allSourcesFileList)
120-
} else {
121-
useInputFileList = false
121+
commandLine.appendPath(remappedSourcesFileList)
122122
}
123123

124124
let usePrimaryInputFileList = primaryInputs.count > fileListThreshold
125125
if usePrimaryInputFileList {
126126
// primary file list
127127
commandLine.appendFlag(.primaryFilelist)
128128
let fileList = try VirtualPath.createUniqueFilelist(RelativePath(validating: "primaryInputs"),
129-
.list(primaryInputs.map(\.file)))
129+
.list(primaryInputs.map{ return remapPath($0.file) }))
130130
commandLine.appendPath(fileList)
131131
}
132132

@@ -166,12 +166,11 @@ extension Driver {
166166
let isPrimary = usesPrimaryFileInputs && primaryInputFiles.contains(input)
167167
if isPrimary {
168168
if !usePrimaryInputFileList {
169-
commandLine.appendFlag(.primaryFile)
170-
commandLine.appendPath(input.file)
169+
try addPathOption(option: .primaryFile, path: input.file, to:&commandLine, remap: true)
171170
}
172171
} else {
173172
if !useInputFileList {
174-
commandLine.appendPath(input.file)
173+
try addPathArgument(input.file, to: &commandLine, remap: true)
175174
}
176175
}
177176

Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift

Lines changed: 113 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ extension Driver {
7575
break
7676
}
7777

78+
let jobNeedPathRemap: Bool
7879
// If in ExplicitModuleBuild mode and the dependency graph has been computed, add module
7980
// dependencies.
8081
// May also be used for generation of the dependency graph itself in ExplicitModuleBuild mode.
@@ -83,17 +84,21 @@ extension Driver {
8384
switch kind {
8485
case .generatePCH:
8586
try addExplicitPCHBuildArguments(inputs: &inputs, commandLine: &commandLine)
87+
jobNeedPathRemap = true
8688
case .compile, .emitModule, .interpret, .verifyModuleInterface:
8789
try addExplicitModuleBuildArguments(inputs: &inputs, commandLine: &commandLine)
90+
jobNeedPathRemap = true
8891
case .backend, .mergeModule, .compileModuleFromInterface,
8992
.generatePCM, .dumpPCM, .repl, .printTargetInfo,
9093
.versionRequest, .autolinkExtract, .generateDSYM,
9194
.help, .link, .verifyDebugInfo, .scanDependencies,
9295
.emitSupportedFeatures, .moduleWrap,
9396
.generateAPIBaseline, .generateABIBaseline, .compareAPIBaseline,
9497
.compareABIBaseline:
95-
break // Do not support creating from dependency scanner output.
98+
jobNeedPathRemap = false
9699
}
100+
} else {
101+
jobNeedPathRemap = false
97102
}
98103

99104
if let variant = parsedOptions.getLastArgument(.targetVariant)?.asSingle {
@@ -126,24 +131,22 @@ extension Driver {
126131
try commandLine.appendLast(.targetCpu, from: &parsedOptions)
127132

128133
if let sdkPath = frontendTargetInfo.sdkPath?.path {
129-
commandLine.appendFlag(.sdk)
130-
commandLine.append(.path(VirtualPath.lookup(sdkPath)))
134+
try addPathOption(option: .sdk, path: VirtualPath.lookup(sdkPath), to: &commandLine, remap: jobNeedPathRemap)
131135
}
132136

133137
for args: (Option, Option) in [
134138
(.visualcToolsRoot, .visualcToolsVersion),
135139
(.windowsSdkRoot, .windowsSdkVersion)
136140
] {
137-
let (rootArg, versionArg) = args
138-
if let value = parsedOptions.getLastArgument(rootArg)?.asSingle,
139-
isFrontendArgSupported(rootArg) {
140-
commandLine.appendFlag(rootArg.spelling)
141-
commandLine.appendPath(try .init(validating: value))
141+
let (rootOpt, versionOpt) = args
142+
if let rootArg = parsedOptions.last(for: rootOpt),
143+
isFrontendArgSupported(rootOpt) {
144+
try addPathOption(rootArg, to: &commandLine, remap: jobNeedPathRemap)
142145
}
143146

144-
if let value = parsedOptions.getLastArgument(versionArg)?.asSingle,
145-
isFrontendArgSupported(versionArg) {
146-
commandLine.appendFlags(versionArg.spelling, value)
147+
if let value = parsedOptions.getLastArgument(versionOpt)?.asSingle,
148+
isFrontendArgSupported(versionOpt) {
149+
commandLine.appendFlags(versionOpt.spelling, value)
147150
}
148151
}
149152

@@ -302,12 +305,14 @@ extension Driver {
302305
commandLine.appendFlag(.Xcc)
303306
commandLine.appendFlag(.workingDirectory)
304307
commandLine.appendFlag(.Xcc)
305-
commandLine.appendPath(.absolute(workingDirectory))
308+
try addPathArgument(.absolute(workingDirectory), to: &commandLine, remap: jobNeedPathRemap)
306309
}
307310

308311
// Resource directory.
309-
commandLine.appendFlag(.resourceDir)
310-
commandLine.appendPath(VirtualPath.lookup(frontendTargetInfo.runtimeResourcePath.path))
312+
try addPathOption(option: .resourceDir,
313+
path: VirtualPath.lookup(frontendTargetInfo.runtimeResourcePath.path),
314+
to: &commandLine,
315+
remap: jobNeedPathRemap)
311316

312317
if self.useStaticResourceDir {
313318
commandLine.appendFlag("-use-static-resource-dir")
@@ -347,6 +352,7 @@ extension Driver {
347352
try commandLine.appendAll(.casPluginOption, from: &parsedOptions)
348353
try commandLine.appendLast(.cacheRemarks, from: &parsedOptions)
349354
}
355+
addCacheReplayMapping(to: &commandLine)
350356
if useClangIncludeTree {
351357
commandLine.appendFlag(.clangIncludeTree)
352358
}
@@ -369,16 +375,16 @@ extension Driver {
369375
// of a lookup failure.
370376
if parsedOptions.contains(.pchOutputDir) &&
371377
!parsedOptions.contains(.driverExplicitModuleBuild) {
372-
commandLine.appendPath(VirtualPath.lookup(importedObjCHeader))
378+
try addPathArgument(VirtualPath.lookup(importedObjCHeader), to:&commandLine, remap: jobNeedPathRemap)
373379
try commandLine.appendLast(.pchOutputDir, from: &parsedOptions)
374380
if !compilerMode.isSingleCompilation {
375381
commandLine.appendFlag(.pchDisableValidation)
376382
}
377383
} else {
378-
commandLine.appendPath(VirtualPath.lookup(pch))
384+
try addPathArgument(VirtualPath.lookup(pch), to:&commandLine, remap: jobNeedPathRemap)
379385
}
380386
} else {
381-
commandLine.appendPath(VirtualPath.lookup(importedObjCHeader))
387+
try addPathArgument(VirtualPath.lookup(importedObjCHeader), to:&commandLine, remap: jobNeedPathRemap)
382388
}
383389
}
384390

@@ -418,16 +424,17 @@ extension Driver {
418424
}
419425
}
420426

421-
func addBridgingHeaderPCHCacheKeyArguments(commandLine: inout [Job.ArgTemplate],
422-
pchCompileJob: Job?) throws {
427+
mutating func addBridgingHeaderPCHCacheKeyArguments(commandLine: inout [Job.ArgTemplate],
428+
pchCompileJob: Job?) throws {
423429
guard let pchJob = pchCompileJob, enableCaching else { return }
424430

425431
// The pch input file (the bridging header) is added as last inputs to the job.
426432
guard let inputFile = pchJob.inputs.last else { assertionFailure("no input files from pch job"); return }
427433
assert(inputFile.type == .objcHeader, "Expect objc header input type")
434+
let mappedInput = remapPath(inputFile.file).intern()
428435
let bridgingHeaderCacheKey = try interModuleDependencyOracle.computeCacheKeyForOutput(kind: .pch,
429436
commandLine: pchJob.commandLine,
430-
input: inputFile.fileHandle)
437+
input: mappedInput)
431438
commandLine.appendFlag("-bridging-header-pch-key")
432439
commandLine.appendFlag(bridgingHeaderCacheKey)
433440
}
@@ -609,7 +616,7 @@ extension Driver {
609616
var entries = [VirtualPath.Handle: [FileType: VirtualPath.Handle]]()
610617
for input in primaryInputs {
611618
if let output = inputOutputMap[input]?.first {
612-
addEntry(&entries, input: input, output: output)
619+
try addEntry(&entries, input: input, output: output)
613620
} else {
614621
// Primary inputs are expected to appear in the output file map even
615622
// if they have no corresponding outputs.
@@ -628,7 +635,7 @@ extension Driver {
628635
}
629636

630637
for flaggedPair in flaggedInputOutputPairs {
631-
addEntry(&entries, input: flaggedPair.input, output: flaggedPair.output)
638+
try addEntry(&entries, input: flaggedPair.input, output: flaggedPair.output)
632639
}
633640
// To match the legacy driver behavior, make sure we add an entry for the
634641
// file under indexing and the primary output file path.
@@ -662,14 +669,15 @@ extension Driver {
662669
try commandLine.appendLast(.symbolGraphMinimumAccessLevel, from: &parsedOptions)
663670
}
664671

665-
func addEntry(_ entries: inout [VirtualPath.Handle: [FileType: VirtualPath.Handle]], input: TypedVirtualPath?, output: TypedVirtualPath) {
672+
mutating func addEntry(_ entries: inout [VirtualPath.Handle: [FileType: VirtualPath.Handle]], input: TypedVirtualPath?, output: TypedVirtualPath) throws {
666673
let entryInput: VirtualPath.Handle
667674
if let input = input?.fileHandle, input != OutputFileMap.singleInputKey {
668675
entryInput = input
669676
} else {
670677
entryInput = inputFiles[0].fileHandle
671678
}
672-
entries[entryInput, default: [:]][output.type] = output.fileHandle
679+
let inputEntry = enableCaching ? remapPath(VirtualPath.lookup(entryInput)).intern() : entryInput
680+
entries[inputEntry, default: [:]][output.type] = output.fileHandle
673681
}
674682

675683
/// Adds all dependencies required for an explicit module build
@@ -706,3 +714,84 @@ extension Driver {
706714
return job.moduleName == moduleOutputInfo.name
707715
}
708716
}
717+
718+
extension Driver {
719+
private func getAbsolutePathFromVirtualPath(_ path: VirtualPath) -> AbsolutePath? {
720+
guard let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory else {
721+
return nil
722+
}
723+
return path.resolvedRelativePath(base: cwd).absolutePath
724+
}
725+
726+
private mutating func remapPath(absolute path: AbsolutePath) -> AbsolutePath {
727+
guard !prefixMapping.isEmpty else {
728+
return path
729+
}
730+
for (prefix, value) in prefixMapping {
731+
if path.isDescendantOfOrEqual(to: prefix) {
732+
return value.appending(path.relative(to: prefix))
733+
}
734+
}
735+
return path
736+
}
737+
738+
public mutating func remapPath(_ path: VirtualPath) -> VirtualPath {
739+
guard !prefixMapping.isEmpty,
740+
let absPath = getAbsolutePathFromVirtualPath(path) else {
741+
return path
742+
}
743+
let mappedPath = remapPath(absolute: absPath)
744+
return try! VirtualPath(path: mappedPath.pathString)
745+
}
746+
747+
/// Helper function to add path to commandLine. Function will validate the path, and remap the path if needed.
748+
public mutating func addPathArgument(_ path: VirtualPath, to commandLine: inout [Job.ArgTemplate], remap: Bool = false) throws {
749+
guard remap && enableCaching else {
750+
commandLine.appendPath(path)
751+
return
752+
}
753+
let mappedPath = remapPath(path)
754+
commandLine.appendPath(mappedPath)
755+
}
756+
757+
public mutating func addPathOption(_ option: ParsedOption, to commandLine: inout [Job.ArgTemplate], remap: Bool = false) throws {
758+
let path = try VirtualPath(path: option.argument.asSingle)
759+
try addPathOption(option: option.option, path: path, to: &commandLine, remap: remap)
760+
}
761+
762+
public mutating func addPathOption(option: Option, path: VirtualPath, to commandLine: inout [Job.ArgTemplate], remap: Bool = false) throws {
763+
commandLine.appendFlag(option)
764+
let needRemap = remap && option.attributes.contains(.argumentIsPath) &&
765+
!option.attributes.contains(.cacheInvariant)
766+
try addPathArgument(path, to: &commandLine, remap: needRemap)
767+
768+
}
769+
770+
/// Helper function to add last argument with path to command-line.
771+
public mutating func addLastArgumentWithPath(_ options: Option...,
772+
from parsedOptions: inout ParsedOptions,
773+
to commandLine: inout [Job.ArgTemplate]) throws {
774+
guard let parsedOption = parsedOptions.last(for: options) else {
775+
return
776+
}
777+
try addPathOption(parsedOption, to: &commandLine)
778+
}
779+
780+
/// Helper function to add all arguments with path to command-line.
781+
public mutating func addAllArgumentsWithPath(_ options: Option...,
782+
from parsedOptions: inout ParsedOptions,
783+
to commandLine: inout [Job.ArgTemplate]) throws {
784+
for matching in parsedOptions.arguments(for: options) {
785+
try addPathOption(matching, to: &commandLine)
786+
}
787+
}
788+
789+
public mutating func addCacheReplayMapping(to commandLine: inout [Job.ArgTemplate]) {
790+
if enableCaching && isFrontendArgSupported(.scannerPrefixMap) {
791+
for (key, value) in prefixMapping {
792+
commandLine.appendFlag("-cache-replay-prefix-map")
793+
commandLine.appendFlag(value.pathString + "=" + key.pathString)
794+
}
795+
}
796+
}
797+
}

0 commit comments

Comments
 (0)