1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.jdeps;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.nio.file.Path;
26 import java.nio.file.Paths;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.LinkedHashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Properties;
34 import java.util.Set;
35 import java.util.StringTokenizer;
36
37 import org.apache.commons.lang3.StringUtils;
38 import org.apache.commons.lang3.SystemUtils;
39 import org.apache.maven.artifact.Artifact;
40 import org.apache.maven.artifact.ArtifactUtils;
41 import org.apache.maven.artifact.DependencyResolutionRequiredException;
42 import org.apache.maven.execution.MavenSession;
43 import org.apache.maven.plugin.AbstractMojo;
44 import org.apache.maven.plugin.MojoExecutionException;
45 import org.apache.maven.plugin.MojoFailureException;
46 import org.apache.maven.plugins.annotations.Parameter;
47 import org.apache.maven.plugins.jdeps.consumers.JDepsConsumer;
48 import org.apache.maven.project.MavenProject;
49 import org.apache.maven.toolchain.Toolchain;
50 import org.apache.maven.toolchain.ToolchainManager;
51 import org.codehaus.plexus.util.MatchPatterns;
52 import org.codehaus.plexus.util.cli.CommandLineException;
53 import org.codehaus.plexus.util.cli.CommandLineUtils;
54 import org.codehaus.plexus.util.cli.Commandline;
55
56
57
58
59
60
61
62 public abstract class AbstractJDepsMojo extends AbstractMojo {
63
64 @Parameter(defaultValue = "${project}", readonly = true, required = true)
65 private MavenProject project;
66
67 @Parameter(defaultValue = "${session}", readonly = true, required = true)
68 private MavenSession session;
69
70 @Parameter(defaultValue = "${project.build.directory}", readonly = true, required = true)
71 private File outputDirectory;
72
73
74
75
76 @Parameter(defaultValue = "true", property = "jdeps.failOnWarning")
77 private boolean failOnWarning;
78
79
80
81
82
83
84 @Parameter(property = "jdeps.multiRelease")
85 private String multiRelease;
86
87
88
89
90
91
92 @Parameter(defaultValue = "true", property = "jdeps.includeClasspath")
93 private boolean includeClasspath;
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109 @Parameter
110 private List<String> dependenciesToAnalyzeIncludes;
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125 @Parameter
126 private List<String> dependenciesToAnalyzeExcludes;
127
128
129
130
131 @Parameter(property = "jdeps.dotOutput")
132 private File dotOutput;
133
134
135
136
137
138
139
140
141 @Parameter(property = "jdeps.verbose")
142 private String verbose;
143
144
145
146
147
148
149 @Parameter
150 private List<String> packages;
151
152
153
154
155
156 @Parameter(property = "jdeps.include")
157 private String include;
158
159
160
161
162
163 @Parameter(defaultValue = "false", property = "jdeps.apionly")
164 private boolean apiOnly;
165
166
167
168
169 @Parameter(defaultValue = "false", property = "jdeps.profile")
170 private boolean profile;
171
172
173
174
175
176 @Parameter(defaultValue = "false", property = "jdeps.recursive")
177 private boolean recursive;
178
179
180
181
182
183
184 @Parameter(property = "jdeps.module")
185 private String module;
186
187
188
189
190
191
192 @Parameter(defaultValue = "false", property = "jdeps.jdkinternals")
193 private boolean jdkinternals;
194
195 private final ToolchainManager toolchainManager;
196
197 protected AbstractJDepsMojo(ToolchainManager toolchainManager) {
198 this.toolchainManager = toolchainManager;
199 }
200
201 protected MavenProject getProject() {
202 return project;
203 }
204
205 public void execute() throws MojoExecutionException, MojoFailureException {
206 if (!new File(getClassesDirectory()).exists()) {
207 getLog().debug("No classes to analyze");
208 return;
209 }
210
211 String jExecutable;
212 try {
213 jExecutable = getJDepsExecutable();
214 } catch (IOException e) {
215 throw new MojoFailureException("Unable to find jdeps command: " + e.getMessage(), e);
216 }
217
218
219
220 Commandline cmd = new Commandline();
221 cmd.setExecutable(jExecutable);
222
223 Set<Path> dependenciesToAnalyze = null;
224 try {
225 dependenciesToAnalyze = getDependenciesToAnalyze(includeClasspath);
226 } catch (DependencyResolutionRequiredException e) {
227 throw new MojoExecutionException(e.getMessage(), e);
228 }
229 addJDepsOptions(cmd, dependenciesToAnalyze);
230 addJDepsClasses(cmd, dependenciesToAnalyze);
231
232 JDepsConsumer consumer = new JDepsConsumer();
233 executeJDepsCommandLine(cmd, outputDirectory, consumer);
234
235
236 if (!consumer.getOffendingPackages().isEmpty()) {
237 final String ls = System.lineSeparator();
238
239 StringBuilder msg = new StringBuilder();
240 msg.append("Found offending packages:").append(ls);
241 for (Map.Entry<String, String> offendingPackage :
242 consumer.getOffendingPackages().entrySet()) {
243 msg.append(' ')
244 .append(offendingPackage.getKey())
245 .append(" -> ")
246 .append(offendingPackage.getValue())
247 .append(ls);
248 }
249
250 if (isFailOnWarning()) {
251 throw new MojoExecutionException(msg.toString());
252 }
253 }
254 }
255
256 protected void addJDepsOptions(Commandline cmd, Set<Path> dependenciesToAnalyze) throws MojoFailureException {
257 if (dotOutput != null) {
258 cmd.createArg().setValue("-dotoutput");
259 cmd.createArg().setFile(dotOutput);
260 }
261
262 if (verbose != null) {
263 if ("class".equals(verbose)) {
264 cmd.createArg().setValue("-verbose:class");
265 } else if ("package".equals(verbose)) {
266 cmd.createArg().setValue("-verbose:package");
267 } else {
268 cmd.createArg().setValue("-v");
269 }
270 }
271
272 try {
273 Collection<Path> cp = new ArrayList<>();
274
275 for (Path path : getClassPath()) {
276 if (!dependenciesToAnalyze.contains(path)) {
277 cp.add(path);
278 }
279 }
280
281 if (!cp.isEmpty()) {
282 cmd.createArg().setValue("-cp");
283
284 cmd.createArg().setValue(StringUtils.join(cp.iterator(), File.pathSeparator));
285 }
286
287 } catch (DependencyResolutionRequiredException e) {
288 throw new MojoFailureException(e.getMessage(), e);
289 }
290
291 if (packages != null) {
292 for (String pkgName : packages) {
293 cmd.createArg().setValue("-p");
294 cmd.createArg().setValue(pkgName);
295 }
296 }
297
298 if (include != null) {
299 cmd.createArg().setValue("-include");
300 cmd.createArg().setValue(include);
301 }
302
303 if (profile) {
304 cmd.createArg().setValue("-P");
305 }
306
307 if (module != null) {
308 cmd.createArg().setValue("-m");
309 cmd.createArg().setValue(module);
310 }
311
312 if (multiRelease != null) {
313 cmd.createArg().setValue("--multi-release");
314 cmd.createArg().setValue(multiRelease);
315 }
316
317 if (apiOnly) {
318 cmd.createArg().setValue("-apionly");
319 }
320
321 if (recursive) {
322 cmd.createArg().setValue("-R");
323 }
324
325 if (jdkinternals) {
326 cmd.createArg().setValue("-jdkinternals");
327 }
328 }
329
330 protected Set<Path> getDependenciesToAnalyze(boolean includeClasspath)
331 throws DependencyResolutionRequiredException {
332 Set<Path> jdepsClasses = new LinkedHashSet<>();
333
334 jdepsClasses.add(Paths.get(getClassesDirectory()));
335
336 if (includeClasspath) {
337 jdepsClasses.addAll(getClassPath());
338 }
339
340 if (dependenciesToAnalyzeIncludes != null) {
341 MatchPatterns includes = MatchPatterns.from(dependenciesToAnalyzeIncludes);
342
343 MatchPatterns excludes;
344 if (dependenciesToAnalyzeExcludes != null) {
345 excludes = MatchPatterns.from(dependenciesToAnalyzeExcludes);
346 } else {
347 excludes = MatchPatterns.from(Collections.<String>emptyList());
348 }
349
350 for (Artifact artifact : project.getArtifacts()) {
351 String versionlessKey = ArtifactUtils.versionlessKey(artifact);
352
353 if (includes.matchesPatternStart(versionlessKey, true)
354 && !excludes.matchesPatternStart(versionlessKey, true)) {
355 jdepsClasses.add(artifact.getFile().toPath());
356 }
357 }
358 }
359
360 return jdepsClasses;
361 }
362
363 protected void addJDepsClasses(Commandline cmd, Set<Path> dependenciesToAnalyze) {
364
365 for (Path dependencyToAnalyze : dependenciesToAnalyze) {
366 cmd.createArg().setFile(dependencyToAnalyze.toFile());
367 }
368 }
369
370 private String getJDepsExecutable() throws IOException {
371 Toolchain tc = getToolchain();
372
373 String jdepsExecutable = null;
374 if (tc != null) {
375 jdepsExecutable = tc.findTool("jdeps");
376 }
377
378 String jdepsCommand = "jdeps" + (SystemUtils.IS_OS_WINDOWS ? ".exe" : "");
379
380 File jdepsExe;
381
382 if (StringUtils.isNotEmpty(jdepsExecutable)) {
383 jdepsExe = new File(jdepsExecutable);
384
385 if (jdepsExe.isDirectory()) {
386 jdepsExe = new File(jdepsExe, jdepsCommand);
387 }
388
389 if (SystemUtils.IS_OS_WINDOWS && jdepsExe.getName().indexOf('.') < 0) {
390 jdepsExe = new File(jdepsExe.getPath() + ".exe");
391 }
392
393 if (!jdepsExe.isFile()) {
394 throw new IOException("The jdeps executable '" + jdepsExe + "' doesn't exist or is not a file.");
395 }
396 return jdepsExe.getAbsolutePath();
397 }
398
399 jdepsExe = new File(SystemUtils.getJavaHome() + File.separator + ".." + File.separator + "sh", jdepsCommand);
400
401
402
403
404 Properties env = CommandLineUtils.getSystemEnvVars();
405 if (!jdepsExe.exists() || !jdepsExe.isFile()) {
406 String javaHome = env.getProperty("JAVA_HOME");
407 if (!StringUtils.isEmpty(javaHome)) {
408 if ((!new File(javaHome).getCanonicalFile().exists())
409 || (new File(javaHome).getCanonicalFile().isFile())) {
410 throw new IOException("The environment variable JAVA_HOME=" + javaHome
411 + " doesn't exist or is not a valid directory.");
412 }
413
414 jdepsExe = new File(javaHome + File.separator + "bin", jdepsCommand);
415 }
416 }
417
418 if (!jdepsExe.getCanonicalFile().exists()
419 || !jdepsExe.getCanonicalFile().isFile()) {
420
421
422
423 String path = env.getProperty("PATH");
424 if (path == null) {
425 path = env.getProperty("Path");
426 }
427 if (path == null) {
428 path = env.getProperty("path");
429 }
430 if (path != null) {
431 String[] pathDirs = path.split(File.pathSeparator);
432 for (String pathDir : pathDirs) {
433 if (StringUtils.isBlank(pathDir)) {
434 continue;
435 }
436 File pathJdepsExe = new File(pathDir, jdepsCommand);
437 File canonicalPathJdepsExe = pathJdepsExe.getCanonicalFile();
438 if (canonicalPathJdepsExe.exists()
439 && canonicalPathJdepsExe.isFile()
440 && canonicalPathJdepsExe.canExecute()) {
441 return canonicalPathJdepsExe.getAbsolutePath();
442 }
443 }
444 }
445
446 throw new IOException(
447 "Unable to locate the jdeps executable. Verify that JAVA_HOME is set correctly or ensure that jdeps is available on the system PATH.");
448 }
449
450 if (!jdepsExe.canExecute()) {
451 throw new IOException("The jdeps executable '" + jdepsExe + "' is not executable.");
452 }
453 return jdepsExe.getAbsolutePath();
454 }
455
456 private void executeJDepsCommandLine(
457 Commandline cmd, File jOutputDirectory, CommandLineUtils.StringStreamConsumer consumer)
458 throws MojoExecutionException {
459 if (getLog().isDebugEnabled()) {
460
461 getLog().debug("Executing: "
462 + CommandLineUtils.toString(cmd.getCommandline()).replaceAll("'", ""));
463 }
464
465 CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer() {
466 @Override
467 public void consumeLine(String line) {
468 if (!line.startsWith("Picked up JAVA_TOOL_OPTIONS:")) {
469 super.consumeLine(line);
470 }
471 }
472 };
473 CommandLineUtils.StringStreamConsumer out;
474 if (consumer != null) {
475 out = consumer;
476 } else {
477 out = new CommandLineUtils.StringStreamConsumer();
478 }
479
480 try {
481 int exitCode = CommandLineUtils.executeCommandLine(cmd, out, err);
482
483 String output = (StringUtils.isEmpty(out.getOutput())
484 ? null
485 : '\n' + out.getOutput().trim());
486
487 if (exitCode != 0) {
488 if (StringUtils.isNotEmpty(output)) {
489 getLog().info(output);
490 }
491
492 StringBuilder msg = new StringBuilder("\nExit code: ");
493 msg.append(exitCode);
494 if (StringUtils.isNotEmpty(err.getOutput())) {
495 msg.append(" - ").append(err.getOutput());
496 }
497 msg.append('\n');
498 msg.append("Command line was: ").append(cmd).append('\n').append('\n');
499
500 throw new MojoExecutionException(msg.toString());
501 }
502
503 if (StringUtils.isNotEmpty(output)) {
504 getLog().info(output);
505 }
506 } catch (CommandLineException e) {
507 throw new MojoExecutionException("Unable to execute jdeps command: " + e.getMessage(), e);
508 }
509
510
511
512
513
514 if (StringUtils.isNotEmpty(err.getOutput()) && getLog().isWarnEnabled()) {
515 getLog().warn("JDeps Warnings");
516
517 StringTokenizer token = new StringTokenizer(err.getOutput(), "\n");
518 while (token.hasMoreTokens()) {
519 String current = token.nextToken().trim();
520
521 getLog().warn(current);
522 }
523 }
524 }
525
526 private Toolchain getToolchain() {
527 Toolchain tc = null;
528 if (toolchainManager != null) {
529 tc = toolchainManager.getToolchainFromBuildContext("jdk", session);
530
531 if (tc == null) {
532
533 try {
534 Method getToolchainsMethod = toolchainManager
535 .getClass()
536 .getMethod("getToolchains", MavenSession.class, String.class, Map.class);
537
538 @SuppressWarnings("unchecked")
539 List<Toolchain> tcs = (List<Toolchain>) getToolchainsMethod.invoke(
540 toolchainManager, session, "jdk", Collections.singletonMap("version", "[1.8,)"));
541
542 if (tcs != null && !tcs.isEmpty()) {
543
544 tc = tcs.get(tcs.size() - 1);
545 }
546 } catch (NoSuchMethodException
547 | SecurityException
548 | IllegalAccessException
549 | IllegalArgumentException
550 | InvocationTargetException e) {
551
552 }
553 }
554 }
555
556 return tc;
557 }
558
559 protected boolean isFailOnWarning() {
560 return failOnWarning;
561 }
562
563 protected abstract String getClassesDirectory();
564
565 protected abstract Collection<Path> getClassPath() throws DependencyResolutionRequiredException;
566 }