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