Skip to content

Commit a1cbb36

Browse files
committed
Specify module dependencies explicitly when pre-building module dependencies.
1 parent 8215541 commit a1cbb36

File tree

6 files changed

+164
-24
lines changed

6 files changed

+164
-24
lines changed

Sources/SwiftDriver/Dependency Scanning/InterModuleDependencyGraph.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,25 @@ extension ModuleDependencyId: Codable {
5050
try container.encode(moduleName, forKey: .clang)
5151
}
5252
}
53+
54+
func getName() -> String {
55+
switch self {
56+
case .swift(let moduleName):
57+
return moduleName
58+
case .clang(let moduleName):
59+
return moduleName
60+
}
61+
}
62+
63+
// Used in testing
64+
init(name: String, kind: CodingKeys) {
65+
switch kind {
66+
case .swift:
67+
self = .swift(name)
68+
case .clang:
69+
self = .clang(name)
70+
}
71+
}
5372
}
5473

5574
/// Details specific to Swift modules.

Sources/SwiftDriver/Dependency Scanning/ModuleDependencyBuildGeneration.swift

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,27 @@ extension Driver {
2626
}
2727
switch id {
2828
case .swift(let moduleName):
29-
let swiftModuleBuildJob = try genSwiftModuleDependencyBuildJob(moduleInfo: moduleInfo,
30-
moduleName: moduleName)
29+
let swiftModuleBuildJob =
30+
try genSwiftModuleDependencyBuildJob(moduleInfo: moduleInfo,
31+
moduleName: moduleName,
32+
dependencyGraph: dependencyGraph)
3133
jobs.append(swiftModuleBuildJob)
3234
case .clang(let moduleName):
33-
let clangModuleBuildJob = try genClangModuleDependencyBuildJob(moduleInfo: moduleInfo,
34-
moduleName: moduleName)
35+
let clangModuleBuildJob =
36+
try genClangModuleDependencyBuildJob(moduleInfo: moduleInfo,
37+
moduleName: moduleName,
38+
dependencyGraph: dependencyGraph)
3539
jobs.append(clangModuleBuildJob)
36-
3740
}
3841
}
3942
return jobs
4043
}
4144

