Skip to content

Commit 9a42dec

Browse files
committed
[Explicit Module Builds] Introduce an InterModuleDependencyOracle abstraction for tracking and sharing dependency scanning results
This PR refactors the handling of inter-module dependencies to centralize their handling in a new, reference-type, oracle. `InterModuleDependencyOracle` will now be responsible for responding to queries about dependencies of a given module. The intent behind it is to abstract the actual mechanism used for tracking/computing/caching inter-module dependencies. A single oracle reference is intended to be passed around to multiple Driver invocations, corresponding to different targets, by build-system clients (e.g. SwiftPM), replacing today's use of `ModuleInfoMap`-based API, to communicate already-computed dependency-scan results. As implemented, it is a simple store of `ModuleInfo` value objects, which a `Driver` invocation populates by invoking a `-scan-dependencies` job; not unlike passing around an accumulating `ModuleInfoMap`. In the future, the oracle's internal implementation may be replaced with a direct dependency-scanner library access, without having to break the clients' API.
1 parent 3812ddd commit 9a42dec

File tree

10 files changed

+316
-221
lines changed

10 files changed

+316
-221
lines changed

Sources/SwiftDriver/CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99
add_library(SwiftDriver
1010
"Explicit Module Builds/ClangModuleBuildJobCache.swift"
1111
"Explicit Module Builds/ClangVersionedDependencyResolution.swift"
12-
"Explicit Module Builds/CommonDependencyGraphOperations.swift"
1312
"Explicit Module Builds/ExplicitDependencyBuildPlanner.swift"
14-
"Explicit Module Builds/InterModuleDependencyGraph.swift"
1513
"Explicit Module Builds/ModuleDependencyScanning.swift"
1614
"Explicit Module Builds/PlaceholderDependencyResolution.swift"
1715
"Explicit Module Builds/SerializableModuleArtifacts.swift"
16+
"Explicit Module Builds/Inter Module Dependencies/CommonDependencyOperations.swift"
17+
"Explicit Module Builds/Inter Module Dependencies/InterModuleDependencyGraph.swift"
18+
"Explicit Module Builds/Inter Module Dependencies/InterModuleDependencyOracle.swift"
1819

1920
Driver/CompilerMode.swift
2021
Driver/DebugInfo.swift

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,21 @@ public struct Driver {
256256
/// dependencies in parallel builds.
257257
var forceEmitModuleBeforeCompile: Bool = false
258258

259+
// FIXME: We should soon be able to remove this from being in the Driver's state.
260+
// Its only remaining use outside of actual dependency-build planning is in
261+
// command-line option generation for the main module compile.
259262
/// Planner for constructing module build jobs using Explicit Module Builds.
260263
/// Constructed during the planning phase only when all modules will be prebuilt and treated
261264
/// as explicit by the various compilation jobs.
262265
@_spi(Testing) public var explicitDependencyBuildPlanner: ExplicitDependencyBuildPlanner? = nil
263266

267+
/// An oracle for querying inter-module dependencies
268+
/// Can either be an argument to the driver in many-module contexts where dependency information
269+
/// is shared across many targets; otherwise, a new instance is created by the driver itself.
270+
@_spi(Testing) public let interModuleDependencyOracle: InterModuleDependencyOracle
271+
272+
// TODO: Once the clients have transitioned to using the InterModuleDependencyOracle API,
273+
// this must convey information about the externally-prebuilt targets only
264274
/// All external artifacts a build system (e.g. SwiftPM) may pass in as input to the explicit
265275
/// build of the current module. Consists of a map of externally-built targets, and a map of all previously
266276
/// discovered/scanned modules and their infos.
@@ -311,7 +321,8 @@ public struct Driver {
311321
diagnosticsEngine: DiagnosticsEngine = DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler]),
312322
fileSystem: FileSystem = localFileSystem,
313323
executor: DriverExecutor,
314-
externalBuildArtifacts: ExternalBuildArtifacts? = nil
324+
externalBuildArtifacts: ExternalBuildArtifacts? = nil,
325+
interModuleDependencyOracle: InterModuleDependencyOracle? = nil
315326
) throws {
316327
self.env = env
317328
self.fileSystem = fileSystem
@@ -370,9 +381,18 @@ public struct Driver {
370381
guard let modTime = try? fileSystem
371382
.getFileInfo($0.file).modTime else { return nil }
372383
return ($0, modTime)
373-
})
384+
})
374385

