diff --git a/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift b/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift index 4116e970b14..50deff7c208 100644 --- a/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift @@ -97,6 +97,12 @@ public final class ClangModuleBuildDescription { /// These are the resource files derived from plugins. private var pluginDerivedResources: [Resource] + /// Header paths form plugins + private var pluginDerivedHeaderPaths: [AbsolutePath] + + /// Public header paths from plugins + public private(set) var pluginDerivedPublicHeaderPaths: [AbsolutePath] + /// Path to the resource accessor header file, if generated. public private(set) var resourceAccessorHeaderFile: AbsolutePath? @@ -152,11 +158,13 @@ public final class ClangModuleBuildDescription { self.tempsPath = target.tempsPath(buildParameters) self.derivedSources = Sources(paths: [], root: tempsPath.appending("DerivedSources")) + var generatedModuleMap: AbsolutePath? = nil + // We did not use to apply package plugins to C-family targets in prior tools-versions, this preserves the behavior. if toolsVersion >= .v5_9 { self.buildToolPluginInvocationResults = buildToolPluginInvocationResults - (self.pluginDerivedSources, self.pluginDerivedResources) = ModulesGraph.computePluginGeneratedFiles( + let pluginGeneratedFiles = ModulesGraph.computePluginGeneratedFiles( target: target, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, @@ -165,10 +173,26 @@ public final class ClangModuleBuildDescription { prebuildCommandResults: prebuildCommandResults, observabilityScope: observabilityScope ) + + self.pluginDerivedSources = Sources( + paths: pluginGeneratedFiles.sources.map(\.self), + root: buildParameters.dataPath + ) + self.pluginDerivedResources = pluginGeneratedFiles.resources.values.map(\.self) + self.pluginDerivedHeaderPaths = pluginGeneratedFiles.headerPaths.map(\.self) + self.pluginDerivedPublicHeaderPaths = pluginGeneratedFiles.publicHeaderPaths.map(\.self) + + if pluginGeneratedFiles.moduleMaps.count > 1 { + observabilityScope.emit(warning: "Only one module map generated by plugins is supported at this time: \(clangTarget.name)") + } else if let moduleMap = pluginGeneratedFiles.moduleMaps.first { + generatedModuleMap = moduleMap + } } else { self.buildToolPluginInvocationResults = [] self.pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath) self.pluginDerivedResources = [] + self.pluginDerivedHeaderPaths = [] + self.pluginDerivedPublicHeaderPaths = [] } // Try computing modulemap path for a C library. This also creates the file in the file system, if needed. @@ -177,6 +201,9 @@ public final class ClangModuleBuildDescription { if case .custom(let path) = clangTarget.moduleMapType { self.moduleMap = path } + else if let generatedModuleMap { + self.moduleMap = generatedModuleMap + } // If a generated module map is needed, generate one now in our temporary directory. else if let generatedModuleMapType = clangTarget.moduleMapType.generatedModuleMapType { let path = tempsPath.appending(component: moduleMapFilename) diff --git a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift index 9a59bf3fff2..33db3086a94 100644 --- a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift @@ -299,7 +299,7 @@ public final class SwiftModuleBuildDescription { self.fileSystem = fileSystem self.observabilityScope = observabilityScope - (self.pluginDerivedSources, self.pluginDerivedResources) = ModulesGraph.computePluginGeneratedFiles( + let pluginGeneratedFiles = ModulesGraph.computePluginGeneratedFiles( target: target, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, @@ -308,6 +308,30 @@ public final class SwiftModuleBuildDescription { prebuildCommandResults: prebuildCommandResults, observabilityScope: observabilityScope ) + self.pluginDerivedSources = Sources( + paths: pluginGeneratedFiles.sources.map(\.self), + root: buildParameters.dataPath + ) + self.pluginDerivedResources = pluginGeneratedFiles.resources.values.map(\.self) + + let nonSwiftSources = pluginDerivedSources.relativePaths.filter({ $0.extension != "swift" }) + if !nonSwiftSources.isEmpty { + for source in nonSwiftSources { + let absPath = pluginDerivedSources.root.appending(source) + observabilityScope.emit(warning: "Only Swift is supported for generated plugin source files at this time: \(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 at this time: \(absPath)") + } + for absPath in pluginGeneratedFiles.moduleMaps { + observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)") + } + for absPath in pluginGeneratedFiles.apiNotes { + observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)") + } // default to -static on Windows self.isWindowsStatic = buildParameters.triple.isWindows() diff --git a/Sources/Build/BuildPlan/BuildPlan+Clang.swift b/Sources/Build/BuildPlan/BuildPlan+Clang.swift index 01306cdcf7d..d1730686d74 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Clang.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Clang.swift @@ -39,8 +39,10 @@ extension BuildPlan { // Setup search paths for C dependencies: clangTarget.additionalFlags += ["-I", target.includeDir.pathString] - // Add the modulemap of the dependency if it has one. if case let .clang(dependencyTargetDescription)? = description { + // Add in public generated header paths + clangTarget.additionalFlags += dependencyTargetDescription.pluginDerivedPublicHeaderPaths.flatMap { ["-I", $0.pathString] } + // Add the modulemap of the dependency if it has one. if let moduleMap = dependencyTargetDescription.moduleMap { clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"] } diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift index b5acf6149d1..fd28e765317 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Swift.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift @@ -39,6 +39,9 @@ extension BuildPlan { "-Xcc", "-fmodule-map-file=\(moduleMap.pathString)", "-Xcc", "-I", "-Xcc", target.clangTarget.includeDir.pathString, ] + swiftTarget.additionalFlags += target.pluginDerivedPublicHeaderPaths.flatMap { + ["-Xcc", "-I", "-Xcc", $0.pathString] + } case let target as SystemLibraryModule: swiftTarget.additionalFlags += ["-Xcc", "-fmodule-map-file=\(target.moduleMapPath.pathString)"] swiftTarget.additionalFlags += try pkgConfig(for: target).cFlags diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index bef12b95fa8..98e5bc307ee 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -794,7 +794,7 @@ extension BuildPlan { // build. let observability = ObservabilitySystem { _, _ in } // Compute the generated files based on all results we have computed so far. - (pluginDerivedSources, pluginDerivedResources) = ModulesGraph.computePluginGeneratedFiles( + let pluginGeneratedFiles = ModulesGraph.computePluginGeneratedFiles( target: module, toolsVersion: package.manifest.toolsVersion, additionalFileRules: additionalFileRules, @@ -803,6 +803,11 @@ extension BuildPlan { prebuildCommandResults: [], observabilityScope: observability.topScope ) + pluginDerivedSources = Sources( + paths: pluginGeneratedFiles.sources.map(\.self), + root: buildParameters.dataPath + ) + pluginDerivedResources = pluginGeneratedFiles.resources.values.map(\.self) } else { pluginDerivedSources = .init(paths: [], root: package.path) pluginDerivedResources = [] diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 77169042aa0..906d63a26ec 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -988,13 +988,7 @@ public final class PackageBuilder { toolsSwiftVersion: self.toolsSwiftVersion() ) - // Compute the path to public headers directory. - let publicHeaderComponent = manifestTarget.publicHeadersPath ?? ClangModule.defaultPublicHeadersComponent - let publicHeadersPath = try potentialModule.path.appending(RelativePath(validating: publicHeaderComponent)) - guard publicHeadersPath.isDescendantOfOrEqual(to: potentialModule.path) else { - throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) - } - + // Classify the sources let sourcesBuilder = TargetSourcesBuilder( packageIdentity: self.identity, packageKind: self.manifest.packageKind, @@ -1009,6 +1003,13 @@ public final class PackageBuilder { ) let (sources, resources, headers, ignored, others) = try sourcesBuilder.run() + // Compute the path to public headers directory. + let publicHeaderComponent = manifestTarget.publicHeadersPath ?? ClangModule.defaultPublicHeadersComponent + let publicHeadersPath = try potentialModule.path.appending(RelativePath(validating: publicHeaderComponent)) + guard publicHeadersPath.isDescendantOfOrEqual(to: potentialModule.path) else { + throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) + } + // Make sure defaultLocalization is set if the target has localized resources. let hasLocalizedResources = resources.contains(where: { $0.localization != nil }) if hasLocalizedResources && self.manifest.defaultLocalization == nil { diff --git a/Sources/PackageLoading/TargetSourcesBuilder.swift b/Sources/PackageLoading/TargetSourcesBuilder.swift index e218bf3f227..33655c56680 100644 --- a/Sources/PackageLoading/TargetSourcesBuilder.swift +++ b/Sources/PackageLoading/TargetSourcesBuilder.swift @@ -306,7 +306,7 @@ public struct TargetSourcesBuilder { /// Returns the `Resource` file associated with a file and a particular rule, if there is one. private static func resource(for path: Basics.AbsolutePath, with rule: FileRuleDescription.Rule, defaultLocalization: String?, targetName: String, targetPath: Basics.AbsolutePath, observabilityScope: ObservabilityScope) -> Resource? { switch rule { - case .compile, .header, .none, .modulemap, .ignored: + case .compile, .header, .none, .modulemap, .apinotes, .ignored: return nil case .processResource: let implicitLocalization: String? = { @@ -519,14 +519,21 @@ public struct TargetSourcesBuilder { return contents } - public static func computeContents(for generatedFiles: [Basics.AbsolutePath], toolsVersion: ToolsVersion, additionalFileRules: [FileRuleDescription], defaultLocalization: String?, targetName: String, targetPath: Basics.AbsolutePath, observabilityScope: ObservabilityScope) -> (sources: [Basics.AbsolutePath], resources: [Resource]) { - var sources = [Basics.AbsolutePath]() - var resources = [Resource]() + public static func computeContents( + for generatedFiles: [Basics.AbsolutePath], + toolsVersion: ToolsVersion, + additionalFileRules: [FileRuleDescription], + defaultLocalization: String?, + targetName: String, + targetPath: Basics.AbsolutePath, + observabilityScope: ObservabilityScope) -> GeneratedFiles + { + var files = GeneratedFiles() generatedFiles.forEach { absPath in // 5.6 handled treated all generated files as sources. if toolsVersion <= .v5_6 { - sources.append(absPath) + files.sources.insert(absPath) return } @@ -539,27 +546,73 @@ public struct TargetSourcesBuilder { switch rule { case .compile: - if absPath.extension == "swift" { - sources.append(absPath) + if absPath.extension == "swift" || toolsVersion >= .v6_3 { + files.sources.insert(absPath) } else { observabilityScope.emit(warning: "Only Swift is supported for generated plugin source files at this time: \(absPath)") } case .copyResource, .processResource, .embedResourceInCode: if let resource = Self.resource(for: absPath, with: rule, defaultLocalization: defaultLocalization, targetName: targetName, targetPath: targetPath, observabilityScope: observabilityScope) { - resources.append(resource) + files.resources[resource.path] = resource } else { // If this is reached, `TargetSourcesBuilder` already emitted a diagnostic, so we can ignore this case here. } case .header: - observabilityScope.emit(warning: "Headers generated by plugins are not supported at this time: \(absPath)") + if toolsVersion >= .v6_3 { + files.headers.insert(absPath) + } else { + observabilityScope.emit(warning: "Headers generated by plugins are not supported at this time: \(absPath)") + } case .modulemap: - observabilityScope.emit(warning: "Module maps generated by plugins are not supported at this time: \(absPath)") + if toolsVersion >= .v6_3 { + files.moduleMaps.insert(absPath) + } else { + observabilityScope.emit(warning: "Module maps generated by plugins are not supported at this time: \(absPath)") + } + case .apinotes: + if toolsVersion >= .v6_3 { + files.apiNotes.insert(absPath) + } else { + observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)") + } case .ignored, .none: break } } - return (sources, resources) + return files + } +} + +public struct GeneratedFiles { + public var headerPaths: Set = [] + public var publicHeaderPaths: Set = [] + public var sources: Set + public var headers: Set + public var moduleMaps: Set + public var apiNotes: Set + public var resources: [Basics.AbsolutePath: Resource] + + public init( + sources: Set = [], + headers: Set = [], + moduleMaps: Set = [], + apiNotes: Set = [], + resources: [Basics.AbsolutePath: Resource] = [:]) + { + self.sources = sources + self.headers = headers + self.moduleMaps = moduleMaps + self.apiNotes = apiNotes + self.resources = resources + } + + public mutating func merge(_ other: GeneratedFiles) { + sources.formUnion(other.sources) + headers.formUnion(other.headers) + moduleMaps.formUnion(other.moduleMaps) + apiNotes.formUnion(other.apiNotes) + resources.merge(other.resources, uniquingKeysWith: { winner, _ in winner }) } } @@ -589,6 +642,9 @@ public struct FileRuleDescription: Sendable { /// A header file. case header + /// An apinotes file, needs to be located in same directory as module map + case apinotes + /// Indicates that the file should be treated as ignored, without causing an unhandled-file warning. case ignored @@ -661,6 +717,15 @@ public struct FileRuleDescription: Sendable { ) }() + /// the rule for detecting apinotes files. + public static let apinotes: FileRuleDescription = { + .init( + rule: .apinotes, + toolsVersion: .v6_3, + fileTypes: ["apinotes"] + ) + }() + /// The rule for detecting header files. public static let header: FileRuleDescription = { .init( @@ -748,6 +813,7 @@ public struct FileRuleDescription: Sendable { clang, asm, modulemap, + apinotes, header, ] diff --git a/Sources/PackageModel/ToolsVersion.swift b/Sources/PackageModel/ToolsVersion.swift index cded5bf9467..4efd26f1876 100644 --- a/Sources/PackageModel/ToolsVersion.swift +++ b/Sources/PackageModel/ToolsVersion.swift @@ -34,6 +34,7 @@ public struct ToolsVersion: Equatable, Hashable, Codable, Sendable { public static let v6_0 = ToolsVersion(version: "6.0.0") public static let v6_1 = ToolsVersion(version: "6.1.0") public static let v6_2 = ToolsVersion(version: "6.2.0") + public static let v6_3 = ToolsVersion(version: "6.3.0") public static let vNext = ToolsVersion(version: "999.0.0") /// The current tools version in use. diff --git a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift index 200727c6990..671e56ca96a 100644 --- a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift +++ b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift @@ -611,41 +611,58 @@ extension ModulesGraph { buildToolPluginInvocationResults: [BuildToolPluginInvocationResult], prebuildCommandResults: [CommandPluginResult], observabilityScope: ObservabilityScope - ) -> (pluginDerivedSources: Sources, pluginDerivedResources: [Resource]) { - var pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath) - + ) -> GeneratedFiles { // Add any derived files that were declared for any commands from plugin invocations. - var pluginDerivedFiles = [AbsolutePath]() - for command in buildToolPluginInvocationResults.reduce([], { $0 + $1.buildCommands }) { - for absPath in command.outputFiles { - pluginDerivedFiles.append(absPath) + var generatedFiles = GeneratedFiles() + + for result in buildToolPluginInvocationResults { + let files = TargetSourcesBuilder.computeContents( + for: result.buildCommands.flatMap(\.outputFiles), + toolsVersion: toolsVersion, + additionalFileRules: additionalFileRules, + defaultLocalization: target.defaultLocalization, + targetName: target.name, + targetPath: target.underlying.path, + observabilityScope: observabilityScope + ) + generatedFiles.merge(files) + if !files.headers.isEmpty { + // Capture the plugin output directory and public include directory if there were header files generated + generatedFiles.headerPaths.insert(result.pluginOutputDirectory) + // Hardcoding as the default for now + let publicDir = result.pluginOutputDirectory.appending(ClangModule.defaultPublicHeadersComponent) + if files.headers.contains(where: { $0.isDescendantOfOrEqual(to: publicDir) }) { + generatedFiles.publicHeaderPaths.insert(publicDir) + } } } // Add any derived files that were discovered from output directories of prebuild commands. for result in prebuildCommandResults { - for path in result.derivedFiles { - pluginDerivedFiles.append(path) + let files = TargetSourcesBuilder.computeContents( + for: result.derivedFiles, + toolsVersion: toolsVersion, + additionalFileRules: additionalFileRules, + defaultLocalization: target.defaultLocalization, + targetName: target.name, + targetPath: target.underlying.path, + observabilityScope: observabilityScope + ) + generatedFiles.merge(files) + for header in files.headers { + let outputDirs = result.outputDirectories.filter { header.isDescendantOfOrEqual(to: $0) } + for dir in outputDirs { + // If the output dir ends in the default public header dir, make it public (yuck) + if dir.basename == ClangModule.defaultPublicHeadersComponent { + generatedFiles.publicHeaderPaths.insert(dir) + } else { + generatedFiles.headerPaths.insert(dir) + } + } } } - // Let `TargetSourcesBuilder` compute the treatment of plugin generated files. - let (derivedSources, derivedResources) = TargetSourcesBuilder.computeContents( - for: pluginDerivedFiles, - toolsVersion: toolsVersion, - additionalFileRules: additionalFileRules, - defaultLocalization: target.defaultLocalization, - targetName: target.name, - targetPath: target.underlying.path, - observabilityScope: observabilityScope - ) - let pluginDerivedResources = derivedResources - derivedSources.forEach { absPath in - let relPath = absPath.relative(to: pluginDerivedSources.root) - pluginDerivedSources.relativePaths.append(relPath) - } - - return (pluginDerivedSources, pluginDerivedResources) + return generatedFiles } } diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index 6e304910499..3419a740d21 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -271,7 +271,7 @@ public final class PIFBuilder { // to the temporary directory). let readOnlyDirectories = [package.path] - // In tools version 6.0 and newer, we vend the list of files generated by previous plugins. + let pluginDerivedSources: Sources let pluginDerivedResources: [Resource] if package.manifest.toolsVersion >= .v6_0 { @@ -279,7 +279,7 @@ public final class PIFBuilder { // build. let observability = ObservabilitySystem { _, _ in } // Compute the generated files based on all results we have computed so far. - (pluginDerivedSources, pluginDerivedResources) = ModulesGraph.computePluginGeneratedFiles( + let pluginGeneratedFiles = ModulesGraph.computePluginGeneratedFiles( target: module, toolsVersion: package.manifest.toolsVersion, additionalFileRules: self.parameters.additionalFileRules, @@ -288,6 +288,11 @@ public final class PIFBuilder { prebuildCommandResults: [], observabilityScope: observability.topScope ) + pluginDerivedSources = Sources( + paths: pluginGeneratedFiles.sources.map(\.self), + root: buildParameters.dataPath + ) + pluginDerivedResources = pluginGeneratedFiles.resources.values.map(\.self) } else { pluginDerivedSources = .init(paths: [], root: package.path) pluginDerivedResources = [] @@ -366,6 +371,7 @@ public final class PIFBuilder { arguments: buildCommand.configuration.arguments, environment: .init(newEnv), workingDir: package.path, + pluginOutputDir: pluginOutputDir, inputPaths: buildCommand.inputFiles, outputPaths: buildCommand.outputFiles.map(\.pathString), sandboxProfile: diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift index f78bb1107fa..9c1483a1aeb 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift @@ -60,6 +60,7 @@ extension PackagePIFBuilder { public var arguments: [String] public var environment: [String: String] public var workingDir: AbsolutePath? + public var pluginOutputDir: AbsolutePath public var inputPaths: [AbsolutePath] = [] /// Output paths can contain references with un-resolved paths (e.g. "$(DERIVED_FILE_DIR)/myOutput.txt") @@ -76,6 +77,7 @@ extension PackagePIFBuilder { arguments: [String], environment: [String: String], workingDir: AbsolutePath?, + pluginOutputDir: AbsolutePath, inputPaths: [AbsolutePath], outputPaths: [String], sandboxProfile: SandboxProfile? @@ -85,6 +87,7 @@ extension PackagePIFBuilder { self.arguments = arguments self.environment = environment self.workingDir = workingDir + self.pluginOutputDir = pluginOutputDir self.inputPaths = inputPaths self.outputPaths = outputPaths self.sandboxProfile = sandboxProfile diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index a36ea1b48f2..1ce21f6aa73 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -25,6 +25,8 @@ import class PackageModel.BinaryModule import class PackageModel.Product import class PackageModel.SystemLibraryModule +import PackageLoading + import struct PackageGraph.ResolvedModule import struct PackageGraph.ResolvedPackage @@ -287,7 +289,7 @@ extension PackagePIFProjectBuilder { } // Deal with any generated source files or resource files. - let (generatedSourceFiles, generatedResourceFiles) = computePluginGeneratedFiles( + let generatedFiles = computePluginGeneratedFiles( module: sourceModule, targetKeyPath: sourceModuleTargetKeyPath, addBuildToolPluginCommands: false @@ -298,10 +300,13 @@ extension PackagePIFProjectBuilder { let shouldGenerateBundleAccessor: Bool let shouldGenerateEmbedInCodeAccessor: Bool if resourceBundleName == nil && desiredModuleType != .executable && desiredModuleType != .macro { + // FIXME: We are not handling resource rules here, but the same is true for non-generated resources. + // (Today, everything gets essentially treated as `.processResource` even if it may have been declared as + // `.copy` in the manifest.) let (result, resourceBundle) = try addResourceBundle( for: sourceModule, targetKeyPath: sourceModuleTargetKeyPath, - generatedResourceFiles: generatedResourceFiles + generatedResourceFiles: generatedFiles.resources.keys.map(\.pathString) ) if let resourceBundle { self.builtModulesAndProducts.append(resourceBundle) } @@ -330,8 +335,8 @@ extension PackagePIFProjectBuilder { module: sourceModule, sourceModuleTargetKeyPath: sourceModuleTargetKeyPath, resourceBundleTargetKeyPath: resourceBundleTargetKeyPath, - sourceFilePaths: generatedSourceFiles, - resourceFilePaths: generatedResourceFiles + sourceFilePaths: generatedFiles.sources.map(\.self), + resourceFilePaths: generatedFiles.resources.keys.map(\.pathString) ) } @@ -368,37 +373,43 @@ extension PackagePIFProjectBuilder { impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] } else { // Otherwise, this is a C library module and we generate a modulemap if one is already not provided. - switch sourceModule.moduleMapType { - case nil, .some(.none): - // No modulemap, no action required. - break - case .custom(let customModuleMapPath): - // We don't need to generate a modulemap, but we should explicitly impart it on dependents, - // even if it will appear in search paths. See: https://github.com/swiftlang/swift-package-manager/issues/9290 - impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(customModuleMapPath)", "$(inherited)"] - impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(customModuleMapPath)", "$(inherited)"] - case .umbrellaHeader(let path): - log(.debug, "\(package.name).\(sourceModule.name) generated umbrella header") - moduleMapFileContents = """ + if let generatedModuleMapPath = generatedFiles.moduleMaps.first { + // The modulemap was already generated, we should explicitly impart it on dependents, + impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] + impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] + } else { + switch sourceModule.moduleMapType { + case nil, .some(.none): + // No modulemap, no action required. + break + case .custom(let customModuleMapPath): + // We don't need to generate a modulemap, but we should explicitly impart it on dependents, + // even if it will appear in search paths. See: https://github.com/swiftlang/swift-package-manager/issues/9290 + impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(customModuleMapPath)", "$(inherited)"] + impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(customModuleMapPath)", "$(inherited)"] + case .umbrellaHeader(let path): + log(.debug, "\(package.name).\(sourceModule.name) generated umbrella header") + moduleMapFileContents = """ module \(sourceModule.c99name) { umbrella header "\(path.escapedPathString)" export * } """ - // Pass the path of the module map up to all direct and indirect clients. - impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] - impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] - case .umbrellaDirectory(let path): - log(.debug, "\(package.name).\(sourceModule.name) generated umbrella directory") - moduleMapFileContents = """ + // Pass the path of the module map up to all direct and indirect clients. + impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] + impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] + case .umbrellaDirectory(let path): + log(.debug, "\(package.name).\(sourceModule.name) generated umbrella directory") + moduleMapFileContents = """ module \(sourceModule.c99name) { umbrella "\(path.escapedPathString)" export * } """ - // Pass the path of the module map up to all direct and indirect clients. - impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] - impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] + // Pass the path of the module map up to all direct and indirect clients. + impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] + impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] + } } } @@ -477,14 +488,26 @@ extension PackagePIFProjectBuilder { settings[.SUPPORTS_TEXT_BASED_API] = "NO" // If the module includes C headers, we set up the HEADER_SEARCH_PATHS setting appropriately. + var headerSearchPaths: [String] = [] if let includeDirAbsPath = sourceModule.includeDirAbsolutePath { + headerSearchPaths.append(includeDirAbsPath.pathString) + } + + // Add paths to plugin generated headers + //headerSearchPaths.append(contentsOf: generatedFiles.headerSearchPaths.map(\.pathString)) + + if !headerSearchPaths.isEmpty { // Let the target itself find its own headers. - settings[.HEADER_SEARCH_PATHS] = [includeDirAbsPath.pathString, "$(inherited)"] - log(.debug, indent: 1, "Added '\(includeDirAbsPath)' to HEADER_SEARCH_PATHS") + settings[.HEADER_SEARCH_PATHS] = headerSearchPaths + ["$(inherited)"] + for path in headerSearchPaths { + log(.debug, indent: 1, "Added '\(path)' to HEADER_SEARCH_PATHS") + } // Also propagate this search path to all direct and indirect clients. - impartedSettings[.HEADER_SEARCH_PATHS] = [includeDirAbsPath.pathString, "$(inherited)"] - log(.debug, indent: 1, "Added '\(includeDirAbsPath)' to imparted HEADER_SEARCH_PATHS") + impartedSettings[.HEADER_SEARCH_PATHS] = headerSearchPaths + ["$(inherited)"] + for path in headerSearchPaths { + log(.debug, indent: 1, "Added '\(path)' to imparted HEADER_SEARCH_PATHS") + } } // Additional settings for the linker. @@ -568,7 +591,7 @@ extension PackagePIFProjectBuilder { let headerFiles = Set(sourceModule.headerFileAbsolutePaths) // Add any additional source files emitted by custom build commands. - for path in generatedSourceFiles { + for path in generatedFiles.sources { let sourceFileRef = self.project.mainGroup[keyPath: targetSourceFileGroupKeyPath].addFileReference { id in FileReference(id: id, path: path.pathString, pathBase: .absolute) } diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift index e7c75b928d6..3e6e9de0dc6 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift @@ -18,6 +18,8 @@ import struct Basics.AbsolutePath import class Basics.ObservabilitySystem import struct Basics.SourceControlURL +import PackageLoading + import class PackageModel.BinaryModule import class PackageModel.Manifest import enum PackageModel.PackageCondition @@ -97,12 +99,12 @@ extension PackagePIFProjectBuilder { } // Deal with any generated source files or resource files. - let (generatedSourceFiles, pluginGeneratedResourceFiles) = computePluginGeneratedFiles( + let generatedFiles = computePluginGeneratedFiles( module: mainModule, targetKeyPath: mainModuleTargetKeyPath, addBuildToolPluginCommands: pifProductType == .application ) - if mainModule.resources.hasContent || pluginGeneratedResourceFiles.hasContent { + if mainModule.resources.hasContent || generatedFiles.resources.hasContent { mainModuleTargetNamesWithResources.insert(mainModule.name) } @@ -155,10 +157,17 @@ extension PackagePIFProjectBuilder { settings[.XROS_DEPLOYMENT_TARGET] = mainTargetDeploymentTargets[.visionOS] ?? nil // If the main module includes C headers, then we need to set up the HEADER_SEARCH_PATHS setting appropriately. + var headerSearchPaths: Set = [] if let includeDirAbsolutePath = mainModule.includeDirAbsolutePath { + headerSearchPaths.insert(includeDirAbsolutePath) + } + + if !headerSearchPaths.isEmpty { // Let the main module itself find its own headers. - settings[.HEADER_SEARCH_PATHS] = [includeDirAbsolutePath.pathString, "$(inherited)"] - log(.debug, indent: 1, "Added '\(includeDirAbsolutePath)' to HEADER_SEARCH_PATHS") + settings[.HEADER_SEARCH_PATHS] = headerSearchPaths.map(\.pathString) + ["$(inherited)"] + for path in headerSearchPaths { + log(.debug, indent: 1, "Added '\(path)' to HEADER_SEARCH_PATHS") + } } // Set the appropriate language versions. @@ -206,7 +215,7 @@ extension PackagePIFProjectBuilder { let headerFiles = Set(mainModule.headerFileAbsolutePaths) // Add any additional source files emitted by custom build commands. - for path in generatedSourceFiles { + for path in generatedFiles.sources { let sourceFileRef = self.project.mainGroup[keyPath: mainTargetSourceFileGroupKeyPath] .addFileReference { id in FileReference( @@ -223,7 +232,7 @@ extension PackagePIFProjectBuilder { // Add any additional resource files emitted by synthesized build commands let generatedResourceFiles: [String] = { - var generatedResourceFiles = pluginGeneratedResourceFiles + var generatedResourceFiles = generatedFiles.resources.keys.map(\.pathString) generatedResourceFiles.append( contentsOf: addBuildToolCommands( from: synthesizedResourceGeneratingPluginInvocationResults, @@ -308,8 +317,8 @@ extension PackagePIFProjectBuilder { module: mainModule, sourceModuleTargetKeyPath: mainModuleTargetKeyPath, resourceBundleTargetKeyPath: resourceBundleTargetKeyPath, - sourceFilePaths: generatedSourceFiles, - resourceFilePaths: generatedResourceFiles + sourceFilePaths: generatedFiles.sources.map(\.self), + resourceFilePaths: generatedFiles.resources.keys.map(\.pathString) ) } else { // Generated resources always trigger the creation of a bundle accessor. @@ -325,8 +334,8 @@ extension PackagePIFProjectBuilder { module: mainModule, sourceModuleTargetKeyPath: mainModuleTargetKeyPath, resourceBundleTargetKeyPath: mainModuleTargetKeyPath, - sourceFilePaths: generatedSourceFiles, - resourceFilePaths: generatedResourceFiles + sourceFilePaths: generatedFiles.sources.map(\.self), + resourceFilePaths: generatedFiles.resources.keys.map(\.pathString) ) } } diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift index 6684a2e5e9e..0ca2f58b792 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift @@ -30,6 +30,7 @@ import struct PackageGraph.ResolvedPackage import struct PackageLoading.FileRuleDescription import struct PackageLoading.TargetSourcesBuilder +import struct PackageLoading.GeneratedFiles import struct SwiftBuild.Pair import enum SwiftBuild.ProjectModel @@ -389,15 +390,13 @@ struct PackagePIFProjectBuilder { module: PackageGraph.ResolvedModule, targetKeyPath: WritableKeyPath, addBuildToolPluginCommands: Bool - ) -> (sourceFilePaths: [AbsolutePath], resourceFilePaths: [String]) { + ) -> GeneratedFiles { + var generatedFiles = GeneratedFiles() guard let pluginResults = pifBuilder.buildToolPluginResultsByTargetName[module.name] else { // We found no results for the target. - return (sourceFilePaths: [], resourceFilePaths: []) + return generatedFiles } - var sourceFilePaths: [AbsolutePath] = [] - var resourceFilePaths: [AbsolutePath] = [] - for pluginResult in pluginResults { // Process the results of applying any build tool plugins on the target. // If we've been asked to add build tool commands for the result, we do so now. @@ -408,19 +407,23 @@ struct PackagePIFProjectBuilder { } // Process all the paths of derived output paths using the same rules as for source. - let result = self.process( - pluginGeneratedFilePaths: pluginResult.allDerivedOutputPaths, - forModule: module, - toolsVersion: self.package.manifest.toolsVersion - ) + for command in pluginResult.buildCommands { + var result = self.process( + pluginGeneratedFilePaths: command.absoluteOutputPaths, + forModule: module, + toolsVersion: self.package.manifest.toolsVersion + ) + + generatedFiles.merge(result) + } - sourceFilePaths.append(contentsOf: result.sourceFilePaths) - resourceFilePaths.append(contentsOf: result.resourceFilePaths.map(\.path)) + generatedFiles.merge(self.process( + pluginGeneratedFilePaths: pluginResult.prebuildCommandOutputPaths, + forModule: module, + toolsVersion: self.package.manifest.toolsVersion)) } - return ( - sourceFilePaths: sourceFilePaths, - resourceFilePaths: resourceFilePaths.map(\.pathString) - ) + + return generatedFiles } /// Helper function for adding build tool commands to the right PIF target depending on whether they generate @@ -501,19 +504,19 @@ struct PackagePIFProjectBuilder { pluginGeneratedFilePaths: [AbsolutePath], forModule module: PackageGraph.ResolvedModule, toolsVersion: PackageModel.ToolsVersion? - ) -> (sourceFilePaths: [AbsolutePath], resourceFilePaths: [Resource]) { + ) -> GeneratedFiles { precondition(module.isSourceModule) // If we have no tools version, all files are treated as *source* files. guard let toolsVersion else { - return (sourceFilePaths: pluginGeneratedFilePaths, resourceFilePaths: []) + return GeneratedFiles() } // FIXME: Will be fixed by (SwiftPM PIFBuilder — adopt ObservabilityScope as the logging API). let observabilityScope = ObservabilitySystem.NOOP // Use the `TargetSourcesBuilder` from libSwiftPM to split the generated files into sources and resources. - let (generatedSourcePaths, generatedResourcePaths) = TargetSourcesBuilder.computeContents( + return TargetSourcesBuilder.computeContents( for: pluginGeneratedFilePaths, toolsVersion: toolsVersion, additionalFileRules: Self.additionalFileRules, @@ -522,11 +525,6 @@ struct PackagePIFProjectBuilder { targetPath: module.path, observabilityScope: observabilityScope ) - - // FIXME: We are not handling resource rules here, but the same is true for non-generated resources. - // (Today, everything gets essentially treated as `.processResource` even if it may have been declared as - // `.copy` in the manifest.) - return (generatedSourcePaths, generatedResourcePaths) } private static let additionalFileRules: [FileRuleDescription] =