Skip to content

[BridgingHeader] Support automatic bridging header chaining #1787

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
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
10 changes: 8 additions & 2 deletions Sources/CSwiftScan/include/swiftscan_header.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
#include <stddef.h>
#include <stdint.h>

#define SWIFTSCAN_VERSION_MAJOR 1
#define SWIFTSCAN_VERSION_MINOR 0
#define SWIFTSCAN_VERSION_MAJOR 2
#define SWIFTSCAN_VERSION_MINOR 1

//=== Public Scanner Data Types -------------------------------------------===//

Expand Down Expand Up @@ -143,6 +143,12 @@ typedef struct {
(*swiftscan_swift_textual_detail_get_swift_overlay_dependencies)(swiftscan_module_details_t);
swiftscan_string_ref_t
(*swiftscan_swift_textual_detail_get_module_cache_key)(swiftscan_module_details_t);
swiftscan_string_ref_t
(*swiftscan_swift_textual_detail_get_user_module_version)(swiftscan_module_details_t);
swiftscan_string_ref_t
(*swiftscan_swift_textual_detail_get_chained_bridging_header_path)(swiftscan_module_details_t);
swiftscan_string_ref_t
(*swiftscan_swift_textual_detail_get_chained_bridging_header_content)(swiftscan_module_details_t);

//=== Swift Binary Module Details query APIs ------------------------------===//
swiftscan_string_ref_t
Expand Down
122 changes: 87 additions & 35 deletions Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -332,17 +332,38 @@ public struct Driver {
}
}

/// If PCH job is needed.
let producePCHJob: Bool

/// Original ObjC Header passed from command-line
let originalObjCHeaderFile: VirtualPath.Handle?

/// The path to the imported Objective-C header.
let importedObjCHeader: VirtualPath.Handle?
lazy var importedObjCHeader: VirtualPath.Handle? = {
assert(explicitDependencyBuildPlanner != nil ||
!parsedOptions.hasArgument(.driverExplicitModuleBuild) ||
!inputFiles.contains { $0.type == .swift },
"should not be queried before scanning")
let chainedBridgingHeader = try? explicitDependencyBuildPlanner?.getChainedBridgingHeaderFile()
return try? computeImportedObjCHeader(&parsedOptions, compilerMode: compilerMode,
chainedBridgingHeader: chainedBridgingHeader) ?? originalObjCHeaderFile
}()

/// The directory to emit PCH file.
lazy var bridgingPrecompiledHeaderOutputDir: VirtualPath? = {
return try? computePrecompiledBridgingHeaderDir(&parsedOptions,
compilerMode: compilerMode)
}()

/// The path to the pch for the imported Objective-C header.
lazy var bridgingPrecompiledHeader: VirtualPath.Handle? = {
let contextHash = try? explicitDependencyBuildPlanner?.getMainModuleContextHash()
return Self.computeBridgingPrecompiledHeader(&parsedOptions,
compilerMode: compilerMode,
importedObjCHeader: importedObjCHeader,
outputFileMap: outputFileMap,
contextHash: contextHash)
return computeBridgingPrecompiledHeader(&parsedOptions,
compilerMode: compilerMode,
importedObjCHeader: importedObjCHeader,
outputFileMap: outputFileMap,
outputDirectory: bridgingPrecompiledHeaderOutputDir,
contextHash: contextHash)
}()

/// Path to the dependencies file.
Expand Down Expand Up @@ -1049,8 +1070,6 @@ public struct Driver {
parsedOptions: parsedOptions,
recordedInputModificationDates: recordedInputModificationDates)

self.importedObjCHeader = try Self.computeImportedObjCHeader(&parsedOptions, compilerMode: compilerMode, diagnosticEngine: diagnosticEngine)

self.supportedFrontendFlags =
try Self.computeSupportedCompilerArgs(of: self.toolchain,
libSwiftScan: self.swiftScanLibInstance,
Expand All @@ -1075,16 +1094,38 @@ public struct Driver {
diagnosticsEngine.emit(.warning("-cache-compile-job cannot be used without explicit module build, turn off caching"),
location: nil)
self.enableCaching = false
} else if importedObjCHeader != nil, !parsedOptions.hasFlag(positive: .enableBridgingPch, negative: .disableBridgingPch, default: true) {
diagnosticsEngine.emit(.warning("-cache-compile-job cannot be used with -disable-bridging-pch, turn off caching"),
location: nil)
self.enableCaching = false
} else {
self.enableCaching = true
}
} else {
self.enableCaching = false
}

