@@ -18,7 +18,7 @@ import SwiftSyntax
18
18
import SwiftSyntaxBuilder
19
19
20
20
/// Add a target plugin to a manifest's source code.
21
- public struct AddTargetPlugin {
21
+ public enum AddTargetPlugin {
22
22
/// The set of argument labels that can occur after the "plugins"
23
23
/// argument in the various target initializers.
24
24
///
@@ -53,20 +53,37 @@ public struct AddTargetPlugin {
53
53
return false
54
54
}
55
55
56
- guard let stringLiteral = nameArgument. expression. as ( StringLiteralExprSyntax . self) ,
57
- let literalValue = stringLiteral. representedLiteralValue else {
56
+ guard
57
+ let stringLiteral = nameArgument. expression. as (
58
+ StringLiteralExprSyntax . self
59
+ ) ,
60
+ let literalValue = stringLiteral. representedLiteralValue
61
+ else {
58
62
return false
59
63
}
60
64
61
65
return literalValue == targetName
62
66
}
63
67
64
- guard let targetCall = FunctionCallExprSyntax . findFirst ( in: targetArray, matching: matchesTargetCall) else {
68
+ guard let targetCall = FunctionCallExprSyntax . findFirst (
69
+ in: targetArray,
70
+ matching: matchesTargetCall
71
+ ) else {
65
72
throw ManifestEditError . cannotFindTarget ( targetName: targetName)
66
73
}
67
74
75
+ guard try ! self . pluginAlreadyAdded (
76
+ plugin,
77
+ to: targetName,
78
+ in: targetCall
79
+ )
80
+ else {
81
+ return PackageEditResult ( manifestEdits: [ ] )
82
+ }
83
+
68
84
let newTargetCall = try addTargetPluginLocal (
69
- plugin, to: targetCall
85
+ plugin,
86
+ to: targetCall
70
87
)
71
88
72
89
return PackageEditResult (
@@ -76,16 +93,77 @@ public struct AddTargetPlugin {
76
93
)
77
94
}
78
95
96
+ private static func pluginAlreadyAdded(
97
+ _ plugin: TargetDescription . PluginUsage ,
98
+ to targetName: String ,
99
+ in packageCall: FunctionCallExprSyntax
100
+ ) throws -> Bool {
101
+ let pluginSyntax = plugin. asSyntax ( )
102
+ guard let pluginFnSyntax = pluginSyntax. as ( FunctionCallExprSyntax . self)
103
+ else {
104
+ throw ManifestEditError . cannotFindPackage
105
+ }
106
+
107
+ guard let id = pluginFnSyntax. arguments. first ( where: {
108
+ $0. label? . text == " name "
109
+ } )
110
+ else {
111
+ throw InternalError ( " Missing 'name' argument in plugin syntax " )
112
+ }
113
+
114
+ if let existingPlugins = packageCall. findArgument ( labeled: " plugins " ) {
115
+ // If we have an existing plugins array, we need to check if
116
+ if let expr = existingPlugins. expression. as ( ArrayExprSyntax . self) {
117
+ // Iterate through existing dependencies and look for an argument that matches
118
+ // either the `id` or `url` argument of the new dependency.
119
+ let existingArgument = expr. elements. first { elem in
120
+ if let funcExpr = elem. expression. as (
121
+ FunctionCallExprSyntax . self
122
+ ) {
123
+ return funcExpr. arguments. contains {
124
+ $0. with ( \. trailingComma, nil ) . trimmedDescription
125
+ == id. with ( \. trailingComma, nil )
126
+ . trimmedDescription
127
+ }
128
+ }
129
+ return true
130
+ }
131
+
132
+ if let existingArgument {
133
+ let normalizedExistingArgument = existingArgument. detached. with ( \. trailingComma, nil )
134
+ // This exact dependency already exists, return false to indicate we should do nothing.
135
+ if normalizedExistingArgument. trimmedDescription == pluginSyntax. trimmedDescription {
136
+ return true
137
+ }
138
+ throw ManifestEditError . existingPlugin (
139
+ pluginName: plugin. identifier,
140
+ taget: targetName
141
+ )
142
+ }
143
+ }
144
+ }
145
+
146
+ return false
147
+ }
148
+
79
149
/// Implementation of adding a target dependency to an existing call.
80
150
static func addTargetPluginLocal(
81
151
_ plugin: TargetDescription . PluginUsage ,
82
152
to targetCall: FunctionCallExprSyntax
83
153
) throws -> FunctionCallExprSyntax {
84
154
try targetCall. appendingToArrayArgument (
85
155
label: " plugins " ,
86
- trailingLabels: Self . argumentLabelsAfterDependencies,
156
+ trailingLabels: self . argumentLabelsAfterDependencies,
87
157
newElement: plugin. asSyntax ( )
88
158
)
89
159
}
90
160
}
91
161
162
+ extension TargetDescription . PluginUsage {
163
+ fileprivate var identifier : String {
164
+ switch self {
165
+ case . plugin( let name, _) :
166
+ name
167
+ }
168
+ }
169
+ }
0 commit comments