From 71467042e748e091354248cc142359ae42440739 Mon Sep 17 00:00:00 2001 From: Doug Schaefer Date: Wed, 15 Oct 2025 10:11:51 -0400 Subject: [PATCH 1/3] Add support for generating more C files with Swift Build. There currently are restrictions on the ability for build plugins to generate C header, modulemap, and APInote files. This removes those restrictions when using Swift Build and adds the output directory for the plugin to the HEADER_SEARCH_PATH for the target. --- .../ClangModuleBuildDescription.swift | 18 +++- .../SwiftModuleBuildDescription.swift | 26 +++++- Sources/Build/BuildPlan/BuildPlan.swift | 11 ++- .../PackageLoading/TargetSourcesBuilder.swift | 90 ++++++++++++++++--- Sources/PackageModel/Resource.swift | 2 +- Sources/PackageModel/ToolsVersion.swift | 1 + .../Plugins/PluginInvocation.swift | 61 ++++++------- Sources/SwiftBuildSupport/PIFBuilder.swift | 10 ++- .../PackagePIFBuilder+Plugins.swift | 5 ++ .../PackagePIFProjectBuilder+Modules.swift | 35 ++++++-- .../PackagePIFProjectBuilder+Products.swift | 32 ++++--- .../PackagePIFProjectBuilder.swift | 36 ++++---- 12 files changed, 239 insertions(+), 88 deletions(-) diff --git a/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift b/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift index 4116e970b14..1148af3aeb4 100644 --- a/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift @@ -156,7 +156,7 @@ public final class ClangModuleBuildDescription { if toolsVersion >= .v5_9 { self.buildToolPluginInvocationResults = buildToolPluginInvocationResults - (self.pluginDerivedSources, self.pluginDerivedResources) = ModulesGraph.computePluginGeneratedFiles( + let pluginTargetFiles = ModulesGraph.computePluginGeneratedFiles( target: target, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, @@ -165,6 +165,22 @@ public final class ClangModuleBuildDescription { prebuildCommandResults: prebuildCommandResults, observabilityScope: observabilityScope ) + self.pluginDerivedSources = Sources( + paths: pluginTargetFiles.sources.map(\.self), + root: buildParameters.dataPath + ) + self.pluginDerivedResources = pluginTargetFiles.resources.values.map(\.self) + + // With Swift Build on the horizon, we won't add support for generated headers, modulemaps, and apinotes here + for absPath in pluginTargetFiles.headers { + observabilityScope.emit(warning: "Module maps generated by plugins are not supported at this time: \(absPath)") + } + for absPath in pluginTargetFiles.moduleMaps { + observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)") + } + for absPath in pluginTargetFiles.apiNotes { + observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)") + } } else { self.buildToolPluginInvocationResults = [] self.pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath) diff --git a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift index b6b6c20fffa..fb281fbf339 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 pluginTargetFiles = ModulesGraph.computePluginGeneratedFiles( target: target, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, @@ -308,6 +308,30 @@ public final class SwiftModuleBuildDescription { prebuildCommandResults: prebuildCommandResults, observabilityScope: observabilityScope ) + self.pluginDerivedSources = Sources( + paths: pluginTargetFiles.sources.map(\.self), + root: buildParameters.dataPath + ) + self.pluginDerivedResources = pluginTargetFiles.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 pluginTargetFiles.headers { + observabilityScope.emit(warning: "Module maps generated by plugins are not supported at this time: \(absPath)") + } + for absPath in pluginTargetFiles.moduleMaps { + observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)") + } + for absPath in pluginTargetFiles.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.swift b/Sources/Build/BuildPlan/BuildPlan.swift index b54f7b939c6..6b3d7de29bd 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -785,13 +785,13 @@ extension BuildPlan { // In tools version 6.0 and newer, we vend the list of files generated by previous plugins. let pluginDerivedSources: Sources - let pluginDerivedResources: [Resource] + let pluginDerivedResources: [Basics.AbsolutePath] if package.manifest.toolsVersion >= .v6_0 { // Set up dummy observability because we don't want to emit diagnostics for this before the actual // build. let observability = ObservabilitySystem { _, _ in } // Compute the generated files based on all results we have computed so far. - (pluginDerivedSources, pluginDerivedResources) = ModulesGraph.computePluginGeneratedFiles( + let pluginTargetFiles = ModulesGraph.computePluginGeneratedFiles( target: module, toolsVersion: package.manifest.toolsVersion, additionalFileRules: additionalFileRules, @@ -800,6 +800,11 @@ extension BuildPlan { prebuildCommandResults: [], observabilityScope: observability.topScope ) + pluginDerivedSources = Sources( + paths: pluginTargetFiles.sources.map(\.self), + root: buildParameters.dataPath + ) + pluginDerivedResources = pluginTargetFiles.resources.keys.map(\.self) } else { pluginDerivedSources = .init(paths: [], root: package.path) pluginDerivedResources = [] @@ -811,7 +816,7 @@ extension BuildPlan { package: package, target: module, pluginGeneratedSources: pluginDerivedSources.paths, - pluginGeneratedResources: pluginDerivedResources.map(\.path) + pluginGeneratedResources: pluginDerivedResources ), buildEnvironment: buildParameters.buildEnvironment, scriptRunner: configuration.scriptRunner, diff --git a/Sources/PackageLoading/TargetSourcesBuilder.swift b/Sources/PackageLoading/TargetSourcesBuilder.swift index e218bf3f227..fe3023061eb 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,75 @@ 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 sources: Set + public var headerSearchPaths: Set + public var headers: Set + public var moduleMaps: Set + public var apiNotes: Set + public var resources: [Basics.AbsolutePath: Resource] + + public init( + sources: Set = [], + headerSearchPaths: Set = [], + headers: Set = [], + moduleMaps: Set = [], + apiNotes: Set = [], + resources: [Basics.AbsolutePath: Resource] = [:]) + { + self.sources = sources + self.headerSearchPaths = headerSearchPaths + self.headers = headers + self.moduleMaps = moduleMaps + self.apiNotes = apiNotes + self.resources = resources + } + + public mutating func merge(_ other: GeneratedFiles) { + sources.formUnion(other.sources) + headerSearchPaths.formUnion(other.headerSearchPaths) + headers.formUnion(other.headers) + moduleMaps.formUnion(other.moduleMaps) + apiNotes.formUnion(other.apiNotes) + resources.merge(other.resources, uniquingKeysWith: { winner, _ in winner }) } } @@ -589,6 +644,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 +719,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 +815,7 @@ public struct FileRuleDescription: Sendable { clang, asm, modulemap, + apinotes, header, ] diff --git a/Sources/PackageModel/Resource.swift b/Sources/PackageModel/Resource.swift index 7e2b6c15e76..cb91d6be6eb 100644 --- a/Sources/PackageModel/Resource.swift +++ b/Sources/PackageModel/Resource.swift @@ -13,7 +13,7 @@ import Basics /// An individual resource file and its corresponding rule. -public struct Resource: Codable, Equatable { +public struct Resource: Codable { public static let localizationDirectoryExtension = "lproj" /// The rule associated with this resource. 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..726ecab7880 100644 --- a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift +++ b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift @@ -611,41 +611,42 @@ 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 { + var files = TargetSourcesBuilder.computeContents( + for: result.buildCommands.flatMap(\.outputFiles), + toolsVersion: toolsVersion, + additionalFileRules: additionalFileRules, + defaultLocalization: target.defaultLocalization, + targetName: target.name, + targetPath: target.underlying.path, + observabilityScope: observabilityScope + ) + if !files.headers.isEmpty || !files.moduleMaps.isEmpty { + // Add plugin output directory as include path + // FIXME: plugins should be able to explicity add header search paths to the target + files.headerSearchPaths.insert(result.pluginOutputDirectory) } + generatedFiles.merge(files) } // 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 `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) + generatedFiles.merge(TargetSourcesBuilder.computeContents( + for: result.derivedFiles, + toolsVersion: toolsVersion, + additionalFileRules: additionalFileRules, + defaultLocalization: target.defaultLocalization, + targetName: target.name, + targetPath: target.underlying.path, + observabilityScope: observabilityScope + )) } - return (pluginDerivedSources, pluginDerivedResources) + return generatedFiles } } @@ -774,21 +775,21 @@ public struct BuildToolPluginInvocationResult { public var prebuildCommands: [PrebuildCommand] /// A command to incorporate into the build graph so that it runs during the build whenever it needs to. - public struct BuildCommand { + public struct BuildCommand: Equatable { public var configuration: CommandConfiguration public var inputFiles: [AbsolutePath] public var outputFiles: [AbsolutePath] } /// A command to run before the start of every build. - public struct PrebuildCommand { + public struct PrebuildCommand: Equatable { // TODO: In the future these should be folded into regular build commands when the build system can handle not knowing the names of all the outputs before the command runs. public var configuration: CommandConfiguration public var outputFilesDirectory: AbsolutePath } /// Launch configuration of a command that can be run (including a display name to show in logs etc). - public struct CommandConfiguration { + public struct CommandConfiguration: Equatable { public var displayName: String? public var executable: AbsolutePath public var arguments: [String] diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index 37276b3538c..389845167c8 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -265,7 +265,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 { @@ -273,7 +273,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, @@ -282,6 +282,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 = [] @@ -360,6 +365,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..8d3906aa9f7 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift @@ -20,6 +20,8 @@ import struct Basics.SourceControlURL import enum SwiftBuild.ProjectModel +import SPMBuildCore + extension PackagePIFBuilder { /// Contains all of the information resulting from applying a build tool plugin to a package target thats affect how /// a target is built. @@ -60,6 +62,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 +79,7 @@ extension PackagePIFBuilder { arguments: [String], environment: [String: String], workingDir: AbsolutePath?, + pluginOutputDir: AbsolutePath?, inputPaths: [AbsolutePath], outputPaths: [String], sandboxProfile: SandboxProfile? @@ -85,6 +89,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 95dc7920a2c..af4176373b9 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -24,6 +24,8 @@ import class PackageModel.Module import class PackageModel.Product import class PackageModel.SystemLibraryModule +import PackageLoading + import struct PackageGraph.ResolvedModule import struct PackageGraph.ResolvedPackage @@ -290,7 +292,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 @@ -301,10 +303,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) } @@ -333,8 +338,8 @@ extension PackagePIFProjectBuilder { module: sourceModule, sourceModuleTargetKeyPath: sourceModuleTargetKeyPath, resourceBundleTargetKeyPath: resourceBundleTargetKeyPath, - sourceFilePaths: generatedSourceFiles, - resourceFilePaths: generatedResourceFiles + sourceFilePaths: generatedFiles.sources.map(\.self), + resourceFilePaths: generatedFiles.resources.keys.map(\.pathString) ) } @@ -470,14 +475,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: Set = [] if let includeDirAbsPath = sourceModule.includeDirAbsolutePath { + headerSearchPaths.insert(includeDirAbsPath.pathString) + } + + // Add paths to plugin generated headers + headerSearchPaths.formUnion(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. @@ -556,7 +573,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 8893186e356..0fe9144d7e8 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) } @@ -153,10 +155,20 @@ 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 !generatedFiles.headers.isEmpty || !generatedFiles.moduleMaps.isEmpty { + headerSearchPaths.formUnion(generatedFiles.headerSearchPaths) + } + + 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. @@ -204,7 +216,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( @@ -221,7 +233,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, @@ -306,8 +318,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. @@ -323,8 +335,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 391fb15be9d..da3597a882b 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 @@ -386,15 +387,13 @@ struct PackagePIFProjectBuilder { module: PackageGraph.ResolvedModule, targetKeyPath: WritableKeyPath, addBuildToolPluginCommands: Bool - ) -> (sourceFilePaths: [AbsolutePath], resourceFilePaths: [String]) { + ) -> GeneratedFiles { + var targetFiles = GeneratedFiles() guard let pluginResults = pifBuilder.buildToolPluginResultsByTargetName[module.name] else { // We found no results for the target. - return (sourceFilePaths: [], resourceFilePaths: []) + return targetFiles } - 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. @@ -405,19 +404,21 @@ struct PackagePIFProjectBuilder { } // Process all the paths of derived output paths using the same rules as for source. - let result = self.process( + var result = self.process( pluginGeneratedFilePaths: pluginResult.allDerivedOutputPaths, forModule: module, toolsVersion: self.package.manifest.toolsVersion ) - sourceFilePaths.append(contentsOf: result.sourceFilePaths) - resourceFilePaths.append(contentsOf: result.resourceFilePaths.map(\.path)) + // if the results contain headers or module maps, add the plugin output dir to the header search path + if !result.headers.isEmpty, !result.moduleMaps.isEmpty { + result.headerSearchPaths.formUnion(pluginResult.buildCommands.compactMap(\.pluginOutputDir)) + } + + targetFiles.merge(result) } - return ( - sourceFilePaths: sourceFilePaths, - resourceFilePaths: resourceFilePaths.map(\.pathString) - ) + + return targetFiles } /// Helper function for adding build tool commands to the right PIF target depending on whether they generate @@ -498,19 +499,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, @@ -519,11 +520,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] = From c0a2c1714c7fd5b61db2078170ba91d0f22bae25 Mon Sep 17 00:00:00 2001 From: Doug Schaefer Date: Wed, 15 Oct 2025 14:26:50 -0400 Subject: [PATCH 2/3] Make Resource equatable for tests --- Sources/PackageModel/Resource.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PackageModel/Resource.swift b/Sources/PackageModel/Resource.swift index cb91d6be6eb..7e2b6c15e76 100644 --- a/Sources/PackageModel/Resource.swift +++ b/Sources/PackageModel/Resource.swift @@ -13,7 +13,7 @@ import Basics /// An individual resource file and its corresponding rule. -public struct Resource: Codable { +public struct Resource: Codable, Equatable { public static let localizationDirectoryExtension = "lproj" /// The rule associated with this resource. From 72707ab39026bc02d5c5e4f166094d97178d13af Mon Sep 17 00:00:00 2001 From: Doug Schaefer Date: Wed, 15 Oct 2025 18:54:35 -0400 Subject: [PATCH 3/3] Some clean up and fix some reversed logic --- .../ClangModuleBuildDescription.swift | 13 +++---- .../SwiftModuleBuildDescription.swift | 16 ++++----- Sources/Build/BuildPlan/BuildPlan.swift | 10 +++--- .../PackageLoading/TargetSourcesBuilder.swift | 9 ++--- .../Plugins/PluginInvocation.swift | 14 ++++---- .../PackagePIFBuilder+Plugins.swift | 6 ++-- .../PackagePIFProjectBuilder+Modules.swift | 6 ++-- .../PackagePIFProjectBuilder.swift | 35 ++++++++++++------- 8 files changed, 60 insertions(+), 49 deletions(-) diff --git a/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift b/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift index 1148af3aeb4..b32d68628de 100644 --- a/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift @@ -156,7 +156,7 @@ public final class ClangModuleBuildDescription { if toolsVersion >= .v5_9 { self.buildToolPluginInvocationResults = buildToolPluginInvocationResults - let pluginTargetFiles = ModulesGraph.computePluginGeneratedFiles( + let pluginGeneratedFiles = ModulesGraph.computePluginGeneratedFiles( target: target, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, @@ -165,20 +165,21 @@ public final class ClangModuleBuildDescription { prebuildCommandResults: prebuildCommandResults, observabilityScope: observabilityScope ) + self.pluginDerivedSources = Sources( - paths: pluginTargetFiles.sources.map(\.self), + paths: pluginGeneratedFiles.sources.map(\.self), root: buildParameters.dataPath ) - self.pluginDerivedResources = pluginTargetFiles.resources.values.map(\.self) + self.pluginDerivedResources = pluginGeneratedFiles.resources.values.map(\.self) // With Swift Build on the horizon, we won't add support for generated headers, modulemaps, and apinotes here - for absPath in pluginTargetFiles.headers { + for absPath in pluginGeneratedFiles.headers { observabilityScope.emit(warning: "Module maps generated by plugins are not supported at this time: \(absPath)") } - for absPath in pluginTargetFiles.moduleMaps { + for absPath in pluginGeneratedFiles.moduleMaps { observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)") } - for absPath in pluginTargetFiles.apiNotes { + for absPath in pluginGeneratedFiles.apiNotes { observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)") } } else { diff --git a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift index fb281fbf339..3be21038a9b 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 - let pluginTargetFiles = ModulesGraph.computePluginGeneratedFiles( + let pluginGeneratedFiles = ModulesGraph.computePluginGeneratedFiles( target: target, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, @@ -309,27 +309,27 @@ public final class SwiftModuleBuildDescription { observabilityScope: observabilityScope ) self.pluginDerivedSources = Sources( - paths: pluginTargetFiles.sources.map(\.self), + paths: pluginGeneratedFiles.sources.map(\.self), root: buildParameters.dataPath ) - self.pluginDerivedResources = pluginTargetFiles.resources.values.map(\.self) + self.pluginDerivedResources = pluginGeneratedFiles.resources.values.map(\.self) - let nonSwiftSources = pluginDerivedSources.relativePaths.filter({ $0.extension == "swift" }) + 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" }) + self.pluginDerivedSources.relativePaths = self.pluginDerivedSources.relativePaths.filter({ $0.extension == "swift" }) } - for absPath in pluginTargetFiles.headers { + for absPath in pluginGeneratedFiles.headers { observabilityScope.emit(warning: "Module maps generated by plugins are not supported at this time: \(absPath)") } - for absPath in pluginTargetFiles.moduleMaps { + for absPath in pluginGeneratedFiles.moduleMaps { observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)") } - for absPath in pluginTargetFiles.apiNotes { + for absPath in pluginGeneratedFiles.apiNotes { observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)") } diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index 6b3d7de29bd..8ae03a18c08 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -785,13 +785,13 @@ extension BuildPlan { // In tools version 6.0 and newer, we vend the list of files generated by previous plugins. let pluginDerivedSources: Sources - let pluginDerivedResources: [Basics.AbsolutePath] + let pluginDerivedResources: [Resource] if package.manifest.toolsVersion >= .v6_0 { // Set up dummy observability because we don't want to emit diagnostics for this before the actual // build. let observability = ObservabilitySystem { _, _ in } // Compute the generated files based on all results we have computed so far. - let pluginTargetFiles = ModulesGraph.computePluginGeneratedFiles( + let pluginGeneratedFiles = ModulesGraph.computePluginGeneratedFiles( target: module, toolsVersion: package.manifest.toolsVersion, additionalFileRules: additionalFileRules, @@ -801,10 +801,10 @@ extension BuildPlan { observabilityScope: observability.topScope ) pluginDerivedSources = Sources( - paths: pluginTargetFiles.sources.map(\.self), + paths: pluginGeneratedFiles.sources.map(\.self), root: buildParameters.dataPath ) - pluginDerivedResources = pluginTargetFiles.resources.keys.map(\.self) + pluginDerivedResources = pluginGeneratedFiles.resources.values.map(\.self) } else { pluginDerivedSources = .init(paths: [], root: package.path) pluginDerivedResources = [] @@ -816,7 +816,7 @@ extension BuildPlan { package: package, target: module, pluginGeneratedSources: pluginDerivedSources.paths, - pluginGeneratedResources: pluginDerivedResources + pluginGeneratedResources: pluginDerivedResources.map(\.path) ), buildEnvironment: buildParameters.buildEnvironment, scriptRunner: configuration.scriptRunner, diff --git a/Sources/PackageLoading/TargetSourcesBuilder.swift b/Sources/PackageLoading/TargetSourcesBuilder.swift index fe3023061eb..055bb3388f5 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, .apinotes, .ignored: + case .compile, .header, .none, .modulemap, .apinotes, .ignored: return nil case .processResource: let implicitLocalization: String? = { @@ -586,7 +586,8 @@ public struct TargetSourcesBuilder { public struct GeneratedFiles { public var sources: Set - public var headerSearchPaths: Set + // Order matters with the header search paths + public var headerSearchPaths: [Basics.AbsolutePath] public var headers: Set public var moduleMaps: Set public var apiNotes: Set @@ -594,7 +595,7 @@ public struct GeneratedFiles { public init( sources: Set = [], - headerSearchPaths: Set = [], + headerSearchPaths: [Basics.AbsolutePath] = [], headers: Set = [], moduleMaps: Set = [], apiNotes: Set = [], @@ -610,7 +611,7 @@ public struct GeneratedFiles { public mutating func merge(_ other: GeneratedFiles) { sources.formUnion(other.sources) - headerSearchPaths.formUnion(other.headerSearchPaths) + headerSearchPaths.append(contentsOf: other.headerSearchPaths) headers.formUnion(other.headers) moduleMaps.formUnion(other.moduleMaps) apiNotes.formUnion(other.apiNotes) diff --git a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift index 726ecab7880..29cc098c34f 100644 --- a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift +++ b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift @@ -625,12 +625,14 @@ extension ModulesGraph { targetPath: target.underlying.path, observabilityScope: observabilityScope ) + generatedFiles.merge(files) if !files.headers.isEmpty || !files.moduleMaps.isEmpty { // Add plugin output directory as include path - // FIXME: plugins should be able to explicity add header search paths to the target - files.headerSearchPaths.insert(result.pluginOutputDirectory) + // TODO: plugins should be able to explicity add header search paths to the target + if !generatedFiles.headerSearchPaths.contains(result.pluginOutputDirectory) { + generatedFiles.headerSearchPaths.append(result.pluginOutputDirectory) + } } - generatedFiles.merge(files) } // Add any derived files that were discovered from output directories of prebuild commands. @@ -775,21 +777,21 @@ public struct BuildToolPluginInvocationResult { public var prebuildCommands: [PrebuildCommand] /// A command to incorporate into the build graph so that it runs during the build whenever it needs to. - public struct BuildCommand: Equatable { + public struct BuildCommand { public var configuration: CommandConfiguration public var inputFiles: [AbsolutePath] public var outputFiles: [AbsolutePath] } /// A command to run before the start of every build. - public struct PrebuildCommand: Equatable { + public struct PrebuildCommand { // TODO: In the future these should be folded into regular build commands when the build system can handle not knowing the names of all the outputs before the command runs. public var configuration: CommandConfiguration public var outputFilesDirectory: AbsolutePath } /// Launch configuration of a command that can be run (including a display name to show in logs etc). - public struct CommandConfiguration: Equatable { + public struct CommandConfiguration { public var displayName: String? public var executable: AbsolutePath public var arguments: [String] diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift index 8d3906aa9f7..9c1483a1aeb 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift @@ -20,8 +20,6 @@ import struct Basics.SourceControlURL import enum SwiftBuild.ProjectModel -import SPMBuildCore - extension PackagePIFBuilder { /// Contains all of the information resulting from applying a build tool plugin to a package target thats affect how /// a target is built. @@ -62,7 +60,7 @@ extension PackagePIFBuilder { public var arguments: [String] public var environment: [String: String] public var workingDir: AbsolutePath? - public var pluginOutputDir: 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") @@ -79,7 +77,7 @@ extension PackagePIFBuilder { arguments: [String], environment: [String: String], workingDir: AbsolutePath?, - pluginOutputDir: AbsolutePath?, + pluginOutputDir: AbsolutePath, inputPaths: [AbsolutePath], outputPaths: [String], sandboxProfile: SandboxProfile? diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index af4176373b9..d1f1ba5ccf7 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -475,13 +475,13 @@ 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: Set = [] + var headerSearchPaths: [String] = [] if let includeDirAbsPath = sourceModule.includeDirAbsolutePath { - headerSearchPaths.insert(includeDirAbsPath.pathString) + headerSearchPaths.append(includeDirAbsPath.pathString) } // Add paths to plugin generated headers - headerSearchPaths.formUnion(generatedFiles.headerSearchPaths.map(\.pathString)) + headerSearchPaths.append(contentsOf: generatedFiles.headerSearchPaths.map(\.pathString)) if !headerSearchPaths.isEmpty { // Let the target itself find its own headers. diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift index da3597a882b..4abcbcf5b80 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift @@ -388,10 +388,10 @@ struct PackagePIFProjectBuilder { targetKeyPath: WritableKeyPath, addBuildToolPluginCommands: Bool ) -> GeneratedFiles { - var targetFiles = GeneratedFiles() + var generatedFiles = GeneratedFiles() guard let pluginResults = pifBuilder.buildToolPluginResultsByTargetName[module.name] else { // We found no results for the target. - return targetFiles + return generatedFiles } for pluginResult in pluginResults { @@ -404,21 +404,30 @@ struct PackagePIFProjectBuilder { } // Process all the paths of derived output paths using the same rules as for source. - var result = self.process( - pluginGeneratedFilePaths: pluginResult.allDerivedOutputPaths, - forModule: module, - toolsVersion: self.package.manifest.toolsVersion - ) - - // if the results contain headers or module maps, add the plugin output dir to the header search path - if !result.headers.isEmpty, !result.moduleMaps.isEmpty { - result.headerSearchPaths.formUnion(pluginResult.buildCommands.compactMap(\.pluginOutputDir)) + for command in pluginResult.buildCommands { + var result = self.process( + pluginGeneratedFilePaths: command.absoluteOutputPaths, + forModule: module, + toolsVersion: self.package.manifest.toolsVersion + ) + + generatedFiles.merge(result) + + // if the results contain headers or module maps, add the plugin output dir to the header search path + if !result.headers.isEmpty, !result.moduleMaps.isEmpty { + if !generatedFiles.headerSearchPaths.contains(command.pluginOutputDir) { + generatedFiles.headerSearchPaths.append(command.pluginOutputDir) + } + } } - targetFiles.merge(result) + generatedFiles.merge(self.process( + pluginGeneratedFilePaths: pluginResult.prebuildCommandOutputPaths, + forModule: module, + toolsVersion: self.package.manifest.toolsVersion)) } - return targetFiles + return generatedFiles } /// Helper function for adding build tool commands to the right PIF target depending on whether they generate