4245
/// For a given swift module dependency, generate a build job
4346
mutating private func genSwiftModuleDependencyBuildJob(moduleInfo: ModuleInfo,
44-
moduleName: String) throws -> Job {
47+
moduleName: String,
48+
dependencyGraph: InterModuleDependencyGraph)
49+
throws -> Job {
4550
// FIXIT: Needs more error handling
4651
guard case .swift(let swiftModuleDetails) = moduleInfo.details else {
4752
throw Error.malformedModuleDependency(moduleName, "no `details` object")
@@ -54,6 +59,11 @@ extension Driver {
5459
var commandLine: [Job.ArgTemplate] = swiftCompilerPrefixArgs.map { Job.ArgTemplate.flag($0) }
5560
commandLine.appendFlag("-frontend")
5661

62+
try addModuleDependencies(moduleInfo: moduleInfo,
63+
dependencyGraph: dependencyGraph,
64+
inputs: &inputs,
65+
commandLine: &commandLine)
66+
5767
// Build the .swiftinterfaces file using a list of command line options specified in the
5868
// `details` field.
5969
guard let moduleInterfacePath = swiftModuleDetails.moduleInterfacePath else {
@@ -76,7 +86,9 @@ extension Driver {
7686

7787
/// For a given clang module dependency, generate a build job
7888
mutating private func genClangModuleDependencyBuildJob(moduleInfo: ModuleInfo,
79-
moduleName: String) throws -> Job {
89+
moduleName: String,
90+
dependencyGraph: InterModuleDependencyGraph)
91+
throws -> Job {
8092
// For clang modules, the Fast Dependency Scanner emits a list of source
8193
// files (with a .modulemap among them), and a list of compile command
8294
// options.
@@ -92,12 +104,18 @@ extension Driver {
92104
commandLine.appendFlag("-frontend")
93105
commandLine.appendFlags("-emit-pcm", "-module-name", moduleName)
94106

107+
try addModuleDependencies(moduleInfo: moduleInfo,
108+
dependencyGraph: dependencyGraph,
109+
inputs: &inputs,
110+
commandLine: &commandLine)
111+
95112
// The only required input is the .modulemap for this module.
96-
commandLine.append(Job.ArgTemplate.path(try VirtualPath(path: clangModuleDetails.moduleMapPath)))
113+
commandLine.append(Job.ArgTemplate.path(
114+
try VirtualPath(path: clangModuleDetails.moduleMapPath)))
97115
inputs.append(TypedVirtualPath(file: try VirtualPath(path: clangModuleDetails.moduleMapPath),
98116
type: .clangModuleMap))
99117
try addCommonModuleOptions(commandLine: &commandLine, outputs: &outputs)
100-
clangModuleDetails.commandLine?.forEach { commandLine.appendFlags("-Xcc", $0) }
118+
clangModuleDetails.commandLine?.forEach { commandLine.appendFlags($0) }
101119

102120
return Job(
103121
moduleName: moduleName,
@@ -108,4 +126,37 @@ extension Driver {
108126
outputs: outputs
109127
)
110128
}
129+
130+
131+
/// For the specified module, update its command line flags and inputs
132+
/// to use explicitly-built module dependencies.
133+
private func addModuleDependencies(moduleInfo: ModuleInfo,
134+
dependencyGraph: InterModuleDependencyGraph,
135+
inputs: inout [TypedVirtualPath],
136+
commandLine: inout [Job.ArgTemplate]) throws {
137+
// These options ensure that the frontend only uses explicitly-specified module dependencies
138+
// and the frontend errors if it has to perform any implicit module builds.
139+
commandLine.appendFlags("-disable-implicit-swift-modules", "-disable-implicit-pcms")
140+
for moduleId in moduleInfo.directDependencies {
141+
guard let dependencyInfo = dependencyGraph.modules[moduleId] else {
142+
throw Error.missingModuleDependency(moduleId.getName())
143+
}
144+
switch dependencyInfo.details {
145+
case .swift:
146+
let swiftModulePath = TypedVirtualPath(file: try VirtualPath(path: moduleInfo.modulePath),
147+
type: .swiftModule)
148+
commandLine.appendFlag("-swift-module-file=\(swiftModulePath.file.description)")
149+
inputs.append(swiftModulePath)
150+
case .clang(let clangDependencyDetails):
151+
let clangModulePath = TypedVirtualPath(file: try VirtualPath(path: moduleInfo.modulePath),
152+
type: .pcm)
153+
let clangModuleMapPath = TypedVirtualPath(file: try VirtualPath(path: clangDependencyDetails.moduleMapPath),
154+
type: .pcm)
155+
commandLine.appendFlag("-clang-module-file=\(clangModulePath.file.description)")
156+
commandLine.appendFlag("-clang-module-map-file=\(clangModuleMapPath.file.description)")
157+
inputs.append(clangModulePath)
158+
inputs.append(clangModuleMapPath)
159+
}
160+
}
161+
}
111162
}

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public struct Driver {
5151
case integratedReplRemoved
5252
case conflictingOptions(Option, Option)
5353
case malformedModuleDependency(String, String)
54+
case missingModuleDependency(String)
5455
case dependencyScanningFailure(Int, String)
5556

5657
public var description: String {
@@ -72,6 +73,8 @@ public struct Driver {
7273
return "conflicting options '\(one.spelling)' and '\(two.spelling)'"
7374
case .malformedModuleDependency(let moduleName, let errorDescription):
7475
return "Malformed Module Dependency: \(moduleName), \(errorDescription)"
76+
case .missingModuleDependency(let moduleName):
77+
return "Missing Module Dependency Info: \(moduleName)"
7578
case .dependencyScanningFailure(let code, let error):
7679
return "Module Dependency Scanner returned with non-zero exit status: \(code), \(error)"
7780
}

Sources/SwiftDriver/Jobs/Planning.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ extension Driver {
6363
}
6464
}
6565

66+
// Precompile module dependencies, if asked.
67+
if parsedOptions.contains(.driverExplicitModuleBuild) {
68+
jobs.append(contentsOf: try generateExplicitModuleBuildJobs())
69+
}
70+
6671
// Precompile the bridging header if needed.
6772
if let importedObjCHeader = importedObjCHeader,
6873
let bridgingPrecompiledHeader = bridgingPrecompiledHeader {

Sources/SwiftOptions/ExtraOptions.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111
//===----------------------------------------------------------------------===//
1212
extension Option {
1313
public static let driverPrintGraphviz: Option = Option("-driver-print-graphviz", .flag, attributes: [.helpHidden, .doesNotAffectIncrementalBuild], helpText: "Write the job graph as a graphviz file", group: .internalDebug)
14-
public static let driverPrebuildModuleDependencies: Option = Option("-driver-prebuild-module-dependencies", .flag, attributes: [.helpHidden], helpText: "Prebuild module dependencies to make them explicit")
14+
public static let driverExplicitModuleBuild: Option = Option("-experimental-explicit-module-build", .flag, attributes: [.helpHidden], helpText: "Prebuild module dependencies to make them explicit")
1515
public static let driverPrintModuleDependenciesJobs: Option = Option("-driver-print-module-dependencies-jobs", .flag, attributes: [.helpHidden], helpText: "Print commands to explicitly build module dependencies")
1616

1717
public static var extraOptions: [Option] {
1818
return [
1919
Option.driverPrintGraphviz,
20-
Option.driverPrebuildModuleDependencies,
20+
Option.driverExplicitModuleBuild,
2121
Option.driverPrintModuleDependenciesJobs
2222
]
2323
}

Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,73 @@ import Foundation
1515
import TSCBasic
1616
import XCTest
1717

18+
/// Check that an explicit module build job contains expected inputs and options
19+
private func checkExplicitModuleBuildJob(job: Job,
20+
moduleName: String,
21+
moduleKind: ModuleDependencyId.CodingKeys,
22+
moduleDependencyGraph: InterModuleDependencyGraph) throws {
23+
let moduleId = ModuleDependencyId(name: moduleName, kind: moduleKind)
24+
let moduleInfo = moduleDependencyGraph.modules[moduleId]!
25+
switch moduleInfo.details {
26+
case .swift(let swiftModuleDetails):
27+
let moduleInterfacePath =
28+
TypedVirtualPath(file: try VirtualPath(path: swiftModuleDetails.moduleInterfacePath!),
29+
type: .swiftInterface)
30+
XCTAssertEqual(job.kind, .emitModule)
31+
XCTAssertTrue(job.inputs.contains(moduleInterfacePath))
32+
case .clang(let clangModuleDetails):
33+
let moduleMapPath =
34+
TypedVirtualPath(file: try VirtualPath(path: clangModuleDetails.moduleMapPath),
35+
type: .clangModuleMap)
36+
XCTAssertEqual(job.kind, .generatePCM)
37+
XCTAssertTrue(job.inputs.contains(moduleMapPath))
38+
}
39+
try checkExplicitModuleBuildJobDependencies(job: job, moduleInfo: moduleInfo,
40+
moduleDependencyGraph: moduleDependencyGraph)
41+
42+
}
43+
44+
/// Checks that the build job for the specified module contains the required options and inputs
45+
/// to build all of its dependencies explicitly
46+
private func checkExplicitModuleBuildJobDependencies(job: Job,
47+
moduleInfo : ModuleInfo,
48+
moduleDependencyGraph: InterModuleDependencyGraph)
49+
throws {
50+
XCTAssertTrue(job.commandLine.contains(.flag(String("-disable-implicit-swift-modules"))))
51+
XCTAssertTrue(job.commandLine.contains(.flag(String("-disable-implicit-pcms"))))
52+
for dependencyId in moduleInfo.directDependencies {
53+
let dependencyInfo = moduleDependencyGraph.modules[dependencyId]!
54+
switch dependencyInfo.details {
55+
case .swift:
56+
let swiftDependencyModulePath =
57+
TypedVirtualPath(file: try VirtualPath(path: moduleInfo.modulePath),
58+
type: .swiftModule)
59+
XCTAssertTrue(job.inputs.contains(swiftDependencyModulePath))
60+
XCTAssertTrue(job.commandLine.contains(
61+
.flag(String("-swift-module-file=\(moduleInfo.modulePath)"))))
62+
case .clang(let clangDependencyDetails):
63+
let clangDependencyModulePath =
64+
TypedVirtualPath(file: try VirtualPath(path: moduleInfo.modulePath),
65+
type: .pcm)
66+
let clangDependencyModuleMapPath =
67+
TypedVirtualPath(file: try VirtualPath(path: clangDependencyDetails.moduleMapPath),
68+
type: .pcm)
69+
XCTAssertTrue(job.inputs.contains(clangDependencyModulePath))
70+
XCTAssertTrue(job.inputs.contains(clangDependencyModuleMapPath))
71+
XCTAssertTrue(job.commandLine.contains(
72+
.flag(String("-clang-module-file=\(moduleInfo.modulePath)"))))
73+
XCTAssertTrue(job.commandLine.contains(
74+
.flag(String("-clang-module-map-file=\(clangDependencyDetails.moduleMapPath)"))))
75+
}
76+
}
77+
}
78+
1879
/// Test that for the given JSON module dependency graph, valid jobs are generated
1980
final class ExplicitModuleBuildTests: XCTestCase {
2081
func testModuleDependencyBuildCommandGeneration() throws {
2182
do {
22-
var driver = try Driver(args: ["swiftc", "-driver-print-module-dependencies-jobs", "test.swift"])
83+
var driver = try Driver(args: ["swiftc", "-driver-print-module-dependencies-jobs",
84+
"test.swift"])
2385
let moduleDependencyGraph =
2486
try JSONDecoder().decode(
2587
InterModuleDependencyGraph.self,
@@ -31,21 +93,21 @@ final class ExplicitModuleBuildTests: XCTestCase {
3193
XCTAssertEqual(job.outputs.count, 1)
3294
switch (job.outputs[0].file) {
3395
case .relative(RelativePath("SwiftShims.pcm")):
34-
XCTAssertEqual(job.kind, .generatePCM)
35-
XCTAssertEqual(job.inputs.count, 1)
36-
XCTAssertTrue(job.inputs[0].file.absolutePath!.pathString.contains("swift/shims/module.modulemap"))
96+
try checkExplicitModuleBuildJob(job: job, moduleName: "SwiftShims",
97+
moduleKind: ModuleDependencyId.CodingKeys.clang,
98+
moduleDependencyGraph: moduleDependencyGraph)
3799
case .relative(RelativePath("c_simd.pcm")):
38-
XCTAssertEqual(job.kind, .generatePCM)
39-
XCTAssertEqual(job.inputs.count, 1)
40-
XCTAssertTrue(job.inputs[0].file.absolutePath!.pathString.contains("clang-importer-sdk/usr/include/module.map"))
100+
try checkExplicitModuleBuildJob(job: job, moduleName: "c_simd",
101+
moduleKind: ModuleDependencyId.CodingKeys.clang,
102+
moduleDependencyGraph: moduleDependencyGraph)
41103
case .relative(RelativePath("Swift.swiftmodule")):
42-
XCTAssertEqual(job.kind, .emitModule)
43-
XCTAssertEqual(job.inputs.count, 1)
44-
XCTAssertTrue(job.inputs[0].file.absolutePath!.pathString.contains("Swift.swiftmodule/x86_64-apple-macos.swiftinterface"))
104+
try checkExplicitModuleBuildJob(job: job, moduleName: "Swift",
105+
moduleKind: ModuleDependencyId.CodingKeys.swift,
106+
moduleDependencyGraph: moduleDependencyGraph)
45107
case .relative(RelativePath("SwiftOnoneSupport.swiftmodule")):
46-
XCTAssertEqual(job.kind, .emitModule)
47-
XCTAssertEqual(job.inputs.count, 1)
48-
XCTAssertTrue(job.inputs[0].file.absolutePath!.pathString.contains("SwiftOnoneSupport.swiftmodule/x86_64-apple-macos.swiftinterface"))
108+
try checkExplicitModuleBuildJob(job: job, moduleName: "SwiftOnoneSupport",
109+
moduleKind: ModuleDependencyId.CodingKeys.swift,
110+
moduleDependencyGraph: moduleDependencyGraph)
49111
default:
50112
XCTFail("Unexpected module dependency build job output")
51113
}

0 commit comments

Comments
 (0)