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