Skip to content

[Prototype] Adding bridging header as module handling #1403

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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: 33 additions & 1 deletion Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
@@ -299,6 +299,9 @@ public struct Driver {
/// The path to the pch for the imported Objective-C header.
let bridgingPrecompiledHeader: VirtualPath.Handle?

/// The path to the module map for imported Objective-C header.
let bridgingModuleMap: VirtualPath.Handle?

/// Path to the dependencies file.
let dependenciesFilePath: VirtualPath.Handle?

@@ -758,6 +761,9 @@ public struct Driver {
compilerMode: compilerMode,
importedObjCHeader: importedObjCHeader,
outputFileMap: outputFileMap)
self.bridgingModuleMap = try Self.generateModuleMapForObjCHeader(&parsedOptions,
fileSystem: fileSystem,
importedObjCHeader: importedObjCHeader)

self.supportedFrontendFlags =
try Self.computeSupportedCompilerArgs(of: self.toolchain,
@@ -2739,7 +2745,8 @@ extension Driver {
outputFileMap: OutputFileMap?) throws -> VirtualPath.Handle? {
guard compilerMode.supportsBridgingPCH,
let input = importedObjCHeader,
parsedOptions.hasFlag(positive: .enableBridgingPch, negative: .disableBridgingPch, default: true) else {
parsedOptions.hasFlag(positive: .enableBridgingPch, negative: .disableBridgingPch, default: true),
!parsedOptions.hasArgument(.experimentalBridgingHeaderAsModule) else {
return nil
}

@@ -2755,6 +2762,31 @@ extension Driver {
return VirtualPath.createUniqueTemporaryFile(RelativePath(pchFileName)).intern()
}
}

/// Write the module map for bridging header.
static func generateModuleMapForObjCHeader(_ parsedOptions: inout ParsedOptions,
fileSystem: FileSystem,
importedObjCHeader: VirtualPath.Handle?) throws -> VirtualPath.Handle? {
guard let header = importedObjCHeader,
parsedOptions.hasArgument(.experimentalBridgingHeaderAsModule) else {
return nil
}
guard let moduleMapContent = """
module __ObjC {
requires swift
header \"\(VirtualPath.lookup(header))\"
export *
}
""".data(using: .utf8) else { return nil }
// Write the modulemap inside -pch-output-dir if specified, otherwise in temporary directory.
if let outputDir = parsedOptions.getLastArgument(.pchOutputDir)?.asSingle {
let moduleMap = try VirtualPath(path: outputDir).appending(components: "module.modulemap")
try fileSystem.writeFileContents(moduleMap, bytes: ByteString(moduleMapContent), atomically: true)
return moduleMap.intern()
}
return VirtualPath.createUniqueTemporaryFileWithKnownContents(.init("module.modulemap"),
moduleMapContent).intern()
}
}

extension Diagnostic.Message {
Original file line number Diff line number Diff line change
@@ -106,8 +106,10 @@ public extension Driver {
var commandLine: [Job.ArgTemplate] = swiftCompilerPrefixArgs.map { Job.ArgTemplate.flag($0) }
commandLine.appendFlag("-frontend")
commandLine.appendFlag("-scan-dependencies")

let bridgingHandling: BridgingHeaderHandling = parsedOptions.hasArgument(.experimentalBridgingHeaderAsModule) ? .module : .parsed
try addCommonFrontendOptions(commandLine: &commandLine, inputs: &inputs, kind: .scanDependencies,
bridgingHeaderHandling: .parsed,
bridgingHeaderHandling: bridgingHandling,
moduleDependencyGraphUse: .dependencyScan)
// FIXME: MSVC runtime flags

3 changes: 2 additions & 1 deletion Sources/SwiftDriver/Jobs/CompileJob.swift
Original file line number Diff line number Diff line change
@@ -284,7 +284,8 @@ extension Driver {
commandLine.appendFlag(.disableObjcAttrRequiresFoundationModule)
}

try addCommonFrontendOptions(commandLine: &commandLine, inputs: &inputs, kind: .compile)
let bridgingHandling: BridgingHeaderHandling = parsedOptions.hasArgument(.experimentalBridgingHeaderAsModule) ? .module : .precompiled
try addCommonFrontendOptions(commandLine: &commandLine, inputs: &inputs, kind: .compile, bridgingHeaderHandling: bridgingHandling)

// FIXME: MSVC runtime flags

26 changes: 20 additions & 6 deletions Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift
Original file line number Diff line number Diff line change
@@ -41,6 +41,9 @@ extension Driver {

/// Use the precompiled bridging header.
case precompiled

/// Use module for bridging header.
case module
}
/// Whether the driver has already constructed a module dependency graph or is in the process
/// of doing so
@@ -364,15 +367,17 @@ extension Driver {
try commandLine.appendAll(.Xcc, from: &parsedOptions)
}

if let importedObjCHeader = importedObjCHeader,
bridgingHeaderHandling != .ignored {
commandLine.appendFlag(.importObjcHeader)
if bridgingHeaderHandling == .precompiled,
let pch = bridgingPrecompiledHeader {
if let importedObjCHeader = importedObjCHeader {
switch bridgingHeaderHandling {
case .ignored:
break
case .precompiled:
guard let pch = bridgingPrecompiledHeader else { break }
// For explicit module build, we directly pass the compiled pch as
// `-import-objc-header`, 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.
commandLine.appendFlag(.importObjcHeader)
if parsedOptions.contains(.pchOutputDir) &&
!parsedOptions.contains(.driverExplicitModuleBuild) {
commandLine.appendPath(VirtualPath.lookup(importedObjCHeader))
@@ -383,8 +388,17 @@ extension Driver {
} else {
commandLine.appendPath(VirtualPath.lookup(pch))
}
} else {
case .parsed:
commandLine.appendFlag(.importObjcHeader)
commandLine.appendPath(VirtualPath.lookup(importedObjCHeader))
case .module:
commandLine.appendFlag(.experimentalBridgingHeaderAsModule)
// Tell clang importer where to look for the module map during dependency scanning.
guard let moduleMapFile = bridgingModuleMap, kind == .scanDependencies else { break }
commandLine.appendFlag(.clangModuleMap)
commandLine.appendPath(VirtualPath.lookup(moduleMapFile))
inputs.append(TypedVirtualPath(file: moduleMapFile,
type: .clangModuleMap))
}
}

6 changes: 6 additions & 0 deletions Sources/SwiftOptions/Options.swift
Original file line number Diff line number Diff line change
@@ -60,6 +60,7 @@ extension Option {
public static let bsdk: Option = Option("-bsdk", .joinedOrSeparate, attributes: [.noDriver, .argumentIsPath], helpText: "path to the baseline SDK to import frameworks")
public static let buildModuleFromParseableInterface: Option = Option("-build-module-from-parseable-interface", .flag, alias: Option.compileModuleFromInterface, attributes: [.helpHidden, .frontend, .noDriver], group: .modes)
public static let bypassBatchModeChecks: Option = Option("-bypass-batch-mode-checks", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Bypass checks for batch-mode errors.")
public static let bypassResilience: Option = Option("-bypass-resilience-checks", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Ignore all checks for module resilience.")
public static let cacheCompileJob: Option = Option("-cache-compile-job", .flag, attributes: [.frontend], helpText: "Enable compiler caching")
public static let cacheDisableReplay: Option = Option("-cache-disable-replay", .flag, attributes: [.frontend], helpText: "Skip loading the compilation result from cache")
public static let candidateModuleFile: Option = Option("-candidate-module-file", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "<path>", helpText: "Specify Swift module may be ready to use for an interface")
@@ -74,6 +75,7 @@ extension Option {
public static let clangHeaderExposeModule: Option = Option("-clang-header-expose-module", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "<imported-module-name>=<generated-header-name>", helpText: "Allow the compiler to assume that APIs from the specified module are exposed to C/C++/Objective-C in another generated header, so that APIs in the current module that depend on declarations from the specified module can be exposed in the generated header.")
public static let clangIncludeTreeRoot: Option = Option("-clang-include-tree-root", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "<cas-id>", helpText: "Clang Include Tree CASID")
public static let clangIncludeTree: Option = Option("-clang-include-tree", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Use clang include tree")
public static let clangModuleMap: Option = Option("-clang-module-map", .separate, attributes: [.frontend, .argumentIsPath], metaVar: "<path>", helpText: "clang module map path")
public static let clangTarget: Option = Option("-clang-target", .separate, attributes: [.frontend], helpText: "Separately set the target we should use for internal Clang instance")
public static let codeCompleteCallPatternHeuristics: Option = Option("-code-complete-call-pattern-heuristics", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Use heuristics to guess whether we want call pattern completions")
public static let codeCompleteInitsInPostfixExpr: Option = Option("-code-complete-inits-in-postfix-expr", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Include initializers when completing a postfix expression")
@@ -431,6 +433,7 @@ extension Option {
public static let enforceExclusivityEQ: Option = Option("-enforce-exclusivity=", .joined, attributes: [.frontend, .moduleInterface], metaVar: "<enforcement>", helpText: "Enforce law of exclusivity")
public static let entryPointFunctionName: Option = Option("-entry-point-function-name", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "<string>", helpText: "Name of the entry point function")
public static let experimentalAllowModuleWithCompilerErrors: Option = Option("-experimental-allow-module-with-compiler-errors", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Attempt to output .swiftmodule, regardless of compilation errors")
public static let experimentalBridgingHeaderAsModule: Option = Option("-experimental-bridging-header-as-module", .flag, attributes: [.frontend], helpText: "Import bridging header as module")
public static let experimentalCForeignReferenceTypes: Option = Option("-experimental-c-foreign-reference-types", .flag, attributes: [.helpHidden, .frontend, .moduleInterface], helpText: "Enable experimental C foreign references types (with reference coutning).")
public static let experimentalCxxStdlib: Option = Option("-experimental-cxx-stdlib", .separate, attributes: [.helpHidden], helpText: "C++ standard library to use; forwarded to Clang's -stdlib flag")
public static let emitModuleSeparately: Option = Option("-experimental-emit-module-separately", .flag, attributes: [.helpHidden], helpText: "Emit module files as a distinct job")
@@ -853,6 +856,7 @@ extension Option {
Option.bsdk,
Option.buildModuleFromParseableInterface,
Option.bypassBatchModeChecks,
Option.bypassResilience,
Option.cacheCompileJob,
Option.cacheDisableReplay,
Option.candidateModuleFile,
@@ -867,6 +871,7 @@ extension Option {
Option.clangHeaderExposeModule,
Option.clangIncludeTreeRoot,
Option.clangIncludeTree,
Option.clangModuleMap,
Option.clangTarget,
Option.codeCompleteCallPatternHeuristics,
Option.codeCompleteInitsInPostfixExpr,
@@ -1224,6 +1229,7 @@ extension Option {
Option.enforceExclusivityEQ,
Option.entryPointFunctionName,
Option.experimentalAllowModuleWithCompilerErrors,
Option.experimentalBridgingHeaderAsModule,
Option.experimentalCForeignReferenceTypes,
Option.experimentalCxxStdlib,
Option.emitModuleSeparately,
6 changes: 6 additions & 0 deletions TestInputs/ExplicitModuleBuilds/CHeaders/BridgingModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "A.h"

@interface A
@end

void bridgingA(A *a);
176 changes: 176 additions & 0 deletions Tests/SwiftDriverTests/CachingBuildTests.swift
Original file line number Diff line number Diff line change
@@ -667,4 +667,180 @@ final class CachingBuildTests: XCTestCase {
XCTAssertEqual(diags[0].message, "CAS error encountered: conflicting CAS options used in scanning service")
}
}

/// Test generation of explicit module build jobs for dependency modules when the driver
/// is invoked with -explicit-module-build and -experimental-bridging-header-as-module.
func testCachingBuildBridgingHeaderAsModuleJobs() throws {
try withTemporaryDirectory { path in
let foo = path.appending(component: "testCachingBuildBridgingHeaderAsModuleJobs.swift")
try localFileSystem.writeFileContents(foo) {
$0 <<< "import C;"
$0 <<< "import E;"
$0 <<< "import G;"
$0 <<< "public class Foo { var name: A? = nil }"
}

let moduleCachePath = path.appending(component: "ModuleCache")
try localFileSystem.createDirectory(moduleCachePath)
let cHeadersPath: AbsolutePath =
testInputsPath.appending(component: "ExplicitModuleBuilds")
.appending(component: "CHeaders")
let bridgingHeaderpath: AbsolutePath =
cHeadersPath.appending(component: "BridgingModule.h")
let swiftModuleInterfacesPath: AbsolutePath =
testInputsPath.appending(component: "ExplicitModuleBuilds")
.appending(component: "Swift")
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
let pchOutputDir: AbsolutePath = path
let casPath = path.appending(component: "cas")
let FooInstallPath = path.appending(component: "Foo")
try localFileSystem.createDirectory(FooInstallPath)
var driver = try Driver(args: ["swiftc",
"-target", "x86_64-apple-macosx11.0",
"-I", cHeadersPath.nativePathString(escaped: true),
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
"-module-cache-path", moduleCachePath.nativePathString(escaped: true),
"-explicit-module-build",
"-cache-compile-job", "-cas-path", casPath.nativePathString(escaped: true),
"-emit-module", "-wmo", "-module-name", "Foo",
"-emit-module-path", FooInstallPath.appending(component: "Foo.swiftmodule").nativePathString(escaped: true),
"-import-objc-header", bridgingHeaderpath.nativePathString(escaped: true),
"-experimental-bridging-header-as-module",
"-pch-output-dir", pchOutputDir.nativePathString(escaped: true),
foo.nativePathString(escaped: true)] + sdkArgumentsForTesting)
guard driver.isFrontendArgSupported(.experimentalBridgingHeaderAsModule) else {
throw XCTSkip("swift-frontend doesn't support building bridging header as module.")
}
let dependencyOracle = InterModuleDependencyOracle()
let scanLibPath = try XCTUnwrap(driver.toolchain.lookupSwiftScanLib())
guard try dependencyOracle
.verifyOrCreateScannerInstance(fileSystem: localFileSystem,
swiftScanLibPath: scanLibPath) else {
XCTFail("Dependency scanner library not found")
return
}
guard try dependencyOracle.supportsCaching() else {
throw XCTSkip("libSwiftScan does not support caching.")
}

let jobs = try driver.planBuild()
// Figure out which Triples to use.
let dependencyGraph = try driver.gatherModuleDependencies()
let fooModuleInfo = try dependencyGraph.moduleInfo(of: .swift("Foo"))
guard case .swift(_) = fooModuleInfo.details else {
XCTFail("Foo module does not have Swift details field")
return
}

for job in jobs {
if job.kind == .compile {
// Check we don't use `-pch-output-dir` anymore during main module job.
XCTAssertFalse(job.commandLine.contains("-pch-output-dir"))
XCTAssertFalse(job.commandLine.contains("-import-objc-header"))
XCTAssertTrue(job.commandLine.contains("-experimental-bridging-header-as-module"))
continue
}
XCTAssertEqual(job.outputs.count, 1)
let outputFilePath = job.outputs[0].file

// Swift dependencies
if outputFilePath.extension != nil,
outputFilePath.extension! == FileType.swiftModule.rawValue {
if pathMatchesSwiftModule(path: outputFilePath, "A") {
try checkCachingBuildJob(job: job, moduleId: .swift("A"),
dependencyGraph: dependencyGraph)
} else if pathMatchesSwiftModule(path: outputFilePath, "E") {
try checkCachingBuildJob(job: job, moduleId: .swift("E"),
dependencyGraph: dependencyGraph)
} else if pathMatchesSwiftModule(path: outputFilePath, "G") {
try checkCachingBuildJob(job: job, moduleId: .swift("G"),
dependencyGraph: dependencyGraph)
} else if pathMatchesSwiftModule(path: outputFilePath, "Swift") {
try checkCachingBuildJob(job: job, moduleId: .swift("Swift"),
dependencyGraph: dependencyGraph)
} else if pathMatchesSwiftModule(path: outputFilePath, "_Concurrency") {
try checkCachingBuildJob(job: job, moduleId: .swift("_Concurrency"),
dependencyGraph: dependencyGraph)
} else if pathMatchesSwiftModule(path: outputFilePath, "_StringProcessing") {
try checkCachingBuildJob(job: job, moduleId: .swift("_StringProcessing"),
dependencyGraph: dependencyGraph)
} else if pathMatchesSwiftModule(path: outputFilePath, "SwiftOnoneSupport") {
try checkCachingBuildJob(job: job, moduleId: .swift("SwiftOnoneSupport"),
dependencyGraph: dependencyGraph)
}
// Clang Dependencies
} else if let outputExtension = outputFilePath.extension,
outputExtension == FileType.pcm.rawValue {
let relativeOutputPathFileName = outputFilePath.basename
if relativeOutputPathFileName.starts(with: "A-") {
try checkCachingBuildJob(job: job, moduleId: .clang("A"),
dependencyGraph: dependencyGraph)
}
else if relativeOutputPathFileName.starts(with: "B-") {
try checkCachingBuildJob(job: job, moduleId: .clang("B"),
dependencyGraph: dependencyGraph)
}
else if relativeOutputPathFileName.starts(with: "C-") {
try checkCachingBuildJob(job: job, moduleId: .clang("C"),
dependencyGraph: dependencyGraph)
}
else if relativeOutputPathFileName.starts(with: "G-") {
try checkCachingBuildJob(job: job, moduleId: .clang("G"),
dependencyGraph: dependencyGraph)
}
else if relativeOutputPathFileName.starts(with: "F-") {
try checkCachingBuildJob(job: job, moduleId: .clang("F"),
dependencyGraph: dependencyGraph)
}
else if relativeOutputPathFileName.starts(with: "SwiftShims-") {
try checkCachingBuildJob(job: job, moduleId: .clang("SwiftShims"),
dependencyGraph: dependencyGraph)
}
else if relativeOutputPathFileName.starts(with: "_SwiftConcurrencyShims-") {
try checkCachingBuildJob(job: job, moduleId: .clang("_SwiftConcurrencyShims"),
dependencyGraph: dependencyGraph)
}
else if relativeOutputPathFileName.starts(with: "__ObjC") {
try checkCachingBuildJob(job: job, moduleId: .clang("__ObjC"),
dependencyGraph: dependencyGraph)
}
else {
XCTFail("Unexpected module dependency build job output: \(outputFilePath)")
}
} else if let outputExtension = outputFilePath.extension,
outputExtension == FileType.pch.rawValue {
// No Bridging header.
XCTFail("Unexpected module dependency build job output: \(outputFilePath)")
}
}
try driver.run(jobs: jobs)
XCTAssertFalse(driver.diagnosticEngine.hasErrors)

// Try consume the binary module.
let main = path.appending(component: "main.swift")
try localFileSystem.writeFileContents(main) {
$0 <<< "import Foo;"
$0 <<< "func test(foo: Foo) {}"
}
var userDriver = try Driver(args: ["swiftc",
"-target", "x86_64-apple-macosx11.0",
"-I", cHeadersPath.nativePathString(escaped: true),
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
"-I", FooInstallPath.nativePathString(escaped: true),
"-cache-compile-job", "-cas-path", casPath.nativePathString(escaped: true),
"-explicit-module-build", "-emit-module", "-wmo", "-emit-module-path",
path.appending(component: "main.swiftmodule").nativePathString(escaped: true),
"-module-cache-path", moduleCachePath.nativePathString(escaped: true),
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
env: ProcessEnv.vars)
let userJobs = try userDriver.planBuild()
// __ObjC module is implementationOnly imported so the user of the binary module doesn't need it.
XCTAssertFalse(userJobs.contains(where: { $0.moduleName == "__ObjC" }))
// We don't import pch.
let compileJob = try XCTUnwrap(userJobs.first(where: { $0.description == "Compiling main main.swift" }))
XCTAssertFalse(compileJob.commandLine.contains(.flag("-include-pch")))
try userDriver.run(jobs: userJobs)
XCTAssertFalse(userDriver.diagnosticEngine.hasErrors)
}
}
}
162 changes: 162 additions & 0 deletions Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift
Original file line number Diff line number Diff line change
@@ -638,6 +638,168 @@ final class ExplicitModuleBuildTests: XCTestCase {
}
}

/// Test generation of explicit module build jobs for dependency modules when the driver
/// is invoked with -explicit-module-build and -experimental-bridging-header-as-module.
func testExplicitModuleBuildBridgingHeaderAsModuleJobs() throws {
try withTemporaryDirectory { path in
let foo = path.appending(component: "testExplicitModuleBuildBridgingHeaderAsModuleJobs.swift")
try localFileSystem.writeFileContents(foo) {
$0 <<< "import C;"
$0 <<< "import E;"
$0 <<< "import G;"
$0 <<< "public class Foo { var name: A? = nil }"
}

let moduleCachePath = path.appending(component: "ModuleCache")
try localFileSystem.createDirectory(moduleCachePath)
let cHeadersPath: AbsolutePath =
testInputsPath.appending(component: "ExplicitModuleBuilds")
.appending(component: "CHeaders")
let bridgingHeaderpath: AbsolutePath =
cHeadersPath.appending(component: "BridgingModule.h")
let swiftModuleInterfacesPath: AbsolutePath =
testInputsPath.appending(component: "ExplicitModuleBuilds")
.appending(component: "Swift")
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
let pchOutputDir: AbsolutePath = path
let FooInstallPath = path.appending(component: "Foo")
try localFileSystem.createDirectory(FooInstallPath)
var driver = try Driver(args: ["swiftc",
"-target", "x86_64-apple-macosx11.0",
"-I", cHeadersPath.nativePathString(escaped: true),
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
"-module-cache-path", moduleCachePath.nativePathString(escaped: true),
"-explicit-module-build",
"-emit-module", "-wmo", "-module-name", "Foo",
"-emit-module-path", FooInstallPath.appending(component: "Foo.swiftmodule").nativePathString(escaped: true),
"-import-objc-header", bridgingHeaderpath.nativePathString(escaped: true),
"-experimental-bridging-header-as-module",
"-pch-output-dir", pchOutputDir.nativePathString(escaped: true),
foo.nativePathString(escaped: true)] + sdkArgumentsForTesting)
guard driver.isFrontendArgSupported(.experimentalBridgingHeaderAsModule) else {
throw XCTSkip("swift-frontend doesn't support building bridging header as module.")
}

let jobs = try driver.planBuild()
// Figure out which Triples to use.
let dependencyGraph = try driver.gatherModuleDependencies()
let fooModuleInfo = try dependencyGraph.moduleInfo(of: .swift("Foo"))
guard case .swift(_) = fooModuleInfo.details else {
XCTFail("Foo module does not have Swift details field")
return
}

for job in jobs {
if job.kind == .compile {
// Check we don't use `-pch-output-dir` anymore during main module job.
XCTAssertFalse(job.commandLine.contains("-pch-output-dir"))
XCTAssertFalse(job.commandLine.contains("-import-objc-header"))
XCTAssertTrue(job.commandLine.contains("-experimental-bridging-header-as-module"))
continue
}
XCTAssertEqual(job.outputs.count, 1)
let outputFilePath = job.outputs[0].file

// Swift dependencies
if outputFilePath.extension != nil,
outputFilePath.extension! == FileType.swiftModule.rawValue {
if pathMatchesSwiftModule(path: outputFilePath, "A") {
try checkExplicitModuleBuildJob(job: job, moduleId: .swift("A"),
dependencyGraph: dependencyGraph)
} else if pathMatchesSwiftModule(path: outputFilePath, "E") {
try checkExplicitModuleBuildJob(job: job, moduleId: .swift("E"),
dependencyGraph: dependencyGraph)
} else if pathMatchesSwiftModule(path: outputFilePath, "G") {
try checkExplicitModuleBuildJob(job: job, moduleId: .swift("G"),
dependencyGraph: dependencyGraph)
} else if pathMatchesSwiftModule(path: outputFilePath, "Swift") {
try checkExplicitModuleBuildJob(job: job, moduleId: .swift("Swift"),
dependencyGraph: dependencyGraph)
} else if pathMatchesSwiftModule(path: outputFilePath, "_Concurrency") {
try checkExplicitModuleBuildJob(job: job, moduleId: .swift("_Concurrency"),
dependencyGraph: dependencyGraph)
} else if pathMatchesSwiftModule(path: outputFilePath, "_StringProcessing") {
try checkExplicitModuleBuildJob(job: job, moduleId: .swift("_StringProcessing"),
dependencyGraph: dependencyGraph)
} else if pathMatchesSwiftModule(path: outputFilePath, "SwiftOnoneSupport") {
try checkExplicitModuleBuildJob(job: job, moduleId: .swift("SwiftOnoneSupport"),
dependencyGraph: dependencyGraph)
}
// Clang Dependencies
} else if let outputExtension = outputFilePath.extension,
outputExtension == FileType.pcm.rawValue {
let relativeOutputPathFileName = outputFilePath.basename
if relativeOutputPathFileName.starts(with: "A-") {
try checkExplicitModuleBuildJob(job: job, moduleId: .clang("A"),
dependencyGraph: dependencyGraph)
}
else if relativeOutputPathFileName.starts(with: "B-") {
try checkExplicitModuleBuildJob(job: job, moduleId: .clang("B"),
dependencyGraph: dependencyGraph)
}
else if relativeOutputPathFileName.starts(with: "C-") {
try checkExplicitModuleBuildJob(job: job, moduleId: .clang("C"),
dependencyGraph: dependencyGraph)
}
else if relativeOutputPathFileName.starts(with: "G-") {
try checkExplicitModuleBuildJob(job: job, moduleId: .clang("G"),
dependencyGraph: dependencyGraph)
}
else if relativeOutputPathFileName.starts(with: "F-") {
try checkExplicitModuleBuildJob(job: job, moduleId: .clang("F"),
dependencyGraph: dependencyGraph)
}
else if relativeOutputPathFileName.starts(with: "SwiftShims-") {
try checkExplicitModuleBuildJob(job: job, moduleId: .clang("SwiftShims"),
dependencyGraph: dependencyGraph)
}
else if relativeOutputPathFileName.starts(with: "_SwiftConcurrencyShims-") {
try checkExplicitModuleBuildJob(job: job, moduleId: .clang("_SwiftConcurrencyShims"),
dependencyGraph: dependencyGraph)
}
else if relativeOutputPathFileName.starts(with: "__ObjC") {
try checkExplicitModuleBuildJob(job: job, moduleId: .clang("__ObjC"),
dependencyGraph: dependencyGraph)
}
else {
XCTFail("Unexpected module dependency build job output: \(outputFilePath)")
}
} else if let outputExtension = outputFilePath.extension,
outputExtension == FileType.pch.rawValue {
// No Bridging header.
XCTFail("Unexpected module dependency build job output: \(outputFilePath)")
}
}
try driver.run(jobs: jobs)
XCTAssertFalse(driver.diagnosticEngine.hasErrors)

// Try consume the binary module.
let main = path.appending(component: "main.swift")
try localFileSystem.writeFileContents(main) {
$0 <<< "import Foo;"
$0 <<< "func test(foo: Foo) {}"
}
var userDriver = try Driver(args: ["swiftc",
"-target", "x86_64-apple-macosx11.0",
"-I", cHeadersPath.nativePathString(escaped: true),
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
"-I", FooInstallPath.nativePathString(escaped: true),
"-explicit-module-build", "-emit-module", "-wmo", "-emit-module-path",
path.appending(component: "main.swiftmodule").nativePathString(escaped: true),
"-module-cache-path", moduleCachePath.nativePathString(escaped: true),
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
env: ProcessEnv.vars)
let userJobs = try userDriver.planBuild()
// __ObjC module is implementationOnly imported so the user of the binary module doesn't need it.
XCTAssertFalse(userJobs.contains(where: { $0.moduleName == "__ObjC" }))
// We don't import pch.
let compileJob = try XCTUnwrap(userJobs.first(where: { $0.description == "Compiling main main.swift" }))
XCTAssertFalse(compileJob.commandLine.contains(.flag("-include-pch")))
try userDriver.run(jobs: userJobs)
XCTAssertFalse(userDriver.diagnosticEngine.hasErrors)
}
}

func testImmediateModeExplicitModuleBuild() throws {
try withTemporaryDirectory { path in
let main = path.appending(component: "testExplicitModuleBuildJobs.swift")