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