Skip to content

Commit d9a63cc

Browse files
committed
[Explicit Module Builds] Emit a JSON file that specifies details of explicit Swift module dependencies
This PR implements the interface expected by the frontend, as built in: swiftlang/swift#32355 swiftlang/swift#32450 Swift module's explicit Swift module dependencies are now encoded in a JSON file, passed with `-explicit-swift-module-map-file` Resolves rdar://problem/64533451
1 parent e2dadc8 commit d9a63cc

File tree

8 files changed

+176
-30
lines changed

8 files changed

+176
-30
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
"Explicit Module Builds/ExplicitModuleBuildHandler.swift"
1212
"Explicit Module Builds/InterModuleDependencyGraph.swift"
1313
"Explicit Module Builds/ModuleDependencyScanning.swift"
14+
"Explicit Module Builds/SwiftModuleArtifacts.swift"
1415

1516
Driver/CompilerMode.swift
1617
Driver/DebugInfo.swift

Sources/SwiftDriver/Explicit Module Builds/ExplicitModuleBuildHandler.swift

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,23 @@ public struct ExplicitModuleBuildHandler {
3131
/// The toolchain to be used for frontend job generation.
3232
private let toolchain: Toolchain
3333

34-
public init(dependencyGraph: InterModuleDependencyGraph, toolchain: Toolchain) throws {
34+
/// The file system which we should interact with.
35+
private let fileSystem: FileSystem
36+
37+
/// Path to the directory that will contain the temporary files.
38+
/// e.g. Explicit Swift module artifact files
39+
private let temporaryDirectory: AbsolutePath
40+
41+
public init(dependencyGraph: InterModuleDependencyGraph, toolchain: Toolchain,
42+
fileSystem: FileSystem) throws {
3543
self.dependencyGraph = dependencyGraph
3644
self.toolchain = toolchain
45+
self.fileSystem = fileSystem
46+
self.temporaryDirectory = try withTemporaryDirectory(removeTreeOnDeinit: false) { path in
47+
// FIXME: TSC removes empty directories even when removeTreeOnDeinit is false. This seems like a bug.
48+
try fileSystem.writeFileContents(path.appending(component: ".keep-directory")) { $0 <<< "" }
49+
return path
50+
}
3751
}
3852

3953
/// Generate build jobs for all dependencies of the main module.
@@ -207,6 +221,19 @@ public struct ExplicitModuleBuildHandler {
207221
)
208222
}
209223

224+
/// Store the output file artifacts for a given module in a JSON file, return the file's path.
225+
private func serializeModuleDependencies(for moduleId: ModuleDependencyId,
226+
dependencyArtifacts: [SwiftModuleArtifactInfo]
227+
) throws -> AbsolutePath {
228+
let dependencyFilePath =
229+
temporaryDirectory.appending(component: "\(moduleId.moduleName)-dependencies.json")
230+
let encoder = JSONEncoder()
231+
encoder.outputFormatting = [.prettyPrinted]
232+
let contents = try encoder.encode(dependencyArtifacts)
233+
try fileSystem.writeFileContents(dependencyFilePath, bytes: ByteString(contents))
234+
return dependencyFilePath
235+
}
236+
210237
/// For the specified module, update the given command line flags and inputs
211238
/// to use explicitly-built module dependencies.
212239
///
@@ -217,27 +244,43 @@ public struct ExplicitModuleBuildHandler {
217244
inputs: inout [TypedVirtualPath],
218245
commandLine: inout [Job.ArgTemplate]
219246
) throws {
247+
220248
// Prohibit the frontend from implicitly building textual modules into binary modules.
221249
commandLine.appendFlags("-disable-implicit-swift-modules", "-Xcc", "-Xclang", "-Xcc",
222250
"-fno-implicit-modules")
251+
var swiftDependencyArtifacts: [SwiftModuleArtifactInfo] = []
223252
try addModuleDependencies(moduleId: moduleId, pcmArgs: pcmArgs, inputs: &inputs,
224-
commandLine: &commandLine)
253+
commandLine: &commandLine,
254+
swiftDependencyArtifacts: &swiftDependencyArtifacts)
255+
256+
if (swiftDependencyArtifacts.isEmpty) {
257+
let dependencyFile = try serializeModuleDependencies(for: moduleId,
258+
dependencyArtifacts: swiftDependencyArtifacts)
259+
commandLine.appendFlag("-explicit-swift-module-map-file")
260+
commandLine.appendPath(dependencyFile)
261+
inputs.append(TypedVirtualPath(file: try VirtualPath(path: dependencyFile.pathString),
262+
type: .jsonSwiftArtifacts))
263+
}
225264
}
226265

