1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.dependency.analyze;
20
21 import java.io.File;
22 import java.io.StringWriter;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Iterator;
26 import java.util.LinkedHashMap;
27 import java.util.LinkedHashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31
32 import org.apache.maven.artifact.Artifact;
33 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
34 import org.apache.maven.plugin.AbstractMojo;
35 import org.apache.maven.plugin.MojoExecutionException;
36 import org.apache.maven.plugin.MojoFailureException;
37 import org.apache.maven.plugins.annotations.Component;
38 import org.apache.maven.plugins.annotations.Parameter;
39 import org.apache.maven.plugins.dependency.utils.StringUtils;
40 import org.apache.maven.project.MavenProject;
41 import org.apache.maven.shared.artifact.filter.StrictPatternExcludesArtifactFilter;
42 import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis;
43 import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzer;
44 import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzerException;
45 import org.codehaus.plexus.PlexusContainer;
46 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
47 import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
48
49
50
51
52
53
54
55
56 public abstract class AbstractAnalyzeMojo extends AbstractMojo {
57
58
59
60
61
62
63 @Component
64 private PlexusContainer plexusContainer;
65
66
67
68
69 @Component
70 private MavenProject project;
71
72
73
74
75
76
77
78
79
80 @Parameter(property = "analyzer", defaultValue = "default")
81 private String analyzer;
82
83
84
85
86 @Parameter(property = "failOnWarning", defaultValue = "false")
87 private boolean failOnWarning;
88
89
90
91
92 @Parameter(property = "verbose", defaultValue = "false")
93 private boolean verbose;
94
95
96
97
98
99
100 @Parameter(property = "ignoreNonCompile", defaultValue = "false")
101 private boolean ignoreNonCompile;
102
103
104
105
106
107
108 @Parameter(property = "ignoreUnusedRuntime", defaultValue = "false")
109 private boolean ignoreUnusedRuntime;
110
111
112
113
114
115
116
117
118
119 @Parameter(property = "ignoreAllNonTestScoped", defaultValue = "false")
120 private boolean ignoreAllNonTestScoped;
121
122
123
124
125
126
127 @Parameter(property = "outputXML", defaultValue = "false")
128 private boolean outputXML;
129
130
131
132
133
134
135 @Parameter(property = "scriptableOutput", defaultValue = "false")
136 private boolean scriptableOutput;
137
138
139
140
141
142
143 @Parameter(property = "scriptableFlag", defaultValue = "$$$%%%")
144 private String scriptableFlag;
145
146
147
148
149
150
151 @Parameter(defaultValue = "${basedir}", readonly = true)
152 private File baseDir;
153
154
155
156
157
158
159 @Parameter(defaultValue = "${project.build.directory}", readonly = true)
160 private File outputDirectory;
161
162
163
164
165
166
167
168 @Parameter
169 private String[] usedDependencies;
170
171
172
173
174
175
176 @Parameter(property = "mdep.analyze.skip", defaultValue = "false")
177 private boolean skip;
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196 @Parameter
197 private String[] ignoredDependencies = new String[0];
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215 @Parameter
216 private String[] ignoredUsedUndeclaredDependencies = new String[0];
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234 @Parameter
235 private String[] ignoredUnusedDeclaredDependencies = new String[0];
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254 @Parameter
255 private String[] ignoredNonTestScopedDependencies = new String[0];
256
257
258
259
260
261
262
263
264 @Parameter(defaultValue = "pom,ear")
265 private List<String> ignoredPackagings;
266
267
268
269
270
271
272 @Parameter(property = "mdep.analyze.excludedClasses")
273 private Set<String> excludedClasses;
274
275
276
277
278
279
280 @Override
281 public void execute() throws MojoExecutionException, MojoFailureException {
282 if (isSkip()) {
283 getLog().info("Skipping plugin execution");
284 return;
285 }
286
287 if (ignoredPackagings.contains(project.getPackaging())) {
288 getLog().info("Skipping " + project.getPackaging() + " project");
289 return;
290 }
291
292 if (outputDirectory == null || !outputDirectory.exists()) {
293 getLog().info("Skipping project with no build directory");
294 return;
295 }
296
297 boolean warning = checkDependencies();
298
299 if (warning && failOnWarning) {
300 throw new MojoExecutionException("Dependency problems found");
301 }
302 }
303
304
305
306
307
308 protected ProjectDependencyAnalyzer createProjectDependencyAnalyzer() throws MojoExecutionException {
309
310 try {
311 return plexusContainer.lookup(ProjectDependencyAnalyzer.class, analyzer);
312 } catch (ComponentLookupException exception) {
313 throw new MojoExecutionException(
314 "Failed to instantiate ProjectDependencyAnalyser" + " / role-hint " + analyzer, exception);
315 }
316 }
317
318
319
320
321 protected final boolean isSkip() {
322 return skip;
323 }
324
325
326
327 private boolean checkDependencies() throws MojoExecutionException {
328 ProjectDependencyAnalysis analysis;
329 try {
330 analysis = createProjectDependencyAnalyzer().analyze(project, excludedClasses);
331
332 if (usedDependencies != null) {
333 analysis = analysis.forceDeclaredDependenciesUsage(usedDependencies);
334 }
335 } catch (ProjectDependencyAnalyzerException exception) {
336 throw new MojoExecutionException("Cannot analyze dependencies", exception);
337 }
338
339 if (ignoreNonCompile) {
340 analysis = analysis.ignoreNonCompile();
341 }
342
343 Set<Artifact> usedDeclared = new LinkedHashSet<>(analysis.getUsedDeclaredArtifacts());
344 Map<Artifact, Set<String>> usedUndeclaredWithClasses =
345 new LinkedHashMap<>(analysis.getUsedUndeclaredArtifactsWithClasses());
346 Set<Artifact> unusedDeclared = new LinkedHashSet<>(analysis.getUnusedDeclaredArtifacts());
347 Set<Artifact> nonTestScope = new LinkedHashSet<>(analysis.getTestArtifactsWithNonTestScope());
348
349 Set<Artifact> ignoredUsedUndeclared = new LinkedHashSet<>();
350 Set<Artifact> ignoredUnusedDeclared = new LinkedHashSet<>();
351 Set<Artifact> ignoredNonTestScope = new LinkedHashSet<>();
352
353 if (ignoreUnusedRuntime) {
354 filterArtifactsByScope(unusedDeclared, Artifact.SCOPE_RUNTIME);
355 }
356
357 ignoredUsedUndeclared.addAll(filterDependencies(usedUndeclaredWithClasses.keySet(), ignoredDependencies));
358 ignoredUsedUndeclared.addAll(
359 filterDependencies(usedUndeclaredWithClasses.keySet(), ignoredUsedUndeclaredDependencies));
360
361 ignoredUnusedDeclared.addAll(filterDependencies(unusedDeclared, ignoredDependencies));
362 ignoredUnusedDeclared.addAll(filterDependencies(unusedDeclared, ignoredUnusedDeclaredDependencies));
363
364 if (ignoreAllNonTestScoped) {
365 ignoredNonTestScope.addAll(filterDependencies(nonTestScope, new String[] {"*"}));
366 } else {
367 ignoredNonTestScope.addAll(filterDependencies(nonTestScope, ignoredDependencies));
368 ignoredNonTestScope.addAll(filterDependencies(nonTestScope, ignoredNonTestScopedDependencies));
369 }
370
371 boolean reported = false;
372 boolean warning = false;
373
374 if (verbose && !usedDeclared.isEmpty()) {
375 getLog().info("Used declared dependencies found:");
376
377 logArtifacts(analysis.getUsedDeclaredArtifacts(), false);
378 reported = true;
379 }
380
381 if (!usedUndeclaredWithClasses.isEmpty()) {
382 logDependencyWarning("Used undeclared dependencies found:");
383
384 if (verbose) {
385 logArtifacts(usedUndeclaredWithClasses, true);
386 } else {
387 logArtifacts(usedUndeclaredWithClasses.keySet(), true);
388 }
389 reported = true;
390 warning = true;
391 }
392
393 if (!unusedDeclared.isEmpty()) {
394 logDependencyWarning("Unused declared dependencies found:");
395
396 logArtifacts(unusedDeclared, true);
397 reported = true;
398 warning = true;
399 }
400
401 if (!nonTestScope.isEmpty()) {
402 logDependencyWarning("Non-test scoped test only dependencies found:");
403
404 logArtifacts(nonTestScope, true);
405 reported = true;
406 warning = true;
407 }
408
409 if (verbose && !ignoredUsedUndeclared.isEmpty()) {
410 getLog().info("Ignored used undeclared dependencies:");
411
412 logArtifacts(ignoredUsedUndeclared, false);
413 reported = true;
414 }
415
416 if (verbose && !ignoredUnusedDeclared.isEmpty()) {
417 getLog().info("Ignored unused declared dependencies:");
418
419 logArtifacts(ignoredUnusedDeclared, false);
420 reported = true;
421 }
422
423 if (verbose && !ignoredNonTestScope.isEmpty()) {
424 getLog().info("Ignored non-test scoped test only dependencies:");
425
426 logArtifacts(ignoredNonTestScope, false);
427 reported = true;
428 }
429
430 if (outputXML) {
431 writeDependencyXML(usedUndeclaredWithClasses.keySet());
432 }
433
434 if (scriptableOutput) {
435 writeScriptableOutput(usedUndeclaredWithClasses.keySet());
436 }
437
438 if (!reported) {
439 getLog().info("No dependency problems found");
440 }
441
442 return warning;
443 }
444
445 private void filterArtifactsByScope(Set<Artifact> artifacts, String scope) {
446 artifacts.removeIf(artifact -> artifact.getScope().equals(scope));
447 }
448
449 private void logArtifacts(Set<Artifact> artifacts, boolean warn) {
450 if (artifacts.isEmpty()) {
451 getLog().info(" None");
452 } else {
453 for (Artifact artifact : artifacts) {
454
455 artifact.isSnapshot();
456
457 if (warn) {
458 logDependencyWarning(" " + artifact);
459 } else {
460 getLog().info(" " + artifact);
461 }
462 }
463 }
464 }
465
466 private void logArtifacts(Map<Artifact, Set<String>> artifacts, boolean warn) {
467 if (artifacts.isEmpty()) {
468 getLog().info(" None");
469 } else {
470 for (Map.Entry<Artifact, Set<String>> entry : artifacts.entrySet()) {
471
472 entry.getKey().isSnapshot();
473
474 if (warn) {
475 logDependencyWarning(" " + entry.getKey());
476 for (String clazz : entry.getValue()) {
477 logDependencyWarning(" class " + clazz);
478 }
479 } else {
480 getLog().info(" " + entry.getKey());
481 for (String clazz : entry.getValue()) {
482 getLog().info(" class " + clazz);
483 }
484 }
485 }
486 }
487 }
488
489 private void logDependencyWarning(CharSequence content) {
490 if (failOnWarning) {
491 getLog().error(content);
492 } else {
493 getLog().warn(content);
494 }
495 }
496
497 private void writeDependencyXML(Set<Artifact> artifacts) {
498 if (!artifacts.isEmpty()) {
499 getLog().info("Add the following to your pom to correct the missing dependencies: ");
500
501 StringWriter out = new StringWriter();
502 PrettyPrintXMLWriter writer = new PrettyPrintXMLWriter(out);
503
504 for (Artifact artifact : artifacts) {
505
506 artifact.isSnapshot();
507
508 writer.startElement("dependency");
509 writer.startElement("groupId");
510 writer.writeText(artifact.getGroupId());
511 writer.endElement();
512 writer.startElement("artifactId");
513 writer.writeText(artifact.getArtifactId());
514 writer.endElement();
515 writer.startElement("version");
516 writer.writeText(artifact.getBaseVersion());
517 String classifier = artifact.getClassifier();
518 if (!StringUtils.isEmpty(classifier)) {
519 writer.startElement("classifier");
520 writer.writeText(classifier);
521 writer.endElement();
522 }
523 writer.endElement();
524
525 if (!Artifact.SCOPE_COMPILE.equals(artifact.getScope())) {
526 writer.startElement("scope");
527 writer.writeText(artifact.getScope());
528 writer.endElement();
529 }
530 writer.endElement();
531 }
532
533 getLog().info(System.lineSeparator() + out.getBuffer());
534 }
535 }
536
537 private void writeScriptableOutput(Set<Artifact> artifacts) {
538 if (!artifacts.isEmpty()) {
539 getLog().info("Missing dependencies: ");
540 String pomFile = baseDir.getAbsolutePath() + File.separatorChar + "pom.xml";
541 StringBuilder buf = new StringBuilder();
542
543 for (Artifact artifact : artifacts) {
544
545 artifact.isSnapshot();
546
547 buf.append(scriptableFlag)
548 .append(":")
549 .append(pomFile)
550 .append(":")
551 .append(artifact.getDependencyConflictId())
552 .append(":")
553 .append(artifact.getClassifier())
554 .append(":")
555 .append(artifact.getBaseVersion())
556 .append(":")
557 .append(artifact.getScope())
558 .append(System.lineSeparator());
559 }
560 getLog().info(System.lineSeparator() + buf);
561 }
562 }
563
564 private List<Artifact> filterDependencies(Set<Artifact> artifacts, String[] excludes) {
565 ArtifactFilter filter = new StrictPatternExcludesArtifactFilter(Arrays.asList(excludes));
566 List<Artifact> result = new ArrayList<>();
567
568 for (Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); ) {
569 Artifact artifact = it.next();
570 if (!filter.include(artifact)) {
571 it.remove();
572 result.add(artifact);
573 }
574 }
575
576 return result;
577 }
578 }