//===----------------------------------------------------------------------===// // // This source file is part of the Swift open source project // // Copyright (c) 2015-2023 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// import Basics import Foundation import PackageGraph import PackageLoading import TSCUtility @_spi(SwiftPMInternal) import PackageModel @_spi(SwiftPMInternal) import SPMBuildCore #if USE_IMPL_ONLY_IMPORTS @_implementationOnly import DriverSupport #else import DriverSupport #endif import struct TSCBasic.ByteString @available(*, deprecated, renamed: "SwiftModuleBuildDescription") public typealias SwiftTargetBuildDescription = SwiftModuleBuildDescription /// Build description for a Swift module. public final class SwiftModuleBuildDescription { /// The package this target belongs to. public let package: ResolvedPackage /// The target described by this target. public let target: ResolvedModule private let swiftTarget: SwiftModule /// The tools version of the package that declared the target. This can /// can be used to conditionalize semantically significant changes in how /// a target is built. public let toolsVersion: ToolsVersion /// The build parameters for this target. let buildParameters: BuildParameters /// The destination for while this module is built. public var destination: BuildParameters.Destination { self.buildParameters.destination } /// The build parameters for the macro dependencies of this target. let macroBuildParameters: BuildParameters /// Path to the temporary directory for this target. let tempsPath: AbsolutePath /// The directory containing derived sources of this target. /// /// These are the source files generated during the build. private var derivedSources: Sources /// These are the source files derived from plugins. private var pluginDerivedSources: Sources /// These are the resource files derived from plugins. private var pluginDerivedResources: [Resource] /// Path to the bundle generated for this module (if any). var bundlePath: AbsolutePath? { if let bundleName = target.underlying.potentialBundleName, needsResourceBundle { let suffix = self.buildParameters.suffix return self.buildParameters.bundlePath(named: bundleName + suffix) } else { return nil } } private var needsResourceBundle: Bool { return resources.filter { $0.rule != .embedInCode }.isEmpty == false } var resourceFilesToEmbed: [AbsolutePath] { return resources.filter { $0.rule == .embedInCode }.map { $0.path } } /// The path to Swift source file embedding resource contents if needed. private(set) var resourcesEmbeddingSource: AbsolutePath? /// The list of all source files in the target, including the derived ones. public var sources: [AbsolutePath] { self.target.sources.paths + self.derivedSources.paths + self.pluginDerivedSources.paths } public var sourcesFileListPath: AbsolutePath { self.tempsPath.appending(component: "sources") } /// The list of all resource files in the target, including the derived ones. public var resources: [Resource] { self.target.underlying.resources + self.pluginDerivedResources } /// The list of files in the target that were marked as ignored. public var ignored: [AbsolutePath] { self.target.underlying.ignored } /// The list of other kinds of files in the target. public var others: [AbsolutePath] { self.target.underlying.others } /// The objects in this target, containing either machine code or bitcode /// depending on the build parameters used. public var objects: [AbsolutePath] { get throws { let relativeSources = self.target.sources.relativePaths + self.derivedSources.relativePaths + self.pluginDerivedSources.relativePaths let ltoEnabled = self.buildParameters.linkingParameters.linkTimeOptimizationMode != nil let objectFileExtension = ltoEnabled ? "bc" : "o" return try relativeSources.map { try AbsolutePath( validating: "\($0.basename).\(objectFileExtension)", relativeTo: self.tempsPath) } } } var modulesPath: AbsolutePath { let suffix = self.buildParameters.suffix return BuildOperation.buildProductsPath(for: self.buildParameters).appending(component: "Modules\(suffix)") } /// The path to the swiftmodule file after compilation. public var moduleOutputPath: AbsolutePath { // note: needs to be public because of sourcekit-lsp // If we're an executable and we're not allowing test targets to link against us, we hide the module. let triple = buildParameters.triple let allowLinkingAgainstExecutables = [.coff, .macho, .elf].contains(triple.objectFormat) && self.toolsVersion >= .v5_5 let dirPath = (target.type == .executable && !allowLinkingAgainstExecutables) ? self.tempsPath : self.modulesPath return dirPath.appending(component: "\(self.target.c99name).swiftmodule") } /// The path to the wrapped swift module which is created using the modulewrap tool. This is required /// for supporting debugging on non-Darwin platforms (On Darwin, we just pass the swiftmodule to the linker /// using the `-add_ast_path` flag). var wrappedModuleOutputPath: AbsolutePath { self.tempsPath.appending(component: self.target.c99name + ".swiftmodule.o") } /// The path to the swiftinterface file after compilation. var parseableModuleInterfaceOutputPath: AbsolutePath { self.modulesPath.appending(component: self.target.c99name + ".swiftinterface") } /// Path to the resource Info.plist file, if generated. public private(set) var resourceBundleInfoPlistPath: AbsolutePath? /// Paths to the binary libraries the target depends on. var libraryBinaryPaths: Set = [] /// Path to Windows DLLs that ship with binary libraries that need to be copied /// over to the build output directory so executables that use them can find them. var windowsDLLBinaryPaths: Set = [] /// Any addition flags to be added. These flags are expected to be computed during build planning. var additionalFlags: [String] = [] /// Describes the purpose of a test target, including any special roles such as containing a list of discovered /// tests or serving as the manifest target which contains the main entry point. public enum TestTargetRole { /// An ordinary test target, defined explicitly in a package, containing test code. case `default` /// A test target which was synthesized automatically, containing a list of discovered tests /// from `plain` test targets. case discovery /// A test target which was either synthesized automatically and contains an entry point file configured to run /// all discovered tests, or contains a custom entry point file. In the latter case, the custom entry point file /// may have been discovered in the package automatically (e.g. `XCTMain.swift`) or may have been provided /// explicitly via a CLI flag. case entryPoint(isSynthesized: Bool) } public let testTargetRole: TestTargetRole? /// If this target is a test target. public var isTestTarget: Bool { self.testTargetRole != nil } /// True if this module needs to be parsed as a library based on the target type and the configuration /// of the source code var needsToBeParsedAsLibrary: Bool { if buildParameters.sanitizers.sanitizers.contains(.fuzzer) { return true } switch self.target.type { case .library, .test: return true case .executable, .snippet, .macro: // This deactivates heuristics in the Swift compiler that treats single-file modules and source files // named "main.swift" specially w.r.t. whether they can have an entry point. // // See https://bugs.swift.org/browse/SR-14488 for discussion about improvements so that SwiftPM can // convey the intent to build an executable module to the compiler regardless of the number of files // in the module or their names. if self.toolsVersion < .v5_5 || self.sources.count != 1 { return false } // looking into the file content to see if it is using the @main annotation which requires parse-as-library return (try? containsAtMain(fileSystem: self.fileSystem, path: self.sources[0])) ?? false default: return false } } /// The filesystem to operate on. let fileSystem: FileSystem /// The modulemap file for this target, if any. private(set) var moduleMap: AbsolutePath? /// The results of applying any build tool plugins to this target. public let buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] /// The results of running any prebuild commands for this target. public let prebuildCommandResults: [CommandPluginResult] public var requiredMacros: [ResolvedModule] { get throws { try self.target.recursiveModuleDependencies().filter { $0.type == .macro } } } /// ObservabilityScope with which to emit diagnostics private let observabilityScope: ObservabilityScope /// Whether or not to generate code for test observation. private let shouldGenerateTestObservation: Bool /// Whether to disable sandboxing (e.g. for macros). private let shouldDisableSandbox: Bool /// Whether to add -static on Windows to reduce symbol exports public var isWindowsStatic: Bool /// Create a new target description with target and build parameters. init( package: ResolvedPackage, target: ResolvedModule, toolsVersion: ToolsVersion, additionalFileRules: [FileRuleDescription] = [], buildParameters: BuildParameters, macroBuildParameters: BuildParameters, buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [], prebuildCommandResults: [CommandPluginResult] = [], testTargetRole: TestTargetRole? = nil, shouldGenerateTestObservation: Bool = false, shouldDisableSandbox: Bool, fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws { // It's an error to contain mixed language source files. if target.sources.containsMixedLanguage { throw StringError("\(target.name): mixed language source files in Swift targets are not supported by the native build system.") } // Bridging headers are only supported by the Swift Build backend. if !buildParameters.createScope(for: target).evaluate(.SWIFT_OBJC_BRIDGING_HEADER).isEmpty { throw StringError("\(target.name): bridging headers are not supported when using the native build system.") } guard let swiftTarget = target.underlying as? SwiftModule else { throw InternalError("underlying target type mismatch \(target)") } self.swiftTarget = swiftTarget self.package = package self.target = target self.toolsVersion = toolsVersion self.buildParameters = buildParameters self.macroBuildParameters = macroBuildParameters // Unless mentioned explicitly, use the target type to determine if this is a test target. if let testTargetRole { self.testTargetRole = testTargetRole } else if target.type == .test { self.testTargetRole = .default } else { self.testTargetRole = nil } self.tempsPath = target.tempsPath(self.buildParameters) self.derivedSources = Sources(paths: [], root: self.tempsPath.appending("DerivedSources")) self.buildToolPluginInvocationResults = buildToolPluginInvocationResults self.prebuildCommandResults = prebuildCommandResults self.shouldGenerateTestObservation = shouldGenerateTestObservation self.shouldDisableSandbox = shouldDisableSandbox self.fileSystem = fileSystem self.observabilityScope = observabilityScope let pluginGeneratedFiles = ModulesGraph.computePluginGeneratedFiles( target: target, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, buildParameters: self.buildParameters, buildToolPluginInvocationResults: buildToolPluginInvocationResults, prebuildCommandResults: prebuildCommandResults, observabilityScope: observabilityScope ) self.pluginDerivedSources = Sources( paths: pluginGeneratedFiles.sources.map(\.self), root: buildParameters.dataPath ) self.pluginDerivedResources = pluginGeneratedFiles.resources.values.map(\.self) let nonSwiftDerivedSources = pluginDerivedSources.relativePaths.filter({ $0.extension != "swift" }) if !nonSwiftDerivedSources.isEmpty { for source in nonSwiftDerivedSources { let absPath = pluginDerivedSources.root.appending(source) observabilityScope.emit(warning: "Only Swift is supported for generated plugin source files: \(absPath)") } self.pluginDerivedSources.relativePaths = self.pluginDerivedSources.relativePaths.filter({ $0.extension == "swift" }) } for absPath in pluginGeneratedFiles.headers { observabilityScope.emit(warning: "Module maps generated by plugins are not supported in Swift modules: \(absPath)") } for absPath in pluginGeneratedFiles.moduleMaps { observabilityScope.emit(warning: "API Notes generated by plugins are not supported in Swift modules: \(absPath)") } for absPath in pluginGeneratedFiles.apiNotes { observabilityScope.emit(warning: "API Notes generated by plugins are not supported in Swift modules: \(absPath)") } // default to -static on Windows self.isWindowsStatic = buildParameters.triple.isWindows() if self.shouldEmitObjCCompatibilityHeader { self.moduleMap = try self.generateModuleMap() } // Do nothing if we're not generating a bundle. if self.bundlePath != nil { try self.generateResourceAccessor() let infoPlistPath = self.tempsPath.appending("Info.plist") if try generateResourceInfoPlist(fileSystem: self.fileSystem, target: target, path: infoPlistPath) { self.resourceBundleInfoPlistPath = infoPlistPath } } if !resourceFilesToEmbed.isEmpty { resourcesEmbeddingSource = try addResourceEmbeddingSource() } try self.generateTestObservation() } private func generateTestObservation() throws { guard target.type == .test else { return } let subpath = try RelativePath(validating: "test_observation.swift") let path = self.derivedSources.root.appending(subpath) guard shouldGenerateTestObservation else { _ = try? fileSystem.removeFileTree(path) return } guard self.buildParameters.triple.isDarwin() && self.buildParameters.testingParameters.experimentalTestOutput else { return } let content = generateTestObservationCode(buildParameters: self.buildParameters) // FIXME: We should generate this file during the actual build. self.derivedSources.relativePaths.append(subpath) try self.fileSystem.writeIfChanged(path: path, string: content) } private func addResourceEmbeddingSource() throws -> AbsolutePath { let subpath = try RelativePath(validating: "embedded_resources.swift") self.derivedSources.relativePaths.append(subpath) return self.derivedSources.root.appending(subpath) } /// Generate the resource bundle accessor, if appropriate. private func generateResourceAccessor() throws { // Do nothing if we're not generating a bundle. guard let bundlePath else { return } let mainPathSubstitution: String if self.buildParameters.triple.isWASI() { // We prefer compile-time evaluation of the bundle path here for WASI. There's no benefit in evaluating this // at runtime, especially as `Bundle` support in WASI Foundation is partial. We expect all resource paths to // evaluate to `/\(resourceBundleName)/\(resourcePath)`, which allows us to pass this path to JS APIs like // `fetch` directly, or to ` [String] { var args = [String]() #if BUILD_MACROS_AS_DYLIBS try self.requiredMacros.forEach { macro in args += [ "-Xfrontend", "-load-plugin-library", "-Xfrontend", BuildOperation.macroBinaryPath(for: macro, parameters: macroBuildParameters).pathString ] } #else let macroModules = try self.requiredMacros try macroModules.forEach { macro in let executablePath = try BuildOperation.macroBinaryPath(for: macro, parameters: macroBuildParameters).pathString args += ["-Xfrontend", "-load-plugin-executable", "-Xfrontend", "\(executablePath)#\(macro.c99name)"] } #endif if self.shouldDisableSandbox { let toolchainSupportsDisablingSandbox = DriverSupport.checkSupportedFrontendFlags( flags: ["-disable-sandbox"], toolchain: self.buildParameters.toolchain, fileSystem: fileSystem ) if toolchainSupportsDisablingSandbox { args += ["-disable-sandbox"] } else { // If there's at least one macro being used, we warn about our inability to disable sandboxing. if !macroModules.isEmpty { observabilityScope.emit(warning: "cannot disable sandboxing for Swift compilation because the selected toolchain does not support it") } } } return args } /// The arguments needed to compile this target. public func compileArguments() throws -> [String] { var args = [String]() args += try self.buildParameters.tripleArgs(for: self.target) // pass `-v` during verbose builds. if self.buildParameters.outputParameters.isVerbose { args += ["-v"] } if self.useWholeModuleOptimization { args.append("-whole-module-optimization") args.append("-num-threads") args.append(String(ProcessInfo.processInfo.activeProcessorCount)) } else { args.append("-incremental") args.append("-enable-batch-mode") } args += ["-serialize-diagnostics"] args += self.buildParameters.indexStoreArguments(for: self.target) args += self.optimizationArguments args += self.testingArguments if self.buildParameters.driverParameters.emitOptimizationRecord { args += ["-save-optimization-record"] args += ["-g"] } args += ["-j\(self.buildParameters.workers)"] args += self.activeCompilationConditions args += self.additionalFlags args += try self.moduleCacheArgs args += self.stdlibArguments args += self.buildParameters.sanitizers.compileSwiftFlags() args += ["-parseable-output"] // If we're compiling the main module of an executable other than the one that // implements a test suite, and if the package tools version indicates that we // should, we rename the `_main` entry point to `__main`. // // This will allow tests to link against the module without any conflicts. And // when we link the executable, we will ask the linker to rename the entry point // symbol to just `_main` again (or if the linker doesn't support it, we'll // generate a source containing a redirect). if (self.target.underlying as? SwiftModule)?.supportsTestableExecutablesFeature == true && !self.isTestTarget && self.toolsVersion >= .v5_5 { // We only do this if the linker supports it, as indicated by whether we // can construct the linker flags. In the future we will use a generated // code stub for the cases in which the linker doesn't support it, so that // we can rename the symbol unconditionally. // No `-` for these flags because the set of Strings in driver.supportedFrontendFlags do // not have a leading `-` if self.buildParameters.driverParameters.canRenameEntrypointFunctionName, self.buildParameters.linkerFlagsForRenamingMainFunction(of: self.target) != nil { args += ["-Xfrontend", "-entry-point-function-name", "-Xfrontend", "\(self.target.c99name)_main"] } } // If the target needs to be parsed without any special semantics involving "main.swift", do so now. if self.needsToBeParsedAsLibrary { args += ["-parse-as-library"] } // Add -static to reduce symbol export count if self.isWindowsStatic { args += ["-static"] } // Only add the build path to the framework search path if there are binary frameworks to link against. if !self.libraryBinaryPaths.isEmpty { args += ["-F", BuildOperation.buildProductsPath(for: self.buildParameters).pathString] } // Emit the ObjC compatibility header if enabled. if self.shouldEmitObjCCompatibilityHeader { args += ["-emit-objc-header", "-emit-objc-header-path", self.objCompatibilityHeaderPath.pathString] } // Add arguments needed for code coverage if it is enabled. if self.buildParameters.testingParameters.enableCodeCoverage { args += ["-profile-coverage-mapping", "-profile-generate"] } // Add arguments to colorize output if stdout is tty if self.buildParameters.outputParameters.isColorized { args += ["-color-diagnostics"] } args += try self.cxxInteroperabilityModeArguments( propagateFromCurrentModuleOtherSwiftFlags: false) // Add arguments from declared build settings. args += try self.buildSettingsFlags() // Add the output for the `.swiftinterface`, if requested or if library evolution has been enabled some other // way. if self.buildParameters.driverParameters.enableParseableModuleInterfaces || args.contains("-enable-library-evolution") { args += ["-emit-module-interface-path", self.parseableModuleInterfaceOutputPath.pathString] } switch self.buildParameters.prepareForIndexing { case .off: break case .on: args += ["-Xfrontend", "-experimental-lazy-typecheck",] if !args.contains("-enable-testing") { // enable-testing needs the non-exportable-decls args += ["-Xfrontend", "-experimental-skip-non-exportable-decls"] } fallthrough case .noLazy: args += [ "-Xfrontend", "-experimental-skip-all-function-bodies", "-Xfrontend", "-experimental-allow-module-with-compiler-errors", "-Xfrontend", "-empty-abi-descriptor" ] } args += self.buildParameters.toolchain.extraFlags.swiftCompilerFlags.rawFlags // User arguments (from -Xswiftc) should follow generated arguments to allow user overrides args += self.buildParameters.flags.swiftCompilerFlags.rawFlags args += self.buildParameters.toolchain.extraFlags.cCompilerFlags.rawFlags.asSwiftcCCompilerFlags() // User arguments (from -Xcc) should follow generated arguments to allow user overrides args += self.buildParameters.flags.cCompilerFlags.rawFlags.asSwiftcCCompilerFlags() // TODO: Pass -Xcxx flags to swiftc (#6491) // Uncomment when downstream support arrives. // args += self.buildParameters.toolchain.extraFlags.cxxCompilerFlags.rawFlags.asSwiftcCXXCompilerFlags() // // User arguments (from -Xcxx) should follow generated arguments to allow user overrides // args += self.buildParameters.flags.cxxCompilerFlags.rawFlags.asSwiftcCXXCompilerFlags() // Enable the correct LTO mode if requested. switch self.buildParameters.linkingParameters.linkTimeOptimizationMode { case nil: break case .full: args += ["-lto=llvm-full"] case .thin: args += ["-lto=llvm-thin"] } // Pass default include paths from the toolchain. for includeSearchPath in self.buildParameters.toolchain.includeSearchPaths { args += ["-I", includeSearchPath.pathString] } // suppress warnings if the package is remote if self.package.isRemote { args = try WarningControlFlags.filterSwiftWarningControlFlags(args) args += ["-suppress-warnings"] } // Pass `-user-module-version` for versioned packages that aren't pre-releases. if let version = package.manifest.version, version.prereleaseIdentifiers.isEmpty && version.buildMetadataIdentifiers.isEmpty && toolsVersion >= .v6_0 { args += ["-user-module-version", version.description] } args += self.package.packageNameArgument( target: self.target, isPackageNameSupported: self.buildParameters.driverParameters.isPackageAccessModifierSupported ) args += try self.macroArguments() // rdar://117578677 // Pass -fno-omit-frame-pointer to support backtraces // this can be removed once the backtracer uses DWARF instead of frame pointers if let omitFramePointers = self.buildParameters.debuggingParameters.omitFramePointers { if omitFramePointers { args += ["-Xcc", "-fomit-frame-pointer"] } else { args += ["-Xcc", "-fno-omit-frame-pointer"] } } return args } /// Determines the arguments needed to run `swift-symbolgraph-extract` for /// this module. package func symbolGraphExtractArguments() throws -> [String] { var args = [String]() args += ["-module-name", self.target.c99name] args += try self.buildParameters.tripleArgs(for: self.target) args += ["-module-cache-path", try self.buildParameters.moduleCache.pathString] args += try self.cxxInteroperabilityModeArguments( propagateFromCurrentModuleOtherSwiftFlags: true) args += self.buildParameters.toolchain.extraFlags.swiftCompilerFlags.rawFlags // Include search paths determined during planning args += self.additionalFlags // FIXME: only pass paths to the actual dependencies of the module // Include search paths for swift module dependencies. args += ["-I", self.modulesPath.pathString] // FIXME: Only include valid args // This condition should instead only include args which are known to be // compatible instead of filtering out specific unknown args. // // swift-symbolgraph-extract does not support parsing `-use-ld=lld` and // will silently error failing the operation. args = args.filter { !$0.starts(with: "-use-ld=") } return args } // FIXME: this function should operation on a strongly typed buildSetting // Move logic from PackageBuilder here. /// Determines the arguments needed for cxx interop for this module. func cxxInteroperabilityModeArguments( // FIXME: Remove argument // This argument is added as a stop gap to support generating arguments // for tools which currently don't leverage "OTHER_SWIFT_FLAGS". In the // fullness of time this function should operate on a strongly typed // "interopMode" property of SwiftTargetBuildDescription instead of // digging through "OTHER_SWIFT_FLAGS" manually. propagateFromCurrentModuleOtherSwiftFlags: Bool ) throws -> [String] { func cxxInteroperabilityModeAndStandard( for module: ResolvedModule ) -> [String]? { let scope = self.buildParameters.createScope(for: module) let flags = scope.evaluate(.OTHER_SWIFT_FLAGS) let mode = flags.first { $0.hasPrefix("-cxx-interoperability-mode=") } guard let mode else { return nil } // FIXME: Use a stored self.cxxLanguageStandard property // It definitely should _never_ reach back into the manifest if let cxxStandard = self.package.manifest.cxxLanguageStandard { return [mode, "-Xcc", "-std=\(cxxStandard)"] } else { return [mode] } } if propagateFromCurrentModuleOtherSwiftFlags { // Look for cxx interop mode in the current module, if set exit early, // the flag is already present. if let args = cxxInteroperabilityModeAndStandard(for: self.target) { return args } } // Implicitly propagate cxx interop flags for generated test targets. // If the current module doesn't have cxx interop mode set, search // through the module's dependencies looking for the a module that // enables cxx interop and copy it's flag. switch self.testTargetRole { case .discovery, .entryPoint: for module in try self.target.recursiveModuleDependencies() { if let args = cxxInteroperabilityModeAndStandard(for: module) { return args } } default: break } return [] } /// - Parameters: /// - scanInvocation: When `true`, omit the side-effect producing arguments such as emitting a module or /// supplementary outputs. /// - writeOutputFileMap: When `false`, we assume that an output file map for this command line already exists at /// the expected location on disk. This is intended for SourceKit-LSP to get build settings for a file without /// writing out an output file map as a side effect. We expect that preparation of the module has already /// created the output file map. public func emitCommandLine(scanInvocation: Bool = false, writeOutputFileMap: Bool = true) throws -> [String] { var result: [String] = [] result.append(self.buildParameters.toolchain.swiftCompilerPath.pathString) result.append("-module-name") result.append(self.target.c99name) result.append( contentsOf: self.package.packageNameArgument( target: self.target, isPackageNameSupported: self.buildParameters.driverParameters.isPackageAccessModifierSupported ) ) if !scanInvocation { result.append("-emit-dependencies") // FIXME: Do we always have a module? result.append("-emit-module") result.append("-emit-module-path") result.append(self.moduleOutputPath.pathString) result.append("-output-file-map") let outputFileMapPath = self.tempsPath.appending("output-file-map.json") if writeOutputFileMap { // FIXME: Eliminate side effect. try self.writeOutputFileMap(to: outputFileMapPath) } result.append(outputFileMapPath.pathString) } result.append("-c") result.append(contentsOf: self.sources.map(\.pathString)) result.append("-I") result.append(self.modulesPath.pathString) result += try self.compileArguments() return result } /// Returns true if ObjC compatibility header should be emitted. private var shouldEmitObjCCompatibilityHeader: Bool { self.target.type == .library } func writeOutputFileMap(to path: AbsolutePath) throws { let primateDepsPath = self.tempsPath.appending("primary.swiftdeps") var content = #""" { "": { """# if self.useWholeModuleOptimization { let moduleName = self.target.c99name content += #""" "dependencies": "\#( self.tempsPath.appending(component: moduleName + ".d")._nativePathString(escaped: true) )", """# // FIXME: Need to record this deps file for processing it later. content += #""" "object": "\#( self.tempsPath.appending(component: moduleName + ".o")._nativePathString(escaped: true) )", """# if self.buildParameters.driverParameters.emitSILFiles { let silPath = try self.silOutputPath() content += #""" "sil": "\#(silPath._nativePathString(escaped: true))", """# } if !self.useParallelWMO { if self.buildParameters.driverParameters.emitIRFiles { let irPath = try self.irOutputPath() content += #""" "llvm-ir": "\#(irPath._nativePathString(escaped: true))", """# } if self.buildParameters.driverParameters.emitOptimizationRecord { content += #""" "yaml-opt-record": "\#(try self.optimizationRecordOutputPath()._nativePathString(escaped: true))", """# } } } content += #""" "swift-dependencies": "\#(primateDepsPath._nativePathString(escaped: true))" }, """# // Write out the entries for each source file. let sources = self.sources let objects = try self.objects let ltoEnabled = self.buildParameters.linkingParameters.linkTimeOptimizationMode != nil let objectKey = ltoEnabled ? "llvm-bc" : "object" for idx in 0.. AbsolutePath { let outputDir = self.buildParameters.driverParameters.silOutputDirectory ?? self.tempsPath let baseName: String if self.useWholeModuleOptimization { baseName = self.target.c99name } else { guard let source = source else { throw InternalError("Source file required for SIL output path in non-WMO mode") } baseName = source.basenameWithoutExt } return outputDir.appending(component: baseName + ".sil") } private func irOutputPath(for source: AbsolutePath? = nil) throws -> AbsolutePath { let outputDir = self.buildParameters.driverParameters.irOutputDirectory ?? self.tempsPath let baseName: String if let source { baseName = source.basenameWithoutExt } else if self.useWholeModuleOptimization { baseName = self.target.c99name } else { throw InternalError("Source file required for IR output path in non-WMO mode") } return outputDir.appending(component: baseName + ".ll") } private func optimizationRecordOutputPath(for source: AbsolutePath? = nil) throws -> AbsolutePath { let outputDir = self.buildParameters.driverParameters.optimizationRecordDirectory ?? self.tempsPath let baseName: String if let source { baseName = source.basenameWithoutExt } else if self.useWholeModuleOptimization { baseName = self.target.c99name } else { throw InternalError("Source file required for optimization record path in non-WMO mode") } return outputDir.appending(component: baseName + ".opt.yaml") } /// Directory for the the compatibility header and module map generated for this target. /// The whole directory should be usable as a header search path. private var compatibilityHeaderDirectory: AbsolutePath { tempsPath.appending("include") } /// Generates the module map for the Swift target and returns its path. private func generateModuleMap() throws -> AbsolutePath { let path = self.compatibilityHeaderDirectory.appending(component: moduleMapFilename) let bytes = ByteString( #""" module \#(self.target.c99name) { header "\#(self.objCompatibilityHeaderPath.pathString)" } """#.utf8 ) // Return early if the contents are identical. if self.fileSystem.isFile(path), try self.fileSystem.readFileContents(path) == bytes { return path } try self.fileSystem.createDirectory(path.parentDirectory, recursive: true) try self.fileSystem.writeFileContents(path, bytes: bytes) return path } /// Returns the path to the ObjC compatibility header for this Swift target. var objCompatibilityHeaderPath: AbsolutePath { self.compatibilityHeaderDirectory.appending("\(self.target.name)-Swift.h") } /// Returns the build flags from the declared build settings. private func buildSettingsFlags() throws -> [String] { let scope = self.buildParameters.createScope(for: self.target) var flags: [String] = [] // A custom swift version. flags += scope.evaluate(.SWIFT_VERSION).flatMap { ["-swift-version", $0] } // Swift defines. let swiftDefines = scope.evaluate(.SWIFT_ACTIVE_COMPILATION_CONDITIONS) flags += swiftDefines.map { "-D" + $0 } // Other Swift flags. flags += scope.evaluate(.OTHER_SWIFT_FLAGS) // Add C flags by prefixing them with -Xcc. // // C defines. let cDefines = scope.evaluate(.GCC_PREPROCESSOR_DEFINITIONS) flags += cDefines.flatMap { ["-Xcc", "-D" + $0] } // Header search paths. let headerSearchPaths = scope.evaluate(.HEADER_SEARCH_PATHS) flags += try headerSearchPaths.flatMap { path in ["-Xcc", "-I\(try AbsolutePath(validating: path, relativeTo: target.sources.root).pathString)"] } // Other C flags. flags += scope.evaluate(.OTHER_CFLAGS).flatMap { ["-Xcc", $0] } // Prebuilt include paths flags += scope.evaluate(.PREBUILT_INCLUDE_PATHS).flatMap { ["-I", $0] } // Include path for the toolchain's copy of SwiftSyntax. #if BUILD_MACROS_AS_DYLIBS if module.type == .macro { flags += try ["-I", self.defaultBuildParameters.toolchain.hostLibDir.pathString] } #endif return flags } /// A list of compilation conditions to enable for conditional compilation expressions. private var activeCompilationConditions: [String] { var compilationConditions = ["-DSWIFT_PACKAGE"] switch self.buildParameters.configuration { case .debug: compilationConditions += ["-DDEBUG"] case .release: break } if bundlePath != nil { compilationConditions += ["-DSWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE"] } else { compilationConditions += ["-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE"] } return compilationConditions } /// Optimization arguments according to the build configuration. private var optimizationArguments: [String] { switch self.buildParameters.configuration { case .debug: return ["-Onone"] case .release: return ["-O"] } } /// Testing arguments according to the build configuration. private var testingArguments: [String] { if self.isTestTarget { // test targets must be built with -enable-testing // since its required for test discovery (the non objective-c reflection kind) var result = ["-enable-testing"] // Test targets need to enable cross-import overlays because Swift // Testing cannot directly link to most other modules and needs to // provide API that works with e.g. Foundation. (Developers can // override this flag by passing -disable-cross-import-overlays.) result += ["-Xfrontend", "-enable-cross-import-overlays"] return result } else if self.buildParameters.enableTestability { return ["-enable-testing"] } else { return [] } } /// Module cache arguments. private var moduleCacheArgs: [String] { get throws { ["-module-cache-path", try self.buildParameters.moduleCache.pathString] } } private var stdlibArguments: [String] { var arguments: [String] = [] let isLinkingStaticStdlib = self.buildParameters.linkingParameters.shouldLinkStaticSwiftStdlib && self.buildParameters.triple.isSupportingStaticStdlib if isLinkingStaticStdlib { arguments += ["-static-stdlib"] } if let resourcesPath = self.buildParameters.toolchain.swiftResourcesPath(isStatic: isLinkingStaticStdlib) { arguments += ["-resource-dir", "\(resourcesPath)"] } return arguments } package var isEmbeddedSwift: Bool { // If the target explicitly declares that it should build with Embedded // Swift, then true. let buildSettings = self.target.underlying.buildSettingsDescription let swiftSettings = buildSettings.swiftSettings.map(\.kind) for case .enableExperimentalFeature("Embedded") in swiftSettings { return true } // Otherwise dig through flags looking for -enable-experimental-feature // Embedded. This is needed to handle Embedded being set via: // - unsafeFlags // - swift build cli flags // - toolset flags let queryFlags = ["-enable-experimental-feature", "Embedded"] let toolchainFlags = self.buildParameters.toolchain.extraFlags.swiftCompilerFlags if toolchainFlags.rawFlags.contains(queryFlags) { return true } let generalFlags = self.buildParameters.flags.swiftCompilerFlags if generalFlags.rawFlags.contains(queryFlags) { return true } return false } /// Whether to build Swift code with whole module optimization (WMO) /// enabled. package var useWholeModuleOptimization: Bool { if self.isEmbeddedSwift { return true } switch self.buildParameters.configuration { case .debug: return false case .release: return true } } /// Whether the Swift driver will use multi-threaded WMO. /// With only one source file, the driver treats it as single-threaded. var useParallelWMO: Bool { ProcessInfo.processInfo.activeProcessorCount > 1 && self.sources.count > 1 } } extension SwiftModuleBuildDescription { package func dependencies( using plan: BuildPlan ) -> [ModuleBuildDescription.Dependency] { ModuleBuildDescription.swift(self).dependencies(using: plan) } package func recursiveLinkDependencies( using plan: BuildPlan ) -> [ModuleBuildDescription.Dependency] { ModuleBuildDescription.swift(self).recursiveLinkDependencies(using: plan) } package func recursiveDependencies( using plan: BuildPlan ) -> [ModuleBuildDescription.Dependency] { ModuleBuildDescription.swift(self).recursiveDependencies(using: plan) } } extension SwiftModuleBuildDescription { package var diagnosticFiles: [AbsolutePath] { // WMO builds have a single frontend invocation and produce a single // diagnostic file named after the module. if self.useWholeModuleOptimization { return [ self.diagnosticFile(name: self.target.name) ] } return self.sources.map(self.diagnosticFile(sourceFile:)) } private func diagnosticFile(name: String) -> AbsolutePath { self.tempsPath.appending(component: "\(name).dia") } private func diagnosticFile(sourceFile: AbsolutePath) -> AbsolutePath { self.diagnosticFile(name: sourceFile.basenameWithoutExt) } }