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