// PCH related options.
if parsedOptions.hasArgument(.importObjcHeader) {
// Check for conflicting options.
if parsedOptions.hasArgument(.importUnderlyingModule) {
diagnosticEngine.emit(.error_framework_bridging_header)
}

if parsedOptions.hasArgument(.emitModuleInterface, .emitModuleInterfacePath) {
diagnosticEngine.emit(.error_bridging_header_module_interface)
}
}
var maybeNeedPCH = parsedOptions.hasFlag(positive: .enableBridgingPch, negative: .disableBridgingPch, default: true)
if enableCaching && !maybeNeedPCH {
diagnosticsEngine.emit(.warning("-cache-compile-job cannot be used with -disable-bridging-pch, turn on PCH generation"),
location: nil)
maybeNeedPCH = true
}
self.producePCHJob = maybeNeedPCH

if let objcHeaderPathArg = parsedOptions.getLastArgument(.importObjcHeader) {
self.originalObjCHeaderFile = try? VirtualPath.intern(path: objcHeaderPathArg.asSingle)
} else {
self.originalObjCHeaderFile = nil
}

self.useClangIncludeTree = !parsedOptions.hasArgument(.noClangIncludeTree) && !env.keys.contains("SWIFT_CACHING_USE_CLANG_CAS_FS")
self.scannerPrefixMap = try Self.computeScanningPrefixMapper(&parsedOptions)
if let sdkMapping = parsedOptions.getLastArgument(.scannerPrefixMapSdk)?.asSingle {
Expand Down Expand Up @@ -3052,36 +3093,47 @@ extension Driver {
// Imported Objective-C header.
extension Driver {
/// Compute the path of the imported Objective-C header.
static func computeImportedObjCHeader(
func computeImportedObjCHeader(
_ parsedOptions: inout ParsedOptions,
compilerMode: CompilerMode,
diagnosticEngine: DiagnosticsEngine
) throws -> VirtualPath.Handle? {
guard let objcHeaderPathArg = parsedOptions.getLastArgument(.importObjcHeader) else {
return nil
chainedBridgingHeader: ChainedBridgingHeaderFile?) throws -> VirtualPath.Handle? {
// handle chained bridging header.
if let chainedHeader = chainedBridgingHeader, !chainedHeader.path.isEmpty {
let path = try VirtualPath(path: chainedHeader.path)
let dirExists = try fileSystem.exists(path.parentDirectory)
if !dirExists, let dirToCreate = path.parentDirectory.absolutePath {
try fileSystem.createDirectory(dirToCreate, recursive: true)
}
try fileSystem.writeFileContents(path,
bytes: ByteString(encodingAsUTF8: chainedHeader.content),
atomically: true)
return path.intern()
}
return originalObjCHeaderFile
}

// Check for conflicting options.
if parsedOptions.hasArgument(.importUnderlyingModule) {
diagnosticEngine.emit(.error_framework_bridging_header)
/// Compute the path to the bridging precompiled header directory path.
func computePrecompiledBridgingHeaderDir(
_ parsedOptions: inout ParsedOptions,
compilerMode: CompilerMode) throws -> VirtualPath? {
if let input = originalObjCHeaderFile,
let outputPath = try? outputFileMap?.existingOutput(inputFile: input, outputType: .pch) {
return VirtualPath.lookup(outputPath).parentDirectory
}

if parsedOptions.hasArgument(.emitModuleInterface, .emitModuleInterfacePath) {
diagnosticEngine.emit(.error_bridging_header_module_interface)
if let outputDir = parsedOptions.getLastArgument(.pchOutputDir)?.asSingle {
return try VirtualPath(path: outputDir)
}

return try VirtualPath.intern(path: objcHeaderPathArg.asSingle)
return nil
}

/// Compute the path of the generated bridging PCH for the Objective-C header.
static func computeBridgingPrecompiledHeader(_ parsedOptions: inout ParsedOptions,
compilerMode: CompilerMode,
importedObjCHeader: VirtualPath.Handle?,
outputFileMap: OutputFileMap?,
contextHash: String?) -> VirtualPath.Handle? {
guard compilerMode.supportsBridgingPCH,
let input = importedObjCHeader,
parsedOptions.hasFlag(positive: .enableBridgingPch, negative: .disableBridgingPch, default: true) else {
func computeBridgingPrecompiledHeader(_ parsedOptions: inout ParsedOptions,
compilerMode: CompilerMode,
importedObjCHeader: VirtualPath.Handle?,
outputFileMap: OutputFileMap?,
outputDirectory: VirtualPath?,
contextHash: String?) -> VirtualPath.Handle? {
guard compilerMode.supportsBridgingPCH, producePCHJob, let input = importedObjCHeader else {
return nil
}

Expand All @@ -3096,8 +3148,8 @@ extension Driver {
} else {
pchFile = baseName.appendingFileTypeExtension(.pch)
}
if let outputDirectory = parsedOptions.getLastArgument(.pchOutputDir)?.asSingle {
return try? VirtualPath(path: outputDirectory).appending(component: pchFile).intern()
if let outputDirectory = outputDirectory {
return outputDirectory.appending(component: pchFile).intern()
} else {
return try? VirtualPath.temporary(RelativePath(validating: pchFile)).intern()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ public struct ExternalTargetModuleDetails {
let isFramework: Bool
}

/// A chained bridging header file.
public struct ChainedBridgingHeaderFile {
let path: String
let content: String
}

public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalTargetModuleDetails]

/// In Explicit Module Build mode, this planner is responsible for generating and providing
Expand Down Expand Up @@ -451,6 +457,17 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
return mainModuleDetails.contextHash
}

/// Get the chained bridging header info
public func getChainedBridgingHeaderFile() throws -> ChainedBridgingHeaderFile? {
let mainModuleId: ModuleDependencyId = .swift(dependencyGraph.mainModuleName)
let mainModuleDetails = try dependencyGraph.swiftModuleDetails(of: mainModuleId)
guard let path = mainModuleDetails.chainedBridgingHeaderPath,
let content = mainModuleDetails.chainedBridgingHeaderContent else{
return nil
}
return ChainedBridgingHeaderFile(path: path, content: content)
}

/// Resolve all module dependencies of the main module and add them to the lists of
/// inputs and command line flags.
public mutating func resolveBridgingHeaderDependencies(inputs: inout [TypedVirtualPath],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public struct SwiftModuleDetails: Codable, Hashable {
/// Options to the compile command
public var commandLine: [String]? = []

/// Options to the compile command
/// Options to the compile bridging header command
public var bridgingPchCommandLine: [String]? = []

/// The context hash for this module that encodes the producing interface's path,
Expand All @@ -140,6 +140,11 @@ public struct SwiftModuleDetails: Codable, Hashable {

/// The module cache key of the output module.
public var moduleCacheKey: String?

/// Chained bridging header path
public var chainedBridgingHeaderPath: String?
/// Chained bridging header content
public var chainedBridgingHeaderContent: String?
}

/// Details specific to Swift placeholder dependencies.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import protocol TSCBasic.FileSystem
import struct TSCBasic.AbsolutePath
import struct TSCBasic.RelativePath
import struct TSCBasic.Diagnostic
import var TSCBasic.localFileSystem
import var TSCBasic.stdoutStream
Expand Down Expand Up @@ -163,6 +164,34 @@ public extension Driver {
}
}

if isFrontendArgSupported(.autoBridgingHeaderChaining) {
if parsedOptions.hasFlag(positive: .autoBridgingHeaderChaining,
negative: .noAutoBridgingHeaderChaining,
default: false) || isCachingEnabled {
if producePCHJob {
commandLine.appendFlag(.autoBridgingHeaderChaining)
} else {
diagnosticEngine.emit(.warning("-auto-bridging-header-chaining requires generatePCH job, no chaining will be performed"))
commandLine.appendFlag(.noAutoBridgingHeaderChaining)
}
} else {
commandLine.appendFlag(.noAutoBridgingHeaderChaining)
}
}

// Provide a directory to path to scanner for where the chained bridging header will be.
// Prefer writing next to pch output, otherwise next to module output path before fallback to temp directory for non-caching build.
if isFrontendArgSupported(.scannerOutputDir) {
if let outputDir = try? computePrecompiledBridgingHeaderDir(&parsedOptions,
compilerMode: compilerMode) {
commandLine.appendFlag(.scannerOutputDir)
commandLine.appendPath(outputDir)
} else {
commandLine.appendFlag(.scannerOutputDir)
commandLine.appendPath(VirtualPath.temporary(try RelativePath(validating: "scanner")))
}
}

// Pass on the input files
commandLine.append(contentsOf: inputFiles.filter { $0.type == .swift }.map { .path($0.file) })
return (inputs, commandLine)
Expand Down
35 changes: 23 additions & 12 deletions Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -455,26 +455,39 @@ extension Driver {
try commandLine.appendAll(.Xcc, from: &parsedOptions)
}

if let importedObjCHeader = importedObjCHeader,
bridgingHeaderHandling != .ignored {
commandLine.appendFlag(.importObjcHeader)
if bridgingHeaderHandling == .precompiled,
let pch = bridgingPrecompiledHeader {
// For explicit module build, we directly pass the compiled pch as
// `-import-objc-header`, rather than rely on swift-frontend to locate
let objcHeaderFile = (kind == .scanDependencies) ? originalObjCHeaderFile : importedObjCHeader
if let importedObjCHeader = objcHeaderFile, bridgingHeaderHandling != .ignored {
if bridgingHeaderHandling == .precompiled, let pch = bridgingPrecompiledHeader {
// For explicit module build, we directly pass the compiled pch to
// swift-frontend, rather than rely on swift-frontend to locate
// the pch in the pchOutputDir and can start an implicit build in case
// of a lookup failure.
if parsedOptions.contains(.pchOutputDir) &&
!parsedOptions.contains(.driverExplicitModuleBuild) {
commandLine.appendFlag(.importObjcHeader)
try addPathArgument(VirtualPath.lookup(importedObjCHeader), to:&commandLine, remap: jobNeedPathRemap)
try commandLine.appendLast(.pchOutputDir, from: &parsedOptions)
if !compilerMode.isSingleCompilation {
commandLine.appendFlag(.pchDisableValidation)
}
} else {
try addPathArgument(VirtualPath.lookup(pch), to:&commandLine, remap: jobNeedPathRemap)
// If header chaining is enabled, pass objc header through `-import-objc-header` and
// PCH file through `-import-pch`. Otherwise, pass either the PCH or header through
// `-import-objc-header` option.
if isFrontendArgSupported(.importPch), importedObjCHeader != originalObjCHeaderFile {
commandLine.appendFlag(.importPch)
try addPathArgument(VirtualPath.lookup(pch), to:&commandLine, remap: jobNeedPathRemap)
if let originalHeader = originalObjCHeaderFile {
commandLine.appendFlag(.importObjcHeader)
try addPathArgument(VirtualPath.lookup(originalHeader), to:&commandLine, remap: jobNeedPathRemap)
}
} else {
commandLine.appendFlag(.importObjcHeader)
try addPathArgument(VirtualPath.lookup(pch), to:&commandLine, remap: jobNeedPathRemap)
}
}
} else {
commandLine.appendFlag(.importObjcHeader)
try addPathArgument(VirtualPath.lookup(importedObjCHeader), to:&commandLine, remap: jobNeedPathRemap)
}
}
Expand Down Expand Up @@ -957,8 +970,7 @@ extension Driver {
return [:]
}
// Resolve command-line first.
let resolver = try ArgsResolver(fileSystem: fileSystem)
let arguments: [String] = try resolver.resolveArgumentList(for: commandLine)
let arguments: [String] = try executor.resolver.resolveArgumentList(for: commandLine)

return try inputs.reduce(into: [:]) { keys, input in
keys[input.0] = try cas.computeCacheKey(commandLine: arguments, index: input.1)
Expand All @@ -972,8 +984,7 @@ extension Driver {
return nil
}
// Resolve command-line first.
let resolver = try ArgsResolver(fileSystem: fileSystem)
let arguments: [String] = try resolver.resolveArgumentList(for: commandLine)
let arguments: [String] = try executor.resolver.resolveArgumentList(for: commandLine)
return try cas.computeCacheKey(commandLine: arguments, index: index)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftDriver/Jobs/VerifyModuleInterfaceJob.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ extension Driver {
var inputs: [TypedVirtualPath] = [interfaceInput]
commandLine.appendFlags("-frontend", "-typecheck-module-from-interface")
try addPathArgument(interfaceInput.file, to: &commandLine)
try addCommonFrontendOptions(commandLine: &commandLine, inputs: &inputs, kind: .verifyModuleInterface)
try addCommonFrontendOptions(commandLine: &commandLine, inputs: &inputs, kind: .verifyModuleInterface, bridgingHeaderHandling: .ignored)
// FIXME: MSVC runtime flags

// Output serialized diagnostics for this job, if specifically requested
Expand Down
8 changes: 7 additions & 1 deletion Sources/SwiftDriver/SwiftScan/DependencyGraphBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ private extension SwiftScan {
let isFramework = api.swiftscan_swift_textual_detail_get_is_framework(moduleDetailsRef)
let moduleCacheKey = supportsCaching ? try getOptionalStringDetail(from: moduleDetailsRef,
using: api.swiftscan_swift_textual_detail_get_module_cache_key) : nil
let chainedBridgingHeaderPath = supportsChainedBridgingHeader ?
try getOptionalStringDetail(from: moduleDetailsRef, using: api.swiftscan_swift_textual_detail_get_chained_bridging_header_path) : nil
let chainedBridgingHeaderContent = supportsChainedBridgingHeader ?
try getOptionalStringDetail(from: moduleDetailsRef, using: api.swiftscan_swift_textual_detail_get_chained_bridging_header_content) : nil

// Decode all dependencies of this module
let swiftOverlayDependencies: [ModuleDependencyId]?
Expand All @@ -204,7 +208,9 @@ private extension SwiftScan {
contextHash: contextHash,
isFramework: isFramework,
swiftOverlayDependencies: swiftOverlayDependencies,
moduleCacheKey: moduleCacheKey)
moduleCacheKey: moduleCacheKey,
chainedBridgingHeaderPath: chainedBridgingHeaderPath,
chainedBridgingHeaderContent: chainedBridgingHeaderContent)
}

/// Construct a `SwiftPrebuiltExternalModuleDetails` from a `swiftscan_module_details_t` reference
Expand Down
Loading