Skip to content

Commit ffe24e5

Browse files
authored
Merge pull request #181 from artemcm/ExplicitPackageBuilds
[Explicit Module Builds] Support explicit module builds with external dependencies.
2 parents b4b0764 + 30149cb commit ffe24e5

10 files changed

+579
-39
lines changed

Sources/SwiftDriver/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
add_library(SwiftDriver
1010
"Explicit Module Builds/ClangModuleBuildJobCache.swift"
11-
"Explicit Module Builds/ExplicitModuleBuildHandler.swift"
11+
"Explicit Module Builds/ExplicitModuleBuildHandler.swift"
12+
"Explicit Module Builds/PlaceholderDependencyResolution.swift"
1213
"Explicit Module Builds/InterModuleDependencyGraph.swift"
1314
"Explicit Module Builds/ModuleDependencyScanning.swift"
1415
"Explicit Module Builds/ModuleArtifacts.swift"

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public struct Driver {
3030
case missingPCMArguments(String)
3131
case missingModuleDependency(String)
3232
case dependencyScanningFailure(Int, String)
33+
case missingExternalDependency(String)
3334

3435
public var description: String {
3536
switch self {
@@ -59,6 +60,8 @@ public struct Driver {
5960
return "Module Dependency Scanner returned with non-zero exit status: \(code), \(error)"
6061
case .unableToLoadOutputFileMap(let path):
6162
return "unable to load output file map '\(path)': no such file or directory"
63+
case .missingExternalDependency(let moduleName):
64+
return "Missing External dependency info for module: \(moduleName)"
6265
}
6366
}
6467
}
@@ -193,6 +196,11 @@ public struct Driver {
193196
/// as explicit by the various compilation jobs.
194197
@_spi(Testing) public var explicitModuleBuildHandler: ExplicitModuleBuildHandler? = nil
195198

199+
/// A collection describing external dependencies for the current main module that may be invisible to
200+
/// the driver itself, but visible to its clients (e.g. build systems like SwiftPM). Along with the external dependencies'
201+
/// module dependency graphs.
202+
internal var externalDependencyArtifactMap: ExternalDependencyArtifactMap? = nil
203+
196204
/// Handler for emitting diagnostics to stderr.
197205
public static let stderrDiagnosticsHandler: DiagnosticsEngine.DiagnosticsHandler = { diagnostic in
198206
let stream = stderrStream
@@ -226,24 +234,30 @@ public struct Driver {
226234
/// - Parameter diagnosticsHandler: A callback executed when a diagnostic is
227235
/// emitted. The default argument prints diagnostics to stderr.
228236
/// - Parameter executor: Used by the driver to execute jobs. The default argument
229-
/// is present to streamline testing, it shouldn't be used in production.
237+
/// is present to streamline testing, it shouldn't be used in production.
238+
/// - Parameter externalModuleDependencies: A collection of external modules that the main module
239+
/// of the current compilation depends on. Explicit Module Build use only.
230240
public init(
231241
args: [String],
232242
env: [String: String] = ProcessEnv.vars,
233243
diagnosticsEngine: DiagnosticsEngine = DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler]),
234244
fileSystem: FileSystem = localFileSystem,
235-
executor: DriverExecutor
245+
executor: DriverExecutor,
246+
externalModuleDependencies: ExternalDependencyArtifactMap? = nil
236247
) throws {
237248
self.env = env
238249
self.fileSystem = fileSystem
239250

240251
self.diagnosticEngine = diagnosticsEngine
241252
self.executor = executor
242253

254+
self.externalDependencyArtifactMap = externalModuleDependencies
255+
243256
if case .subcommand = try Self.invocationRunMode(forArgs: args).mode {
244257
throw Error.subcommandPassedToDriver
245258
}
246259

260+
247261
var args = try Self.expandResponseFiles(args, fileSystem: fileSystem, diagnosticsEngine: self.diagnosticEngine)
248262

249263
self.driverKind = try Self.determineDriverKind(args: &args)

Sources/SwiftDriver/Explicit Module Builds/ExplicitModuleBuildHandler.swift

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import TSCBasic
1313
import TSCUtility
1414
import Foundation
1515

16+
/// A map from a module identifier to a pair consisting of a path to its .swiftmodule file and its module dependency graph.
17+
public typealias ExternalDependencyArtifactMap =
18+
[ModuleDependencyId: (AbsolutePath, InterModuleDependencyGraph)]
19+
1620
/// In Explicit Module Build mode, this handler is responsible for generating and providing
1721
/// build jobs for all module dependencies and providing compile command options
1822
/// that specify said explicit module dependencies.
@@ -30,6 +34,9 @@ import Foundation
3034
/// The toolchain to be used for frontend job generation.
3135
private let toolchain: Toolchain
3236

37+
/// A collection of external dependency modules, and their binary module file paths and dependency graph.
38+
internal let externalDependencyArtifactMap: ExternalDependencyArtifactMap
39+
3340
/// The file system which we should interact with.
3441
/// FIXME: Our end goal is to not have any direct filesystem manipulation in here, but that's dependent on getting the
3542
/// dependency scanner/dependency job generation moved into a Job.
@@ -42,9 +49,11 @@ import Foundation
4249
private let temporaryDirectory: AbsolutePath
4350

4451
public init(dependencyGraph: InterModuleDependencyGraph, toolchain: Toolchain,
45-
fileSystem: FileSystem) throws {
52+
fileSystem: FileSystem,
53+
externalDependencyArtifactMap: ExternalDependencyArtifactMap) throws {
4654
self.dependencyGraph = dependencyGraph
4755
self.toolchain = toolchain
56+
self.externalDependencyArtifactMap = externalDependencyArtifactMap
4857
self.fileSystem = fileSystem
4958
self.temporaryDirectory = try determineTempDirectory()
5059
}
@@ -110,10 +119,17 @@ import Foundation
110119
/// - Generate Job: S1
111120
///
112121
mutating public func generateExplicitModuleDependenciesBuildJobs() throws -> [Job] {
122+
// Resolve placeholder dependencies in the dependency graph, if any.
123+
if (!externalDependencyArtifactMap.isEmpty) {
124+
try resolvePlaceholderDependencies()
125+
}
126+
127+
// Compute jobs for all main module dependencies
113128
var mainModuleInputs: [TypedVirtualPath] = []
114129
var mainModuleCommandLine: [Job.ArgTemplate] = []
115130
try resolveMainModuleDependencies(inputs: &mainModuleInputs,
116131
commandLine: &mainModuleCommandLine)
132+
117133
return Array(swiftModuleBuildCache.values) + clangTargetModuleBuildCache.allJobs
118134
}
119135

@@ -297,7 +313,7 @@ import Foundation
297313
clangDependencyArtifacts: inout [ClangModuleArtifactInfo],
298314
swiftDependencyArtifacts: inout [SwiftModuleArtifactInfo]
299315
) throws {
300-
for dependencyId in try dependencyGraph.moduleInfo(of: moduleId).directDependencies {
316+
for dependencyId in try dependencyGraph.moduleInfo(of: moduleId).directDependencies! {
301317
guard addedDependenciesSet.insert(dependencyId).inserted else {
302318
continue
303319
}
@@ -314,6 +330,8 @@ import Foundation
314330
addedDependenciesSet: &addedDependenciesSet,
315331
clangDependencyArtifacts: &clangDependencyArtifacts,
316332
swiftDependencyArtifacts: &swiftDependencyArtifacts)
333+
case .swiftPlaceholder:
334+
fatalError("Unresolved placeholder dependencies at planning stage: \(dependencyId) of \(moduleId)")
317335
}
318336
}
319337
}
@@ -334,7 +352,7 @@ import Foundation
334352

335353
let swiftModulePath: TypedVirtualPath
336354
if case .swift(let details) = dependencyInfo.details,
337-
let compiledModulePath = details.compiledModulePath {
355+
let compiledModulePath = details.explicitCompiledModulePath {
338356
// If an already-compiled module is available, use it.
339357
swiftModulePath = .init(file: try VirtualPath(path: compiledModulePath),
340358
type: .swiftModule)
@@ -457,4 +475,10 @@ private extension InterModuleDependencyGraph {
457475
}
458476
}
459477

460-
478+
// To keep the ExplicitModuleBuildHandler an implementation detail, provide an API
479+
// to access the dependency graph
480+
extension Driver {
481+
public var interModuleDependencyGraph: InterModuleDependencyGraph? {
482+
return explicitModuleBuildHandler?.dependencyGraph
483+
}
484+
}

Sources/SwiftDriver/Explicit Module Builds/InterModuleDependencyGraph.swift

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,24 @@
1212
import Foundation
1313

1414

15-
@_spi(Testing) public enum ModuleDependencyId: Hashable {
15+
public enum ModuleDependencyId: Hashable {
1616
case swift(String)
17+
case swiftPlaceholder(String)
1718
case clang(String)
1819

1920
public var moduleName: String {
2021
switch self {
21-
case .swift(let name): return name
22-
case .clang(let name): return name
22+
case .swift(let name): return name
23+
case .swiftPlaceholder(let name): return name
24+
case .clang(let name): return name
2325
}
2426
}
2527
}
2628

2729
extension ModuleDependencyId: Codable {
2830
enum CodingKeys: CodingKey {
2931
case swift
32+
case swiftPlaceholder
3033
case clang
3134
}
3235

@@ -36,8 +39,13 @@ extension ModuleDependencyId: Codable {
3639
let moduleName = try container.decode(String.self, forKey: .swift)
3740
self = .swift(moduleName)
3841
} catch {
39-
let moduleName = try container.decode(String.self, forKey: .clang)
40-
self = .clang(moduleName)
42+
do {
43+
let moduleName = try container.decode(String.self, forKey: .swiftPlaceholder)
44+
self = .swiftPlaceholder(moduleName)
45+
} catch {
46+
let moduleName = try container.decode(String.self, forKey: .clang)
47+
self = .clang(moduleName)
48+
}
4149
}
4250
}
4351

@@ -46,59 +54,81 @@ extension ModuleDependencyId: Codable {
4654
switch self {
4755
case .swift(let moduleName):
4856
try container.encode(moduleName, forKey: .swift)
57+
case .swiftPlaceholder(let moduleName):
58+
try container.encode(moduleName, forKey: .swift)
4959
case .clang(let moduleName):
5060
try container.encode(moduleName, forKey: .clang)
5161
}
5262
}
5363
}
5464

65+
/// Bridging header
66+
public struct BridgingHeader: Codable {
67+
var path: String
68+
var sourceFiles: [String]
69+
var moduleDependencies: [String]
70+
}
71+
5572
/// Details specific to Swift modules.
56-
@_spi(Testing) public struct SwiftModuleDetails: Codable {
73+
public struct SwiftModuleDetails: Codable {
5774
/// The module interface from which this module was built, if any.
58-
public var moduleInterfacePath: String?
75+
@_spi(Testing) public var moduleInterfacePath: String?
5976

6077
/// The paths of potentially ready-to-use compiled modules for the interface.
61-
public var compiledModuleCandidates: [String]?
78+
@_spi(Testing) public var compiledModuleCandidates: [String]?
6279

63-
/// The path to the already-compiled module.
64-
public var compiledModulePath: String?
80+
/// The path to the already-compiled module that must be used instead of
81+
/// generating a job to build this module. In standard compilation, the dependency scanner
82+
/// may discover compiled module candidates to be used instead of re-compiling from interface.
83+
/// In contrast, this explicitCompiledModulePath is only to be used for precompiled modules
84+
/// external dependencies in Explicit Module Build mode
85+
@_spi(Testing) public var explicitCompiledModulePath: String?
6586

6687
/// The bridging header, if any.
67-
public var bridgingHeaderPath: String?
88+
var bridgingHeaderPath: String?
6889

6990
/// The source files referenced by the bridging header.
70-
public var bridgingSourceFiles: [String]? = []
91+
var bridgingSourceFiles: [String]? = []
7192

7293
/// Options to the compile command
73-
public var commandLine: [String]? = []
94+
var commandLine: [String]? = []
7495

7596
/// To build a PCM to be used by this Swift module, we need to append these
7697
/// arguments to the generic PCM build arguments reported from the dependency
7798
/// graph.
78-
public var extraPcmArgs: [String]? = []
99+
@_spi(Testing) public var extraPcmArgs: [String]?
100+
}
101+
102+
/// Details specific to Swift external modules.
103+
public struct swiftPlaceholderModuleDetails: Codable {
104+
/// The path to the .swiftModuleDoc file.
105+
var moduleDocPath: String?
106+
107+
/// The path to the .swiftSourceInfo file.
108+
var moduleSourceInfoPath: String?
79109
}
80110

81111
/// Details specific to Clang modules.
82-
@_spi(Testing) public struct ClangModuleDetails: Codable {
112+
public struct ClangModuleDetails: Codable {
83113
/// The path to the module map used to build this module.
84-
public var moduleMapPath: String
114+
@_spi(Testing) public var moduleMapPath: String
85115

86116
/// clang-generated context hash
87-
public var contextHash: String?
117+
var contextHash: String?
88118

89119
/// Options to the compile command
90-
public var commandLine: [String]? = []
120+
var commandLine: [String]? = []
91121
}
92122

93-
@_spi(Testing) public struct ModuleInfo: Codable {
123+
public struct ModuleInfo: Codable {
94124
/// The path for the module.
95125
public var modulePath: String
96126

97127
/// The source files used to build this module.
98-
public var sourceFiles: [String] = []
128+
public var sourceFiles: [String]? = []
99129

100130
/// The set of direct module dependencies of this module.
101-
public var directDependencies: [ModuleDependencyId] = []
131+
public var directDependencies: [ModuleDependencyId]? = []
102132

103133
/// Specific details of a particular kind of module.
104134
public var details: Details
@@ -109,6 +139,10 @@ extension ModuleDependencyId: Codable {
109139
/// a bridging header.
110140
case swift(SwiftModuleDetails)
111141

142+
/// Swift external modules carry additional details that specify their
143+
/// module doc path and source info paths.
144+
case swiftPlaceholder(swiftPlaceholderModuleDetails)
145+
112146
/// Clang modules are built from a module map file.
113147
case clang(ClangModuleDetails)
114148
}
@@ -117,6 +151,7 @@ extension ModuleDependencyId: Codable {
117151
extension ModuleInfo.Details: Codable {
118152
enum CodingKeys: CodingKey {
119153
case swift
154+
case swiftPlaceholder
120155
case clang
121156
}
122157

@@ -126,8 +161,13 @@ extension ModuleInfo.Details: Codable {
126161
let details = try container.decode(SwiftModuleDetails.self, forKey: .swift)
127162
self = .swift(details)
128163
} catch {
129-
let details = try container.decode(ClangModuleDetails.self, forKey: .clang)
130-
self = .clang(details)
164+
do {
165+
let details = try container.decode(swiftPlaceholderModuleDetails.self, forKey: .swiftPlaceholder)
166+
self = .swiftPlaceholder(details)
167+
} catch {
168+
let details = try container.decode(ClangModuleDetails.self, forKey: .clang)
169+
self = .clang(details)
170+
}
131171
}
132172
}
133173

@@ -136,6 +176,8 @@ extension ModuleInfo.Details: Codable {
136176
switch self {
137177
case .swift(let details):
138178
try container.encode(details, forKey: .swift)
179+
case .swiftPlaceholder(let details):
180+
try container.encode(details, forKey: .swiftPlaceholder)
139181
case .clang(let details):
140182
try container.encode(details, forKey: .clang)
141183
}
@@ -144,7 +186,7 @@ extension ModuleInfo.Details: Codable {
144186

145187
/// Describes the complete set of dependencies for a Swift module, including
146188
/// all of the Swift and C modules and source files it depends on.
147-
@_spi(Testing) public struct InterModuleDependencyGraph: Codable {
189+
public struct InterModuleDependencyGraph: Codable {
148190
/// The name of the main module.
149191
public var mainModuleName: String
150192

Sources/SwiftDriver/Explicit Module Builds/ModuleDependencyScanning.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ extension Driver {
3131
moduleDependencyGraphUse: .dependencyScan)
3232
// FIXME: MSVC runtime flags
3333

34+
// Pass in external dependencies to be treated as placeholder dependencies by the scanner
35+
if let externalDependencyArtifactMap = externalDependencyArtifactMap {
36+
let dependencyPlaceholderMapFile =
37+
try serializeExternalDependencyArtifacts(externalDependencyArtifactMap:
38+
externalDependencyArtifactMap)
39+
commandLine.appendFlag("-placeholder-dependency-module-map-file")
40+
commandLine.appendPath(dependencyPlaceholderMapFile)
41+
}
42+
3443
// Pass on the input files
3544
commandLine.append(contentsOf: inputFiles.map { .path($0.file)})
3645

@@ -44,4 +53,24 @@ extension Driver {
4453
outputs: [TypedVirtualPath(file: .standardOutput, type: .jsonDependencies)],
4554
supportsResponseFiles: true)
4655
}
56+
57+
/// Serialize a map of placeholder (external) dependencies for the dependency scanner.
58+
func serializeExternalDependencyArtifacts(externalDependencyArtifactMap: ExternalDependencyArtifactMap)
59+
throws -> AbsolutePath {
60+
let temporaryDirectory = try determineTempDirectory()
61+
let placeholderMapFilePath =
62+
temporaryDirectory.appending(component: "\(moduleOutputInfo.name)-placeholder-modules.json")
63+
64+
var placeholderArtifacts: [SwiftModuleArtifactInfo] = []
65+
for (moduleId, dependencyInfo) in externalDependencyArtifactMap {
66+
placeholderArtifacts.append(
67+
SwiftModuleArtifactInfo(name: moduleId.moduleName,
68+
modulePath: dependencyInfo.0.description))
69+
}
70+
let encoder = JSONEncoder()
71+
encoder.outputFormatting = [.prettyPrinted]
72+
let contents = try encoder.encode(placeholderArtifacts)
73+
try fileSystem.writeFileContents(placeholderMapFilePath, bytes: ByteString(contents))
74+
return placeholderMapFilePath
75+
}
4776
}

0 commit comments

Comments
 (0)