Skip to content

Commit 46b88f7

Browse files
committed
Revert "[Explicit Module Builds] Remove Placeholder Dependency Resolution"
This reverts commit 00678f3. Placeholder resolution needs to stick around while SwiftPM transitions to the new Oracle API.
1 parent 7df88ad commit 46b88f7

File tree

5 files changed

+177
-7
lines changed

5 files changed

+177
-7
lines changed

Sources/SwiftDriver/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ add_library(SwiftDriver
1111
"ExplicitModuleBuilds/ClangVersionedDependencyResolution.swift"
1212
"ExplicitModuleBuilds/ExplicitDependencyBuildPlanner.swift"
1313
"ExplicitModuleBuilds/ModuleDependencyScanning.swift"
14+
"ExplicitModuleBuilds/PlaceholderDependencyResolution.swift"
1415
"ExplicitModuleBuilds/SerializableModuleArtifacts.swift"
1516
"ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift"
1617
"ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyGraph.swift"

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
throws {
1919
let externalTargetModulePathMap = externalBuildArtifacts.0
2020

21-
for (externalModuleId, externalModulePath) in externalTargetModulePathMap {
21+
for (externalModuleId, externalModulePath) in externalTargetModulePathMap {
2222
// Replace the occurence of a Swift module to-be-built from source-file
2323
// to an info that describes a pre-built binary module.
2424
let swiftModuleId: ModuleDependencyId = .swift(externalModuleId.moduleName)
@@ -30,15 +30,15 @@
3030
continue
3131
}
3232

33-
let newModuleId: ModuleDependencyId = .swiftPrebuiltExternal(externalModuleId.moduleName)
33+
let newModuleId: ModuleDependencyId = .swiftPrebuiltExternal(externalModuleId.moduleName)
3434
let newExternalModuleDetails =
3535
SwiftPrebuiltExternalModuleDetails(compiledModulePath: externalModulePath.description)
3636
let newInfo = ModuleInfo(modulePath: externalModulePath.description,
3737
sourceFiles: [],
3838
directDependencies: currentInfo.directDependencies,
3939
details: .swiftPrebuiltExternal(newExternalModuleDetails))
4040

41-
Self.replaceModule(originalId: swiftModuleId, replacementId: newModuleId,
41+
Self.replaceModule(originalId: swiftModuleId, replacementId: newModuleId,
4242
replacementInfo: newInfo, in: &modules)
4343
}
4444
}

Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,6 @@ internal extension Driver {
9696
}
9797

9898
mutating func performDependencyScan() throws -> InterModuleDependencyGraph {
99-
// FIXME: It is a bit hack to have to generate a job and then ask the executor to
100-
// generate a canonical command line for it. Can do better.
10199
let scannerJob = try dependencyScanningJob()
102100
let forceResponseFiles = parsedOptions.hasArgument(.driverForceResponseFiles)
103101
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory!
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
//===--------------- PlaceholderDependencyResolution.swift ----------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
import TSCBasic
13+
import TSCUtility
14+
import Foundation
15+
16+
@_spi(Testing) public extension InterModuleDependencyGraph {
17+
// Building a Swift module in Explicit Module Build mode requires passing all of its module
18+
// dependencies as explicit arguments to the build command.
19+
//
20+
// When the driver's clients (build systems) are planning a build that involves multiple
21+
// Swift modules, planning for each individual module may take place before its dependencies
22+
// have been built. This means that the dependency scanning action will not be able to
23+
// discover such modules. In such cases, the clients must provide the driver with information
24+
// about such external dependencies, including the path to where their compiled .swiftmodule
25+
// will be located, once built, and a full inter-module dependency graph for each such dependence.
26+
//
27+
// The driver will pass down the information about such external dependencies to the scanning
28+
// action, which will generate `placeholder` swift modules for them in the resulting dependency
29+
// graph. The driver will then use the complete dependency graph provided by
30+
// the client for each external dependency and use it to "resolve" the dependency's "placeholder"
31+
// module.
32+
//
33+
// Consider an example SwiftPM package with two targets: target B, and target A, where A
34+
// depends on B:
35+
// SwiftPM will process targets in a topological order and “bubble-up” each target’s
36+
// inter-module dependency graph to its dependees. First, SwiftPM will process B, and be
37+
// able to plan its full build because it does not have any target dependencies. Then the
38+
// driver is tasked with planning a build for A. SwiftPM will pass as input to the driver
39+
// the module dependency graph of its target’s dependencies, in this case, just the
40+
// dependency graph of B. The scanning action for module A will contain a placeholder module B,
41+
// which the driver will then resolve using B's full dependency graph provided by the client.
42+
43+
/// Resolve all placeholder dependencies using external dependency information provided by the client
44+
mutating func resolvePlaceholderDependencies(for externalBuildArtifacts: ExternalBuildArtifacts,
45+
using dependencyOracle: InterModuleDependencyOracle)
46+
throws {
47+
let externalTargetModulePathMap = externalBuildArtifacts.0
48+
let placeholderFilter : (ModuleDependencyId) -> Bool = {
49+
if case .swiftPlaceholder(_) = $0 {
50+
return true
51+
}
52+
return false
53+
}
54+
var placeholderModules = modules.keys.filter(placeholderFilter)
55+
56+
// Resolve all target placeholder modules
57+
let placeholderTargetModules = placeholderModules.filter { externalTargetModulePathMap[$0] != nil }
58+
for moduleId in placeholderTargetModules {
59+
guard let placeholderModulePath = externalTargetModulePathMap[moduleId] else {
60+
throw Driver.Error.missingExternalDependency(moduleId.moduleName)
61+
}
62+
try resolveTargetPlaceholder(placeholderId: moduleId,
63+
placeholderPath: placeholderModulePath,
64+
dependencyOracle: dependencyOracle)
65+
}
66+
67+
// Process remaining placeholders until there are none left
68+
placeholderModules = modules.keys.filter(placeholderFilter)
69+
while !placeholderModules.isEmpty {
70+
let moduleId = placeholderModules.first!
71+
let swiftModuleId = ModuleDependencyId.swift(moduleId.moduleName)
72+
guard let moduleInfo = dependencyOracle.getExternalModuleInfo(of: swiftModuleId) else {
73+
throw Driver.Error.missingExternalDependency(moduleId.moduleName)
74+
}
75+
76+
// Insert the resolved module, replacing the placeholder.
77+
try Self.mergeModule(swiftModuleId, moduleInfo, into: &modules)
78+
79+
// Traverse and add all of this external module's dependencies to the current graph.
80+
try resolvePlaceholderModuleDependencies(moduleId: swiftModuleId,
81+
dependencyOracle: dependencyOracle)
82+
83+
// Update the set of remaining placeholders to resolve
84+
placeholderModules = modules.keys.filter(placeholderFilter)
85+
}
86+
}
87+
}
88+
89+
fileprivate extension InterModuleDependencyGraph {
90+
/// Resolve a placeholder dependency that is an external target.
91+
mutating func resolveTargetPlaceholder(placeholderId: ModuleDependencyId,
92+
placeholderPath: AbsolutePath,
93+
dependencyOracle: InterModuleDependencyOracle)
94+
throws {
95+
// For this placeholder dependency, generate a new module info containing only the pre-compiled
96+
// module path, and insert it into the current module's dependency graph,
97+
// replacing equivalent placeholder module.
98+
//
99+
// For all dependencies of this placeholder (direct and transitive), insert them
100+
// into this module's graph.
101+
// - Swift dependencies are inserted as-is
102+
// - Clang dependencies are inserted as-is, if a matching Clang module is already found
103+
// in this module's graph, we merge moduleInfos of the two modules, in order to obtain
104+
// a super-set of their dependencies at all possible PCMArgs variants.
105+
//
106+
// The placeholder is resolved into a .swiftPrebuiltExternal module in the dependency graph.
107+
// The placeholder's corresponding module may appear in the externalModuleInfoMap as either
108+
// a .swift module or a .swiftPrebuiltExternal module if it had been resolved earlier
109+
// in the multi-module build planning context.
110+
let swiftModuleId = ModuleDependencyId.swift(placeholderId.moduleName)
111+
let swiftPrebuiltModuleId = ModuleDependencyId.swiftPrebuiltExternal(placeholderId.moduleName)
112+
let externalModuleId: ModuleDependencyId
113+
let externalModuleInfo: ModuleInfo
114+
if let moduleInfo = dependencyOracle.getExternalModuleInfo(of: swiftModuleId) {
115+
externalModuleId = swiftModuleId
116+
externalModuleInfo = moduleInfo
117+
} else if let prebuiltModuleInfo = dependencyOracle.getExternalModuleInfo(of: swiftPrebuiltModuleId) {
118+
externalModuleId = swiftPrebuiltModuleId
119+
externalModuleInfo = prebuiltModuleInfo
120+
} else {
121+
throw Driver.Error.missingExternalDependency(placeholderId.moduleName)
122+
}
123+
124+
let newExternalModuleDetails =
125+
SwiftPrebuiltExternalModuleDetails(compiledModulePath: placeholderPath.description)
126+
let newInfo = ModuleInfo(modulePath: placeholderPath.description,
127+
sourceFiles: [],
128+
directDependencies: externalModuleInfo.directDependencies,
129+
details: .swiftPrebuiltExternal(newExternalModuleDetails))
130+
131+
// Insert the resolved module, replacing the placeholder.
132+
try Self.mergeModule(swiftPrebuiltModuleId, newInfo, into: &modules)
133+
134+
// Traverse and add all of this external target's dependencies to the current graph.
135+
try resolvePlaceholderModuleDependencies(moduleId: externalModuleId,
136+
dependencyOracle: dependencyOracle)
137+
}
138+
139+
/// Resolve all dependencies of a placeholder module (direct and transitive), but merging them into the current graph.
140+
mutating func resolvePlaceholderModuleDependencies(moduleId: ModuleDependencyId,
141+
dependencyOracle: InterModuleDependencyOracle)
142+
throws {
143+
guard let resolvingModuleInfo = dependencyOracle.getExternalModuleInfo(of: moduleId) else {
144+
throw Driver.Error.missingExternalDependency(moduleId.moduleName)
145+
}
146+
147+
// Breadth-first traversal of all the dependencies of this module
148+
var visited: Set<ModuleDependencyId> = []
149+
var toVisit: [ModuleDependencyId] = resolvingModuleInfo.directDependencies ?? []
150+
var currentIndex = 0
151+
while let currentId = toVisit[currentIndex...].first {
152+
currentIndex += 1
153+
visited.insert(currentId)
154+
guard let currentInfo = dependencyOracle.getExternalModuleInfo(of: currentId) else {
155+
throw Driver.Error.missingExternalDependency(currentId.moduleName)
156+
}
157+
158+
try Self.mergeModule(currentId, currentInfo, into: &modules)
159+
160+
let currentDependencies = currentInfo.directDependencies ?? []
161+
for childId in currentDependencies where !visited.contains(childId) {
162+
if !toVisit.contains(childId) {
163+
toVisit.append(childId)
164+
}
165+
}
166+
}
167+
}
168+
}

Sources/SwiftDriver/Jobs/Planning.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -470,9 +470,12 @@ extension Driver {
470470
throws -> InterModuleDependencyGraph {
471471
var dependencyGraph = try performDependencyScan()
472472

473-
// Resolve external dependencies in the dependency graph to be treated as pre-built
474-
// binary modules.
475473
if externalBuildArtifacts != nil {
474+
// Resolve placeholder dependencies in the dependency graph, if any.
475+
// TODO: Should be deprecated once switched over to libSwiftScan in the clients
476+
try dependencyGraph.resolvePlaceholderDependencies(for: externalBuildArtifacts!,
477+
using: interModuleDependencyOracle)
478+
476479
try dependencyGraph.resolveExternalDependencies(for: externalBuildArtifacts!)
477480
}
478481

0 commit comments

Comments
 (0)