diff --git a/Sources/PackageDescription/PackageDescriptionSerialization.swift b/Sources/PackageDescription/PackageDescriptionSerialization.swift index ad852b9d859..c0277884b8f 100644 --- a/Sources/PackageDescription/PackageDescriptionSerialization.swift +++ b/Sources/PackageDescription/PackageDescriptionSerialization.swift @@ -196,7 +196,7 @@ enum Serialization { } enum PluginUsage: Codable { - case plugin(name: String, package: String?) + case plugin(name: String, package: String?, arguments: [String]) } struct Target: Codable { diff --git a/Sources/PackageDescription/PackageDescriptionSerializationConversion.swift b/Sources/PackageDescription/PackageDescriptionSerializationConversion.swift index e85d9c0cd1f..c804ff0060a 100644 --- a/Sources/PackageDescription/PackageDescriptionSerializationConversion.swift +++ b/Sources/PackageDescription/PackageDescriptionSerializationConversion.swift @@ -274,7 +274,7 @@ extension Serialization.PluginNetworkPermissionScope { extension Serialization.PluginUsage { init(_ usage: PackageDescription.Target.PluginUsage) { switch usage { - case .plugin(let name, let package): self = .plugin(name: name, package: package) + case .plugin(let name, let package, let arguments): self = .plugin(name: name, package: package, arguments: arguments) } } } diff --git a/Sources/PackageDescription/Target.swift b/Sources/PackageDescription/Target.swift index ce77847d291..8dc8d837a75 100644 --- a/Sources/PackageDescription/Target.swift +++ b/Sources/PackageDescription/Target.swift @@ -161,7 +161,7 @@ public final class Target { /// The plug-in to apply to each target that uses it, and creates commands /// that run before or during the build of the target. @available(_PackageDescription, introduced: 5.5) - case buildTool + case buildTool(arguments: [String]) /// Specifies that the plug-in provides a user command capability. /// @@ -227,7 +227,7 @@ public final class Target { /// - Parameters: /// - name: The name of the plug-in target. /// - package: The name of the package that defines the plug-in target. - case plugin(name: String, package: String?) + case plugin(name: String, package: String?, arguments: [String]) } /// Construct a target. @@ -1398,7 +1398,7 @@ extension Target.PluginCapability { /// - Returns: A plug-in capability that defines a build tool. @available(_PackageDescription, introduced: 5.5) public static func buildTool() -> Target.PluginCapability { - return .buildTool + return .buildTool(arguments: []) } } @@ -1507,8 +1507,8 @@ extension Target.PluginUsage { /// - Parameter name: The name of the plugin target. /// - Returns: A `PluginUsage` instance. @available(_PackageDescription, introduced: 5.5) - public static func plugin(name: String) -> Target.PluginUsage { - return .plugin(name: name, package: nil) + public static func plugin(name: String, arguments: [String]) -> Target.PluginUsage { + return .plugin(name: name, package: nil, arguments: arguments) } } @@ -1535,7 +1535,7 @@ extension Target.PluginUsage: ExpressibleByStringLiteral { /// /// - Parameter value: A string literal. public init(stringLiteral value: String) { - self = .plugin(name: value, package: nil) + self = .plugin(name: value, package: nil, arguments: []) } } diff --git a/Sources/PackageLoading/ManifestJSONParser.swift b/Sources/PackageLoading/ManifestJSONParser.swift index 564e5991982..e850db7af62 100644 --- a/Sources/PackageLoading/ManifestJSONParser.swift +++ b/Sources/PackageLoading/ManifestJSONParser.swift @@ -427,8 +427,8 @@ extension TargetDescription.PluginNetworkPermissionScope { extension TargetDescription.PluginUsage { init(_ usage: Serialization.PluginUsage) { switch usage { - case .plugin(let name, let package): - self = .plugin(name: name, package: package) + case .plugin(let name, let package, let arguments): + self = .plugin(name: name, package: package, arguments: arguments) } } } diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 6f5f17f9563..d913a61619a 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -672,7 +672,7 @@ public final class PackageBuilder { let potentialModuleMap = Dictionary(potentialModules.map { ($0.name, $0) }, uniquingKeysWith: { $1 }) let successors: (PotentialModule) -> [PotentialModule] = { // No reference of this target in manifest, i.e. it has no dependencies. - guard let target = self.manifest.targetMap[$0.name] else { return [] } + guard let target: TargetDescription = self.manifest.targetMap[$0.name] else { return [] } // Collect the successors from declared dependencies. var successors: [PotentialModule] = target.dependencies.compactMap { switch $0 { @@ -692,9 +692,9 @@ public final class PackageBuilder { if let pluginUsages = target.pluginUsages { successors += pluginUsages.compactMap { switch $0 { - case .plugin(_, .some(_)): + case .plugin(_, .some(_), _): nil - case .plugin(let name, nil): + case .plugin(let name, nil, _): if let potentialModule = potentialModuleMap[name] { potentialModule } else if let targetName = pluginTargetName(for: name), @@ -766,13 +766,17 @@ public final class PackageBuilder { let pluginUsages: [Target.PluginUsage] = manifestTarget?.pluginUsages.map { $0.compactMap { usage in switch usage { - case .plugin(let name, let package): + case .plugin(let name, let package, let arguments): if let package { return .product(Target.ProductReference(name: name, package: package), conditions: []) } else { if let target = targets[name] { + if let arguments { + (target as? PluginTarget)?.arguments = arguments + } return .target(target, conditions: []) } else if let targetName = pluginTargetName(for: name), let target = targets[targetName] { + // TODO: Check return .target(target, conditions: []) } else { self.observabilityScope.emit(.pluginNotFound(name: name)) @@ -960,7 +964,8 @@ public final class PackageBuilder { apiVersion: self.manifest.toolsVersion, pluginCapability: PluginCapability(from: declaredCapability), dependencies: dependencies, - packageAccess: potentialModule.packageAccess + packageAccess: potentialModule.packageAccess, + arguments: [] ) } diff --git a/Sources/PackageModel/Manifest/Manifest.swift b/Sources/PackageModel/Manifest/Manifest.swift index 73adb0802e0..507d05b3d19 100644 --- a/Sources/PackageModel/Manifest/Manifest.swift +++ b/Sources/PackageModel/Manifest/Manifest.swift @@ -237,7 +237,7 @@ public final class Manifest: Sendable { let plugins: [String] = target.pluginUsages?.compactMap { pluginUsage in switch pluginUsage { - case .plugin(name: let name, package: nil): + case .plugin(name: let name, package: nil, _): if targetsByName.keys.contains(name) { return name } else if let targetName = productsByName[name]?.targets.first { @@ -334,7 +334,7 @@ public final class Manifest: Sendable { referencedBy pluginUsage: TargetDescription.PluginUsage ) -> PackageDependency? { switch pluginUsage { - case .plugin(_, .some(let package)): + case .plugin(_, .some(let package), _): return self.packageDependency(referencedBy: package) default: return nil @@ -433,7 +433,7 @@ public final class Manifest: Sendable { availablePackages: Set ) { switch requiredPlugIn { - case .plugin(let name, let package): + case .plugin(let name, let package, _): if let package { if !self.register( product: name, diff --git a/Sources/PackageModel/Manifest/TargetDescription.swift b/Sources/PackageModel/Manifest/TargetDescription.swift index 1c943a5313c..2bf945af381 100644 --- a/Sources/PackageModel/Manifest/TargetDescription.swift +++ b/Sources/PackageModel/Manifest/TargetDescription.swift @@ -157,7 +157,7 @@ public struct TargetDescription: Hashable, Encodable, Sendable { /// Represents a target's usage of a plugin target or product. public enum PluginUsage: Hashable, Sendable { - case plugin(name: String, package: String?) + case plugin(name: String, package: String?, arguments: [String]?) } public init( @@ -357,10 +357,11 @@ extension TargetDescription.PluginUsage: Codable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { - case let .plugin(name, package): + case let .plugin(name, package, arguments): var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .plugin) try unkeyedContainer.encode(name) try unkeyedContainer.encode(package) + try unkeyedContainer.encode(arguments) } } @@ -374,7 +375,8 @@ extension TargetDescription.PluginUsage: Codable { var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key) let name = try unkeyedValues.decode(String.self) let package = try unkeyedValues.decodeIfPresent(String.self) - self = .plugin(name: name, package: package) + let arguments = try? unkeyedValues.decodeIfPresent([String].self) + self = .plugin(name: name, package: package, arguments: arguments) } } } diff --git a/Sources/PackageModel/Target/PluginTarget.swift b/Sources/PackageModel/Target/PluginTarget.swift index 84d04d0fee5..ba13be68800 100644 --- a/Sources/PackageModel/Target/PluginTarget.swift +++ b/Sources/PackageModel/Target/PluginTarget.swift @@ -18,16 +18,20 @@ public final class PluginTarget: Target { /// API version to use for PackagePlugin API availability. public let apiVersion: ToolsVersion + public var arguments: [String] + public init( name: String, sources: Sources, apiVersion: ToolsVersion, pluginCapability: PluginCapability, dependencies: [Target.Dependency] = [], - packageAccess: Bool + packageAccess: Bool, + arguments: [String] ) { self.capability = pluginCapability self.apiVersion = apiVersion + self.arguments = arguments super.init( name: name, type: .plugin, @@ -45,12 +49,14 @@ public final class PluginTarget: Target { private enum CodingKeys: String, CodingKey { case capability case apiVersion + case arguments } public override func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.capability, forKey: .capability) try container.encode(self.apiVersion, forKey: .apiVersion) + try container.encode(self.arguments, forKey: .arguments) try super.encode(to: encoder) } @@ -58,6 +64,7 @@ public final class PluginTarget: Target { let container = try decoder.container(keyedBy: CodingKeys.self) self.capability = try container.decode(PluginCapability.self, forKey: .capability) self.apiVersion = try container.decode(ToolsVersion.self, forKey: .apiVersion) + self.arguments = try container.decode([String].self, forKey: .arguments) try super.init(from: decoder) } } diff --git a/Sources/PackagePlugin/Plugin.swift b/Sources/PackagePlugin/Plugin.swift index 8ec16fe2730..e95f372bb3c 100644 --- a/Sources/PackagePlugin/Plugin.swift +++ b/Sources/PackagePlugin/Plugin.swift @@ -141,7 +141,7 @@ extension Plugin { fileprivate static func handleMessage(_ message: HostToPluginMessage) async throws { switch message { - case .createBuildToolCommands(let wireInput, let rootPackageId, let targetId, let generatedSources, let generatedResources): + case .createBuildToolCommands(let wireInput, let rootPackageId, let targetId, let generatedSources, let generatedResources, let arguments): // Deserialize the context from the wire input structures. The root // package is the one we'll set the context's `package` property to. let context: PluginContext @@ -195,7 +195,7 @@ extension Plugin { } // Invoke the plugin to create build commands for the target. - let generatedCommands = try await plugin.createBuildCommands(context: context, target: target) + let generatedCommands = try await plugin.createBuildCommands(context: context, target: target, arguments: arguments) // Send each of the generated commands to the host. for command in generatedCommands { diff --git a/Sources/PackagePlugin/PluginMessages.swift b/Sources/PackagePlugin/PluginMessages.swift index 75b03fdab06..404e225eecb 100644 --- a/Sources/PackagePlugin/PluginMessages.swift +++ b/Sources/PackagePlugin/PluginMessages.swift @@ -21,7 +21,8 @@ enum HostToPluginMessage: Codable { rootPackageId: InputContext.Package.Id, targetId: InputContext.Target.Id, pluginGeneratedSources: [InputContext.URL.Id], - pluginGeneratedResources: [InputContext.URL.Id] + pluginGeneratedResources: [InputContext.URL.Id], + arguments: [String] ) /// The host requests that the plugin perform a user command (corresponding to a `.command` capability) on a package in the graph. diff --git a/Sources/PackagePlugin/Protocols.swift b/Sources/PackagePlugin/Protocols.swift index 3e3fd35fb05..6e894f07cea 100644 --- a/Sources/PackagePlugin/Protocols.swift +++ b/Sources/PackagePlugin/Protocols.swift @@ -36,7 +36,8 @@ public protocol BuildToolPlugin: Plugin { /// that it does not directly run those commands. func createBuildCommands( context: PluginContext, - target: Target + target: Target, + arguments: [String] ) async throws -> [Command] } diff --git a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift index 3ab08cd47d5..1428bd7cab0 100644 --- a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift +++ b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift @@ -23,7 +23,8 @@ public enum PluginAction { package: ResolvedPackage, target: ResolvedModule, pluginGeneratedSources: [AbsolutePath], - pluginGeneratedResources: [AbsolutePath] + pluginGeneratedResources: [AbsolutePath], + arguments: [String] ) case performCommand(package: ResolvedPackage, arguments: [String]) } @@ -46,7 +47,8 @@ extension PluginTarget { modulesGraph: ModulesGraph, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, - delegate: PluginInvocationDelegate + delegate: PluginInvocationDelegate, + arguments: [String] ) async throws -> Bool { try await safe_async { self.invoke( @@ -142,7 +144,7 @@ extension PluginTarget { let actionMessage: HostToPluginMessage switch action { - case .createBuildToolCommands(let package, let target, let pluginGeneratedSources, let pluginGeneratedResources): + case .createBuildToolCommands(let package, let target, let pluginGeneratedSources, let pluginGeneratedResources, let arguments): let rootPackageId = try serializer.serialize(package: package) guard let targetId = try serializer.serialize(target: target) else { throw StringError("unexpectedly was unable to serialize target \(target)") @@ -162,7 +164,8 @@ extension PluginTarget { rootPackageId: rootPackageId, targetId: targetId, pluginGeneratedSources: generatedSources, - pluginGeneratedResources: generatedResources + pluginGeneratedResources: generatedResources, + arguments: arguments ) case .performCommand(let package, let arguments): let rootPackageId = try serializer.serialize(package: package) @@ -561,7 +564,8 @@ extension ModulesGraph { package: package, target: target, pluginGeneratedSources: pluginDerivedSources.paths, - pluginGeneratedResources: pluginDerivedResources.map { $0.path } + pluginGeneratedResources: pluginDerivedResources.map { $0.path }, + arguments: pluginTarget.arguments ), buildEnvironment: buildParameters.buildEnvironment, scriptRunner: pluginScriptRunner, diff --git a/Tests/FunctionalTests/PluginTests.swift b/Tests/FunctionalTests/PluginTests.swift index d33a0c17da1..c3823a4e5f9 100644 --- a/Tests/FunctionalTests/PluginTests.swift +++ b/Tests/FunctionalTests/PluginTests.swift @@ -831,7 +831,8 @@ final class PluginTests: XCTestCase { modulesGraph: packageGraph, observabilityScope: observability.topScope, callbackQueue: delegateQueue, - delegate: delegate + delegate: delegate, + arguments: [] ) } onCancel: { do { diff --git a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift index eb9be4cd970..908a4114df1 100644 --- a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift +++ b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift @@ -57,7 +57,7 @@ final class PluginInvocationTests: XCTestCase { TargetDescription( name: "Foo", type: .regular, - pluginUsages: [.plugin(name: "FooPlugin", package: nil)] + pluginUsages: [.plugin(name: "FooPlugin", package: nil, arguments: ["123", "321"])] ), TargetDescription( name: "FooPlugin",