375-
do {
386+
// Create an instance of an inter-module dependency oracle, if the driver's
387+
// client did not provide one. The clients are expected to provide an oracle
388+
// when they wish to share module dependency information across targets.
389+
if let dependencyOracle = interModuleDependencyOracle {
390+
self.interModuleDependencyOracle = dependencyOracle
391+
} else {
392+
self.interModuleDependencyOracle = InterModuleDependencyOracle()
393+
}
394+
395+
do {
376396
let outputFileMap: OutputFileMap?
377397
// Initialize an empty output file map, which will be populated when we start creating jobs.
378398
if let outputFileMapArg = parsedOptions.getLastArgument(.outputFileMap)?.asSingle {
@@ -382,14 +402,12 @@ public struct Driver {
382402
} catch {
383403
throw Error.unableToLoadOutputFileMap(outputFileMapArg)
384404
}
385-
} else {
386-
outputFileMap = nil
387-
}
388405

389-
if let workingDirectory = self.workingDirectory {
390-
self.outputFileMap = outputFileMap?.resolveRelativePaths(relativeTo: workingDirectory)
391-
} else {
392-
self.outputFileMap = outputFileMap
406+
if let workingDirectory = self.workingDirectory {
407+
self.outputFileMap = outputFileMap?.resolveRelativePaths(relativeTo: workingDirectory)
408+
} else {
409+
self.outputFileMap = outputFileMap
410+
}
393411
}
394412
}
395413

Sources/SwiftDriver/Explicit Module Builds/ClangVersionedDependencyResolution.swift

Lines changed: 30 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ internal extension Driver {
6565
}
6666
}
6767

