Skip to content

Commit 7055a62

Browse files
authored
Merge pull request #356 from artemcm/DependencyOracle
[Explicit Module Builds] Introduce an InterModuleDependencyOracle abstraction for tracking and sharing dependency scanning results
2 parents 4200941 + cc413d8 commit 7055a62

11 files changed

+343
-164
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: 40 additions & 5 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 input option generation for the explicit main module compile job.
259262
/// Planner for constructing module build jobs using Explicit Module Builds.
260-
/// Constructed during the planning phase only when all modules will be prebuilt and treated
261-
/// as explicit by the various compilation jobs.
263+
/// Constructed during the planning phase only when all module dependencies will be prebuilt and treated
264+
/// as explicit inputs 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,15 +321,23 @@ public struct Driver {
311321
diagnosticsEngine: DiagnosticsEngine = DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler]),
312322
fileSystem: FileSystem = localFileSystem,
313323
executor: DriverExecutor,
314-
externalBuildArtifacts: ExternalBuildArtifacts? = nil
324+
// FIXME: Duplication with externalBuildArtifacts and externalTargetModulePathMap
325+
// is a temporary backwards-compatibility shim to help transition SwiftPM to the new API
326+
externalBuildArtifacts: ExternalBuildArtifacts? = nil,
327+
externalTargetModulePathMap: ExternalTargetModulePathMap? = nil,
328+
interModuleDependencyOracle: InterModuleDependencyOracle? = nil
315329
) throws {
316330
self.env = env
317331
self.fileSystem = fileSystem
318332

319333
self.diagnosticEngine = diagnosticsEngine
320334
self.executor = executor
321335

322-
self.externalBuildArtifacts = externalBuildArtifacts
336+
if let externalArtifacts = externalBuildArtifacts {
337+
self.externalBuildArtifacts = externalArtifacts
338+
} else if let externalTargetPaths = externalTargetModulePathMap {
339+
self.externalBuildArtifacts = (externalTargetPaths, [:])
340+
}
323341

324342
if case .subcommand = try Self.invocationRunMode(forArgs: args).mode {
325343
throw Error.subcommandPassedToDriver
@@ -370,7 +388,24 @@ public struct Driver {
370388
guard let modTime = try? fileSystem
371389
.getFileInfo($0.file).modTime else { return nil }
372390
return ($0, modTime)
373-
})
391+
})
392+
393+
// Create an instance of an inter-module dependency oracle, if the driver's
394+
// client did not provide one. The clients are expected to provide an oracle
395+
// when they wish to share module dependency information across targets.
396+
if let dependencyOracle = interModuleDependencyOracle {
397+
self.interModuleDependencyOracle = dependencyOracle
398+
} else {
399+
self.interModuleDependencyOracle = InterModuleDependencyOracle()
400+
401+
// This is a shim for backwards-compatibility with ModuleInfoMap-based API
402+
// used by SwiftPM
403+
if let externalArtifacts = externalBuildArtifacts {
404+
if !externalArtifacts.1.isEmpty {
405+
try self.interModuleDependencyOracle.mergeModules(from: externalArtifacts.1)
406+
}
407+
}
408+
}
374409

