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.commons.lang3.StringUtils;
33 import org.apache.maven.artifact.Artifact;
34 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
35 import org.apache.maven.plugin.AbstractMojo;
36 import org.apache.maven.plugin.MojoExecutionException;
37 import org.apache.maven.plugin.MojoFailureException;
38 import org.apache.maven.plugins.annotations.Component;
39 import org.apache.maven.plugins.annotations.Parameter;
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 @Parameter(defaultValue = "${project}", readonly = true, required = true)
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
265
266 @Parameter
267 private List<String> ignoredPackagings = Arrays.asList("pom", "ear");
268
269
270
271
272
273
274 @Override
275 public void execute() throws MojoExecutionException, MojoFailureException {
276 if (isSkip()) {
277 getLog().info("Skipping plugin execution");
278 return;
279 }
280
281 if (ignoredPackagings.contains(project.getPackaging())) {
282 getLog().info("Skipping " + project.getPackaging() + " project");
283 return;
284 }
285
286 if (outputDirectory == null || !outputDirectory.exists()) {
287 getLog().info("Skipping project with no build directory");
288 return;
289 }
290
291 boolean warning = checkDependencies();
292
293 if (warning && failOnWarning) {
294 throw new MojoExecutionException("Dependency problems found");
295 }
296 }
297
298
299
300
301
302 protected ProjectDependencyAnalyzer createProjectDependencyAnalyzer() throws MojoExecutionException {
303
304 try {
305 return plexusContainer.lookup(ProjectDependencyAnalyzer.class, analyzer);
306 } catch (ComponentLookupException exception) {
307 throw new MojoExecutionException(
308 "Failed to instantiate ProjectDependencyAnalyser" + " / role-hint " + analyzer, exception);
309 }
310 }
311
312
313
314
315 protected final boolean isSkip() {
316 return skip;
317 }
318
319
320
321 private boolean checkDependencies() throws MojoExecutionException {
322 ProjectDependencyAnalysis analysis;
323 try {
324 analysis = createProjectDependencyAnalyzer().analyze(project);
325
326 if (usedDependencies != null) {
327 analysis = analysis.forceDeclaredDependenciesUsage(usedDependencies);
328 }
329 } catch (ProjectDependencyAnalyzerException exception) {
330 throw new MojoExecutionException("Cannot analyze dependencies", exception);
331 }
332
333 if (ignoreNonCompile) {
334 analysis = analysis.ignoreNonCompile();
335 }
336
337 Set<Artifact> usedDeclared = new LinkedHashSet<>(analysis.getUsedDeclaredArtifacts());
338 Map<Artifact, Set<String>> usedUndeclaredWithClasses =
339 new LinkedHashMap<>(analysis.getUsedUndeclaredArtifactsWithClasses());
340 Set<Artifact> unusedDeclared = new LinkedHashSet<>(analysis.getUnusedDeclaredArtifacts());
341 Set<Artifact> nonTestScope = new LinkedHashSet<>(analysis.getTestArtifactsWithNonTestScope());
342
343 Set<Artifact> ignoredUsedUndeclared = new LinkedHashSet<>();
344 Set<Artifact> ignoredUnusedDeclared = new LinkedHashSet<>();
345 Set<Artifact> ignoredNonTestScope = new LinkedHashSet<>();
346
347 if (ignoreUnusedRuntime) {
348 filterArtifactsByScope(unusedDeclared, Artifact.SCOPE_RUNTIME);
349 }
350
351 ignoredUsedUndeclared.addAll(filterDependencies(usedUndeclaredWithClasses.keySet(), ignoredDependencies));
352 ignoredUsedUndeclared.addAll(
353 filterDependencies(usedUndeclaredWithClasses.keySet(), ignoredUsedUndeclaredDependencies));
354
355 ignoredUnusedDeclared.addAll(filterDependencies(unusedDeclared, ignoredDependencies));
356 ignoredUnusedDeclared.addAll(filterDependencies(unusedDeclared, ignoredUnusedDeclaredDependencies));
357
358 if (ignoreAllNonTestScoped) {
359 ignoredNonTestScope.addAll(filterDependencies(nonTestScope, new String[] {"*"}));
360 } else {
361 ignoredNonTestScope.addAll(filterDependencies(nonTestScope, ignoredDependencies));
362 ignoredNonTestScope.addAll(filterDependencies(nonTestScope, ignoredNonTestScopedDependencies));
363 }
364
365 boolean reported = false;
366 boolean warning = false;
367
368 if (verbose && !usedDeclared.isEmpty()) {
369 getLog().info("Used declared dependencies found:");
370
371 logArtifacts(analysis.getUsedDeclaredArtifacts(), false);
372 reported = true;
373 }
374
375 if (!usedUndeclaredWithClasses.isEmpty()) {
376 logDependencyWarning("Used undeclared dependencies found:");
377
378 if (verbose) {
379 logArtifacts(usedUndeclaredWithClasses, true);
380 } else {
381 logArtifacts(usedUndeclaredWithClasses.keySet(), true);
382 }
383 reported = true;
384 warning = true;
385 }
386
387 if (!unusedDeclared.isEmpty()) {
388 logDependencyWarning("Unused declared dependencies found:");
389
390 logArtifacts(unusedDeclared, true);
391 reported = true;
392 warning = true;
393 }
394
395 if (!nonTestScope.isEmpty()) {
396 logDependencyWarning("Non-test scoped test only dependencies found:");
397
398 logArtifacts(nonTestScope, true);
399 reported = true;
400 warning = true;
401 }
402
403 if (verbose && !ignoredUsedUndeclared.isEmpty()) {
404 getLog().info("Ignored used undeclared dependencies:");
405
406 logArtifacts(ignoredUsedUndeclared, false);
407 reported = true;
408 }
409
410 if (verbose && !ignoredUnusedDeclared.isEmpty()) {
411 getLog().info("Ignored unused declared dependencies:");
412
413 logArtifacts(ignoredUnusedDeclared, false);
414 reported = true;
415 }
416
417 if (verbose && !ignoredNonTestScope.isEmpty()) {
418 getLog().info("Ignored non-test scoped test only dependencies:");
419
420 logArtifacts(ignoredNonTestScope, false);
421 reported = true;
422 }
423
424 if (outputXML) {
425 writeDependencyXML(usedUndeclaredWithClasses.keySet());
426 }
427
428 if (scriptableOutput) {
429 writeScriptableOutput(usedUndeclaredWithClasses.keySet());
430 }
431
432 if (!reported) {
433 getLog().info("No dependency problems found");
434 }
435
436 return warning;
437 }
438
439 private void filterArtifactsByScope(Set<Artifact> artifacts, String scope) {
440 artifacts.removeIf(artifact -> artifact.getScope().equals(scope));
441 }
442
443 private void logArtifacts(Set<Artifact> artifacts, boolean warn) {
444 if (artifacts.isEmpty()) {
445 getLog().info(" None");
446 } else {
447 for (Artifact artifact : artifacts) {
448
449 artifact.isSnapshot();
450
451 if (warn) {
452 logDependencyWarning(" " + artifact);
453 } else {
454 getLog().info(" " + artifact);
455 }
456 }
457 }
458 }
459
460 private void logArtifacts(Map<Artifact, Set<String>> artifacts, boolean warn) {
461 if (artifacts.isEmpty()) {
462 getLog().info(" None");
463 } else {
464 for (Map.Entry<Artifact, Set<String>> entry : artifacts.entrySet()) {
465
466 entry.getKey().isSnapshot();
467
468 if (warn) {
469 logDependencyWarning(" " + entry.getKey());
470 for (String clazz : entry.getValue()) {
471 logDependencyWarning(" class " + clazz);
472 }
473 } else {
474 getLog().info(" " + entry.getKey());
475 for (String clazz : entry.getValue()) {
476 getLog().info(" class " + clazz);
477 }
478 }
479 }
480 }
481 }
482
483 private void logDependencyWarning(CharSequence content) {
484 if (failOnWarning) {
485 getLog().error(content);
486 } else {
487 getLog().warn(content);
488 }
489 }
490
491 private void writeDependencyXML(Set<Artifact> artifacts) {
492 if (!artifacts.isEmpty()) {
493 getLog().info("Add the following to your pom to correct the missing dependencies: ");
494
495 StringWriter out = new StringWriter();
496 PrettyPrintXMLWriter writer = new PrettyPrintXMLWriter(out);
497
498 for (Artifact artifact : artifacts) {
499
500 artifact.isSnapshot();
501
502 writer.startElement("dependency");
503 writer.startElement("groupId");
504 writer.writeText(artifact.getGroupId());
505 writer.endElement();
506 writer.startElement("artifactId");
507 writer.writeText(artifact.getArtifactId());
508 writer.endElement();
509 writer.startElement("version");
510 writer.writeText(artifact.getBaseVersion());
511 String classifier = artifact.getClassifier();
512 if (StringUtils.isNotBlank(classifier)) {
513 writer.startElement("classifier");
514 writer.writeText(artifact.getClassifier());
515 writer.endElement();
516 }
517 writer.endElement();
518
519 if (!Artifact.SCOPE_COMPILE.equals(artifact.getScope())) {
520 writer.startElement("scope");
521 writer.writeText(artifact.getScope());
522 writer.endElement();
523 }
524 writer.endElement();
525 }
526
527 getLog().info(System.lineSeparator() + out.getBuffer());
528 }
529 }
530
531 private void writeScriptableOutput(Set<Artifact> artifacts) {
532 if (!artifacts.isEmpty()) {
533 getLog().info("Missing dependencies: ");
534 String pomFile = baseDir.getAbsolutePath() + File.separatorChar + "pom.xml";
535 StringBuilder buf = new StringBuilder();
536
537 for (Artifact artifact : artifacts) {
538
539 artifact.isSnapshot();
540
541 buf.append(scriptableFlag)
542 .append(":")
543 .append(pomFile)
544 .append(":")
545 .append(artifact.getDependencyConflictId())
546 .append(":")
547 .append(artifact.getClassifier())
548 .append(":")
549 .append(artifact.getBaseVersion())
550 .append(":")
551 .append(artifact.getScope())
552 .append(System.lineSeparator());
553 }
554 getLog().info(System.lineSeparator() + buf);
555 }
556 }
557
558 private List<Artifact> filterDependencies(Set<Artifact> artifacts, String[] excludes) {
559 ArtifactFilter filter = new StrictPatternExcludesArtifactFilter(Arrays.asList(excludes));
560 List<Artifact> result = new ArrayList<>();
561
562 for (Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); ) {
563 Artifact artifact = it.next();
564 if (!filter.include(artifact)) {
565 it.remove();
566 result.add(artifact);
567 }
568 }
569
570 return result;
571 }
572 }