Skip to content

Add support for Clang module emission via -emit-pcm #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions Sources/SwiftDriver/Driver/CompilerMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public enum CompilerMode: Equatable {

/// Compile and execute the inputs immediately.
case immediate

/// Compile a Clang module (.pcm).
case compilePCM
}

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

case .standardCompile, .batchCompile:
Expand All @@ -54,26 +57,27 @@ extension CompilerMode {
case .immediate, .repl, .standardCompile, .batchCompile:
return false

case .singleCompile:
case .singleCompile, .compilePCM:
return true
}
}
}

extension CompilerMode: CustomStringConvertible {
public var description: String {
switch self {

case .standardCompile:
return "standard compilation"
case .batchCompile:
return "batch compilation"
case .singleCompile:
return "whole module optimization"
case .repl:
return "read-eval-print-loop compilation"
case .immediate:
return "immediate compilation"
}
switch self {
case .standardCompile:
return "standard compilation"
case .batchCompile:
return "batch compilation"
case .singleCompile:
return "whole module optimization"
case .repl:
return "read-eval-print-loop compilation"
case .immediate:
return "immediate compilation"
case .compilePCM:
return "compile Clang module (.pcm)"
}
}
}
6 changes: 6 additions & 0 deletions Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,9 @@ extension Driver {
case .repl, .deprecatedIntegratedRepl, .lldbRepl:
return .repl

case .emitPcm:
return .compilePCM

default:
// Output flag doesn't determine the compiler mode.
break
Expand Down Expand Up @@ -860,6 +863,9 @@ extension Driver {
case .emitPch:
compilerOutputType = .pch

case .emitPcm:
compilerOutputType = .pcm

case .emitImportedModules:
compilerOutputType = .importedModules

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ fileprivate extension CompilerMode {
var supportsIncrementalCompilation: Bool {
switch self {
case .standardCompile, .immediate, .repl, .batchCompile: return true
case .singleCompile: return false
case .singleCompile, .compilePCM: return false
}
}
}
Expand Down
28 changes: 23 additions & 5 deletions Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,29 @@ fileprivate func shouldColorDiagnostics() -> Bool {
}

extension Driver {
/// How the bridging header should be handled.
enum BridgingHeaderHandling {
/// Ignore the bridging header entirely.
case ignored

/// Parse the bridging header, even if other jobs will use a precompiled
/// bridging header.
///
/// This is typically used only when precompiling the bridging header.
case parsed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On further inspection, the emit-pch job doesn't even use -import-objc-header. So parsed isn't needed. I'll have another PR soon to fix up issues I'm finding while doing integration testing.

I like this enum though.


/// Use the precompiled bridging header.
case precompiled
}
/// Add frontend options that are common to different frontend invocations.
mutating func addCommonFrontendOptions(commandLine: inout [Job.ArgTemplate],
requestPrecompiledObjCHeader: Bool = true) throws {
mutating func addCommonFrontendOptions(
commandLine: inout [Job.ArgTemplate],
bridgingHeaderHandling: BridgingHeaderHandling = .precompiled
) throws {
// Only pass -target to the REPL or immediate modes if it was explicitly
// specified on the command line.
switch compilerMode {
case .standardCompile, .singleCompile, .batchCompile:
case .standardCompile, .singleCompile, .batchCompile, .compilePCM:
commandLine.appendFlag(.target)
commandLine.appendFlag(targetTriple.triple)

Expand Down Expand Up @@ -156,9 +172,11 @@ extension Driver {
try commandLine.appendAll(.Xllvm, from: &parsedOptions)
try commandLine.appendAll(.Xcc, from: &parsedOptions)

if let importedObjCHeader = importedObjCHeader {
if let importedObjCHeader = importedObjCHeader,
bridgingHeaderHandling != .ignored {
commandLine.appendFlag(.importObjcHeader)
if requestPrecompiledObjCHeader, let pch = bridgingPrecompiledHeader {
if bridgingHeaderHandling == .precompiled,
let pch = bridgingPrecompiledHeader {
if parsedOptions.contains(.pchOutputDir) {
commandLine.appendPath(importedObjCHeader)
try commandLine.appendLast(.pchOutputDir, from: &parsedOptions)
Expand Down
5 changes: 4 additions & 1 deletion Sources/SwiftDriver/Jobs/GeneratePCHJob.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ extension Driver {

commandLine.appendFlag("-frontend")

try addCommonFrontendOptions(commandLine: &commandLine, requestPrecompiledObjCHeader: false)
try addCommonFrontendOptions(
commandLine: &commandLine, bridgingHeaderHandling: .parsed)

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

// TODO: Should this just be pch output with extension changed?
if parsedOptions.hasArgument(.serializeDiagnostics), let outputDirectory = parsedOptions.getLastArgument(.pchOutputDir)?.asSingle {
Expand Down
64 changes: 64 additions & 0 deletions Sources/SwiftDriver/Jobs/GeneratePCMJob.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//===--------------- GeneratePCMJob.swift - Generate PCM Job ----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

extension Driver {
/// Create a job that generates a Clang module (.pcm) that is suitable for
/// use.
///
/// The input is a Clang module map
/// (https://clang.llvm.org/docs/Modules.html#module-map-language) and the
/// output is a compiled module that also includes the additional information
/// needed by Swift's Clang importer, e.g., the Swift name lookup tables.
mutating func generatePCMJob(input: TypedVirtualPath) throws -> Job {
var inputs = [TypedVirtualPath]()
var outputs = [TypedVirtualPath]()

var commandLine: [Job.ArgTemplate] = swiftCompilerPrefixArgs.map { Job.ArgTemplate.flag($0) }

commandLine.appendFlag("-frontend")
commandLine.appendFlag(.emitPcm)

// Input module map.
inputs.append(input)
commandLine.appendPath(input.file)

// Compute the output file.
let output: TypedVirtualPath
if let outputArg = parsedOptions.getLastArgument(.o) {
output = .init(file: try VirtualPath(path: outputArg.asSingle),
type: .pcm)
} else {
output = .init(
file: try VirtualPath(
path: moduleName.appendingFileTypeExtension(.pcm)),
type: .pcm)
}

outputs.append(output)
commandLine.appendFlag(.o)
commandLine.appendPath(output.file)

try addCommonFrontendOptions(
commandLine: &commandLine, bridgingHeaderHandling: .ignored)

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

return Job(
kind: .generatePCM,
tool: .absolute(try toolchain.getToolPath(.swiftCompiler)),
commandLine: commandLine,
displayInputs: [],
inputs: inputs,
outputs: outputs
)
}
}
3 changes: 3 additions & 0 deletions Sources/SwiftDriver/Jobs/Job.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public struct Job: Codable, Equatable {
case autolinkExtract = "autolink-extract"
case emitModule = "emit-module"
case generatePCH = "generate-pch"

/// Generate a compiled Clang module.
case generatePCM = "generate-pcm"
case interpret
case repl
case verifyDebugInfo = "verify-debug-info"
Expand Down
19 changes: 14 additions & 5 deletions Sources/SwiftDriver/Jobs/Planning.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@

public enum PlanningError: Error, DiagnosticData {
case replReceivedInput
case emitPCMWrongInputFiles

public var description: String {
switch self {
case .replReceivedInput:
return "REPL mode requires no input files"

case .emitPCMWrongInputFiles:
return "Clang module emission requires exactly one input file (the module map)"
}
}
}
Expand Down Expand Up @@ -45,7 +49,8 @@ extension Driver {
}
}
}


// Precompile the bridging header if needed.
if let importedObjCHeader = importedObjCHeader,
let bridgingPrecompiledHeader = bridgingPrecompiledHeader {
jobs.append(try generatePCHJob(input: .init(file: importedObjCHeader, type: .objcHeader),
Expand All @@ -62,8 +67,8 @@ extension Driver {
case .batchCompile(let batchInfo):
partitions = batchPartitions(batchInfo)

case .immediate, .repl:
fatalError("immediate and REPL modes are currently unsupported")
case .immediate, .repl, .compilePCM:
fatalError("compiler mode \(compilerMode) is handled elsewhere")

case .singleCompile:
// Create a single compile job for all of the files, none of which
Expand Down Expand Up @@ -157,8 +162,6 @@ extension Driver {
}
}

// FIXME: Lots of follow-up actions for merging modules, etc.

return jobs
}

Expand All @@ -177,6 +180,12 @@ extension Driver {

case .standardCompile, .batchCompile, .singleCompile:
return try planStandardCompile()

case .compilePCM:
if inputFiles.count != 1 {
throw PlanningError.emitPCMWrongInputFiles
}
return [try generatePCMJob(input: inputFiles.first!)]
}
}
}
Expand Down
16 changes: 15 additions & 1 deletion Tests/SwiftDriverTests/SwiftDriverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1202,7 +1202,7 @@ final class SwiftDriverTests: XCTestCase {
let driver2 = try Driver(args: ["swift", "main.swift"], env: env)
XCTAssertNoThrow(try driver2.toolchain.getToolPath(.dsymutil))
}

func testPCHGeneration() throws {
do {
var driver = try Driver(args: ["swiftc", "-typecheck", "-import-objc-header", "TestInputHeader.h", "foo.swift"])
Expand Down Expand Up @@ -1438,6 +1438,20 @@ final class SwiftDriverTests: XCTestCase {
XCTAssertEqual(plannedJobs[1].inputs[0].file, try VirtualPath(path: "foo.swift"))
}
}

func testPCMGeneration() throws {
do {
var driver = try Driver(args: ["swiftc", "-emit-pcm", "module.modulemap", "-module-name", "Test"])
let plannedJobs = try driver.planBuild()
XCTAssertEqual(plannedJobs.count, 1)

XCTAssertEqual(plannedJobs[0].kind, .generatePCM)
XCTAssertEqual(plannedJobs[0].inputs.count, 1)
XCTAssertEqual(plannedJobs[0].inputs[0].file, .relative(RelativePath("module.modulemap")))
XCTAssertEqual(plannedJobs[0].outputs.count, 1)
XCTAssertEqual(plannedJobs[0].outputs[0].file, .relative(RelativePath("Test.pcm")))
}
}
}

func assertString(
Expand Down