diff --git a/Fixtures/Miscellaneous/PluginGeneratedResources/Package.swift b/Fixtures/Miscellaneous/PluginGeneratedResources/Package.swift new file mode 100644 index 00000000000..8e2b4359b3c --- /dev/null +++ b/Fixtures/Miscellaneous/PluginGeneratedResources/Package.swift @@ -0,0 +1,11 @@ +// swift-tools-version: 5.7 + +import PackageDescription + +let package = Package( + name: "PluginGeneratedResources", + targets: [ + .executableTarget(name: "PluginGeneratedResources", plugins: ["Generator"]), + .plugin(name: "Generator", capability: .buildTool()), + ] +) diff --git a/Fixtures/Miscellaneous/PluginGeneratedResources/Plugins/Generator/plugin.swift b/Fixtures/Miscellaneous/PluginGeneratedResources/Plugins/Generator/plugin.swift new file mode 100644 index 00000000000..abf10eeb159 --- /dev/null +++ b/Fixtures/Miscellaneous/PluginGeneratedResources/Plugins/Generator/plugin.swift @@ -0,0 +1,15 @@ +import PackagePlugin + +@main +struct GeneratorPlugin: BuildToolPlugin { + func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { + return [ + .prebuildCommand( + displayName: "Generating empty file", + executable: .init("/usr/bin/touch"), + arguments: [context.pluginWorkDirectory.appending("best.txt")], + outputFilesDirectory: context.pluginWorkDirectory + ) + ] + } +} diff --git a/Fixtures/Miscellaneous/PluginGeneratedResources/Sources/PluginGeneratedResources/PluginGeneratedResources.swift b/Fixtures/Miscellaneous/PluginGeneratedResources/Sources/PluginGeneratedResources/PluginGeneratedResources.swift new file mode 100644 index 00000000000..29e47b2e48f --- /dev/null +++ b/Fixtures/Miscellaneous/PluginGeneratedResources/Sources/PluginGeneratedResources/PluginGeneratedResources.swift @@ -0,0 +1,13 @@ +import Foundation + +@main +public struct PluginGeneratedResources { + public private(set) var text = "Hello, World!" + + public static func main() { + let path = Bundle.module.path(forResource: "best", ofType: "txt") + let exists = FileManager.default.fileExists(atPath: path!) + assert(exists, "generated file is missing") + print(PluginGeneratedResources().text) + } +} diff --git a/Sources/Build/BuildOperation.swift b/Sources/Build/BuildOperation.swift index 4cd1900351f..b959ff20c5b 100644 --- a/Sources/Build/BuildOperation.swift +++ b/Sources/Build/BuildOperation.swift @@ -13,6 +13,7 @@ import Basics import LLBuildManifest import PackageGraph +import PackageLoading import PackageModel import SPMBuildCore import SPMLLBuild @@ -78,10 +79,14 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS (try? getBuildDescription())?.builtTestProducts ?? [] } + /// File rules to determine resource handling behavior. + private let additionalFileRules: [FileRuleDescription] + public init( buildParameters: BuildParameters, cacheBuildManifest: Bool, packageGraphLoader: @escaping () throws -> PackageGraph, + additionalFileRules: [FileRuleDescription], pluginScriptRunner: PluginScriptRunner, pluginWorkDirectory: AbsolutePath, disableSandboxForPluginCommands: Bool = false, @@ -97,6 +102,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS self.buildParameters = buildParameters self.cacheBuildManifest = cacheBuildManifest self.packageGraphLoader = packageGraphLoader + self.additionalFileRules = additionalFileRules self.pluginScriptRunner = pluginScriptRunner self.pluginWorkDirectory = pluginWorkDirectory self.disableSandboxForPluginCommands = disableSandboxForPluginCommands @@ -351,6 +357,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS let plan = try BuildPlan( buildParameters: buildParameters, graph: graph, + additionalFileRules: additionalFileRules, buildToolPluginInvocationResults: buildToolPluginInvocationResults, prebuildCommandResults: prebuildCommandResults, fileSystem: self.fileSystem, @@ -443,7 +450,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS return try pluginResults.map { pluginResult in // As we go we will collect a list of prebuild output directories whose contents should be input to the build, // and a list of the files in those directories after running the commands. - var derivedSourceFiles: [AbsolutePath] = [] + var derivedFiles: [AbsolutePath] = [] var prebuildOutputDirs: [AbsolutePath] = [] for command in pluginResult.prebuildCommands { self.observabilityScope.emit(info: "Running" + (command.configuration.displayName ?? command.configuration.executable.basename)) @@ -463,7 +470,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS // Add any files found in the output directory declared for the prebuild command after the command ends. let outputFilesDir = command.outputFilesDirectory if let swiftFiles = try? self.fileSystem.getDirectoryContents(outputFilesDir).sorted() { - derivedSourceFiles.append(contentsOf: swiftFiles.map{ outputFilesDir.appending(component: $0) }) + derivedFiles.append(contentsOf: swiftFiles.map{ outputFilesDir.appending(component: $0) }) } // Add the output directory to the list of directories whose structure should affect the build plan. @@ -471,7 +478,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS } // Add the results of running any prebuild commands for this invocation. - return PrebuildCommandResult(derivedSourceFiles: derivedSourceFiles, outputDirectories: prebuildOutputDirs) + return PrebuildCommandResult(derivedFiles: derivedFiles, outputDirectories: prebuildOutputDirs) } } diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index ddde8985e38..08ae1c5194d 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -174,6 +174,17 @@ public enum TargetBuildDescription { } } + /// The resources in this target. + var resources: [Resource] { + switch self { + case .swift(let target): + return target.resources + case .clang(let target): + // TODO: Clang targets should support generated resources in the future. + return target.target.underlyingTarget.resources + } + } + /// Path to the bundle generated for this module (if any). var bundlePath: AbsolutePath? { switch self { @@ -239,7 +250,7 @@ public final class ClangTargetBuildDescription { /// Path to the bundle generated for this module (if any). var bundlePath: AbsolutePath? { - buildParameters.bundlePath(for: target) + target.underlyingTarget.bundleName.map(buildParameters.bundlePath(named:)) } /// The modulemap file for this target, if any. @@ -542,9 +553,16 @@ public final class SwiftTargetBuildDescription { /// These are the source files derived from plugins. private var pluginDerivedSources: Sources + /// These are the resource files derived from plugins. + private var pluginDerivedResources: [Resource] + /// Path to the bundle generated for this module (if any). var bundlePath: AbsolutePath? { - buildParameters.bundlePath(for: target) + if let bundleName = target.underlyingTarget.potentialBundleName, !resources.isEmpty { + return buildParameters.bundlePath(named: bundleName) + } else { + return .none + } } /// The list of all source files in the target, including the derived ones. @@ -552,6 +570,11 @@ public final class SwiftTargetBuildDescription { target.sources.paths + derivedSources.paths + pluginDerivedSources.paths } + /// The list of all resource files in the target, including the derived ones. + public var resources: [Resource] { + target.underlyingTarget.resources + pluginDerivedResources + } + /// The objects in this target. public var objects: [AbsolutePath] { let relativePaths = target.sources.relativePaths + derivedSources.relativePaths + pluginDerivedSources.relativePaths @@ -633,16 +656,21 @@ public final class SwiftTargetBuildDescription { /// The results of running any prebuild commands for this target. public let prebuildCommandResults: [PrebuildCommandResult] + /// ObservabilityScope with which to emit diagnostics + private let observabilityScope: ObservabilityScope + /// Create a new target description with target and build parameters. init( target: ResolvedTarget, toolsVersion: ToolsVersion, + additionalFileRules: [FileRuleDescription] = [], buildParameters: BuildParameters, buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [], prebuildCommandResults: [PrebuildCommandResult] = [], isTestTarget: Bool? = nil, isTestDiscoveryTarget: Bool = false, - fileSystem: FileSystem + fileSystem: FileSystem, + observabilityScope: ObservabilityScope ) throws { guard target.underlyingTarget is SwiftTarget else { throw InternalError("underlying target type mismatch \(target)") @@ -659,25 +687,31 @@ public final class SwiftTargetBuildDescription { self.pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath) self.buildToolPluginInvocationResults = buildToolPluginInvocationResults self.prebuildCommandResults = prebuildCommandResults + self.observabilityScope = observabilityScope - // Add any derived source files that were declared for any commands from plugin invocations. + // Add any derived files that were declared for any commands from plugin invocations. + var pluginDerivedFiles = [AbsolutePath]() for command in buildToolPluginInvocationResults.reduce([], { $0 + $1.buildCommands }) { - // TODO: What should we do if we find non-Swift sources here? for absPath in command.outputFiles { - let relPath = absPath.relative(to: self.pluginDerivedSources.root) - self.pluginDerivedSources.relativePaths.append(relPath) + pluginDerivedFiles.append(absPath) } } - // Add any derived source files that were discovered from output directories of prebuild commands. + // Add any derived files that were discovered from output directories of prebuild commands. for result in self.prebuildCommandResults { - // TODO: What should we do if we find non-Swift sources here? - for path in result.derivedSourceFiles { - let relPath = path.relative(to: self.pluginDerivedSources.root) - self.pluginDerivedSources.relativePaths.append(relPath) + 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.underlyingTarget.path, observabilityScope: observabilityScope) + self.pluginDerivedResources = derivedResources + derivedSources.forEach { absPath in + let relPath = absPath.relative(to: self.pluginDerivedSources.root) + self.pluginDerivedSources.relativePaths.append(relPath) + } + if shouldEmitObjCCompatibilityHeader { self.moduleMap = try self.generateModuleMap() } @@ -1585,7 +1619,8 @@ public class BuildPlan { toolsVersion: toolsVersion, buildParameters: buildParameters, isTestTarget: true, - fileSystem: fileSystem + fileSystem: fileSystem, + observabilityScope: observabilityScope ) result.append((testProduct, desc)) @@ -1621,7 +1656,8 @@ public class BuildPlan { buildParameters: buildParameters, isTestTarget: true, isTestDiscoveryTarget: true, - fileSystem: fileSystem + fileSystem: fileSystem, + observabilityScope: observabilityScope ) result.append((testProduct, target)) @@ -1635,28 +1671,11 @@ public class BuildPlan { return result } - @available(*, deprecated, message: "use observability system instead") - public convenience init( - buildParameters: BuildParameters, - graph: PackageGraph, - buildToolPluginInvocationResults: [ResolvedTarget: [BuildToolPluginInvocationResult]] = [:], - prebuildCommandResults: [ResolvedTarget: [PrebuildCommandResult]] = [:], - diagnostics: DiagnosticsEngine, - fileSystem: FileSystem - ) throws { - let observabilitySystem = ObservabilitySystem(diagnosticEngine: diagnostics) - try self.init( - buildParameters: buildParameters, - graph: graph, - fileSystem: fileSystem, - observabilityScope: observabilitySystem.topScope - ) - } - /// Create a build plan with build parameters and a package graph. public init( buildParameters: BuildParameters, graph: PackageGraph, + additionalFileRules: [FileRuleDescription] = [], buildToolPluginInvocationResults: [ResolvedTarget: [BuildToolPluginInvocationResult]] = [:], prebuildCommandResults: [ResolvedTarget: [PrebuildCommandResult]] = [:], fileSystem: FileSystem, @@ -1700,10 +1719,12 @@ public class BuildPlan { targetMap[target] = try .swift(SwiftTargetBuildDescription( target: target, toolsVersion: toolsVersion, + additionalFileRules: additionalFileRules, buildParameters: buildParameters, buildToolPluginInvocationResults: buildToolPluginInvocationResults[target] ?? [], prebuildCommandResults: prebuildCommandResults[target] ?? [], - fileSystem: fileSystem) + fileSystem: fileSystem, + observabilityScope: observabilityScope) ) case is ClangTarget: targetMap[target] = try .clang(ClangTargetBuildDescription( @@ -2236,11 +2257,9 @@ private extension Basics.Diagnostic { } extension BuildParameters { - /// Returns a target's bundle path inside the build directory. - fileprivate func bundlePath(for target: ResolvedTarget) -> AbsolutePath? { - target.underlyingTarget.bundleName - .map{ $0 + triple.nsbundleExtension } - .map(buildPath.appending(component:)) + /// Returns a named bundle's path inside the build directory. + fileprivate func bundlePath(named name: String) -> AbsolutePath { + return buildPath.appending(component: name + triple.nsbundleExtension) } } diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index d461d9c38f7..8b1f6881f16 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -171,7 +171,7 @@ extension LLBuildManifestBuilder { let infoPlistDestination = RelativePath("Info.plist") // Create a copy command for each resource file. - for resource in target.target.underlyingTarget.resources { + for resource in target.resources { let destination = bundlePath.appending(resource.destination) let (_, output) = addCopyCommand(from: resource.path, to: destination) outputs.append(output) diff --git a/Sources/Commands/SwiftTool.swift b/Sources/Commands/SwiftTool.swift index a5defd20436..3d5e76015f6 100644 --- a/Sources/Commands/SwiftTool.swift +++ b/Sources/Commands/SwiftTool.swift @@ -752,6 +752,7 @@ public class SwiftTool { buildParameters: customBuildParameters ?? self.buildParameters(), cacheBuildManifest: cacheBuildManifest && self.canUseCachedBuildManifest(), packageGraphLoader: customPackageGraphLoader ?? graphLoader, + additionalFileRules: FileRuleDescription.swiftpmFileTypes, pluginScriptRunner: self.getPluginScriptRunner(), pluginWorkDirectory: try self.getActiveWorkspace().location.pluginWorkingDirectory, disableSandboxForPluginCommands: self.options.security.shouldDisableSandbox, diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index ad68fab4fc3..e949b128afe 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -767,7 +767,7 @@ public final class PackageBuilder { // FIXME: use identity instead? // The name of the bundle, if one is being generated. - let bundleName = resources.isEmpty ? nil : self.manifest.displayName + "_" + potentialModule.name + let potentialBundleName = self.manifest.displayName + "_" + potentialModule.name if sources.relativePaths.isEmpty && resources.isEmpty { return nil @@ -808,8 +808,9 @@ public final class PackageBuilder { if sources.hasSwiftSources { return SwiftTarget( name: potentialModule.name, - bundleName: bundleName, + potentialBundleName: potentialBundleName, type: targetType, + path: potentialModule.path, sources: sources, resources: resources, ignored: ignored, @@ -836,13 +837,14 @@ public final class PackageBuilder { return try ClangTarget( name: potentialModule.name, - bundleName: bundleName, + potentialBundleName: potentialBundleName, cLanguageStandard: manifest.cLanguageStandard, cxxLanguageStandard: manifest.cxxLanguageStandard, includeDir: publicHeadersPath, moduleMapType: moduleMapType, headers: headers, type: targetType, + path: potentialModule.path, sources: sources, resources: resources, ignored: ignored, @@ -1362,6 +1364,7 @@ extension PackageBuilder { return SwiftTarget( name: name, type: .snippet, + path: .root, sources: sources, dependencies: dependencies, swiftVersion: try swiftVersion(), diff --git a/Sources/PackageLoading/TargetSourcesBuilder.swift b/Sources/PackageLoading/TargetSourcesBuilder.swift index eb308761315..a5cb2db95bb 100644 --- a/Sources/PackageLoading/TargetSourcesBuilder.swift +++ b/Sources/PackageLoading/TargetSourcesBuilder.swift @@ -75,9 +75,7 @@ public struct TargetSourcesBuilder { self.target = target self.defaultLocalization = defaultLocalization self.targetPath = path - // In version 5.4 and earlier, SwiftPM did not support `additionalFileRules` and always implicitly included XCBuild file types. - let actualAdditionalRules = (toolsVersion <= ToolsVersion.v5_4 ? FileRuleDescription.xcbuildFileTypes : additionalFileRules) - self.rules = FileRuleDescription.builtinRules + actualAdditionalRules + self.rules = Self.rules(additionalFileRules: additionalFileRules, toolsVersion: toolsVersion) self.toolsVersion = toolsVersion let excludedPaths = target.exclude.map { AbsolutePath($0, relativeTo: path) } self.excludedPaths = Set(excludedPaths) @@ -125,6 +123,12 @@ public struct TargetSourcesBuilder { #endif } + private static func rules(additionalFileRules: [FileRuleDescription], toolsVersion: ToolsVersion) -> [FileRuleDescription] { + // In version 5.4 and earlier, SwiftPM did not support `additionalFileRules` and always implicitly included XCBuild file types. + let actualAdditionalRules = (toolsVersion <= .v5_4 ? FileRuleDescription.xcbuildFileTypes : additionalFileRules) + return FileRuleDescription.builtinRules + actualAdditionalRules + } + @discardableResult private func validTargetPath(at: AbsolutePath) -> Error? { // Check if paths that are enumerated in targets: [] exist @@ -185,26 +189,35 @@ public struct TargetSourcesBuilder { } /// Compute the rule for the given path. - private func computeRule(for path: AbsolutePath) -> FileRuleDescription.Rule { + private static func computeRule(for path: AbsolutePath, + toolsVersion: ToolsVersion, + additionalFileRules: [FileRuleDescription], + observabilityScope: ObservabilityScope) -> FileRuleDescription.Rule { + let rules = Self.rules(additionalFileRules: additionalFileRules, toolsVersion: toolsVersion) + // For now, we are not passing in any declared resources or sources here and instead handle any generated files automatically at the callsite. Eventually, we will want the ability to declare opinions for generated files in the manifest as well. + return Self.computeRule(for: path, toolsVersion: toolsVersion, rules: rules, declaredResources: [], declaredSources: nil, observabilityScope: observabilityScope) + } + + private static func computeRule(for path: AbsolutePath, toolsVersion: ToolsVersion, rules: [FileRuleDescription], declaredResources: [(path: AbsolutePath, rule: TargetDescription.Resource.Rule)], declaredSources: [AbsolutePath]?, observabilityScope: ObservabilityScope) -> FileRuleDescription.Rule { var matchedRule: FileRuleDescription.Rule = .none // First match any resources explicitly declared in the manifest file. - for declaredResource in target.resources { - let resourcePath = AbsolutePath(declaredResource.path, relativeTo: self.targetPath) + for declaredResource in declaredResources { + let resourcePath = declaredResource.path if path.isDescendantOfOrEqual(to: resourcePath) { if matchedRule != .none { - self.observabilityScope.emit(error: "duplicate resource rule '\(declaredResource.rule)' found for file at '\(path)'") + observabilityScope.emit(error: "duplicate resource rule '\(declaredResource.rule)' found for file at '\(path)'") } matchedRule = .init(declaredResource.rule) } } // Match any sources explicitly declared in the manifest file. - if let declaredSources = self.declaredSources { + if let declaredSources = declaredSources { for sourcePath in declaredSources { if path.isDescendantOfOrEqual(to: sourcePath) { if matchedRule != .none { - self.observabilityScope.emit(error: "duplicate rule found for file at '\(path)'") + observabilityScope.emit(error: "duplicate rule found for file at '\(path)'") } // Check for header files as they're allowed to be mixed with sources. @@ -231,10 +244,10 @@ public struct TargetSourcesBuilder { let effectiveRules: [FileRuleDescription] = { // Don't automatically match compile rules if target's sources are // explicitly declared in the package manifest. - if target.sources != nil { - return self.rules.filter { $0.rule != .compile } + if declaredSources != nil { + return rules.filter { $0.rule != .compile } } - return self.rules + return rules }() if let needle = effectiveRules.first(where: { $0.match(path: path, toolsVersion: toolsVersion) }) { @@ -247,8 +260,13 @@ public struct TargetSourcesBuilder { return matchedRule } + private func computeRule(for path: AbsolutePath) -> FileRuleDescription.Rule { + let declaredResources = target.resources.map { (path: AbsolutePath($0.path, relativeTo: self.targetPath), rule: $0.rule) } + return Self.computeRule(for: path, toolsVersion: toolsVersion, rules: rules, declaredResources: declaredResources, declaredSources: declaredSources, observabilityScope: observabilityScope) + } + /// Returns the `Resource` file associated with a file and a particular rule, if there is one. - private func resource(for path: AbsolutePath, with rule: FileRuleDescription.Rule) -> Resource? { + private static func resource(for path: AbsolutePath, with rule: FileRuleDescription.Rule, defaultLocalization: String?, targetName: String, targetPath: AbsolutePath, observabilityScope: ObservabilityScope) -> Resource? { switch rule { case .compile, .header, .none, .modulemap, .ignored: return nil @@ -275,7 +293,7 @@ public struct TargetSourcesBuilder { // If a resource is both inside a localization directory and has an explicit localization, it's ambiguous. guard implicitLocalization == nil || explicitLocalization == nil else { let relativePath = path.relative(to: targetPath) - self.observabilityScope.emit(.localizationAmbiguity(path: relativePath, targetName: target.name)) + observabilityScope.emit(.localizationAmbiguity(path: relativePath, targetName: targetName)) return nil } @@ -285,6 +303,10 @@ public struct TargetSourcesBuilder { } } + private func resource(for path: AbsolutePath, with rule: FileRuleDescription.Rule) -> Resource? { + return Self.resource(for: path, with: rule, defaultLocalization: defaultLocalization, targetName: target.name, targetPath: targetPath, observabilityScope: observabilityScope) + } + private func diagnoseConflictingResources(in resources: [Resource]) { let duplicateResources = resources.spm_findDuplicateElements(by: \.destination) for resources in duplicateResources { @@ -454,6 +476,49 @@ public struct TargetSourcesBuilder { return contents } + + public static func computeContents(for generatedFiles: [AbsolutePath], toolsVersion: ToolsVersion, additionalFileRules: [FileRuleDescription], defaultLocalization: String?, targetName: String, targetPath: AbsolutePath, observabilityScope: ObservabilityScope) -> (sources: [AbsolutePath], resources: [Resource]) { + var sources = [AbsolutePath]() + var resources = [Resource]() + + generatedFiles.forEach { absPath in + // 5.6 handled treated all generated files as sources. + if toolsVersion <= .v5_6 { + sources.append(absPath) + return + } + + var rule = Self.computeRule(for: absPath, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, observabilityScope: observabilityScope) + + // If we did not find a rule for a generated file, we treat it as to be processed for now. Eventually, we should handle generated files the same as other files and require explicit handling in the manifest for unknown types. + if rule == .none { + rule = .processResource(localization: .none) + } + + switch rule { + case .compile: + if absPath.extension == "swift" { + sources.append(absPath) + } else { + observabilityScope.emit(warning: "Only Swift is supported for generated plugin source files at this time: \(absPath)") + } + case .copy, .processResource: + if let resource = Self.resource(for: absPath, with: rule, defaultLocalization: defaultLocalization, targetName: targetName, targetPath: targetPath, observabilityScope: observabilityScope) { + resources.append(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)") + case .modulemap: + observabilityScope.emit(warning: "Module maps generated by plugins are not supported at this time: \(absPath)") + case .ignored, .none: + break + } + } + + return (sources, resources) + } } /// Describes a rule for including a source or resource file in a target. diff --git a/Sources/PackageModel/Target.swift b/Sources/PackageModel/Target.swift index 8808c26f1ea..6f776cfdecb 100644 --- a/Sources/PackageModel/Target.swift +++ b/Sources/PackageModel/Target.swift @@ -153,7 +153,10 @@ public class Target: PolymorphicCodableProtocol { public private(set) var c99name: String /// The bundle name, if one is being generated. - public let bundleName: String? + public var bundleName: String? { + return resources.isEmpty ? nil : potentialBundleName + } + public let potentialBundleName: String? /// Suffix that's expected for test targets. public static let testModuleNameSuffix = "Tests" @@ -161,6 +164,9 @@ public class Target: PolymorphicCodableProtocol { /// The kind of target. public let type: Kind + /// The path of the target. + public let path: AbsolutePath + /// The sources for the target. public let sources: Sources @@ -181,8 +187,9 @@ public class Target: PolymorphicCodableProtocol { fileprivate init( name: String, - bundleName: String? = nil, + potentialBundleName: String? = nil, type: Kind, + path: AbsolutePath, sources: Sources, resources: [Resource] = [], ignored: [AbsolutePath] = [], @@ -192,8 +199,9 @@ public class Target: PolymorphicCodableProtocol { pluginUsages: [PluginUsage] ) { self.name = name - self.bundleName = bundleName + self.potentialBundleName = potentialBundleName self.type = type + self.path = path self.sources = sources self.resources = resources self.ignored = ignored @@ -205,7 +213,7 @@ public class Target: PolymorphicCodableProtocol { } private enum CodingKeys: String, CodingKey { - case name, bundleName, defaultLocalization, platforms, type, sources, resources, ignored, others, buildSettings, pluginUsages + case name, potentialBundleName, defaultLocalization, platforms, type, path, sources, resources, ignored, others, buildSettings, pluginUsages } public func encode(to encoder: Encoder) throws { @@ -214,10 +222,9 @@ public class Target: PolymorphicCodableProtocol { // FIXME: dependencies property is skipped on purpose as it points to // the actual target dependency object. try container.encode(name, forKey: .name) - try container.encode(bundleName, forKey: .bundleName) - //try container.encode(defaultLocalization, forKey: .defaultLocalization) - //try container.encode(platforms, forKey: .platforms) + try container.encode(potentialBundleName, forKey: .potentialBundleName) try container.encode(type, forKey: .type) + try container.encode(path, forKey: .path) try container.encode(sources, forKey: .sources) try container.encode(resources, forKey: .resources) try container.encode(ignored, forKey: .ignored) @@ -230,8 +237,9 @@ public class Target: PolymorphicCodableProtocol { required public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.name = try container.decode(String.self, forKey: .name) - self.bundleName = try container.decodeIfPresent(String.self, forKey: .bundleName) + self.potentialBundleName = try container.decodeIfPresent(String.self, forKey: .potentialBundleName) self.type = try container.decode(Kind.self, forKey: .type) + self.path = try container.decode(AbsolutePath.self, forKey: .path) self.sources = try container.decode(Sources.self, forKey: .sources) self.resources = try container.decode([Resource].self, forKey: .resources) self.ignored = try container.decode([AbsolutePath].self, forKey: .ignored) @@ -274,6 +282,7 @@ public final class SwiftTarget: Target { super.init( name: name, type: .executable, + path: .root, sources: testDiscoverySrc, dependencies: dependencies, buildSettings: .init(), @@ -286,8 +295,9 @@ public final class SwiftTarget: Target { public init( name: String, - bundleName: String? = nil, + potentialBundleName: String? = nil, type: Kind, + path: AbsolutePath, sources: Sources, resources: [Resource] = [], ignored: [AbsolutePath] = [], @@ -300,8 +310,9 @@ public final class SwiftTarget: Target { self.swiftVersion = swiftVersion super.init( name: name, - bundleName: bundleName, + potentialBundleName: potentialBundleName, type: type, + path: path, sources: sources, resources: resources, ignored: ignored, @@ -332,6 +343,7 @@ public final class SwiftTarget: Target { super.init( name: name, type: .executable, + path: .root, sources: sources, dependencies: dependencies, buildSettings: .init(), @@ -364,11 +376,6 @@ public final class SystemLibraryTarget: Target { /// List of system package providers, if any. public let providers: [SystemPackageProviderDescription]? - /// The package path. - public var path: AbsolutePath { - return sources.root - } - /// True if this system library should become implicit target /// dependency of its dependent packages. public let isImplicit: Bool @@ -387,6 +394,7 @@ public final class SystemLibraryTarget: Target { super.init( name: name, type: .systemModule, + path: sources.root, sources: sources, dependencies: [], buildSettings: .init(), @@ -442,13 +450,14 @@ public final class ClangTarget: Target { public init( name: String, - bundleName: String? = nil, + potentialBundleName: String? = nil, cLanguageStandard: String?, cxxLanguageStandard: String?, includeDir: AbsolutePath, moduleMapType: ModuleMapType, headers: [AbsolutePath] = [], type: Kind, + path: AbsolutePath, sources: Sources, resources: [Resource] = [], ignored: [AbsolutePath] = [], @@ -467,8 +476,9 @@ public final class ClangTarget: Target { self.headers = headers super.init( name: name, - bundleName: bundleName, + potentialBundleName: potentialBundleName, type: type, + path: path, sources: sources, resources: resources, ignored: ignored, @@ -530,6 +540,7 @@ public final class BinaryTarget: Target { super.init( name: name, type: .binary, + path: .root, sources: sources, dependencies: [], buildSettings: .init(), @@ -650,6 +661,7 @@ public final class PluginTarget: Target { super.init( name: name, type: .plugin, + path: .root, sources: sources, dependencies: dependencies, buildSettings: .init(), diff --git a/Sources/SPMBuildCore/PrebuildCommandResult.swift b/Sources/SPMBuildCore/PrebuildCommandResult.swift index 25717164f95..7a2c9890c80 100644 --- a/Sources/SPMBuildCore/PrebuildCommandResult.swift +++ b/Sources/SPMBuildCore/PrebuildCommandResult.swift @@ -16,14 +16,14 @@ import TSCBasic /// Represents the result of running prebuild commands for a single plugin invocation for a target. public struct PrebuildCommandResult { - /// Paths of any derived source files that should be included in the build. - public var derivedSourceFiles: [AbsolutePath] + /// Paths of any derived files that should be included in the build. + public var derivedFiles: [AbsolutePath] /// Paths of any directories whose contents influence the build plan. public var outputDirectories: [AbsolutePath] - public init(derivedSourceFiles: [AbsolutePath], outputDirectories: [AbsolutePath]) { - self.derivedSourceFiles = derivedSourceFiles + public init(derivedFiles: [AbsolutePath], outputDirectories: [AbsolutePath]) { + self.derivedFiles = derivedFiles self.outputDirectories = outputDirectories } } diff --git a/Tests/FunctionalTests/MiscellaneousTests.swift b/Tests/FunctionalTests/MiscellaneousTests.swift index 4f36e7832db..1df0ce63dc4 100644 --- a/Tests/FunctionalTests/MiscellaneousTests.swift +++ b/Tests/FunctionalTests/MiscellaneousTests.swift @@ -802,4 +802,16 @@ class MiscellaneousTestCase: XCTestCase { } #endif } + + func testPluginGeneratedResources() throws { + #if swift(<5.6) + try XCTSkipIf(true, "skipping because of potential concurrency back deployment issues") + #endif + + try fixture(name: "Miscellaneous/PluginGeneratedResources") { path in + let result = try SwiftPMProduct.SwiftRun.execute([], packagePath: path) + XCTAssertEqual(result.stdout, "Hello, World!\n", "executable did not produce expected output") + XCTAssertTrue(result.stderr.contains("Copying best.txt\n"), "build log is missing message about copying resource file") + } + } } diff --git a/Tests/PackageGraphTests/TargetTests.swift b/Tests/PackageGraphTests/TargetTests.swift index 95ca7a9b97e..b506796552c 100644 --- a/Tests/PackageGraphTests/TargetTests.swift +++ b/Tests/PackageGraphTests/TargetTests.swift @@ -20,7 +20,9 @@ private extension ResolvedTarget { convenience init(name: String, deps: ResolvedTarget...) { self.init( target: SwiftTarget( - name: name, type: .library, + name: name, + type: .library, + path: .root, sources: Sources(paths: [], root: AbsolutePath("/")), dependencies: [], swiftVersion: .v4