227266
/// Add a specific module dependency as an input and a corresponding command
228267
/// line flag. Dispatches to clang and swift-specific variants.
229268
mutating private func addModuleDependencies(moduleId: ModuleDependencyId,
230269
pcmArgs: [String],
231270
inputs: inout [TypedVirtualPath],
232-
commandLine: inout [Job.ArgTemplate]) throws {
271+
commandLine: inout [Job.ArgTemplate],
272+
swiftDependencyArtifacts: inout [SwiftModuleArtifactInfo]
273+
) throws {
233274
for dependencyId in try dependencyGraph.moduleInfo(of: moduleId).directDependencies {
234275
switch dependencyId {
235276
case .swift:
236277
try addSwiftModuleDependency(moduleId: moduleId, dependencyId: dependencyId,
237-
pcmArgs: pcmArgs, inputs: &inputs, commandLine: &commandLine)
278+
pcmArgs: pcmArgs, inputs: &inputs, commandLine: &commandLine,
279+
swiftDependencyArtifacts: &swiftDependencyArtifacts)
238280
case .clang:
239281
try addClangModuleDependency(moduleId: moduleId, dependencyId: dependencyId,
240-
pcmArgs: pcmArgs, inputs: &inputs, commandLine: &commandLine)
282+
pcmArgs: pcmArgs, inputs: &inputs, commandLine: &commandLine,
283+
swiftDependencyArtifacts: &swiftDependencyArtifacts)
241284
}
242285
}
243286
}
@@ -250,7 +293,9 @@ public struct ExplicitModuleBuildHandler {
250293
dependencyId: ModuleDependencyId,
251294
pcmArgs: [String],
252295
inputs: inout [TypedVirtualPath],
253-
commandLine: inout [Job.ArgTemplate]) throws {
296+
commandLine: inout [Job.ArgTemplate],
297+
swiftDependencyArtifacts: inout [SwiftModuleArtifactInfo]
298+
) throws {
254299
// Generate a build job for the dependency module, if not already generated
255300
if swiftModuleBuildCache[dependencyId] == nil {
256301
try genSwiftModuleBuildJob(moduleId: dependencyId)
@@ -261,13 +306,17 @@ public struct ExplicitModuleBuildHandler {
261306
let dependencyInfo = try dependencyGraph.moduleInfo(of: dependencyId)
262307
let swiftModulePath = TypedVirtualPath(file: try VirtualPath(path: dependencyInfo.modulePath),
263308
type: .swiftModule)
264-
commandLine.appendFlags("-swift-module-file")
265-
commandLine.appendPath(swiftModulePath.file)
266-
inputs.append(swiftModulePath)
309+
310+
// Collect the requried information about this module
311+
// TODO: add .swiftdoc and .swiftsourceinfo for this module.
312+
swiftDependencyArtifacts.append(
313+
SwiftModuleArtifactInfo(name: dependencyId.moduleName,
314+
modulePath: swiftModulePath.file.description))
267315

268316
// Process all transitive dependencies as direct
269-
try addModuleDependencies(moduleId: dependencyId, pcmArgs: pcmArgs, inputs: &inputs,
270-
commandLine: &commandLine)
317+
try addModuleDependencies(moduleId: dependencyId, pcmArgs: pcmArgs,
318+
inputs: &inputs, commandLine: &commandLine,
319+
swiftDependencyArtifacts: &swiftDependencyArtifacts)
271320
}
272321

273322
/// Add a specific Clang module dependency as an input and a corresponding command
@@ -278,7 +327,9 @@ public struct ExplicitModuleBuildHandler {
278327
dependencyId: ModuleDependencyId,
279328
pcmArgs: [String],
280329
inputs: inout [TypedVirtualPath],
281-
commandLine: inout [Job.ArgTemplate]) throws {
330+
commandLine: inout [Job.ArgTemplate],
331+
swiftDependencyArtifacts: inout [SwiftModuleArtifactInfo]
332+
) throws {
282333
// Generate a build job for the dependency module at the given target, if not already generated
283334
if clangTargetModuleBuildCache[(dependencyId, pcmArgs)] == nil {
284335
try genClangModuleBuildJob(moduleId: dependencyId, pcmArgs: pcmArgs)
@@ -302,8 +353,9 @@ public struct ExplicitModuleBuildHandler {
302353
inputs.append(clangModuleMapPath)
303354

304355
// Process all transitive dependencies as direct
305-
try addModuleDependencies(moduleId: dependencyId, pcmArgs: pcmArgs, inputs: &inputs,
306-
commandLine: &commandLine)
356+
try addModuleDependencies(moduleId: dependencyId, pcmArgs: pcmArgs,
357+
inputs: &inputs, commandLine: &commandLine,
358+
swiftDependencyArtifacts: &swiftDependencyArtifacts)
307359
}
308360
}
309361

Sources/SwiftDriver/Explicit Module Builds/InterModuleDependencyGraph.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//===--------------- ModuleDependencyGraph.swift --------------------------===//
1+
//===--------------- InterModuleDependencyGraph.swift ---------------------===//
22
//
33
// This source file is part of the Swift.org open source project
44
//
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//===--------------- SwiftModuleArtifacts.swift ---------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 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 Foundation
13+
14+
/// Describes a given Swift module's pre-built module artifacts:
15+
/// - Swift Module (name)
16+
/// - Swift Module Path
17+
/// - Swift Doc Path
18+
/// - Swift Source Info Path
19+
public struct SwiftModuleArtifactInfo: Codable {
20+
/// The module's name
21+
public let moduleName: String
22+
/// The path for the module's .swiftmodule file
23+
public let modulePath: String
24+
/// The path for the module's .swiftdoc file
25+
public let docPath: String?
26+
/// The path for the module's .swiftsourceinfo file
27+
public let sourceInfoPath: String?
28+
29+
init(name: String, modulePath: String, docPath: String? = nil, sourceInfoPath: String? = nil) {
30+
self.moduleName = name
31+
self.modulePath = modulePath
32+
self.docPath = docPath
33+
self.sourceInfoPath = sourceInfoPath
34+
}
35+
36+
private enum CodingKeys : String, CodingKey {
37+
case moduleName = "SwiftModule"
38+
case modulePath = "SwiftModulePath"
39+
case docPath = "SwiftDocPath"
40+
case sourceInfoPath = "SwiftSourceInfoPath"
41+
}
42+
}

Sources/SwiftDriver/Jobs/CompileJob.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ extension Driver {
6969
.swiftDocumentation, .swiftInterface,
7070
.swiftSourceInfoFile, .raw_sib, .llvmBitcode, .diagnostics,
7171
.objcHeader, .swiftDeps, .remap, .importedModules, .tbd, .moduleTrace,
72-
.indexData, .optimizationRecord, .pcm, .pch, .clangModuleMap, nil:
72+
.indexData, .optimizationRecord, .pcm, .pch, .clangModuleMap, .jsonSwiftArtifacts, nil:
7373
return false
7474
}
7575
}
@@ -254,7 +254,8 @@ extension FileType {
254254

255255
case .swift, .dSYM, .autolink, .dependencies, .swiftDocumentation, .pcm,
256256
.diagnostics, .objcHeader, .image, .swiftDeps, .moduleTrace, .tbd,
257-
.optimizationRecord, .swiftInterface, .swiftSourceInfoFile, .clangModuleMap:
257+
.optimizationRecord, .swiftInterface, .swiftSourceInfoFile, .clangModuleMap,
258+
.jsonSwiftArtifacts:
258259
fatalError("Output type can never be a primary output")
259260
}
260261
}

Sources/SwiftDriver/Jobs/Planning.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@ extension Driver {
227227
fatalError("Attempting to perform Explicit Module Build job generation, but the Inter Module Dependency Graph does not exist.")
228228
}
229229
explicitModuleBuildHandler = try ExplicitModuleBuildHandler(dependencyGraph: dependencyGraph,
230-
toolchain: toolchain)
230+
toolchain: toolchain,
231+
fileSystem: fileSystem)
231232
return try explicitModuleBuildHandler!.generateExplicitModuleDependenciesBuildJobs()
232233
}
233234

