22
22
import javax .inject .Named ;
23
23
24
24
import java .text .ChoiceFormat ;
25
- import java .util .ArrayDeque ;
26
25
import java .util .ArrayList ;
27
26
import java .util .Collection ;
28
27
import java .util .Collections ;
29
- import java .util .Deque ;
30
28
import java .util .List ;
31
29
import java .util .Objects ;
32
30
import java .util .function .Predicate ;
40
38
import org .apache .maven .project .MavenProject ;
41
39
import org .eclipse .aether .RepositorySystem ;
42
40
import org .eclipse .aether .collection .DependencyCollectionException ;
41
+ import org .eclipse .aether .graph .DependencyFilter ;
43
42
import org .eclipse .aether .graph .DependencyNode ;
44
- import org .eclipse .aether .graph .DependencyVisitor ;
45
- import org .eclipse .aether .util .graph .visitor .TreeDependencyVisitor ;
43
+ import org .eclipse .aether .util .graph .manager .DependencyManagerUtils ;
44
+ import org .eclipse .aether .util .graph .visitor .PathRecordingDependencyVisitor ;
45
+ import org .eclipse .aether .util .version .GenericVersionScheme ;
46
+ import org .eclipse .aether .version .InvalidVersionSpecificationException ;
46
47
import org .eclipse .aether .version .VersionConstraint ;
47
48
48
49
/**
@@ -110,6 +111,14 @@ public final class BanDynamicVersions extends AbstractStandardEnforcerRule {
110
111
*/
111
112
private List <String > ignores = null ;
112
113
114
+ /**
115
+ * {@code true} if dependencies should be checked before Maven computes the final
116
+ * dependency tree. Setting this property will make the rule check dependencies
117
+ * before any conflicts are resolved. This is similar to the {@code verbose}
118
+ * parameter for the {@code tree} goal for {@code maven-dependency-plugin}.
119
+ */
120
+ private boolean verbose ;
121
+
113
122
private final ResolverUtil resolverUtil ;
114
123
115
124
@ Inject
@@ -118,25 +127,24 @@ public BanDynamicVersions(
118
127
this .resolverUtil = Objects .requireNonNull (resolverUtil );
119
128
}
120
129
121
- private final class BannedDynamicVersionCollector implements DependencyVisitor {
122
-
123
- private final Deque <DependencyNode > nodeStack ; // all intermediate nodes (without the root node)
130
+ private final class BannedDynamicVersionCollector implements DependencyFilter {
124
131
125
132
private boolean isRoot = true ;
126
133
127
134
private List <String > violations ;
128
135
129
136
private final Predicate <DependencyNode > predicate ;
130
137
138
+ private GenericVersionScheme versionScheme ;
139
+
131
140
public List <String > getViolations () {
132
141
return violations ;
133
142
}
134
143
135
144
BannedDynamicVersionCollector (Predicate <DependencyNode > predicate ) {
136
- this .nodeStack = new ArrayDeque <>();
137
145
this .predicate = predicate ;
138
- this .isRoot = true ;
139
146
this .violations = new ArrayList <>();
147
+ this .versionScheme = new GenericVersionScheme ();
140
148
}
141
149
142
150
private boolean isBannedDynamicVersion (VersionConstraint versionConstraint ) {
@@ -163,30 +171,51 @@ private boolean isBannedDynamicVersion(VersionConstraint versionConstraint) {
163
171
}
164
172
165
173
@ Override
166
- public boolean visitEnter (DependencyNode node ) {
174
+ public boolean accept (DependencyNode node , List < DependencyNode > parents ) {
167
175
if (isRoot ) {
168
176
isRoot = false ;
169
- } else {
170
- getLog ().debug ("Found node " + node + " with version constraint " + node .getVersionConstraint ());
171
- if (predicate .test (node ) && isBannedDynamicVersion (node .getVersionConstraint ())) {
172
- violations .add ("Dependency "
173
- + node .getDependency ()
174
- + dumpIntermediatePath (nodeStack )
175
- + " is referenced with a banned dynamic version "
176
- + node .getVersionConstraint ());
177
- return false ;
177
+ return false ;
178
+ }
179
+ getLog ().debug ("Found node " + node + " with version constraint " + node .getVersionConstraint ());
180
+ if (!predicate .test (node )) {
181
+ return false ;
182
+ }
183
+ VersionConstraint versionConstraint = node .getVersionConstraint ();
184
+ if (isBannedDynamicVersion (versionConstraint )) {
185
+ addViolation (versionConstraint , node , parents );
186
+ return true ;
187
+ }
188
+ try {
189
+ if (verbose ) {
190
+ String premanagedVersion = DependencyManagerUtils .getPremanagedVersion (node );
191
+ if (premanagedVersion != null ) {
192
+ VersionConstraint premanagedContraint = versionScheme .parseVersionConstraint (premanagedVersion );
193
+ if (isBannedDynamicVersion (premanagedContraint )) {
194
+ addViolation (premanagedContraint , node , parents );
195
+ return true ;
196
+ }
197
+ }
178
198
}
179
- nodeStack .addLast (node );
199
+ } catch (InvalidVersionSpecificationException ex ) {
200
+ // This should never happen.
201
+ throw new RuntimeException ("Failed to parse version for " + node , ex );
180
202
}
181
- return true ;
203
+ return false ;
182
204
}
183
205
184
- @ Override
185
- public boolean visitLeave (DependencyNode node ) {
186
- if (!nodeStack .isEmpty ()) {
187
- nodeStack .removeLast ();
206
+ private void addViolation (
207
+ VersionConstraint versionContraint , DependencyNode node , List <DependencyNode > parents ) {
208
+ List <DependencyNode > intermediatePath = new ArrayList <>(parents );
209
+ if (!intermediatePath .isEmpty ()) {
210
+ // This project is also included in the path, but we do
211
+ // not want that in the report.
212
+ intermediatePath .remove (intermediatePath .size () - 1 );
188
213
}
189
- return true ;
214
+ violations .add ("Dependency "
215
+ + node .getDependency ()
216
+ + dumpIntermediatePath (intermediatePath )
217
+ + " is referenced with a banned dynamic version "
218
+ + versionContraint );
190
219
}
191
220
}
192
221
@@ -195,7 +224,7 @@ public void execute() throws EnforcerRuleException {
195
224
196
225
try {
197
226
DependencyNode rootDependency =
198
- resolverUtil .resolveTransitiveDependencies (excludeOptionals , excludedScopes );
227
+ resolverUtil .resolveTransitiveDependencies (verbose , excludeOptionals , excludedScopes );
199
228
200
229
List <String > violations = collectDependenciesWithBannedDynamicVersions (rootDependency );
201
230
if (!violations .isEmpty ()) {
@@ -239,23 +268,27 @@ private List<String> collectDependenciesWithBannedDynamicVersions(DependencyNode
239
268
} else {
240
269
predicate = d -> true ;
241
270
}
242
- BannedDynamicVersionCollector bannedDynamicVersionCollector = new BannedDynamicVersionCollector (predicate );
243
- DependencyVisitor depVisitor = new TreeDependencyVisitor (bannedDynamicVersionCollector );
244
- rootDependency .accept (depVisitor );
245
- return bannedDynamicVersionCollector .getViolations ();
271
+ BannedDynamicVersionCollector collector = new BannedDynamicVersionCollector (predicate );
272
+ rootDependency .accept (new PathRecordingDependencyVisitor (collector ));
273
+ return collector .getViolations ();
274
+ }
275
+
276
+ public void setVerbose (boolean verbose ) {
277
+ this .verbose = verbose ;
246
278
}
247
279
248
280
@ Override
249
281
public String toString () {
250
282
return String .format (
251
- "BanDynamicVersions[allowSnapshots=%b, allowLatest=%b, allowRelease=%b, allowRanges=%b, allowRangesWithIdenticalBounds=%b, excludeOptionals=%b, excludedScopes=%s, ignores=%s]" ,
283
+ "BanDynamicVersions[allowSnapshots=%b, allowLatest=%b, allowRelease=%b, allowRanges=%b, allowRangesWithIdenticalBounds=%b, excludeOptionals=%b, excludedScopes=%s, ignores=%s, verbose=%b ]" ,
252
284
allowSnapshots ,
253
285
allowLatest ,
254
286
allowRelease ,
255
287
allowRanges ,
256
288
allowRangesWithIdenticalBounds ,
257
289
excludeOptionals ,
258
290
excludedScopes ,
259
- ignores );
291
+ ignores ,
292
+ verbose );
260
293
}
261
294
}
0 commit comments