diff --git a/Sources/SwiftDriver/Driver/Driver.swift b/Sources/SwiftDriver/Driver/Driver.swift index 5d6d3b3ff..20a19b893 100644 --- a/Sources/SwiftDriver/Driver/Driver.swift +++ b/Sources/SwiftDriver/Driver/Driver.swift @@ -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,30 @@ 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 { + umbrella 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 { diff --git a/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift b/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift index b9a840b8e..baae70459 100644 --- a/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift +++ b/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift @@ -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 diff --git a/Sources/SwiftDriver/Jobs/CompileJob.swift b/Sources/SwiftDriver/Jobs/CompileJob.swift index 05bc1a057..9dcdf345b 100644 --- a/Sources/SwiftDriver/Jobs/CompileJob.swift +++ b/Sources/SwiftDriver/Jobs/CompileJob.swift @@ -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 diff --git a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift index 4fe14b919..1c934e526 100644 --- a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift +++ b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift @@ -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)) } } diff --git a/Sources/SwiftOptions/Options.swift b/Sources/SwiftOptions/Options.swift index 656411779..31b3ac2e9 100644 --- a/Sources/SwiftOptions/Options.swift +++ b/Sources/SwiftOptions/Options.swift @@ -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: "", 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: "=", 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: "", 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: "", 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: "", helpText: "Enforce law of exclusivity") public static let entryPointFunctionName: Option = Option("-entry-point-function-name", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "", 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, diff --git a/TestInputs/ExplicitModuleBuilds/CHeaders/BridgingModule.h b/TestInputs/ExplicitModuleBuilds/CHeaders/BridgingModule.h new file mode 100644 index 000000000..f3f375d27 --- /dev/null +++ b/TestInputs/ExplicitModuleBuilds/CHeaders/BridgingModule.h @@ -0,0 +1,6 @@ +#include "A.h" + +@interface A +@end + +void bridgingA(A *a); diff --git a/Tests/SwiftDriverTests/CachingBuildTests.swift b/Tests/SwiftDriverTests/CachingBuildTests.swift index ab47db4bd..ae5b4d985 100644 --- a/Tests/SwiftDriverTests/CachingBuildTests.swift +++ b/Tests/SwiftDriverTests/CachingBuildTests.swift @@ -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) + } + } } diff --git a/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift b/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift index b5f07e120..5dd2fdf75 100644 --- a/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift +++ b/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift @@ -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")