Sources/SwiftDriver/Utilities/FileType.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ public enum FileType: String, Hashable, CaseIterable, Codable {
9090
/// JSON-based Module Dependency Scanner output
9191
case jsonDependencies = "dependencies.json"
9292

93+
/// JSON-based binary Swift module artifact description
94+
case jsonSwiftArtifacts = "artifacts.json"
95+
9396
/// Module trace file.
9497
///
9598
/// Module traces are used by Apple's internal build infrastructure. Apple
@@ -147,6 +150,9 @@ extension FileType: CustomStringConvertible {
147150
case .jsonDependencies:
148151
return "json-dependencies"
149152

153+
case .jsonSwiftArtifacts:
154+
return "json-module-artifacts"
155+
150156
case .importedModules:
151157
return "imported-modules"
152158

@@ -176,7 +182,7 @@ extension FileType {
176182
.importedModules, .indexData, .remap, .dSYM, .autolink, .dependencies,
177183
.swiftDocumentation, .pcm, .diagnostics, .objcHeader, .image,
178184
.swiftDeps, .moduleTrace, .tbd, .optimizationRecord, .swiftInterface,
179-
.swiftSourceInfoFile, .jsonDependencies, .clangModuleMap:
185+
.swiftSourceInfoFile, .jsonDependencies, .jsonSwiftArtifacts, .clangModuleMap:
180186
return false
181187
}
182188
}
@@ -247,6 +253,8 @@ extension FileType {
247253
return "swift-dependencies"
248254
case .jsonDependencies:
249255
return "json-dependencies"
256+
case .jsonSwiftArtifacts:
257+
return "json-module-artifacts"
250258
case .importedModules:
251259
return "imported-modules"
252260
case .moduleTrace:
@@ -266,7 +274,8 @@ extension FileType {
266274
switch self {
267275
case .swift, .sil, .dependencies, .assembly, .ast, .raw_sil, .llvmIR,
268276
.objcHeader, .autolink, .importedModules, .tbd, .moduleTrace,
269-
.optimizationRecord, .swiftInterface, .jsonDependencies, .clangModuleMap:
277+
.optimizationRecord, .swiftInterface, .jsonDependencies, .jsonSwiftArtifacts,
278+
.clangModuleMap:
270279
return true
271280
case .image, .object, .dSYM, .pch, .sib, .raw_sib, .swiftModule,
272281
.swiftDocumentation, .swiftSourceInfoFile, .llvmBitcode, .diagnostics,
@@ -285,7 +294,7 @@ extension FileType {
285294
.swiftModule, .swiftDocumentation, .swiftInterface, .swiftSourceInfoFile,
286295
.raw_sil, .raw_sib, .diagnostics, .objcHeader, .swiftDeps, .remap, .importedModules,
287296
.tbd, .moduleTrace, .indexData, .optimizationRecord, .pcm, .pch, .jsonDependencies,
288-
.clangModuleMap:
297+
.jsonSwiftArtifacts, .clangModuleMap:
289298
return false
290299
}
291300
}

Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,23 @@ private func checkExplicitModuleBuildJobDependencies(job: Job,
5959
let dependencyInfo = moduleDependencyGraph.modules[dependencyId]!
6060
switch dependencyInfo.details {
6161
case .swift:
62-
let swiftDependencyModulePath =
63-
TypedVirtualPath(file: try VirtualPath(path: dependencyInfo.modulePath),
64-
type: .swiftModule)
65-
XCTAssertTrue(job.inputs.contains(swiftDependencyModulePath))
66-
XCTAssertTrue(job.commandLine.contains(
67-
.flag(String("-swift-module-file"))))
68-
XCTAssertTrue(
69-
job.commandLine.contains(.path(try VirtualPath(path: dependencyInfo.modulePath))))
62+
// Load the dependency JSON and verify this dependency was encoded correctly
63+
let explicitDepsFlag =
64+
SwiftDriver.Job.ArgTemplate.flag(String("-explicit-swift-module-map-file"))
65+
XCTAssert(job.commandLine.contains(explicitDepsFlag))
66+
let jsonDepsPathIndex = job.commandLine.firstIndex(of: explicitDepsFlag)
67+
let jsonDepsPathArg = job.commandLine[jsonDepsPathIndex! + 1]
68+
guard case .path(let jsonDepsPath) = jsonDepsPathArg else {
69+
XCTFail("No JSON dependency file path found.")
70+
return
71+
}
72+
let contents =
73+
try localFileSystem.readFileContents(jsonDepsPath.absolutePath!)
74+
let dependencyInfoList = try JSONDecoder().decode(Array<SwiftModuleArtifactInfo>.self,
75+
from: Data(contents.contents))
76+
let dependencyArtifacts =
77+
dependencyInfoList.first(where:{ $0.moduleName == dependencyId.moduleName })
78+
XCTAssertEqual(dependencyArtifacts!.modulePath, dependencyInfo.modulePath)
7079
case .clang(let clangDependencyDetails):
7180
let clangDependencyModulePathString =
7281
try ExplicitModuleBuildHandler.targetEncodedClangModuleFilePath(
@@ -113,7 +122,8 @@ final class ExplicitModuleBuildTests: XCTestCase {
113122
InterModuleDependencyGraph.self,
114123
from: ModuleDependenciesInputs.fastDependencyScannerOutput.data(using: .utf8)!)
115124
driver.explicitModuleBuildHandler = try ExplicitModuleBuildHandler(dependencyGraph: moduleDependencyGraph,
116-
toolchain: driver.toolchain)
125+
toolchain: driver.toolchain,
126+
fileSystem: localFileSystem)
117127
let modulePrebuildJobs =
118128
try driver.explicitModuleBuildHandler!.generateExplicitModuleDependenciesBuildJobs()
119129
XCTAssertEqual(modulePrebuildJobs.count, 4)
@@ -223,4 +233,34 @@ final class ExplicitModuleBuildTests: XCTestCase {
223233
}
224234
}
225235
}
236+
237+
func testExplicitSwiftModuleMap() throws {
238+
let jsonExample : String = """
239+
[
240+
{
241+
"SwiftModule": "A",
242+
"SwiftModulePath": "A.swiftmodule",
243+
"SwiftDocPath": "A.swiftdoc",
244+
"SwiftSourceInfoPath": "A.swiftsourceinfo"
245+
},
246+
{
247+
"SwiftModule": "B",
248+
"SwiftModulePath": "B.swiftmodule",
249+
"SwiftDocPath": "B.swiftdoc",
250+
"SwiftSourceInfoPath": "B.swiftsourceinfo"
251+
}
252+
]
253+
"""
254+
let moduleMap = try JSONDecoder().decode(Array<SwiftModuleArtifactInfo>.self,
255+
from: jsonExample.data(using: .utf8)!)
256+
XCTAssertEqual(moduleMap.count, 2)
257+
XCTAssertEqual(moduleMap[0].moduleName, "A")
258+
XCTAssertEqual(moduleMap[0].modulePath, "A.swiftmodule")
259+
XCTAssertEqual(moduleMap[0].docPath, "A.swiftdoc")
260+
XCTAssertEqual(moduleMap[0].sourceInfoPath, "A.swiftsourceinfo")
261+
XCTAssertEqual(moduleMap[1].moduleName, "B")
262+
XCTAssertEqual(moduleMap[1].modulePath, "B.swiftmodule")
263+
XCTAssertEqual(moduleMap[1].docPath, "B.swiftdoc")
264+
XCTAssertEqual(moduleMap[1].sourceInfoPath, "B.swiftsourceinfo")
265+
}
226266
}

0 commit comments

Comments
 (0)