diff --git a/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift b/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift index 4116e970b14..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 - (self.pluginDerivedSources, self.pluginDerivedResources) = ModulesGraph.computePluginGeneratedFiles( + let pluginGeneratedFiles = ModulesGraph.computePluginGeneratedFiles( target: target, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, @@ -165,6 +165,23 @@ 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) + + // With Swift Build on the horizon, we won't add support for generated headers, modulemaps, and apinotes here + 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)") + } } 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..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 - (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.swift b/Sources/Build/BuildPlan/BuildPlan.swift index b54f7b939c6..8ae03a18c08 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -791,7 +791,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, @@ -800,6 +800,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/TargetSourcesBuilder.swift b/Sources/PackageLoading/TargetSourcesBuilder.swift index e218bf3f227..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, .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,76 @@ 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 + // Order matters with the header search paths + public var headerSearchPaths: [Basics.AbsolutePath] + public var headers: Set + public var moduleMaps: Set + public var apiNotes: Set + public var resources: [Basics.AbsolutePath: Resource] + + public init( + sources: Set = [], + headerSearchPaths: [Basics.AbsolutePath] = [], + 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.append(contentsOf: other.headerSearchPaths) + headers.formUnion(other.headers) + moduleMaps.formUnion(other.moduleMaps) + apiNotes.formUnion(other.apiNotes) + resources.merge(other.resources, uniquingKeysWith: { winner, _ in winner }) } } @@ -589,6 +645,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 +720,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 +816,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..29cc098c34f 100644 --- a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift +++ b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift @@ -611,41 +611,44 @@ 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 + ) + generatedFiles.merge(files) + if !files.headers.isEmpty || !files.moduleMaps.isEmpty { + // Add plugin output directory as include path + // 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) + } } } // 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 } } 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..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 95dc7920a2c..d1f1ba5ccf7 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: [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. @@ -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..4abcbcf5b80 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 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. @@ -405,19 +404,30 @@ 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) + + // 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) + } + } + } - 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 @@ -498,19 +508,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 +529,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] =