68+
/// Resolution of versioned clang dependencies.
69+
/// FIXME: This code currently operates on instances of InterModuleDependencyGraph,
70+
/// It should be transitioned to operate on an instance of an InterModuleDependencyOracle.
6871
private extension InterModuleDependencyGraph {
6972
/// For each module scanned at multiple target versions, combine their dependencies across version-specific graphs.
7073
mutating func resolveVersionedClangModules(using versionedGraphMap: ModuleVersionedGraphMap)
@@ -108,24 +111,35 @@ private extension InterModuleDependencyGraph {
108111
pathPCMArtSet: Set<[String]>,
109112
pcmArgSetMap: inout [ModuleDependencyId : Set<[String]>])
110113
throws {
114+
guard let moduleInfo = modules[moduleId] else {
115+
throw Driver.Error.missingModuleDependency(moduleId.moduleName)
116+
}
111117
switch moduleId {
112118
case .swift:
119+
guard case .swift(let swiftModuleDetails) = moduleInfo.details else {
120+
throw Driver.Error.malformedModuleDependency(moduleId.moduleName,
121+
"no Swift `details` object")
122+
}
113123
// Add extraPCMArgs of the visited node to the current path set
114124
// and proceed to visit all direct dependencies
115-
let modulePCMArgs = try swiftModulePCMArgs(of: moduleId)
125+
let modulePCMArgs = swiftModuleDetails.extraPcmArgs
116126
var newPathPCMArgSet = pathPCMArtSet
117127
newPathPCMArgSet.insert(modulePCMArgs)
118-
for dependencyId in try moduleInfo(of: moduleId).directDependencies! {
128+
for dependencyId in moduleInfo.directDependencies! {
119129
try visit(dependencyId,
120130
pathPCMArtSet: newPathPCMArgSet,
121131
pcmArgSetMap: &pcmArgSetMap)
122132
}
123133
case .clang:
134+
guard case .clang(let clangModuleDetails) = moduleInfo.details else {
135+
throw Driver.Error.malformedModuleDependency(moduleId.moduleName,
136+
"no Clang `details` object")
137+
}
124138
// The details of this module contain information on which sets of PCMArgs are already
125139
// captured in the described dependencies of this module. Only re-scan at PCMArgs not
126140
// already captured.
127-
let moduleDetails = try clangModuleDetails(of: moduleId)
128-
let alreadyCapturedPCMArgs = moduleDetails.dependenciesCapturedPCMArgs ?? Set<[String]>()
141+
let alreadyCapturedPCMArgs =
142+
clangModuleDetails.dependenciesCapturedPCMArgs ?? Set<[String]>()
129143
let newPCMArgSet = pathPCMArtSet.filter { !alreadyCapturedPCMArgs.contains($0) }
130144
// Add current path's PCMArgs to the SetMap and stop traversal
131145
if pcmArgSetMap[moduleId] != nil {
@@ -138,7 +152,7 @@ private extension InterModuleDependencyGraph {
138152
// We can rely on the fact that this pre-built module already has its
139153
// versioned-PCM dependencies satisfied, so we do not need to add additional
140154
// arguments. Proceed traversal to its dependencies.
141-
for dependencyId in try moduleInfo(of: moduleId).directDependencies! {
155+
for dependencyId in moduleInfo.directDependencies! {
142156
try visit(dependencyId,
143157
pathPCMArtSet: pathPCMArtSet,
144158
pcmArgSetMap: &pcmArgSetMap)
@@ -159,57 +173,19 @@ private extension InterModuleDependencyGraph {
159173
[ModuleDependencyId : Set<[String]>]
160174
) throws {
161175
for (moduleId, newPCMArgs) in pcmArgSetMap {
162-
var moduleDetails = try clangModuleDetails(of: moduleId)
163-
if moduleDetails.dependenciesCapturedPCMArgs == nil {
164-
moduleDetails.dependenciesCapturedPCMArgs = Set<[String]>()
176+
guard let moduleInfo = modules[moduleId] else {
177+
throw Driver.Error.missingModuleDependency(moduleId.moduleName)
178+
}
179+
guard case .clang(var clangModuleDetails) = moduleInfo.details else {
180+
throw Driver.Error.malformedModuleDependency(moduleId.moduleName,
181+
"no Clang `details` object")
165182
}
166-
newPCMArgs.forEach { moduleDetails.dependenciesCapturedPCMArgs!.insert($0) }
167-
modules[moduleId]!.details = .clang(moduleDetails)
183+
if clangModuleDetails.dependenciesCapturedPCMArgs == nil {
184+
clangModuleDetails.dependenciesCapturedPCMArgs = Set<[String]>()
185+
}
186+
newPCMArgs.forEach { clangModuleDetails.dependenciesCapturedPCMArgs!.insert($0) }
187+
modules[moduleId]!.details = .clang(clangModuleDetails)
168188
}
169189
}
170190
}
171191

172-
public extension InterModuleDependencyGraph {
173-
/// Given two moduleInfos of clang modules, merge them by combining their directDependencies and
174-
/// dependenciesCapturedPCMArgs and sourceFiles fields. These fields may differ across the same module
175-
/// scanned at different PCMArgs (e.g. -target option).
176-
static func mergeClangModuleInfoDependencies(_ firstInfo: ModuleInfo, _ secondInfo:ModuleInfo
177-
) -> ModuleInfo {
178-
guard case .clang(let firstDetails) = firstInfo.details,
179-
case .clang(let secondDetails) = secondInfo.details
180-
else {
181-
fatalError("mergeClangModules expected two valid ClangModuleDetails objects.")
182-
}
183-
184-
// As far as their dependencies go, these module infos are identical
185-
if firstInfo.directDependencies == secondInfo.directDependencies,
186-
firstDetails.dependenciesCapturedPCMArgs == secondDetails.dependenciesCapturedPCMArgs,
187-
firstInfo.sourceFiles == secondInfo.sourceFiles {
188-
return firstInfo
189-
}
190-
191-
// Create a new moduleInfo that represents this module with combined dependency information
192-
let firstModuleSources = firstInfo.sourceFiles ?? []
193-
let secondModuleSources = secondInfo.sourceFiles ?? []
194-
let combinedSourceFiles = Array(Set(firstModuleSources + secondModuleSources))
195-
196-
let firstModuleDependencies = firstInfo.directDependencies ?? []
197-
let secondModuleDependencies = secondInfo.directDependencies ?? []
198-
let combinedDependencies = Array(Set(firstModuleDependencies + secondModuleDependencies))
199-
200-
let firstModuleCapturedPCMArgs = firstDetails.dependenciesCapturedPCMArgs ?? Set<[String]>()
201-
let secondModuleCapturedPCMArgs = secondDetails.dependenciesCapturedPCMArgs ?? Set<[String]>()
202-
let combinedCapturedPCMArgs = firstModuleCapturedPCMArgs.union(secondModuleCapturedPCMArgs)
203-
204-
let combinedModuleDetails =
205-
ClangModuleDetails(moduleMapPath: firstDetails.moduleMapPath,
206-
dependenciesCapturedPCMArgs: combinedCapturedPCMArgs,
207-
contextHash: firstDetails.contextHash,
208-
commandLine: firstDetails.commandLine)
209-
210-
return ModuleInfo(modulePath: firstInfo.modulePath,
211-
sourceFiles: combinedSourceFiles,
212-
directDependencies: combinedDependencies,
213-
details: .clang(combinedModuleDetails))
214-
}
215-
}

0 commit comments

Comments
 (0)