1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.checkstyle;
20
21 import java.io.BufferedReader;
22 import java.io.ByteArrayOutputStream;
23 import java.io.File;
24 import java.io.FileNotFoundException;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.OutputStream;
28 import java.io.Reader;
29 import java.nio.file.Files;
30 import java.nio.file.Path;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.Map;
35
36 import com.puppycrawl.tools.checkstyle.DefaultLogger;
37 import com.puppycrawl.tools.checkstyle.SarifLogger;
38 import com.puppycrawl.tools.checkstyle.XMLLogger;
39 import com.puppycrawl.tools.checkstyle.api.AuditListener;
40 import com.puppycrawl.tools.checkstyle.api.AutomaticBean.OutputStreamOptions;
41 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
42 import org.apache.maven.artifact.Artifact;
43 import org.apache.maven.model.Dependency;
44 import org.apache.maven.model.Plugin;
45 import org.apache.maven.model.PluginManagement;
46 import org.apache.maven.model.Resource;
47 import org.apache.maven.plugin.AbstractMojo;
48 import org.apache.maven.plugin.MojoExecutionException;
49 import org.apache.maven.plugin.MojoFailureException;
50 import org.apache.maven.plugin.descriptor.PluginDescriptor;
51 import org.apache.maven.plugins.annotations.Component;
52 import org.apache.maven.plugins.annotations.LifecyclePhase;
53 import org.apache.maven.plugins.annotations.Mojo;
54 import org.apache.maven.plugins.annotations.Parameter;
55 import org.apache.maven.plugins.annotations.ResolutionScope;
56 import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutor;
57 import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutorException;
58 import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutorRequest;
59 import org.apache.maven.project.MavenProject;
60 import org.codehaus.plexus.configuration.PlexusConfiguration;
61 import org.codehaus.plexus.util.FileUtils;
62 import org.codehaus.plexus.util.PathTool;
63 import org.codehaus.plexus.util.ReaderFactory;
64 import org.codehaus.plexus.util.xml.pull.MXParser;
65 import org.codehaus.plexus.util.xml.pull.XmlPullParser;
66 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
67
68
69
70
71
72
73
74
75
76 @Mojo(
77 name = "check",
78 defaultPhase = LifecyclePhase.VERIFY,
79 requiresDependencyResolution = ResolutionScope.NONE,
80 threadSafe = true)
81 public class CheckstyleViolationCheckMojo extends AbstractMojo {
82
83 private static final String JAVA_FILES = "**\\/*.java";
84 private static final String DEFAULT_CONFIG_LOCATION = "sun_checks.xml";
85
86
87
88
89
90
91 @Parameter(property = "checkstyle.output.file", defaultValue = "${project.build.directory}/checkstyle-result.xml")
92 private File outputFile;
93
94
95
96
97
98 @Parameter(property = "checkstyle.output.format", defaultValue = "xml")
99 private String outputFileFormat;
100
101
102
103
104
105
106
107 @Parameter(property = "checkstyle.failOnViolation", defaultValue = "true")
108 private boolean failOnViolation;
109
110
111
112
113
114
115
116 @Parameter(property = "checkstyle.maxAllowedViolations", defaultValue = "0")
117 private int maxAllowedViolations;
118
119
120
121
122
123
124
125 @Parameter(property = "checkstyle.violationSeverity", defaultValue = "error")
126 private String violationSeverity = "error";
127
128
129
130
131
132
133
134 @Parameter(property = "checkstyle.violation.ignore")
135 private String violationIgnore;
136
137
138
139
140
141
142 @Parameter(property = "checkstyle.skip", defaultValue = "false")
143 private boolean skip;
144
145
146
147
148
149
150 @Parameter(property = "checkstyle.skipExec", defaultValue = "false")
151 private boolean skipExec;
152
153
154
155
156
157
158 @Parameter(property = "checkstyle.console", defaultValue = "true")
159 private boolean logViolationsToConsole;
160
161
162
163
164
165
166 @Parameter(property = "checkstyle.logViolationCount", defaultValue = "true")
167 private boolean logViolationCountToConsole;
168
169
170
171
172
173
174 @Parameter(defaultValue = "${project.resources}", readonly = true)
175 protected List<Resource> resources;
176
177
178
179
180
181
182 @Parameter(defaultValue = "${project.testResources}", readonly = true)
183 protected List<Resource> testResources;
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208 @Parameter(property = "checkstyle.config.location", defaultValue = DEFAULT_CONFIG_LOCATION)
209 private String configLocation;
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227 @Parameter(property = "checkstyle.properties.location")
228 private String propertiesLocation;
229
230
231
232
233 @Parameter
234 private String propertyExpansion;
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254 @Parameter(property = "checkstyle.header.file", defaultValue = "LICENSE.txt")
255 private String headerLocation;
256
257
258
259
260 @Parameter(defaultValue = "${project.build.directory}/checkstyle-cachefile")
261 private String cacheFile;
262
263
264
265
266
267
268 @Parameter(property = "checkstyle.suppression.expression", defaultValue = "checkstyle.suppressions.file")
269 private String suppressionsFileExpression;
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285 @Parameter(property = "checkstyle.suppressions.location")
286 private String suppressionsLocation;
287
288
289
290
291
292
293
294
295 @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}")
296 private String inputEncoding;
297
298
299
300
301 @Component(role = CheckstyleExecutor.class, hint = "default")
302 protected CheckstyleExecutor checkstyleExecutor;
303
304
305
306
307 @Parameter(property = "checkstyle.consoleOutput", defaultValue = "false")
308 private boolean consoleOutput;
309
310
311
312
313 @Parameter(defaultValue = "${project}", readonly = true, required = true)
314 protected MavenProject project;
315
316
317
318
319 @Parameter(defaultValue = "${plugin}", readonly = true, required = true)
320 private PluginDescriptor plugin;
321
322
323
324
325
326 @Parameter
327 private File useFile;
328
329
330
331
332
333 @Parameter(property = "checkstyle.excludes")
334 private String excludes;
335
336
337
338
339 @Parameter(property = "checkstyle.includes", defaultValue = JAVA_FILES, required = true)
340 private String includes;
341
342
343
344
345
346
347 @Parameter(property = "checkstyle.resourceExcludes")
348 private String resourceExcludes;
349
350
351
352
353
354 @Parameter(property = "checkstyle.resourceIncludes", defaultValue = "**/*.properties", required = true)
355 private String resourceIncludes;
356
357
358
359
360
361
362
363 @Parameter(defaultValue = "false")
364 private boolean failsOnError;
365
366
367
368
369
370
371
372
373 @Deprecated
374 @Parameter
375 private File testSourceDirectory;
376
377
378
379
380
381
382
383 @Parameter
384 private List<String> testSourceDirectories;
385
386
387
388
389
390
391 @Parameter(defaultValue = "false")
392 private boolean includeTestSourceDirectory;
393
394
395
396
397
398
399
400 @Deprecated
401 @Parameter
402 private File sourceDirectory;
403
404
405
406
407
408
409
410 @Parameter
411 private List<String> sourceDirectories;
412
413
414
415
416
417 @Parameter(property = "checkstyle.includeResources", defaultValue = "true", required = true)
418 private boolean includeResources = true;
419
420
421
422
423
424 @Parameter(property = "checkstyle.includeTestResources", defaultValue = "true", required = true)
425 private boolean includeTestResources = true;
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451 @Parameter
452 private PlexusConfiguration checkstyleRules;
453
454
455
456
457 @Parameter(
458 property = "checkstyle.output.rules.file",
459 defaultValue = "${project.build.directory}/checkstyle-rules.xml")
460 private File rulesFiles;
461
462
463
464
465
466 @Parameter(
467 defaultValue = "<?xml version=\"1.0\"?>\n"
468 + "<!DOCTYPE module PUBLIC \"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\"\n"
469 + " \"https://checkstyle.org/dtds/configuration_1_3.dtd\">\n")
470 private String checkstyleRulesHeader;
471
472
473
474
475
476
477
478 @Parameter(defaultValue = "false")
479 private boolean omitIgnoredModules;
480
481
482
483
484
485
486 @Parameter(property = "checkstyle.excludeGeneratedSources", defaultValue = "false")
487 private boolean excludeGeneratedSources;
488
489 private ByteArrayOutputStream stringOutputStream;
490
491 private File outputXmlFile;
492
493
494 public void execute() throws MojoExecutionException, MojoFailureException {
495 checkDeprecatedParameterUsage(sourceDirectory, "sourceDirectory", "sourceDirectories");
496 checkDeprecatedParameterUsage(testSourceDirectory, "testSourceDirectory", "testSourceDirectories");
497 if (skip) {
498 return;
499 }
500
501 outputXmlFile = outputFile;
502
503 if (!skipExec) {
504 String effectiveConfigLocation = configLocation;
505 if (checkstyleRules != null) {
506 if (!DEFAULT_CONFIG_LOCATION.equals(configLocation)) {
507 throw new MojoExecutionException(
508 "If you use inline configuration for rules, don't specify " + "a configLocation");
509 }
510 if (checkstyleRules.getChildCount() > 1) {
511 throw new MojoExecutionException("Currently only one root module is supported");
512 }
513
514 PlexusConfiguration checkerModule = checkstyleRules.getChild(0);
515
516 try {
517 FileUtils.forceMkdir(rulesFiles.getParentFile());
518 FileUtils.fileWrite(rulesFiles, checkstyleRulesHeader + checkerModule.toString());
519 } catch (final IOException e) {
520 throw new MojoExecutionException(e.getMessage(), e);
521 }
522 effectiveConfigLocation = rulesFiles.getAbsolutePath();
523 }
524
525 ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
526
527 try {
528 CheckstyleExecutorRequest request = new CheckstyleExecutorRequest();
529 request.setConsoleListener(getConsoleListener())
530 .setConsoleOutput(consoleOutput)
531 .setExcludes(excludes)
532 .setFailsOnError(failsOnError)
533 .setIncludes(includes)
534 .setResourceIncludes(resourceIncludes)
535 .setResourceExcludes(resourceExcludes)
536 .setIncludeResources(includeResources)
537 .setIncludeTestResources(includeTestResources)
538 .setIncludeTestSourceDirectory(includeTestSourceDirectory)
539 .setListener(getListener())
540 .setProject(project)
541 .setSourceDirectories(getSourceDirectories())
542 .setResources(resources)
543 .setTestResources(testResources)
544 .setStringOutputStream(stringOutputStream)
545 .setSuppressionsLocation(suppressionsLocation)
546 .setTestSourceDirectories(getTestSourceDirectories())
547 .setConfigLocation(effectiveConfigLocation)
548 .setConfigurationArtifacts(collectArtifacts("config"))
549 .setPropertyExpansion(propertyExpansion)
550 .setHeaderLocation(headerLocation)
551 .setLicenseArtifacts(collectArtifacts("license"))
552 .setCacheFile(cacheFile)
553 .setSuppressionsFileExpression(suppressionsFileExpression)
554 .setEncoding(inputEncoding)
555 .setPropertiesLocation(propertiesLocation)
556 .setOmitIgnoredModules(omitIgnoredModules);
557 checkstyleExecutor.executeCheckstyle(request);
558
559 } catch (CheckstyleException e) {
560 throw new MojoExecutionException("Failed during checkstyle configuration", e);
561 } catch (CheckstyleExecutorException e) {
562 throw new MojoExecutionException("Failed during checkstyle execution", e);
563 } finally {
564
565 Thread.currentThread().setContextClassLoader(currentClassLoader);
566 }
567 }
568
569 if (!"xml".equals(outputFileFormat) && skipExec) {
570 throw new MojoExecutionException("Output format is '" + outputFileFormat
571 + "', checkstyle:check requires format to be 'xml' when using skipExec.");
572 }
573
574 if (!outputXmlFile.exists()) {
575 getLog().info("Unable to perform checkstyle:check, unable to find checkstyle:checkstyle outputFile.");
576 return;
577 }
578
579 try (Reader reader = new BufferedReader(ReaderFactory.newXmlReader(outputXmlFile))) {
580 XmlPullParser xpp = new MXParser();
581 xpp.setInput(reader);
582
583 final List<Violation> violationsList = getViolations(xpp);
584 long violationCount = countViolations(violationsList);
585 printViolations(violationsList);
586
587 String msg = "You have " + violationCount + " Checkstyle violation"
588 + ((violationCount > 1 || violationCount == 0) ? "s" : "") + ".";
589
590 if (violationCount > maxAllowedViolations) {
591 if (failOnViolation) {
592 if (maxAllowedViolations > 0) {
593 msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
594 }
595 throw new MojoFailureException(msg);
596 }
597
598 getLog().warn("checkstyle:check violations detected but failOnViolation set to false");
599 }
600 if (logViolationCountToConsole) {
601 if (maxAllowedViolations > 0) {
602 msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
603 }
604 getLog().info(msg);
605 }
606 } catch (IOException | XmlPullParserException e) {
607 throw new MojoExecutionException(
608 "Unable to read Checkstyle results xml: " + outputXmlFile.getAbsolutePath(), e);
609 }
610 }
611
612 private void checkDeprecatedParameterUsage(Object parameter, String name, String replacement)
613 throws MojoFailureException {
614 if (parameter != null) {
615 throw new MojoFailureException("You are using '" + name + "' which has been removed"
616 + " from the maven-checkstyle-plugin. " + "Please use '" + replacement
617 + "' and refer to the >>Major Version Upgrade to version 3.0.0<< " + "on the plugin site.");
618 }
619 }
620
621 private List<Violation> getViolations(XmlPullParser xpp) throws XmlPullParserException, IOException {
622 List<Violation> violations = new ArrayList<>();
623
624 String basedir = project.getBasedir().getAbsolutePath();
625 String file = "";
626
627 for (int eventType = xpp.getEventType(); eventType != XmlPullParser.END_DOCUMENT; eventType = xpp.next()) {
628 if (eventType != XmlPullParser.START_TAG) {
629 continue;
630 } else if ("file".equals(xpp.getName())) {
631 file = PathTool.getRelativeFilePath(basedir, xpp.getAttributeValue("", "name"));
632 continue;
633 } else if (!"error".equals(xpp.getName())) {
634 continue;
635 }
636
637 String severity = xpp.getAttributeValue("", "severity");
638 String source = xpp.getAttributeValue("", "source");
639 String line = xpp.getAttributeValue("", "line");
640
641 String column = xpp.getAttributeValue("", "column");
642 String message = xpp.getAttributeValue("", "message");
643 String rule = RuleUtil.getName(source);
644 String category = RuleUtil.getCategory(source);
645
646 Violation violation = new Violation(source, file, line, severity, message, rule, category);
647 if (column != null) {
648 violation.setColumn(column);
649 }
650
651 violations.add(violation);
652 }
653
654 return violations;
655 }
656
657 private int countViolations(List<Violation> violations) {
658 List<RuleUtil.Matcher> ignores = violationIgnore == null
659 ? Collections.<RuleUtil.Matcher>emptyList()
660 : RuleUtil.parseMatchers(violationIgnore.split(","));
661
662 int ignored = 0;
663 int countedViolations = 0;
664
665 for (Violation violation : violations) {
666 if (!isViolation(violation.getSeverity())) {
667 continue;
668 }
669
670 if (ignore(ignores, violation.getSource())) {
671 ignored++;
672 continue;
673 }
674
675 countedViolations++;
676 }
677
678 if (ignored > 0) {
679 getLog().info("Ignored " + ignored + " error" + ((ignored > 1L) ? "s" : "") + ", " + countedViolations
680 + " violation" + ((countedViolations > 1) ? "s" : "") + " remaining.");
681 }
682
683 return countedViolations;
684 }
685
686 private void printViolations(List<Violation> violations) {
687 if (!logViolationsToConsole) {
688 return;
689 }
690
691 List<RuleUtil.Matcher> ignores = violationIgnore == null
692 ? Collections.<RuleUtil.Matcher>emptyList()
693 : RuleUtil.parseMatchers(violationIgnore.split(","));
694
695 violations.stream()
696 .filter(violation -> isViolation(violation.getSeverity()))
697 .filter(violation -> !ignore(ignores, violation.getSource()))
698 .forEach(violation -> {
699 final String message = String.format(
700 "%s:[%s%s] (%s) %s: %s",
701 violation.getFile(),
702 violation.getLine(),
703 (Violation.NO_COLUMN.equals(violation.getColumn())) ? "" : (',' + violation.getColumn()),
704 violation.getCategory(),
705 violation.getRuleName(),
706 violation.getMessage());
707 log(violation.getSeverity(), message);
708 });
709 }
710
711 private void log(String severity, String message) {
712 if ("info".equals(severity)) {
713 getLog().info(message);
714 } else if ("warning".equals(severity)) {
715 getLog().warn(message);
716 } else {
717 getLog().error(message);
718 }
719 }
720
721
722
723
724
725
726
727 private boolean isViolation(String severity) {
728 if ("error".equals(severity)) {
729 return "error".equals(violationSeverity)
730 || "warning".equals(violationSeverity)
731 || "info".equals(violationSeverity);
732 } else if ("warning".equals(severity)) {
733 return "warning".equals(violationSeverity) || "info".equals(violationSeverity);
734 } else if ("info".equals(severity)) {
735 return "info".equals(violationSeverity);
736 } else {
737 return false;
738 }
739 }
740
741 private boolean ignore(List<RuleUtil.Matcher> ignores, String source) {
742 for (RuleUtil.Matcher ignore : ignores) {
743 if (ignore.match(source)) {
744 return true;
745 }
746 }
747 return false;
748 }
749
750 private DefaultLogger getConsoleListener() throws MojoExecutionException {
751 DefaultLogger consoleListener;
752
753 if (useFile == null) {
754 stringOutputStream = new ByteArrayOutputStream();
755 consoleListener = new DefaultLogger(stringOutputStream, OutputStreamOptions.NONE);
756 } else {
757 OutputStream out = getOutputStream(useFile);
758
759 consoleListener = new DefaultLogger(out, OutputStreamOptions.CLOSE);
760 }
761
762 return consoleListener;
763 }
764
765 private OutputStream getOutputStream(File file) throws MojoExecutionException {
766 File parentFile = file.getAbsoluteFile().getParentFile();
767
768 if (!parentFile.exists()) {
769 parentFile.mkdirs();
770 }
771
772 FileOutputStream fileOutputStream;
773 try {
774 fileOutputStream = new FileOutputStream(file);
775 } catch (FileNotFoundException e) {
776 throw new MojoExecutionException("Unable to create output stream: " + file, e);
777 }
778 return fileOutputStream;
779 }
780
781 private AuditListener getListener() throws MojoFailureException, MojoExecutionException {
782 AuditListener listener = null;
783
784 if (outputFileFormat != null && !outputFileFormat.isEmpty()) {
785 File resultFile = outputFile;
786
787 OutputStream out = getOutputStream(resultFile);
788
789 if ("xml".equals(outputFileFormat)) {
790 listener = new XMLLogger(out, OutputStreamOptions.CLOSE);
791 } else if ("plain".equals(outputFileFormat)) {
792 try {
793
794
795 outputXmlFile =
796 Files.createTempFile("checkstyle-result", ".xml").toFile();
797 outputXmlFile.deleteOnExit();
798 OutputStream xmlOut = getOutputStream(outputXmlFile);
799 CompositeAuditListener compoundListener = new CompositeAuditListener();
800 compoundListener.addListener(new XMLLogger(xmlOut, OutputStreamOptions.CLOSE));
801 compoundListener.addListener(new DefaultLogger(out, OutputStreamOptions.CLOSE));
802 listener = compoundListener;
803 } catch (IOException e) {
804 throw new MojoExecutionException("Unable to create temporary file", e);
805 }
806 } else if ("sarif".equals(outputFileFormat)) {
807 try {
808
809
810 outputXmlFile =
811 Files.createTempFile("checkstyle-result", ".xml").toFile();
812 outputXmlFile.deleteOnExit();
813 OutputStream xmlOut = getOutputStream(outputXmlFile);
814 CompositeAuditListener compoundListener = new CompositeAuditListener();
815 compoundListener.addListener(new XMLLogger(xmlOut, OutputStreamOptions.CLOSE));
816 compoundListener.addListener(new SarifLogger(out, OutputStreamOptions.CLOSE));
817 listener = compoundListener;
818 } catch (IOException e) {
819 throw new MojoExecutionException("Unable to create temporary file", e);
820 }
821 } else {
822 throw new MojoFailureException(
823 "Invalid output file format: (" + outputFileFormat + "). Must be 'plain' or 'xml'.");
824 }
825 }
826
827 return listener;
828 }
829
830 private List<Artifact> collectArtifacts(String hint) {
831 List<Artifact> artifacts = new ArrayList<>();
832
833 PluginManagement pluginManagement = project.getBuild().getPluginManagement();
834 if (pluginManagement != null) {
835 artifacts.addAll(getCheckstylePluginDependenciesAsArtifacts(pluginManagement.getPluginsAsMap(), hint));
836 }
837
838 artifacts.addAll(
839 getCheckstylePluginDependenciesAsArtifacts(project.getBuild().getPluginsAsMap(), hint));
840
841 return artifacts;
842 }
843
844 private List<Artifact> getCheckstylePluginDependenciesAsArtifacts(Map<String, Plugin> plugins, String hint) {
845 List<Artifact> artifacts = new ArrayList<>();
846
847 Plugin checkstylePlugin = plugins.get(plugin.getGroupId() + ":" + plugin.getArtifactId());
848 if (checkstylePlugin != null) {
849 for (Dependency dep : checkstylePlugin.getDependencies()) {
850
851 String depKey = dep.getGroupId() + ":" + dep.getArtifactId();
852 artifacts.add(plugin.getArtifactMap().get(depKey));
853 }
854 }
855 return artifacts;
856 }
857
858 private List<File> getSourceDirectories() {
859 if (sourceDirectories == null) {
860 sourceDirectories = filterBuildTarget(project.getCompileSourceRoots());
861 }
862 List<File> sourceDirs = new ArrayList<>(sourceDirectories.size());
863 for (String sourceDir : sourceDirectories) {
864 sourceDirs.add(FileUtils.resolveFile(project.getBasedir(), sourceDir));
865 }
866 return sourceDirs;
867 }
868
869 private List<File> getTestSourceDirectories() {
870 if (testSourceDirectories == null) {
871 testSourceDirectories = filterBuildTarget(project.getTestCompileSourceRoots());
872 }
873 List<File> testSourceDirs = new ArrayList<>(testSourceDirectories.size());
874 for (String testSourceDir : testSourceDirectories) {
875 testSourceDirs.add(FileUtils.resolveFile(project.getBasedir(), testSourceDir));
876 }
877 return testSourceDirs;
878 }
879
880 private List<String> filterBuildTarget(List<String> sourceDirectories) {
881 if (!excludeGeneratedSources) {
882 return sourceDirectories;
883 }
884
885 List<String> filtered = new ArrayList<>(sourceDirectories.size());
886 Path buildTarget = FileUtils.resolveFile(
887 project.getBasedir(), project.getBuild().getDirectory())
888 .toPath();
889
890 for (String sourceDir : sourceDirectories) {
891 Path src = FileUtils.resolveFile(project.getBasedir(), sourceDir).toPath();
892 if (!src.startsWith(buildTarget)) {
893 filtered.add(sourceDir);
894 }
895 }
896 return filtered;
897 }
898 }