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