Skip to content

Commit f07b471

Browse files
committed
Add support for Clang module emission via -emit-pcm
1 parent 52eff70 commit f07b471

File tree

9 files changed

+148
-28
lines changed

9 files changed

+148
-28
lines changed

Sources/SwiftDriver/Driver/CompilerMode.swift

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ public enum CompilerMode: Equatable {
2626

2727
/// Compile and execute the inputs immediately.
2828
case immediate
29+
30+
/// Compile a Clang module (.pcm).
31+
case compilePcm
2932
}
3033

3134
/// Information about batch mode, which is used to determine how to form
@@ -40,7 +43,7 @@ extension CompilerMode {
4043
/// Whether this compilation mode uses -primary-file to specify its inputs.
4144
public var usesPrimaryFileInputs: Bool {
4245
switch self {
43-
case .immediate, .repl, .singleCompile:
46+
case .immediate, .repl, .singleCompile, .compilePcm:
4447
return false
4548

4649
case .standardCompile, .batchCompile:
@@ -54,26 +57,27 @@ extension CompilerMode {
5457
case .immediate, .repl, .standardCompile, .batchCompile:
5558
return false
5659

57-
case .singleCompile:
60+
case .singleCompile, .compilePcm:
5861
return true
5962
}
6063
}
6164
}
6265

6366
extension CompilerMode: CustomStringConvertible {
6467
public var description: String {
65-
switch self {
66-
67-
case .standardCompile:
68-
return "standard compilation"
69-
case .batchCompile:
70-
return "batch compilation"
71-
case .singleCompile:
72-
return "whole module optimization"
73-
case .repl:
74-
return "read-eval-print-loop compilation"
75-
case .immediate:
76-
return "immediate compilation"
77-
}
68+
switch self {
69+
case .standardCompile:
70+
return "standard compilation"
71+
case .batchCompile:
72+
return "batch compilation"
73+
case .singleCompile:
74+
return "whole module optimization"
75+
case .repl:
76+
return "read-eval-print-loop compilation"
77+
case .immediate:
78+
return "immediate compilation"
79+
case .compilePcm:
80+
return "compile Clang module (.pcm)"
81+
}
7882
}
7983
}

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,9 @@ extension Driver {
692692
case .repl, .deprecatedIntegratedRepl, .lldbRepl:
693693
return .repl
694694

695+
case .emitPcm:
696+
return .compilePcm
697+
695698
default:
696699
// Output flag doesn't determine the compiler mode.
697700
break
@@ -851,6 +854,9 @@ extension Driver {
851854
case .emitPch:
852855
compilerOutputType = .pch
853856

857+
case .emitPcm:
858+
compilerOutputType = .pcm
859+
854860
case .emitImportedModules:
855861
compilerOutputType = .importedModules
856862

Sources/SwiftDriver/Incremental Compilation/IncrementalCompilation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ fileprivate extension CompilerMode {
154154
var supportsIncrementalCompilation: Bool {
155155
switch self {
156156
case .standardCompile, .immediate, .repl, .batchCompile: return true
157-
case .singleCompile: return false
157+
case .singleCompile, .compilePcm: return false
158158
}
159159
}
160160
}

Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,29 @@ fileprivate func shouldColorDiagnostics() -> Bool {
2121
}
2222

2323
extension Driver {
24+
/// How the bridging header should be handled.
25+
enum BridgingHeaderHandling {
26+
/// Ignore the bridging header entirely.
27+
case ignored
28+
29+
/// Parse the bridging header, even if other jobs will use a precompiled
30+
/// bridging header.
31+
///
32+
/// This is typically used only when precompiling the bridging header.
33+
case parsed
34+
35+
/// Use the precompiled bridging header.
36+
case precompiled
37+
}
2438
/// Add frontend options that are common to different frontend invocations.
25-
mutating func addCommonFrontendOptions(commandLine: inout [Job.ArgTemplate],
26-
requestPrecompiledObjCHeader: Bool = true) throws {
39+
mutating func addCommonFrontendOptions(
40+
commandLine: inout [Job.ArgTemplate],
41+
bridgingHeaderHandling: BridgingHeaderHandling = .precompiled
42+
) throws {
2743
// Only pass -target to the REPL or immediate modes if it was explicitly
2844
// specified on the command line.
2945
switch compilerMode {
30-
case .standardCompile, .singleCompile, .batchCompile:
46+
case .standardCompile, .singleCompile, .batchCompile, .compilePcm:
3147
commandLine.appendFlag(.target)
3248
commandLine.appendFlag(targetTriple.triple)
3349

@@ -156,9 +172,11 @@ extension Driver {
156172
try commandLine.appendAll(.Xllvm, from: &parsedOptions)
157173
try commandLine.appendAll(.Xcc, from: &parsedOptions)
158174

159-
if let importedObjCHeader = importedObjCHeader {
175+
if let importedObjCHeader = importedObjCHeader,
176+
bridgingHeaderHandling != .ignored {
160177
commandLine.appendFlag(.importObjcHeader)
161-
if requestPrecompiledObjCHeader, let pch = bridgingPrecompiledHeader {
178+
if bridgingHeaderHandling == .precompiled,
179+
let pch = bridgingPrecompiledHeader {
162180
if parsedOptions.contains(.pchOutputDir) {
163181
commandLine.appendPath(importedObjCHeader)
164182
switch compilerMode {
@@ -167,6 +185,8 @@ extension Driver {
167185
case .singleCompile:
168186
// Don't disable validation for single compile
169187
break
188+
case .compilePcm:
189+
fatalError("Module compiles don't use bridging headers")
170190
}
171191
} else {
172192
commandLine.appendPath(pch)

Sources/SwiftDriver/Jobs/GeneratePCHJob.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ extension Driver {
2626

2727
outputs.append(output)
2828

29-
try addCommonFrontendOptions(commandLine: &commandLine, requestPrecompiledObjCHeader: false)
29+
try addCommonFrontendOptions(
30+
commandLine: &commandLine, bridgingHeaderHandling: .parsed)
3031

3132
try commandLine.appendLast(.indexStorePath, from: &parsedOptions)
3233

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//===--------------- GeneratePCMJob.swift - Generate PCM Job ----===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 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+
13+
extension Driver {
14+
/// Create a job that generates a Clang module (.pcm) that is suitable for
15+
/// use.
16+
///
17+
/// The input is a Clang module map
18+
/// (https://clang.llvm.org/docs/Modules.html#module-map-language) and the
19+
/// output is a compiled module that also includes the additional information
20+
/// needed by Swift's Clang importer, e.g., the Swift name lookup tables.
21+
mutating func generatePCMJob(input: TypedVirtualPath) throws -> Job {
22+
var inputs = [TypedVirtualPath]()
23+
var outputs = [TypedVirtualPath]()
24+
25+
var commandLine: [Job.ArgTemplate] = swiftCompilerPrefixArgs.map { Job.ArgTemplate.flag($0) }
26+
27+
commandLine.appendFlag("-frontend")
28+
commandLine.appendFlag(.emitPcm)
29+
30+
// Input module map.
31+
inputs.append(input)
32+
commandLine.appendPath(input.file)
33+
34+
// Compute the output file.
35+
let output: TypedVirtualPath
36+
if let outputArg = parsedOptions.getLastArgument(.o) {
37+
output = .init(file: try VirtualPath(path: outputArg.asSingle),
38+
type: .pcm)
39+
} else {
40+
output = .init(
41+
file: try VirtualPath(path: moduleName + "." + FileType.pcm.name),
42+
type: .pcm)
43+
}
44+
45+
outputs.append(output)
46+
commandLine.appendFlag(.o)
47+
commandLine.appendPath(output.file)
48+
49+
try addCommonFrontendOptions(
50+
commandLine: &commandLine, bridgingHeaderHandling: .ignored)
51+
52+
try commandLine.appendLast(.indexStorePath, from: &parsedOptions)
53+
54+
return Job(
55+
kind: .generatePCM,
56+
tool: self.swiftCompiler,
57+
commandLine: commandLine,
58+
displayInputs: [],
59+
inputs: inputs,
60+
outputs: outputs
61+
)
62+
}
63+
}

Sources/SwiftDriver/Jobs/Job.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public struct Job: Codable, Equatable {
2121
case autolinkExtract = "autolink-extract"
2222
case emitModule = "emit-module"
2323
case generatePCH = "generate-pch"
24+
25+
/// Generate a compiled Clang module.
26+
case generatePCM = "generate-pcm"
2427
case interpret
2528
case repl
2629
case verifyDebugInfo = "verify-debug-info"

Sources/SwiftDriver/Jobs/Planning.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@
1212

1313
public enum PlanningError: Error, DiagnosticData {
1414
case replReceivedInput
15+
case emitPCMWrongInputFiles
1516

1617
public var description: String {
1718
switch self {
1819
case .replReceivedInput:
1920
return "REPL mode requires no input files"
21+
22+
case .emitPCMWrongInputFiles:
23+
return "Clang module emission requires exactly one input file (the module map)"
2024
}
2125
}
2226
}
@@ -45,7 +49,8 @@ extension Driver {
4549
}
4650
}
4751
}
48-
52+
53+
// Precompile the bridging header if needed.
4954
if let importedObjCHeader = importedObjCHeader,
5055
let bridgingPrecompiledHeader = bridgingPrecompiledHeader {
5156
jobs.append(try generatePCHJob(input: .init(file: importedObjCHeader, type: .objcHeader),
@@ -62,8 +67,8 @@ extension Driver {
6267
case .batchCompile(let batchInfo):
6368
partitions = batchPartitions(batchInfo)
6469

65-
case .immediate, .repl:
66-
fatalError("immediate and REPL modes are currently unsupported")
70+
case .immediate, .repl, .compilePcm:
71+
fatalError("compiler mode \(compilerMode) is handled elsewhere")
6772

6873
case .singleCompile:
6974
// Create a single compile job for all of the files, none of which
@@ -157,8 +162,6 @@ extension Driver {
157162
}
158163
}
159164

160-
// FIXME: Lots of follow-up actions for merging modules, etc.
161-
162165
return jobs
163166
}
164167

@@ -177,6 +180,12 @@ extension Driver {
177180

178181
case .standardCompile, .batchCompile, .singleCompile:
179182
return try planStandardCompile()
183+
184+
case .compilePcm:
185+
if inputFiles.count != 1 {
186+
throw PlanningError.emitPCMWrongInputFiles
187+
}
188+
return [try generatePCMJob(input: inputFiles.first!)]
180189
}
181190
}
182191
}

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1200,7 +1200,7 @@ final class SwiftDriverTests: XCTestCase {
12001200
let driver2 = try Driver(args: ["swift", "main.swift"], env: env)
12011201
XCTAssertNoThrow(try driver2.toolchain.getToolPath(.dsymutil))
12021202
}
1203-
1203+
12041204
func testPCHGeneration() throws {
12051205
do {
12061206
var driver = try Driver(args: ["swiftc", "-typecheck", "-import-objc-header", "TestInputHeader.h", "foo.swift"])
@@ -1428,6 +1428,20 @@ final class SwiftDriverTests: XCTestCase {
14281428
XCTAssertEqual(plannedJobs[1].inputs[0].file, try VirtualPath(path: "foo.swift"))
14291429
}
14301430
}
1431+
1432+
func testPCMGeneration() throws {
1433+
do {
1434+
var driver = try Driver(args: ["swiftc", "-emit-pcm", "module.modulemap", "-module-name", "Test"])
1435+
let plannedJobs = try driver.planBuild()
1436+
XCTAssertEqual(plannedJobs.count, 1)
1437+
1438+
XCTAssertEqual(plannedJobs[0].kind, .generatePCM)
1439+
XCTAssertEqual(plannedJobs[0].inputs.count, 1)
1440+
XCTAssertEqual(plannedJobs[0].inputs[0].file, .relative(RelativePath("module.modulemap")))
1441+
XCTAssertEqual(plannedJobs[0].outputs.count, 1)
1442+
XCTAssertEqual(plannedJobs[0].outputs[0].file, .relative(RelativePath("Test.pcm")))
1443+
}
1444+
}
14311445
}
14321446

14331447
func assertString(

0 commit comments

Comments
 (0)