375410
do {
376411
let outputFileMap: OutputFileMap?

Sources/SwiftDriver/Explicit Module Builds/ClangVersionedDependencyResolution.swift

Lines changed: 27 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -108,24 +108,35 @@ private extension InterModuleDependencyGraph {
108108
pathPCMArtSet: Set<[String]>,
109109
pcmArgSetMap: inout [ModuleDependencyId : Set<[String]>])
110110
throws {
111+
guard let moduleInfo = modules[moduleId] else {
112+
throw Driver.Error.missingModuleDependency(moduleId.moduleName)
113+
}
111114
switch moduleId {
112115
case .swift:
116+
guard case .swift(let swiftModuleDetails) = moduleInfo.details else {
117+
throw Driver.Error.malformedModuleDependency(moduleId.moduleName,
118+
"no Swift `details` object")
119+
}
113120
// Add extraPCMArgs of the visited node to the current path set
114121
// and proceed to visit all direct dependencies
115-
let modulePCMArgs = try swiftModulePCMArgs(of: moduleId)
122+
let modulePCMArgs = swiftModuleDetails.extraPcmArgs
116123
var newPathPCMArgSet = pathPCMArtSet
117124
newPathPCMArgSet.insert(modulePCMArgs)
118-
for dependencyId in try moduleInfo(of: moduleId).directDependencies! {
125+
for dependencyId in moduleInfo.directDependencies! {
119126
try visit(dependencyId,
120127
pathPCMArtSet: newPathPCMArgSet,
121128
pcmArgSetMap: &pcmArgSetMap)
122129
}
123130
case .clang:
131+
guard case .clang(let clangModuleDetails) = moduleInfo.details else {
132+
throw Driver.Error.malformedModuleDependency(moduleId.moduleName,
133+
"no Clang `details` object")
134+
}
124135
// The details of this module contain information on which sets of PCMArgs are already
125136
// captured in the described dependencies of this module. Only re-scan at PCMArgs not
126137
// already captured.
127-
let moduleDetails = try clangModuleDetails(of: moduleId)
128-
let alreadyCapturedPCMArgs = moduleDetails.dependenciesCapturedPCMArgs ?? Set<[String]>()
138+
let alreadyCapturedPCMArgs =
139+
clangModuleDetails.dependenciesCapturedPCMArgs ?? Set<[String]>()
129140
let newPCMArgSet = pathPCMArtSet.filter { !alreadyCapturedPCMArgs.contains($0) }
130141
// Add current path's PCMArgs to the SetMap and stop traversal
131142
if pcmArgSetMap[moduleId] != nil {
@@ -138,7 +149,7 @@ private extension InterModuleDependencyGraph {
138149
// We can rely on the fact that this pre-built module already has its
139150
// versioned-PCM dependencies satisfied, so we do not need to add additional
140151
// arguments. Proceed traversal to its dependencies.
141-
for dependencyId in try moduleInfo(of: moduleId).directDependencies! {
152+
for dependencyId in moduleInfo.directDependencies! {
142153
try visit(dependencyId,
143154
pathPCMArtSet: pathPCMArtSet,
144155
pcmArgSetMap: &pcmArgSetMap)
@@ -159,57 +170,19 @@ private extension InterModuleDependencyGraph {
159170
[ModuleDependencyId : Set<[String]>]
160171
) throws {
161172
for (moduleId, newPCMArgs) in pcmArgSetMap {
162-
var moduleDetails = try clangModuleDetails(of: moduleId)
163-
if moduleDetails.dependenciesCapturedPCMArgs == nil {
164-
moduleDetails.dependenciesCapturedPCMArgs = Set<[String]>()
173+
guard let moduleInfo = modules[moduleId] else {
174+
throw Driver.Error.missingModuleDependency(moduleId.moduleName)
175+
}
176+
guard case .clang(var clangModuleDetails) = moduleInfo.details else {
177+
throw Driver.Error.malformedModuleDependency(moduleId.moduleName,
178+
"no Clang `details` object")
165179
}
166-
newPCMArgs.forEach { moduleDetails.dependenciesCapturedPCMArgs!.insert($0) }
167-
modules[moduleId]!.details = .clang(moduleDetails)
180+
if clangModuleDetails.dependenciesCapturedPCMArgs == nil {
181+
clangModuleDetails.dependenciesCapturedPCMArgs = Set<[String]>()
182+
}
183+
newPCMArgs.forEach { clangModuleDetails.dependenciesCapturedPCMArgs!.insert($0) }
184+
modules[moduleId]!.details = .clang(clangModuleDetails)
168185
}
169186
}
170187
}
171188

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-
}

Sources/SwiftDriver/Explicit Module Builds/ExplicitDependencyBuildPlanner.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import Foundation
1515

1616
/// A map from a module identifier to a path to its .swiftmodule file.
1717
public typealias ExternalTargetModulePathMap = [ModuleDependencyId: AbsolutePath]
18+
19+
// FIXME: ExternalBuildArtifacts is a temporary backwards-compatibility shim
20+
// to help transition SwiftPM to the new API.
1821
/// A tuple all external artifacts a build system may pass in as input to the explicit build of the current module
1922
/// Consists of a map of externally-built targets, and a map of all previously discovered/scanned modules.
2023
public typealias ExternalBuildArtifacts = (ExternalTargetModulePathMap, ModuleInfoMap)
@@ -513,11 +516,3 @@ internal extension InterModuleDependencyGraph {
513516
return String(data: contents, encoding: .utf8)!
514517
}
515518
}
516-
517-
// To keep the ExplicitDependencyBuildPlanner an implementation detail, provide an API
518-
// to access the dependency graph
519-
extension Driver {
520-
public var interModuleDependencyGraph: InterModuleDependencyGraph? {
521-
return explicitDependencyBuildPlanner?.dependencyGraph
522-
}
523-
}

Sources/SwiftDriver/Explicit Module Builds/CommonDependencyGraphOperations.swift renamed to Sources/SwiftDriver/Explicit Module Builds/Inter Module Dependencies/CommonDependencyOperations.swift

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//===-------------- CommonDependencyGraphOperations.swift -----------------===//
1+
//===----------------- CommonDependencyOperations.swift -------------------===//
22
//
33
// This source file is part of the Swift.org open source project
44
//
@@ -10,9 +10,31 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
public extension InterModuleDependencyGraph {
13+
@_spi(Testing) public extension InterModuleDependencyOracle {
1414
/// An API to allow clients to accumulate InterModuleDependencyGraphs across mutiple main modules/targets
1515
/// into a single collection of discovered modules.
16+
func mergeModules(from dependencyGraph: InterModuleDependencyGraph) throws {
17+
try self.lock.withLock {
18+
for (moduleId, moduleInfo) in dependencyGraph.modules {
19+
try InterModuleDependencyGraph.mergeModule(moduleId, moduleInfo, into: &modules)
20+
}
21+
}
22+
}
23+
24+
// This is a backwards-compatibility shim to handle existing ModuleInfoMap-based API
25+
// used by SwiftPM
26+
func mergeModules(from moduleInfoMap: ModuleInfoMap) throws {
27+
try self.lock.withLock {
28+
for (moduleId, moduleInfo) in moduleInfoMap {
29+
try InterModuleDependencyGraph.mergeModule(moduleId, moduleInfo, into: &modules)
30+
}
31+
}
32+
}
33+
}
34+
35+
public extension InterModuleDependencyGraph {
36+
// This is a shim for backwards-compatibility with existing API used by SwiftPM.
37+
// TODO: After SwiftPM switches to using the oracle, this should be deleted.
1638
static func mergeModules(
1739
from dependencyGraph: InterModuleDependencyGraph,
1840
into discoveredModules: inout ModuleInfoMap
@@ -23,7 +45,8 @@ public extension InterModuleDependencyGraph {
2345
}
2446
}
2547

26-
internal extension InterModuleDependencyGraph {
48+
49+
@_spi(Testing) public extension InterModuleDependencyGraph {
2750
/// Merge a module with a given ID and Info into a ModuleInfoMap
2851
static func mergeModule(_ moduleId: ModuleDependencyId,
2952
_ moduleInfo: ModuleInfo,
@@ -114,4 +137,47 @@ internal extension InterModuleDependencyGraph {
114137
}
115138
}
116139
}
140+
141+
/// Given two moduleInfos of clang modules, merge them by combining their directDependencies and
142+
/// dependenciesCapturedPCMArgs and sourceFiles fields. These fields may differ across the same module
143+
/// scanned at different PCMArgs (e.g. -target option).
144+
static func mergeClangModuleInfoDependencies(_ firstInfo: ModuleInfo, _ secondInfo:ModuleInfo
145+
) -> ModuleInfo {
146+
guard case .clang(let firstDetails) = firstInfo.details,
147+
case .clang(let secondDetails) = secondInfo.details
148+
else {
149+
fatalError("mergeClangModules expected two valid ClangModuleDetails objects.")
150+
}
151+
152+
// As far as their dependencies go, these module infos are identical
153+
if firstInfo.directDependencies == secondInfo.directDependencies,
154+
firstDetails.dependenciesCapturedPCMArgs == secondDetails.dependenciesCapturedPCMArgs,
155+
firstInfo.sourceFiles == secondInfo.sourceFiles {
156+
return firstInfo
157+
}
158+
159+
// Create a new moduleInfo that represents this module with combined dependency information
160+
let firstModuleSources = firstInfo.sourceFiles ?? []
161+
let secondModuleSources = secondInfo.sourceFiles ?? []
162+
let combinedSourceFiles = Array(Set(firstModuleSources + secondModuleSources))
163+
164+
let firstModuleDependencies = firstInfo.directDependencies ?? []
165+
let secondModuleDependencies = secondInfo.directDependencies ?? []
166+
let combinedDependencies = Array(Set(firstModuleDependencies + secondModuleDependencies))
167+
168+
let firstModuleCapturedPCMArgs = firstDetails.dependenciesCapturedPCMArgs ?? Set<[String]>()
169+
let secondModuleCapturedPCMArgs = secondDetails.dependenciesCapturedPCMArgs ?? Set<[String]>()
170+
let combinedCapturedPCMArgs = firstModuleCapturedPCMArgs.union(secondModuleCapturedPCMArgs)
171+
172+
let combinedModuleDetails =
173+
ClangModuleDetails(moduleMapPath: firstDetails.moduleMapPath,
174+
dependenciesCapturedPCMArgs: combinedCapturedPCMArgs,
175+
contextHash: firstDetails.contextHash,
176+
commandLine: firstDetails.commandLine)
177+
178+
return ModuleInfo(modulePath: firstInfo.modulePath,
179+
sourceFiles: combinedSourceFiles,
180+
directDependencies: combinedDependencies,
181+
details: .clang(combinedModuleDetails))
182+
}
117183
}

0 commit comments

Comments
 (0)