1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.pmd;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.List;
26 import java.util.Locale;
27
28 import net.sourceforge.pmd.renderers.Renderer;
29 import org.apache.maven.plugins.annotations.Component;
30 import org.apache.maven.plugins.annotations.Mojo;
31 import org.apache.maven.plugins.annotations.Parameter;
32 import org.apache.maven.plugins.annotations.ResolutionScope;
33 import org.apache.maven.plugins.pmd.exec.PmdExecutor;
34 import org.apache.maven.plugins.pmd.exec.PmdRequest;
35 import org.apache.maven.plugins.pmd.exec.PmdResult;
36 import org.apache.maven.project.DefaultProjectBuildingRequest;
37 import org.apache.maven.project.MavenProject;
38 import org.apache.maven.project.ProjectBuildingRequest;
39 import org.apache.maven.reporting.MavenReportException;
40 import org.apache.maven.shared.artifact.filter.resolve.AndFilter;
41 import org.apache.maven.shared.artifact.filter.resolve.ExclusionsFilter;
42 import org.apache.maven.shared.artifact.filter.resolve.ScopeFilter;
43 import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
44 import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
45 import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
46 import org.apache.maven.toolchain.Toolchain;
47 import org.codehaus.plexus.i18n.I18N;
48 import org.codehaus.plexus.resource.ResourceManager;
49 import org.codehaus.plexus.resource.loader.FileResourceCreationException;
50 import org.codehaus.plexus.resource.loader.FileResourceLoader;
51 import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
52 import org.codehaus.plexus.util.StringUtils;
53
54
55
56
57
58
59
60
61
62 @Mojo(name = "pmd", threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST)
63 public class PmdReport extends AbstractPmdReport {
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 @Parameter(property = "targetJdk", defaultValue = "${maven.compiler.source}")
80 private String targetJdk;
81
82
83
84
85
86
87
88 @Parameter(defaultValue = "java")
89 private String language;
90
91
92
93
94
95
96 @Parameter(property = "minimumPriority", defaultValue = "5")
97 private int minimumPriority = 5;
98
99
100
101
102
103
104 @Parameter(property = "pmd.skip", defaultValue = "false")
105 private boolean skip;
106
107
108
109
110
111
112
113
114 @Parameter
115 String[] rulesets = new String[] {"/rulesets/java/maven-pmd-plugin-default.xml"};
116
117
118
119
120
121
122
123 @Parameter(property = "pmd.typeResolution", defaultValue = "true")
124 private boolean typeResolution;
125
126
127
128
129
130
131 @Parameter(property = "pmd.benchmark", defaultValue = "false")
132 private boolean benchmark;
133
134
135
136
137
138
139 @Parameter(property = "pmd.benchmarkOutputFilename", defaultValue = "${project.build.directory}/pmd-benchmark.txt")
140 private String benchmarkOutputFilename;
141
142
143
144
145
146
147
148
149 @Parameter(property = "pmd.suppressMarker")
150 private String suppressMarker;
151
152
153
154
155
156
157 @Parameter(property = "pmd.skipPmdError", defaultValue = "true")
158 private boolean skipPmdError;
159
160
161
162
163
164
165
166
167
168 @Parameter(property = "pmd.analysisCache", defaultValue = "false")
169 private boolean analysisCache;
170
171
172
173
174
175
176
177
178
179
180 @Parameter(property = "pmd.analysisCacheLocation", defaultValue = "${project.build.directory}/pmd/pmd.cache")
181 private String analysisCacheLocation;
182
183
184
185
186
187
188
189
190
191
192 @Parameter(property = "pmd.renderProcessingErrors", defaultValue = "true")
193 private boolean renderProcessingErrors = true;
194
195
196
197
198
199
200 @Parameter(property = "pmd.renderRuleViolationPriority", defaultValue = "true")
201 private boolean renderRuleViolationPriority = true;
202
203
204
205
206
207
208
209 @Parameter(property = "pmd.renderViolationsByPriority", defaultValue = "true")
210 private boolean renderViolationsByPriority = true;
211
212
213
214
215
216
217 @Parameter(property = "pmd.renderSuppressedViolations", defaultValue = "true")
218 private boolean renderSuppressedViolations = true;
219
220
221
222
223
224
225
226 @Parameter(property = "pmd.rulesetsTargetDirectory", defaultValue = "${project.build.directory}/pmd/rulesets")
227 private File rulesetsTargetDirectory;
228
229
230
231
232
233 @Component
234 private ResourceManager locator;
235
236 @Component
237 private DependencyResolver dependencyResolver;
238
239
240
241
242 @Component
243 private I18N i18n;
244
245
246
247
248
249
250 private PmdResult pmdResult;
251
252
253 public String getName(Locale locale) {
254 return getI18nString(locale, "name");
255 }
256
257
258 public String getDescription(Locale locale) {
259 return getI18nString(locale, "description");
260 }
261
262
263
264
265
266
267 protected String getI18nString(Locale locale, String key) {
268 return i18n.getString("pmd-report", locale, "report.pmd." + key);
269 }
270
271
272
273
274
275
276
277
278 public void setRulesets(String[] rulesets) {
279 this.rulesets = Arrays.copyOf(rulesets, rulesets.length);
280 }
281
282
283
284
285 @Override
286 public void executeReport(Locale locale) throws MavenReportException {
287 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
288 try {
289 Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
290
291 PmdReportRenderer r = new PmdReportRenderer(
292 getLog(),
293 getSink(),
294 i18n,
295 locale,
296 filesToProcess,
297 pmdResult.getViolations(),
298 renderRuleViolationPriority,
299 renderViolationsByPriority,
300 isAggregator());
301 if (renderSuppressedViolations) {
302 r.setSuppressedViolations(pmdResult.getSuppressedViolations());
303 }
304 if (renderProcessingErrors) {
305 r.setProcessingErrors(pmdResult.getErrors());
306 }
307
308 r.render();
309 } finally {
310 Thread.currentThread().setContextClassLoader(origLoader);
311 }
312 }
313
314 @Override
315 public boolean canGenerateReport() {
316 if (skip) {
317 getLog().info("Skipping PMD execution");
318 return false;
319 }
320
321 boolean result = super.canGenerateReport();
322 if (result) {
323 try {
324 executePmd();
325 if (skipEmptyReport) {
326 result = pmdResult.hasViolations();
327 if (!result) {
328 getLog().debug("Skipping report since skipEmptyReport is true and "
329 + "there are no PMD violations.");
330 }
331 }
332 } catch (MavenReportException e) {
333 throw new RuntimeException(e);
334 }
335 }
336 return result;
337 }
338
339 private void executePmd() throws MavenReportException {
340 if (pmdResult != null) {
341
342 getLog().debug("PMD has already been run - skipping redundant execution.");
343 return;
344 }
345
346 try {
347 filesToProcess = getFilesToProcess();
348
349 if (filesToProcess.isEmpty() && !"java".equals(language)) {
350 getLog().warn("No files found to process. Did you add your additional source folders like javascript?"
351 + " (see also build-helper-maven-plugin)");
352 }
353 } catch (IOException e) {
354 throw new MavenReportException("Can't get file list", e);
355 }
356
357 PmdRequest request = new PmdRequest();
358 request.setLanguageAndVersion(language, targetJdk);
359 request.setRulesets(resolveRulesets());
360 request.setAuxClasspath(typeResolution ? determineAuxClasspath() : null);
361 request.setSourceEncoding(getInputEncoding());
362 request.addFiles(filesToProcess.keySet());
363 request.setMinimumPriority(minimumPriority);
364 request.setSuppressMarker(suppressMarker);
365 request.setBenchmarkOutputLocation(benchmark ? benchmarkOutputFilename : null);
366 request.setAnalysisCacheLocation(analysisCache ? analysisCacheLocation : null);
367 request.setExcludeFromFailureFile(excludeFromFailureFile);
368
369 request.setTargetDirectory(targetDirectory.getAbsolutePath());
370 request.setOutputEncoding(getOutputEncoding());
371 request.setFormat(format);
372 request.setShowPmdLog(showPmdLog);
373 request.setSkipPmdError(skipPmdError);
374 request.setIncludeXmlInSite(includeXmlInSite);
375 request.setReportOutputDirectory(getReportOutputDirectory().getAbsolutePath());
376 request.setLogLevel(determineCurrentRootLogLevel());
377
378 Toolchain tc = getToolchain();
379 if (tc != null) {
380 getLog().info("Toolchain in maven-pmd-plugin: " + tc);
381 String javaExecutable = tc.findTool("java");
382 request.setJavaExecutable(javaExecutable);
383 }
384
385 getLog().info("PMD version: " + AbstractPmdReport.getPmdVersion());
386 pmdResult = PmdExecutor.execute(request);
387 }
388
389
390
391
392
393
394
395 private List<String> resolveRulesets() throws MavenReportException {
396
397
398 locator.addSearchPath(
399 FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath());
400
401 locator.addSearchPath(FileResourceLoader.ID, project.getBasedir().getAbsolutePath());
402
403
404 locator.addSearchPath(FileResourceLoader.ID, session.getRequest().getBaseDirectory());
405 locator.setOutputDirectory(rulesetsTargetDirectory);
406
407 String[] sets = new String[rulesets.length];
408 try {
409 for (int idx = 0; idx < rulesets.length; idx++) {
410 String set = rulesets[idx];
411 getLog().debug("Preparing ruleset: " + set);
412 String rulesetFilename = determineRulesetFilename(set);
413 File ruleset = locator.getResourceAsFile(rulesetFilename, getLocationTemp(set));
414 if (null == ruleset) {
415 throw new MavenReportException("Could not resolve " + set);
416 }
417 sets[idx] = ruleset.getAbsolutePath();
418 }
419 } catch (ResourceNotFoundException | FileResourceCreationException e) {
420 throw new MavenReportException(e.getMessage(), e);
421 }
422 return Arrays.asList(sets);
423 }
424
425 private String determineRulesetFilename(String ruleset) {
426 String result = ruleset.trim();
427 String lowercase = result.toLowerCase(Locale.ROOT);
428 if (lowercase.startsWith("http://") || lowercase.startsWith("https://") || lowercase.endsWith(".xml")) {
429 return result;
430 }
431
432
433 if (result.indexOf('/') > -1) {
434 String rulesetFilename = result.substring(0, result.lastIndexOf('/'));
435 if (rulesetFilename.toLowerCase(Locale.ROOT).endsWith(".xml")) {
436 return rulesetFilename;
437 }
438 }
439
440 int dashIndex = lowercase.indexOf('-');
441 if (dashIndex > -1 && lowercase.indexOf('-', dashIndex + 1) == -1) {
442 String language = result.substring(0, dashIndex);
443 String rulesetName = result.substring(dashIndex + 1);
444 return "rulesets/" + language + "/" + rulesetName + ".xml";
445 }
446
447 return result;
448 }
449
450
451
452
453
454
455
456 protected String getLocationTemp(String name) {
457 String loc = name;
458 if (loc.indexOf('/') != -1) {
459 loc = loc.substring(loc.lastIndexOf('/') + 1);
460 }
461 if (loc.indexOf('\\') != -1) {
462 loc = loc.substring(loc.lastIndexOf('\\') + 1);
463 }
464
465
466
467
468
469 loc = loc.replaceAll("[\\?\\:\\&\\=\\%]", "_");
470
471 if (!loc.endsWith(".xml")) {
472 loc = loc + ".xml";
473 }
474
475 getLog().debug("Before: " + name + " After: " + loc);
476 return loc;
477 }
478
479 private String determineAuxClasspath() throws MavenReportException {
480 try {
481 List<String> classpath = new ArrayList<>();
482 if (isAggregator()) {
483 List<String> dependencies = new ArrayList<>();
484
485
486
487
488 List<String> exclusionPatterns = new ArrayList<>();
489 for (MavenProject localProject : getAggregatedProjects()) {
490 exclusionPatterns.add(localProject.getGroupId() + ":" + localProject.getArtifactId());
491 }
492 TransformableFilter filter = new AndFilter(Arrays.asList(
493 new ExclusionsFilter(exclusionPatterns),
494 includeTests
495 ? ScopeFilter.including("compile", "provided", "test")
496 : ScopeFilter.including("compile", "provided")));
497
498 for (MavenProject localProject : getAggregatedProjects()) {
499 ProjectBuildingRequest buildingRequest =
500 new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
501
502 buildingRequest.getRemoteRepositories().addAll(localProject.getRemoteArtifactRepositories());
503
504 Iterable<ArtifactResult> resolvedDependencies = dependencyResolver.resolveDependencies(
505 buildingRequest, localProject.getDependencies(), null, filter);
506
507 for (ArtifactResult resolvedArtifact : resolvedDependencies) {
508 dependencies.add(
509 resolvedArtifact.getArtifact().getFile().toString());
510 }
511
512 List<String> projectClasspath = includeTests
513 ? localProject.getTestClasspathElements()
514 : localProject.getCompileClasspathElements();
515
516
517 classpath.addAll(projectClasspath);
518 if (!localProject.isExecutionRoot()) {
519 for (String path : projectClasspath) {
520 File pathFile = new File(path);
521 String[] children = pathFile.list();
522
523 if (!pathFile.exists() || (children != null && children.length == 0)) {
524 getLog().warn("The project " + localProject.getArtifactId()
525 + " does not seem to be compiled. PMD results might be inaccurate.");
526 }
527 }
528 }
529 }
530
531
532 classpath.addAll(dependencies);
533
534 getLog().debug("Using aggregated aux classpath: " + classpath);
535 } else {
536 classpath.addAll(
537 includeTests ? project.getTestClasspathElements() : project.getCompileClasspathElements());
538
539 getLog().debug("Using aux classpath: " + classpath);
540 }
541 String path = StringUtils.join(classpath.iterator(), File.pathSeparator);
542 return path;
543 } catch (Exception e) {
544 throw new MavenReportException(e.getMessage(), e);
545 }
546 }
547
548
549
550
551 @Override
552 public String getOutputName() {
553 return "pmd";
554 }
555
556
557
558
559
560
561
562
563 @Deprecated
564 public final Renderer createRenderer() throws MavenReportException {
565 return PmdExecutor.createRenderer(format, getOutputEncoding());
566 }
567 }