From 46ee42edffd134bced2690f1874cf3b156a8e012 Mon Sep 17 00:00:00 2001 From: Steven Wu Date: Mon, 27 Jan 2025 12:59:28 -0800 Subject: [PATCH] [BridgingHeader] Support automatic bridging header chaining Teach swift driver to handle bridging header chaining and pass the correct arguments to swift-frontend. --- Sources/CSwiftScan/include/swiftscan_header.h | 10 +- Sources/SwiftDriver/Driver/Driver.swift | 122 +++++++++++++----- .../ExplicitDependencyBuildPlanner.swift | 17 +++ .../InterModuleDependencyGraph.swift | 7 +- .../ModuleDependencyScanning.swift | 29 +++++ .../SwiftDriver/Jobs/FrontendJobHelpers.swift | 35 +++-- .../Jobs/VerifyModuleInterfaceJob.swift | 2 +- .../SwiftScan/DependencyGraphBuilder.swift | 8 +- Sources/SwiftDriver/SwiftScan/SwiftScan.swift | 8 ++ Sources/SwiftOptions/Options.swift | 22 +++- .../SwiftDriverTests/CachingBuildTests.swift | 7 +- .../ExplicitModuleBuildTests.swift | 105 +++++++++++++++ Tests/SwiftDriverTests/SwiftDriverTests.swift | 3 + 13 files changed, 317 insertions(+), 58 deletions(-) diff --git a/Sources/CSwiftScan/include/swiftscan_header.h b/Sources/CSwiftScan/include/swiftscan_header.h index 1a2b3cf14..b751978ff 100644 --- a/Sources/CSwiftScan/include/swiftscan_header.h +++ b/Sources/CSwiftScan/include/swiftscan_header.h @@ -17,8 +17,8 @@ #include #include -#define SWIFTSCAN_VERSION_MAJOR 1 -#define SWIFTSCAN_VERSION_MINOR 0 +#define SWIFTSCAN_VERSION_MAJOR 2 +#define SWIFTSCAN_VERSION_MINOR 1 //=== Public Scanner Data Types -------------------------------------------===// @@ -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 diff --git a/Sources/SwiftDriver/Driver/Driver.swift b/Sources/SwiftDriver/Driver/Driver.swift index ce71772f3..73e069ced 100644 --- a/Sources/SwiftDriver/Driver/Driver.swift +++ b/Sources/SwiftDriver/Driver/Driver.swift @@ -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. @@ -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, @@ -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 { @@ -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 } @@ -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() } diff --git a/Sources/SwiftDriver/ExplicitModuleBuilds/ExplicitDependencyBuildPlanner.swift b/Sources/SwiftDriver/ExplicitModuleBuilds/ExplicitDependencyBuildPlanner.swift index 073a0b2e3..5f098143e 100644 --- a/Sources/SwiftDriver/ExplicitModuleBuilds/ExplicitDependencyBuildPlanner.swift +++ b/Sources/SwiftDriver/ExplicitModuleBuilds/ExplicitDependencyBuildPlanner.swift @@ -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 @@ -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], diff --git a/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyGraph.swift b/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyGraph.swift index 238d30368..5a64acfd6 100644 --- a/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyGraph.swift +++ b/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyGraph.swift @@ -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, @@ -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. diff --git a/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift b/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift index ff0674eff..9deaedd5a 100644 --- a/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift +++ b/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift @@ -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 @@ -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) diff --git a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift index a9ba9d055..fa5c73d77 100644 --- a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift +++ b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift @@ -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) } } @@ -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) @@ -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) } } diff --git a/Sources/SwiftDriver/Jobs/VerifyModuleInterfaceJob.swift b/Sources/SwiftDriver/Jobs/VerifyModuleInterfaceJob.swift index 5136b2053..03ef59e26 100644 --- a/Sources/SwiftDriver/Jobs/VerifyModuleInterfaceJob.swift +++ b/Sources/SwiftDriver/Jobs/VerifyModuleInterfaceJob.swift @@ -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 diff --git a/Sources/SwiftDriver/SwiftScan/DependencyGraphBuilder.swift b/Sources/SwiftDriver/SwiftScan/DependencyGraphBuilder.swift index 007bdd828..4e2cb06c2 100644 --- a/Sources/SwiftDriver/SwiftScan/DependencyGraphBuilder.swift +++ b/Sources/SwiftDriver/SwiftScan/DependencyGraphBuilder.swift @@ -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]? @@ -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 diff --git a/Sources/SwiftDriver/SwiftScan/SwiftScan.swift b/Sources/SwiftDriver/SwiftScan/SwiftScan.swift index aae56134d..6e6818a30 100644 --- a/Sources/SwiftDriver/SwiftScan/SwiftScan.swift +++ b/Sources/SwiftDriver/SwiftScan/SwiftScan.swift @@ -290,6 +290,10 @@ private extension String { return api.swiftscan_swift_textual_detail_get_bridging_pch_command_line != nil } + @_spi(Testing) public var supportsChainedBridgingHeader : Bool { + return api.swiftscan_swift_textual_detail_get_chained_bridging_header_path != nil && + api.swiftscan_swift_textual_detail_get_chained_bridging_header_content != nil + } @_spi(Testing) public var canQueryPerScanDiagnostics : Bool { return api.swiftscan_dependency_graph_get_diagnostics != nil && @@ -499,6 +503,10 @@ private extension swiftscan_functions_t { // Bridging PCH build command-line self.swiftscan_swift_textual_detail_get_bridging_pch_command_line = loadOptional("swiftscan_swift_textual_detail_get_bridging_pch_command_line") + self.swiftscan_swift_textual_detail_get_chained_bridging_header_path = + loadOptional("swiftscan_swift_textual_detail_get_chained_bridging_header_path") + self.swiftscan_swift_textual_detail_get_chained_bridging_header_content = + loadOptional("swiftscan_swift_textual_detail_get_chained_bridging_header_content") // Caching related APIs. self.swiftscan_swift_textual_detail_get_module_cache_key = diff --git a/Sources/SwiftOptions/Options.swift b/Sources/SwiftOptions/Options.swift index a2d8b8a7b..f1da0b506 100644 --- a/Sources/SwiftOptions/Options.swift +++ b/Sources/SwiftOptions/Options.swift @@ -39,6 +39,7 @@ extension Option { public static let enableAppExtension: Option = Option("-application-extension", .flag, attributes: [.frontend, .noInteractive], helpText: "Restrict code to those available for App Extensions") public static let AssertConfig: Option = Option("-assert-config", .separate, attributes: [.frontend, .moduleInterface], helpText: "Specify the assert_configuration replacement. Possible values are Debug, Release, Unchecked, DisableReplacement.") public static let AssumeSingleThreaded: Option = Option("-assume-single-threaded", .flag, attributes: [.helpHidden, .frontend], helpText: "Assume that code will be executed in a single-threaded environment") + public static let autoBridgingHeaderChaining: Option = Option("-auto-bridging-header-chaining", .flag, attributes: [.helpHidden, .frontend], helpText: "Automatically chaining all the bridging headers") public static let autolinkForceLoad: Option = Option("-autolink-force-load", .flag, attributes: [.helpHidden, .frontend, .moduleInterface], helpText: "Force ld to link against this module even if no symbols are used") public static let autolinkLibrary: Option = Option("-autolink-library", .separate, attributes: [.frontend, .noDriver], helpText: "Add dependent library") public static let avoidEmitModuleSourceInfo: Option = Option("-avoid-emit-module-source-info", .flag, attributes: [.noInteractive, .doesNotAffectIncrementalBuild], helpText: "don't emit Swift source info file") @@ -51,7 +52,6 @@ extension Option { public static let badFileDescriptorRetryCount: Option = Option("-bad-file-descriptor-retry-count", .separate, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Number of retrying opening a file if previous open returns a bad file descriptor error.") public static let baselineDir: Option = Option("-baseline-dir", .joinedOrSeparate, attributes: [.noDriver, .argumentIsPath], helpText: "The path to a directory containing baseline files: macos.json, iphoneos.json, appletvos.json, watchos.json, and iosmac.json") public static let baselinePath: Option = Option("-baseline-path", .joinedOrSeparate, attributes: [.noDriver, .argumentIsPath], helpText: "The path to the Json file that we should use as the baseline") - public static let batchScanInputFile: Option = Option("-batch-scan-input-file", .separate, attributes: [.frontend, .noDriver], metaVar: "", helpText: "Specify a JSON file containing modules to perform batch dependencies scanning") public static let BFEQ: Option = Option("-BF=", .joined, alias: Option.BF, attributes: [.noDriver]) public static let BF: Option = Option("-BF", .joinedOrSeparate, attributes: [.noDriver, .argumentIsPath], helpText: "add a directory to the baseline framework search path") public static let BIEQ: Option = Option("-BI=", .joined, alias: Option.BI, attributes: [.noDriver]) @@ -133,6 +133,9 @@ extension Option { public static let debuggerSupport: Option = Option("-debugger-support", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Process swift code as if running in the debugger") public static let debuggerTestingTransform: Option = Option("-debugger-testing-transform", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Instrument the code with calls to an intrinsic that record the expected values of local variables so they can be compared against the results from the debugger.") public static let defineAvailability: Option = Option("-define-availability", .separate, attributes: [.frontend, .noInteractive], metaVar: "", helpText: "Define an availability macro in the format 'macroName : iOS 13.0, macOS 10.15'") + public static let defineDisabledAvailabilityDomain: Option = Option("-define-disabled-availability-domain", .separate, attributes: [.helpHidden, .frontend, .noInteractive], metaVar: "", helpText: "Defines a custom availability domain that is unavailable at compile time") + public static let defineDynamicAvailabilityDomain: Option = Option("-define-dynamic-availability-domain", .separate, attributes: [.helpHidden, .frontend, .noInteractive], metaVar: "", helpText: "Defines a custom availability domain that can be enabled or disabled at runtime") + public static let defineEnabledAvailabilityDomain: Option = Option("-define-enabled-availability-domain", .separate, attributes: [.helpHidden, .frontend, .noInteractive], metaVar: "", helpText: "Defines a custom availability domain that is available at compile time") public static let dependencyScanCachePath: Option = Option("-dependency-scan-cache-path", .separate, attributes: [.frontend, .noDriver], helpText: "The path to output the dependency scanner's internal state.") public static let deprecatedIntegratedRepl: Option = Option("-deprecated-integrated-repl", .flag, attributes: [.frontend, .noBatch], group: .modes) public static let deserializeDiff: Option = Option("-deserialize-diff", .flag, attributes: [.noDriver], helpText: "Deserialize diff items in a JSON file") @@ -446,7 +449,6 @@ extension Option { public static let enableImplicitDynamic: Option = Option("-enable-implicit-dynamic", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Add 'dynamic' to all declarations") public static let enableImportPtrauthFieldFunctionPointers: Option = Option("-enable-import-ptrauth-field-function-pointers", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable import of custom ptrauth qualified field function pointers. This is on by default.") public static let enableIncrementalImports: Option = Option("-enable-incremental-imports", .flag, attributes: [.frontend], helpText: "Enable cross-module incremental build metadata and driver scheduling for Swift modules") - public static let enableInferPublicConcurrentValue: Option = Option("-enable-infer-public-sendable", .flag, attributes: [.frontend, .noDriver], helpText: "Enable inference of Sendable conformances for public structs and enums") public static let enableInvalidEphemeralnessAsError: Option = Option("-enable-invalid-ephemeralness-as-error", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Diagnose invalid ephemeral to non-ephemeral conversions as errors") public static let enableLargeLoadableTypesReg2mem: Option = Option("-enable-large-loadable-types-reg2mem", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable large loadable types register to memory pass") public static let enableLayoutStringValueWitnessesInstantiation: Option = Option("-enable-layout-string-value-witnesses-instantiation", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable runtime instantiation of layout string value witnesses for generic types") @@ -586,6 +588,7 @@ extension Option { public static let importCfTypes: Option = Option("-import-cf-types", .flag, attributes: [.helpHidden, .frontend], helpText: "Recognize and import CF types as class types") public static let importModule: Option = Option("-import-module", .separate, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Implicitly import the specified module") public static let importObjcHeader: Option = Option("-import-objc-header", .separate, attributes: [.helpHidden, .frontend, .argumentIsPath], helpText: "Implicitly imports an Objective-C header file") + public static let importPch: Option = Option("-import-pch", .separate, attributes: [.helpHidden, .frontend, .argumentIsPath], helpText: "Import bridging header PCH file") public static let importPrescan: Option = Option("-import-prescan", .flag, attributes: [.frontend, .noDriver], helpText: "When performing a dependency scan, only identify all imports of the main Swift module sources") public static let importUnderlyingModule: Option = Option("-import-underlying-module", .flag, attributes: [.frontend, .noInteractive], helpText: "Implicitly imports the Objective-C half of a module") public static let inPlace: Option = Option("-in-place", .flag, attributes: [.noInteractive, .noBatch], helpText: "Overwrite input file with formatted file.", group: .codeFormatting) @@ -664,6 +667,7 @@ extension Option { public static let module_: Option = Option("--module", .separate, alias: Option.module, attributes: [.noDriver], metaVar: "", helpText: "Names of modules") public static let newDriverPath: Option = Option("-new-driver-path", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "", helpText: "Path of the new driver to be used") public static let noAllocations: Option = Option("-no-allocations", .flag, attributes: [.helpHidden, .frontend], helpText: "Diagnose any code that needs to heap allocate (classes, closures, etc.)") + public static let noAutoBridgingHeaderChaining: Option = Option("-no-auto-bridging-header-chaining", .flag, attributes: [.helpHidden, .frontend], helpText: "Do not automatically chaining all the bridging headers") public static let noClangIncludeTree: Option = Option("-no-clang-include-tree", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Do not use clang include tree, fallback to use CAS filesystem to build clang modules") public static let noClangModuleBreadcrumbs: Option = Option("-no-clang-module-breadcrumbs", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Don't emit DWARF skeleton CUs for imported Clang modules. Use this when building a redistributable static archive.") public static let noColorDiagnostics: Option = Option("-no-color-diagnostics", .flag, attributes: [.frontend, .doesNotAffectIncrementalBuild], helpText: "Do not print diagnostics in color") @@ -791,7 +795,9 @@ extension Option { public static let saveOptimizationRecord: Option = Option("-save-optimization-record", .flag, attributes: [.frontend], helpText: "Generate a YAML optimization record file") public static let saveTemps: Option = Option("-save-temps", .flag, attributes: [.noInteractive, .doesNotAffectIncrementalBuild], helpText: "Save intermediate compilation results") public static let scanDependencies: Option = Option("-scan-dependencies", .flag, attributes: [.frontend, .noInteractive, .doesNotAffectIncrementalBuild], helpText: "Scan dependencies of the given Swift sources", group: .modes) + public static let scannerDebugWriteOutput: Option = Option("-scanner-debug-write-output", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Write generated output from scanner for debugging purpose") public static let scannerModuleValidation: Option = Option("-scanner-module-validation", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Validate binary modules in the dependency scanner") + public static let scannerOutputDir: Option = Option("-scanner-output-dir", .separate, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Directory for generated files from swift dependency scanner") public static let scannerPrefixMapSdk: Option = Option("-scanner-prefix-map-sdk", .separate, attributes: [], metaVar: "", helpText: "Remap paths within SDK reported by dependency scanner") public static let scannerPrefixMapToolchain: Option = Option("-scanner-prefix-map-toolchain", .separate, attributes: [], metaVar: "", helpText: "Remap paths within toolchain directory reported by dependency scanner") public static let scannerPrefixMap: Option = Option("-scanner-prefix-map", .separate, attributes: [.frontend], metaVar: "", helpText: "Remap paths reported by dependency scanner") @@ -829,7 +835,7 @@ extension Option { public static let staticExecutable: Option = Option("-static-executable", .flag, helpText: "Statically link the executable") public static let staticStdlib: Option = Option("-static-stdlib", .flag, attributes: [.doesNotAffectIncrementalBuild], helpText: "Statically link the Swift standard library") public static let `static`: Option = Option("-static", .flag, attributes: [.frontend, .noInteractive, .moduleInterface], helpText: "Make this module statically linkable and make the output of -emit-library a static library.") - public static let statsOutputDir: Option = Option("-stats-output-dir", .separate, attributes: [.helpHidden, .frontend, .argumentIsPath], helpText: "Directory to write unified compilation-statistics files to") + public static let statsOutputDir: Option = Option("-stats-output-dir", .separate, attributes: [.helpHidden, .frontend, .argumentIsPath, .cacheInvariant], helpText: "Directory to write unified compilation-statistics files to") public static let strictConcurrency: Option = Option("-strict-concurrency=", .joined, attributes: [.frontend, .doesNotAffectIncrementalBuild], helpText: "Specify the how strict concurrency checking will be. The value may be 'minimal' (most 'Sendable' checking is disabled), 'targeted' ('Sendable' checking is enabled in code that uses the concurrency model, or 'complete' ('Sendable' and other checking is enabled for all code in the module)") public static let strictImplicitModuleContext: Option = Option("-strict-implicit-module-context", .flag, attributes: [.helpHidden, .frontend], helpText: "Enable the strict forwarding of compilation context to downstream implicit module dependencies") public static let supplementaryOutputFileMap: Option = Option("-supplementary-output-file-map", .separate, attributes: [.frontend, .noDriver, .cacheInvariant], helpText: "Specify supplementary outputs in a file rather than on the command line") @@ -963,6 +969,7 @@ extension Option { Option.enableAppExtension, Option.AssertConfig, Option.AssumeSingleThreaded, + Option.autoBridgingHeaderChaining, Option.autolinkForceLoad, Option.autolinkLibrary, Option.avoidEmitModuleSourceInfo, @@ -975,7 +982,6 @@ extension Option { Option.badFileDescriptorRetryCount, Option.baselineDir, Option.baselinePath, - Option.batchScanInputFile, Option.BFEQ, Option.BF, Option.BIEQ, @@ -1057,6 +1063,9 @@ extension Option { Option.debuggerSupport, Option.debuggerTestingTransform, Option.defineAvailability, + Option.defineDisabledAvailabilityDomain, + Option.defineDynamicAvailabilityDomain, + Option.defineEnabledAvailabilityDomain, Option.dependencyScanCachePath, Option.deprecatedIntegratedRepl, Option.deserializeDiff, @@ -1370,7 +1379,6 @@ extension Option { Option.enableImplicitDynamic, Option.enableImportPtrauthFieldFunctionPointers, Option.enableIncrementalImports, - Option.enableInferPublicConcurrentValue, Option.enableInvalidEphemeralnessAsError, Option.enableLargeLoadableTypesReg2mem, Option.enableLayoutStringValueWitnessesInstantiation, @@ -1510,6 +1518,7 @@ extension Option { Option.importCfTypes, Option.importModule, Option.importObjcHeader, + Option.importPch, Option.importPrescan, Option.importUnderlyingModule, Option.inPlace, @@ -1588,6 +1597,7 @@ extension Option { Option.module_, Option.newDriverPath, Option.noAllocations, + Option.noAutoBridgingHeaderChaining, Option.noClangIncludeTree, Option.noClangModuleBreadcrumbs, Option.noColorDiagnostics, @@ -1715,7 +1725,9 @@ extension Option { Option.saveOptimizationRecord, Option.saveTemps, Option.scanDependencies, + Option.scannerDebugWriteOutput, Option.scannerModuleValidation, + Option.scannerOutputDir, Option.scannerPrefixMapSdk, Option.scannerPrefixMapToolchain, Option.scannerPrefixMap, diff --git a/Tests/SwiftDriverTests/CachingBuildTests.swift b/Tests/SwiftDriverTests/CachingBuildTests.swift index 26eae0633..778ad0023 100644 --- a/Tests/SwiftDriverTests/CachingBuildTests.swift +++ b/Tests/SwiftDriverTests/CachingBuildTests.swift @@ -339,7 +339,7 @@ final class CachingBuildTests: XCTestCase { let baseName = "testCachingBuildJobs" XCTAssertTrue(matchTemporary(outputFilePath, basename: baseName, fileExtension: "o") || matchTemporary(outputFilePath, basename: baseName, fileExtension: "autolink") || - matchTemporary(outputFilePath, basename: "Bridging", fileExtension: "pch")) + matchTemporary(outputFilePath, basename: "", fileExtension: "pch")) default: XCTFail("Unexpected module dependency build job output: \(outputFilePath)") } @@ -722,6 +722,11 @@ final class CachingBuildTests: XCTestCase { for job in jobs { XCTAssertFalse(job.outputCacheKeys.isEmpty) } + if driver.isFrontendArgSupported(.importPch) { + XCTAssertTrue(jobs.contains { $0.kind == .generatePCH }) + } + try driver.run(jobs: jobs) + XCTAssertFalse(driver.diagnosticEngine.hasErrors) } } diff --git a/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift b/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift index 7358aa011..5feafc4b2 100644 --- a/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift +++ b/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift @@ -395,6 +395,111 @@ final class ExplicitModuleBuildTests: XCTestCase { } } + func testExplicitBuildEndToEndWithBinaryHeaderDeps() throws { + try withTemporaryDirectory { path in + try localFileSystem.changeCurrentWorkingDirectory(to: path) + let moduleCachePath = path.appending(component: "ModuleCache") + try localFileSystem.createDirectory(moduleCachePath) + let PCHPath = path.appending(component: "PCH") + try localFileSystem.createDirectory(PCHPath) + let FooInstallPath = path.appending(component: "Foo") + try localFileSystem.createDirectory(FooInstallPath) + let foo = path.appending(component: "foo.swift") + try localFileSystem.writeFileContents(foo) { + $0.send("extension Profiler {") + $0.send(" public static let count: Int = 42") + $0.send("}") + } + let fooHeader = path.appending(component: "foo.h") + try localFileSystem.writeFileContents(fooHeader) { + $0.send("struct Profiler { void* ptr; };") + } + let main = path.appending(component: "main.swift") + try localFileSystem.writeFileContents(main) { + $0.send("import Foo") + } + let userHeader = path.appending(component: "user.h") + try localFileSystem.writeFileContents(userHeader) { + $0.send("struct User { void* ptr; };") + } + let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? [] + + var fooBuildDriver = try Driver(args: ["swiftc", + "-explicit-module-build", "-auto-bridging-header-chaining", + "-module-cache-path", moduleCachePath.nativePathString(escaped: true), + "-working-directory", path.nativePathString(escaped: true), + foo.nativePathString(escaped: true), + "-emit-module", "-wmo", "-module-name", "Foo", + "-emit-module-path", FooInstallPath.nativePathString(escaped: true), + "-import-objc-header", fooHeader.nativePathString(escaped: true), + "-pch-output-dir", PCHPath.nativePathString(escaped: true), + FooInstallPath.appending(component: "Foo.swiftmodule").nativePathString(escaped: true)] + + sdkArgumentsForTesting) + + let fooJobs = try fooBuildDriver.planBuild() + try fooBuildDriver.run(jobs: fooJobs) + XCTAssertFalse(fooBuildDriver.diagnosticEngine.hasErrors) + + // If no chained bridging header is used, always pass pch through -import-objc-header + var driver = try Driver(args: ["swiftc", + "-I", FooInstallPath.nativePathString(escaped: true), + "-explicit-module-build", "-no-auto-bridging-header-chaining", + "-pch-output-dir", FooInstallPath.nativePathString(escaped: true), + "-import-objc-header", userHeader.nativePathString(escaped: true), + "-emit-module", "-emit-module-path", + path.appending(component: "testEMBETEWBHD.swiftmodule").nativePathString(escaped: true), + "-module-cache-path", moduleCachePath.nativePathString(escaped: true), + "-working-directory", path.nativePathString(escaped: true), + main.nativePathString(escaped: true)] + sdkArgumentsForTesting) + var jobs = try driver.planBuild() + XCTAssertTrue(jobs.contains { $0.kind == .generatePCH }) + XCTAssertTrue(jobs.allSatisfy { + !$0.kind.isCompile || $0.commandLine.contains(.flag("-import-objc-header")) + }) + XCTAssertTrue(jobs.allSatisfy { + !$0.kind.isCompile || !$0.commandLine.contains(.flag("-import-pch")) + }) + + // Remaining tests require a compiler supporting auto chaining. + guard driver.isFrontendArgSupported(.autoBridgingHeaderChaining) else { return } + + // Warn if -disable-bridging-pch is used with auto bridging header chaining. + driver = try Driver(args: ["swiftc", "-v", + "-I", FooInstallPath.nativePathString(escaped: true), + "-explicit-module-build", "-auto-bridging-header-chaining", "-disable-bridging-pch", + "-pch-output-dir", FooInstallPath.nativePathString(escaped: true), + "-emit-module", "-emit-module-path", + path.appending(component: "testEMBETEWBHD.swiftmodule").nativePathString(escaped: true), + "-module-cache-path", moduleCachePath.nativePathString(escaped: true), + "-working-directory", path.nativePathString(escaped: true), + main.nativePathString(escaped: true)] + sdkArgumentsForTesting) + jobs = try driver.planBuild() + XCTAssertTrue(driver.diagnosticEngine.diagnostics.contains { + $0.behavior == .warning && $0.message.text == "-auto-bridging-header-chaining requires generatePCH job, no chaining will be performed" + }) + XCTAssertFalse(jobs.contains { $0.kind == .generatePCH }) + + driver = try Driver(args: ["swiftc", + "-I", FooInstallPath.nativePathString(escaped: true), + "-explicit-module-build", "-auto-bridging-header-chaining", + "-pch-output-dir", FooInstallPath.nativePathString(escaped: true), + "-emit-module", "-emit-module-path", + path.appending(component: "testEMBETEWBHD.swiftmodule").nativePathString(escaped: true), + "-module-cache-path", moduleCachePath.nativePathString(escaped: true), + "-working-directory", path.nativePathString(escaped: true), + main.nativePathString(escaped: true)] + sdkArgumentsForTesting) + jobs = try driver.planBuild() + XCTAssertTrue(jobs.contains { $0.kind == .generatePCH }) + XCTAssertTrue(jobs.allSatisfy { + !$0.kind.isCompile || $0.commandLine.contains(.flag("-import-pch")) + }) + XCTAssertTrue(jobs.allSatisfy { + !$0.kind.isCompile || !$0.commandLine.contains(.flag("-import-objc-header")) + }) + XCTAssertFalse(driver.diagnosticEngine.hasErrors) + } + } + func testExplicitLinkLibraries() throws { try withTemporaryDirectory { path in let (_, _, toolchain, _) = try getDriverArtifactsForScanning() diff --git a/Tests/SwiftDriverTests/SwiftDriverTests.swift b/Tests/SwiftDriverTests/SwiftDriverTests.swift index fc59a5230..ac2bbeefa 100644 --- a/Tests/SwiftDriverTests/SwiftDriverTests.swift +++ b/Tests/SwiftDriverTests/SwiftDriverTests.swift @@ -6668,6 +6668,9 @@ final class SwiftDriverTests: XCTestCase { XCTAssertEqual(plannedJobs[1].kind, .compile) XCTAssertTrue(commandContainsFlagTemporaryPathSequence(plannedJobs[1].commandLine, flag: "-import-objc-header", + filename: "header.pch") || + commandContainsFlagTemporaryPathSequence(plannedJobs[1].commandLine, + flag: "-import-pch", filename: "header.pch")) XCTAssertEqual(plannedJobs[2].kind, .mergeModule) try XCTAssertJobInvocationMatches(plannedJobs[2], .flag("-import-objc-header"), toPathOption("header.h"))