org.codehaus.mojo.spotbugs.SpotBugsMojo.groovy Maven / Gradle / Ivy
Show all versions of spotbugs-maven-plugin Show documentation
/*
* Copyright 2005-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.mojo.spotbugs
import groovy.ant.AntBuilder
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import groovy.xml.XmlSlurper
import groovy.xml.slurpersupport.GPathResult;
import groovy.xml.StreamingMarkupBuilder
import org.apache.maven.artifact.repository.ArtifactRepository
import org.apache.maven.doxia.siterenderer.Renderer
import org.apache.maven.doxia.tools.SiteTool
import org.apache.maven.execution.MavenSession
import org.apache.maven.plugin.MojoExecutionException
import org.apache.maven.plugins.annotations.Component
import org.apache.maven.plugins.annotations.Mojo
import org.apache.maven.plugins.annotations.Parameter
import org.apache.maven.plugins.annotations.ResolutionScope
import org.apache.maven.project.MavenProject
import org.apache.maven.reporting.AbstractMavenReport
import org.apache.maven.reporting.MavenReport
import org.apache.maven.repository.RepositorySystem
import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver
import org.codehaus.plexus.resource.ResourceManager
import org.codehaus.plexus.resource.loader.FileResourceLoader
import java.nio.charset.Charset
import java.nio.file.Files
import java.nio.file.Paths
import java.util.stream.Collectors
/**
* Generates a SpotBugs Report when the site plugin is run.
* The HTML report is generated for site commands only.
*/
@Mojo(name = "spotbugs", requiresDependencyResolution = ResolutionScope.TEST, requiresProject = true, threadSafe = true)
class SpotBugsMojo extends AbstractMavenReport implements SpotBugsPluginsTrait {
/**
* Location where generated html will be created allowed to be not read only as defined in AbstractMavenParent.
*
*/
@Parameter(defaultValue = '${project.reporting.outputDirectory}', required = true)
File outputDirectory
/**
* Turn on and off xml output of the Spotbugs report.
*
* @since 1.0.0
*/
@Parameter(defaultValue = "false", property = "spotbugs.xmlOutput", required = true)
boolean xmlOutput
/**
* Output empty warning file if no classes are specified.
*
* @since 4.8.3.0
*/
@Parameter(defaultValue = "false", property = "spotbugs.noClassOk", required = true)
boolean noClassOk
/**
* Turn on and off HTML output of the Spotbugs report.
*
* @since 4.7.3.1
*/
@Parameter(defaultValue = "false", property = "spotbugs.htmlOutput", required = true)
boolean htmlOutput
/**
* Turn on and off SARIF output of the Spotbugs report.
* SARIF is a JSON format standardize for all code scanning tools.
* https://docs.github.com/en/code-security/secure-coding/integrating-with-code-scanning/sarif-support-for-code-scanning
*
* @since 4.3.1
*/
@Parameter(defaultValue = "false", property = "spotbugs.sarifOutput", required = true)
boolean sarifOutput
/**
* Sarif full Path used with sarif.
*
* @since 4.3.1
*/
@Parameter(defaultValue = "false", property = "spotbugs.sarifFullPath", required = true)
boolean sarifFullPath
/**
* Specifies the directory where the sarif output will be generated.
*
* @since 4.7.2.2
*/
@Parameter(defaultValue = '${project.build.directory}', property = "spotbugs.sarifOutputDirectory", required = true)
File sarifOutputDirectory
/**
* Set the name of the output SARIF file produced.
*
* @since 4.7.2.2
*/
@Parameter(property = "spotbugs.sarifOutputFilename", defaultValue = "spotbugsSarif.json", required = true)
String sarifOutputFilename
/**
* Specifies the directory where the xml output will be generated.
*
* @since 1.0.0
*/
@Parameter(defaultValue = '${project.build.directory}', required = true)
File xmlOutputDirectory
/**
* Specifies the directory where the Spotbugs native xml output will be generated.
*
* @since 1.2.0
*/
@Parameter(defaultValue = '${project.build.directory}', required = true)
File spotbugsXmlOutputDirectory
/**
* Set the name of the output XML file produced
*
* @since 3.1.12.2
*/
@Parameter(property = "spotbugs.outputXmlFilename", defaultValue = "spotbugsXml.xml")
String spotbugsXmlOutputFilename
/**
* Doxia Site Renderer.
*/
@Component(role = Renderer.class)
Renderer siteRenderer
/**
* Directory containing the class files for Spotbugs to analyze.
*/
@Parameter(defaultValue = '${project.build.outputDirectory}', required = true)
File classFilesDirectory
/**
* Directory containing the test class files for Spotbugs to analyze.
*/
@Parameter(defaultValue = '${project.build.testOutputDirectory}', required = true)
File testClassFilesDirectory
/**
* Location of the Xrefs to link to.
*/
@Parameter(defaultValue = '${project.reporting.outputDirectory}/xref')
File xrefLocation
/**
* Location of the Test Xrefs to link to.
*/
@Parameter(defaultValue = '${project.reporting.outputDirectory}/xref-test')
File xrefTestLocation
/**
* The directories containing the sources to be compiled.
*/
@Parameter(defaultValue = '${project.compileSourceRoots}', required = true, readonly = true)
List compileSourceRoots
/**
* The directories containing the test-sources to be compiled.
*
* @since 2.0
*/
@Parameter(defaultValue = '${project.testCompileSourceRoots}', required = true, readonly = true)
List testSourceRoots
/**
* Run Spotbugs on the tests.
*
* @since 2.0
*/
@Parameter(defaultValue = "false", property = "spotbugs.includeTests")
boolean includeTests
/**
* Run Spotbugs with -sourcepath parameter populated with the known source roots.
*/
@Parameter(defaultValue = "false", property = "spotbugs.addSourceDirs")
boolean addSourceDirs
/**
* List of artifacts this plugin depends on. Used for resolving the Spotbugs core plugin.
*/
@Parameter(property = "plugin.artifacts", required = true, readonly = true)
List pluginArtifacts
/**
* List of Remote Repositories used by the resolver.
*
*/
@Parameter(property = "project.remoteArtifactRepositories", required = true, readonly = true)
List remoteRepositories
/**
* The local repository, needed to download the coreplugin jar.
*/
@Parameter(property = "localRepository", required = true, readonly = true)
ArtifactRepository localRepository
/**
* Remote repositories which will be searched for the coreplugin jar.
*/
@Parameter(property = "project.remoteArtifactRepositories", required = true, readonly = true)
List remoteArtifactRepositories
/**
* Maven Session.
*/
@Parameter (defaultValue = '${session}', required = true, readonly = true)
MavenSession session
/**
* Maven Project.
*/
@Parameter(property = "project", required = true, readonly = true)
MavenProject project
/**
* Encoding used for xml files. Default value is UTF-8.
*/
@Parameter(defaultValue = "UTF-8", readonly = true)
String xmlEncoding
/**
* The file encoding to use when reading the source files. If the property project.build.sourceEncoding
* is not set, the platform default encoding is used.
*
* @since 2.2
*/
@Parameter(defaultValue = '${project.build.sourceEncoding}', property = "encoding")
String sourceEncoding
/**
* The file encoding to use when creating the HTML reports. If the property project.reporting.outputEncoding
* is not set, the platform default encoding is used.
*
* @since 2.2
*/
@Parameter(defaultValue = '${project.reporting.outputEncoding}', property = "outputEncoding")
String outputEncoding
/**
* Threshold of minimum bug severity to report. Valid values are High, Default, Low, Ignore, and Exp (for experimental).
*/
@Parameter(defaultValue = "Default", property = "spotbugs.threshold")
String threshold
/**
* Artifact resolver, needed to download the coreplugin jar.
*/
@Component(role = ArtifactResolver.class)
ArtifactResolver artifactResolver
/**
* Used to look up Artifacts in the remote repository.
*/
@Component(role = RepositorySystem.class)
RepositorySystem factory
/**
* File name of the include filter. Only bugs in matching the filters are reported.
*
* Potential values are a filesystem path, a URL, or a classpath resource.
*
* This parameter is resolved as resource, URL, then file. If successfully
* resolved, the contents of the configuration is copied into the
* ${project.build.directory}
* directory before being passed to Spotbugs as a filter file.
* It supports multiple files separated by a comma
*
* @since 1.0-beta-1
*/
@Parameter(property = "spotbugs.includeFilterFile")
String includeFilterFile
/**
* File name for include filter files. Only bugs in matching the filters are reported.
*
* Potential values are a filesystem path, a URL, or a classpath resource.
*
* This is an alternative to <includeFilterFile>
which allows multiple
* files to be specified as separate elements in a pom.
*
* This parameter is resolved as resource, URL, then file. If successfully
* resolved, the contents of the configuration is copied into the
* ${project.build.directory}
* directory before being passed to Spotbugs as a filter file.
*
* @since 4.7.1.0
*/
@Parameter(property = "spotbugs.includeFilterFiles")
List includeFilterFiles
/**
* File name of the exclude filter. Bugs matching the filters are not reported.
*
* Potential values are a filesystem path, a URL, or a classpath resource.
*
* This parameter is resolved as resource, URL, then file. If successfully
* resolved, the contents of the configuration is copied into the
* ${project.build.directory}
* directory before being passed to Spotbugs as a filter file.
* It supports multiple files separated by a comma
*
* @since 1.0-beta-1
*/
@Parameter(property = "spotbugs.excludeFilterFile")
String excludeFilterFile
/**
* File name for exclude filter files. Bugs matching the filters are not reported.
*
* This is an alternative to <excludeFilterFile>
which allows multiple
* files to be specified as separate elements in a pom.
*
* This parameter is resolved as resource, URL, then file. If successfully
* resolved, the contents of the configuration is copied into the
* ${project.build.directory}
* directory before being passed to Spotbugs as a filter file.
*
* @since 4.7.1.0
*/
@Parameter(property = "spotbugs.excludeFilterFiles")
List excludeFilterFiles
/**
* File names of the baseline files. Bugs found in the baseline files won't be reported.
*
* Potential values are a filesystem path, a URL, or a classpath resource.
*
* This parameter is resolved as resource, URL, then file. If successfully
* resolved, the contents of the configuration is copied into the
* ${project.build.directory}
* directory before being passed to Spotbugs as a filter file.
*
* This is a comma-delimited list.
*
* @since 2.4.1
*/
@Parameter(property = "spotbugs.excludeBugsFile")
String excludeBugsFile
/**
* File names of the baseline files. Bugs found in the baseline files won't be reported.
*
* Potential values are a filesystem path, a URL, or a classpath resource.
*
* This is an alternative to <excludeBugsFile>
which allows multiple
* files to be specified as separate elements in a pom.
*
* This parameter is resolved as resource, URL, then file. If successfully
* resolved, the contents of the configuration is copied into the
* ${project.build.directory}
* directory before being passed to Spotbugs as a filter file.
*
* @since 4.7.1.0
*/
@Parameter(property = "spotbugs.excludeBugsFiles")
List excludeBugsFiles
/**
* Effort of the bug finders. Valid values are Min, Default and Max.
*
* @since 1.0-beta-1
*/
@Parameter(defaultValue = "Default", property = "spotbugs.effort")
String effort
/**
* Turn on Spotbugs debugging.
*
*/
@Parameter(defaultValue = "false", property = "spotbugs.debug")
boolean debug
/**
* Relaxed reporting mode. For many detectors, this option suppresses the heuristics used to avoid reporting false
* positives.
*
* @since 1.1
*/
@Parameter(defaultValue = "false", property = "spotbugs.relaxed")
boolean relaxed
/**
* The visitor list to run. This is a comma-delimited list.
*
* @since 1.0-beta-1
*/
@Parameter(property = "spotbugs.visitors")
String visitors
/**
* The visitor list to omit. This is a comma-delimited list.
*
* @since 1.0-beta-1
*/
@Parameter(property = "spotbugs.omitVisitors")
String omitVisitors
/**
* The plugin list to include in the report. This is a comma-delimited list.
*
* Potential values are a filesystem path, a URL, or a classpath resource.
*
* This parameter is resolved as resource, URL, then file. If successfully
* resolved, the contents of the configuration is copied into the
* ${project.build.directory}
* directory before being passed to Spotbugs as a plugin file.
*
* @since 1.0-beta-1
*/
@Parameter(property = "spotbugs.pluginList")
String pluginList
/**
* Collection of PluginArtifact to work on. (PluginArtifact contains groupId, artifactId, version, type, classifier.)
* See Usage for details.
*
* @since 2.4.1
* @since 4.8.3.0 includes classfier
*/
@Parameter
PluginArtifact[] plugins;
/**
* Restrict analysis to the given comma-separated list of classes and packages.
*
* @since 1.1
*/
@Parameter(property = "spotbugs.onlyAnalyze")
String onlyAnalyze
/**
* This option enables or disables scanning of nested jar and zip files found
* in the list of files and directories to be analyzed.
*
* @since 2.3.2
*/
@Parameter(property = "spotbugs.nested", defaultValue = "false")
boolean nested
/**
* Prints a trace of detectors run and classes analyzed to standard output.
* Useful for troubleshooting unexpected analysis failures.
*
* @since 2.3.2
*/
@Parameter(property = "spotbugs.trace", defaultValue = "false")
boolean trace
/**
* Maximum bug ranking to record.
*
* @since 2.4.1
*/
@Parameter(property = "spotbugs.maxRank")
int maxRank
/**
* Skip entire check.
*
* @since 1.1
*/
@Parameter(property = "spotbugs.skip", defaultValue = "false")
boolean skip
/**
* Resource Manager.
*
* @since 2.0
*/
@Component(role = ResourceManager.class)
ResourceManager resourceManager
/**
* SiteTool.
*
* @since 2.1-SNAPSHOT
*/
@Component(role = SiteTool.class)
SiteTool siteTool
/**
* Fail the build on an error.
*
* @since 2.0
*/
@Parameter(property = "spotbugs.failOnError", defaultValue = "true")
boolean failOnError
/**
* Fork a VM for Spotbugs analysis. This will allow you to set timeouts and heap size.
*
* @since 2.3.2
*/
@Parameter(property = "spotbugs.fork", defaultValue = "true")
boolean fork
/**
* Maximum Java heap size in megabytes (default=512).
* This only works if the fork parameter is set true.
*
* @since 2.2
*/
@Parameter(property = "spotbugs.maxHeap", defaultValue = "512")
int maxHeap
/**
* Specifies the amount of time, in milliseconds, that Spotbugs may run before
* it is assumed to be hung and is terminated.
* The default is 600,000 milliseconds, which is ten minutes.
* This only works if the fork parameter is set true.
*
* @since 2.2
*/
@Parameter(property = "spotbugs.timeout", defaultValue = "600000")
int timeout
/**
* The arguments to pass to the forked VM (ignored if fork is disabled).
*
* @since 2.4.1
*/
@Parameter(property = "spotbugs.jvmArgs")
String jvmArgs
/**
* Skip the Spotbugs HTML report generation if there are no violations found. Defaults to
* false
.
*
* @since 3.0.1
*/
@Parameter(property = "spotbugs.skipEmptyReport", defaultValue = "false")
boolean skipEmptyReport
/**
* Set the path of the user preferences file to use.
* Will try to read the path as a resource before treating it as a local path.
*
* This will read in a configuration file to set up Spotbugs.
*
* The parameters in the POM file will override anything in the config file
*
* @since 3.0.2
*/
@Parameter(property = "spotbugs.userPrefs")
String userPrefs
/**
* System properties to set in the VM (or the forked VM if fork is enabled).
*
* @since 4.3.0
*/
@Parameter(property = "spotbugs.systemPropertyVariables")
Map systemPropertyVariables
int bugCount
int errorCount
ResourceBundle bundle
File outputSpotbugsFile
/**
* Checks whether prerequisites for generating this report are given.
*
* @return true if report can be generated, otherwise false
* @see AbstractMavenReport#canGenerateReport()
*/
@Override
boolean canGenerateReport() {
boolean canGenerate
log.debug("****** SpotBugsMojo canGenerateReport *******")
if (!skip && classFilesDirectory.exists()) {
classFilesDirectory.eachFileRecurse {
if (it.name.contains(SpotBugsInfo.CLASS_SUFFIX)) {
canGenerate = true
}
}
log.debug("canGenerate Src is ${canGenerate}")
}
if (!skip && testClassFilesDirectory.exists() && includeTests) {
testClassFilesDirectory.eachFileRecurse {
if (it.name.contains(SpotBugsInfo.CLASS_SUFFIX)) {
canGenerate = true
}
}
log.debug("canGenerate Test Src is ${canGenerate}")
}
if (canGenerate && outputSpotbugsFile == null) {
outputSpotbugsFile = new File("${spotbugsXmlOutputDirectory}/${spotbugsXmlOutputFilename}")
executeSpotbugs(outputSpotbugsFile)
if (skipEmptyReport && bugCount == 0) {
canGenerate = false
}
}
log.debug("canGenerate is ${canGenerate}")
return canGenerate
}
/**
* Returns the plugins description for the "generated reports" overview page.
*
* @param locale
* the locale the report should be generated for
*
* @return description of the report
* @see MavenReport#getDescription(Locale)
*/
@Override
String getDescription(Locale locale) {
return getBundle(locale).getString(SpotBugsInfo.DESCRIPTION_KEY)
}
/**
* Returns the plugins name for the "generated reports" overview page and the menu.
*
* @param locale
* the locale the report should be generated for
*
* @return name of the report
* @see MavenReport#getName(Locale)
*/
@Override
String getName(Locale locale) {
return getBundle(locale).getString(SpotBugsInfo.NAME_KEY)
}
/**
* Returns report output file name, without the extension.
*
* Called by AbstractMavenReport.execute() for creating the sink.
*
* @return name of the generated page
* @see MavenReport#getOutputName()
*/
@Override
String getOutputName() {
return SpotBugsInfo.PLUGIN_NAME
}
/**
* Executes the generation of the report.
*
* Callback from Maven Site Plugin.
*
* @param locale the wanted locale to generate the report, could be null.
*
* @see AbstractMavenReport#executeReport(Locale)
*/
@Override
void executeReport(Locale locale) {
log.debug("****** SpotBugsMojo executeReport *******")
executeCheck()
if (skip || !canGenerateReport()) {
log.info("cannot generate report")
return
}
if (log.isDebugEnabled()) {
log.debug("Locale is ${locale.getLanguage()}")
log.debug("****** SpotBugsMojo executeReport *******")
log.debug("report Output Directory is " + getReportOutputDirectory())
log.debug("Output Directory is " + outputDirectory)
log.debug("Classes Directory is " + classFilesDirectory)
log.debug(" Plugin Artifacts to be added ->" + pluginArtifacts.toString())
}
generateXDoc(locale)
if (!outputDirectory.exists() && !outputDirectory.mkdirs()) {
throw new MojoExecutionException("Cannot create html output directory")
}
if (outputSpotbugsFile != null && outputSpotbugsFile.exists()) {
if (skipEmptyReport && bugCount == 0) {
log.info("Skipping Generation Spotbugs HTML since there are not any bugs")
} else {
log.debug("Generating Spotbugs HTML")
SpotbugsReportGenerator generator = new SpotbugsReportGenerator(getSink(), getBundle(locale), this.project.getBasedir(), siteTool)
boolean isJxrPluginEnabled = isJxrPluginEnabled()
generator.setIsJXRReportEnabled(isJxrPluginEnabled)
if (isJxrPluginEnabled) {
generator.setCompileSourceRoots(this.compileSourceRoots)
generator.setTestSourceRoots(this.testSourceRoots)
generator.setXrefLocation(this.xrefLocation)
generator.setXrefTestLocation(this.xrefTestLocation)
generator.setIncludeTests(this.includeTests)
}
generator.setLog(log)
generator.threshold = threshold
generator.effort = effort
generator.setSpotbugsResults(new XmlSlurper().parse(outputSpotbugsFile))
generator.setOutputDirectory(new File(outputDirectory.getAbsolutePath()))
generator.generateReport()
log.debug("xmlOutput is ${xmlOutput}")
}
}
}
@Override
public void execute() {
log.debug("****** SpotBugsMojo execute *******")
if (!skip) {
executeCheck()
if (canGenerateReport()) {
Locale locale = Locale.getDefault()
generateXDoc(locale)
}
}
}
private void executeCheck() {
log.debug("****** SpotBugsMojo executeCheck *******")
log.debug("Generating Spotbugs XML")
if (!spotbugsXmlOutputDirectory.exists() && !spotbugsXmlOutputDirectory.mkdirs()) {
throw new MojoExecutionException("Cannot create xml output directory")
}
}
private void generateXDoc(Locale locale) {
log.debug("****** SpotBugsMojo generateXDoc *******")
if (outputSpotbugsFile == null || !outputSpotbugsFile.exists()) {
return
}
log.debug("xmlOutput is ${xmlOutput}")
if (xmlOutput) {
log.debug(" Using the xdoc format")
if (!xmlOutputDirectory.exists() && !xmlOutputDirectory.mkdirs()) {
throw new MojoExecutionException("Cannot create xdoc output directory")
}
XDocsReporter xDocsReporter = new XDocsReporter(getBundle(locale), log, threshold, effort, outputEncoding)
xDocsReporter.setOutputWriter(Files.newBufferedWriter(Paths.get("${xmlOutputDirectory}/spotbugs.xml"), Charset.forName(outputEncoding)))
xDocsReporter.setSpotbugsResults(new XmlSlurper().parse(outputSpotbugsFile))
xDocsReporter.setCompileSourceRoots(this.compileSourceRoots)
xDocsReporter.setTestSourceRoots(this.testSourceRoots)
xDocsReporter.generateReport()
}
}
/**
* Returns the report output directory allowed to be not read only as defined in AbstractMavenParent.
*
* Called by AbstractMavenReport.execute() for creating the sink.
*
* @return full path to the directory where the files in the site get copied to
* @see AbstractMavenReport#getOutputDirectory()
*/
@Override
protected String getOutputDirectory() {
return outputDirectory.getAbsolutePath()
}
/**
* Return the project.
*
* @return the project.
* @see AbstractMavenReport#getProject()
*/
@Override
protected MavenProject getProject() {
return this.project
}
/**
* Return the Site Renderer.
*
*/
@Override
protected Renderer getSiteRenderer() {
return this.siteRenderer
}
/**
* Determines if the JXR-Plugin is included in the report section of the POM.
*
* @param bundle
* The bundle to load the artifactIf of the jxr plugin.
* @return True if the JXR-Plugin is included in the POM, false otherwise.
*
*/
protected boolean isJxrPluginEnabled() {
if (xrefLocation.exists()) {
return true
}
List reportPlugins = getProject().getReportPlugins()
boolean isEnabled
reportPlugins.each() { reportPlugin ->
log.debug("report plugin -> ${reportPlugin.getArtifactId()}")
if ("maven-jxr-plugin".equals(reportPlugin.getArtifactId())) {
isEnabled = true
}
}
log.debug("jxr report links are ${isEnabled ? "enabled" : "disabled"}")
return isEnabled
}
ResourceBundle getBundle(locale) {
this.bundle = ResourceBundle.getBundle(SpotBugsInfo.BUNDLE_NAME, locale, SpotBugsMojo.class.getClassLoader())
log.debug("Mojo Locale is " + this.bundle.getLocale().getLanguage())
return bundle
}
/**
* Get the Spotbugs command line arguments.
*
* @param htmlTempFile Spotbugs html temp output file
* @param xmlTempFile Spotbugs xml temp output file
* @param sarifTempFile Spotbugs sarif temp output file
*
* @return Spotbugs command line arguments.
*
*/
private ArrayList getSpotbugsArgs(File htmlTempFile, File xmlTempFile, File sarifTempFile) {
ResourceHelper resourceHelper = new ResourceHelper(log, spotbugsXmlOutputDirectory, resourceManager)
List args = new ArrayList<>()
File auxClasspathFile = createSpotbugsAuxClasspathFile()
if (userPrefs) {
log.debug(" Adding User Preferences File -> ${userPrefs}")
args << "-userPrefs"
args << resourceHelper.getResourceFile(userPrefs.trim())
}
if (htmlOutput) {
args << "-html=" + htmlTempFile.getAbsolutePath()
}
args << "-xml:withMessages=" + xmlTempFile.getAbsolutePath()
if (sarifOutput) {
args << "-sarif=" + sarifTempFile.getAbsolutePath()
}
if (auxClasspathFile) {
args << "-auxclasspathFromFile"
args << auxClasspathFile.getAbsolutePath()
}
args << "-projectName"
args << "${project.name}"
args << getEffortParameter()
args << getThresholdParameter()
if (debug) {
log.debug("progress on")
args << "-progress"
}
if (pluginList || plugins) {
args << "-pluginList"
args << getSpotbugsPlugins()
}
if (visitors) {
args << "-visitors"
args << visitors
}
if (omitVisitors) {
args << "-omitVisitors"
args << omitVisitors
}
if (relaxed) {
args << "-relaxed"
}
if (nested) {
args << "-nested:true"
} else {
args << "-nested:false"
}
if (onlyAnalyze) {
args << "-onlyAnalyze"
args << Arrays.stream(onlyAnalyze.split(",")).map {
it.startsWith("file:") ? Files.lines(resourceHelper.getResourceFile(it.substring(5)).toPath()).collect(Collectors.joining(",")) : it
}.collect(Collectors.joining(","))
}
if (includeFilterFile) {
log.debug(" Adding Include Filter File ")
String[] includefilters = includeFilterFile.split(SpotBugsInfo.COMMA)
includefilters.each { includefilter ->
args << "-include"
args << resourceHelper.getResourceFile(includefilter.trim())
}
}
if (includeFilterFiles) {
log.debug(" Adding Include Filter Files ")
includeFilterFiles.each { includefilter ->
args << "-include"
args << resourceHelper.getResourceFile(includefilter.trim())
}
}
if (excludeFilterFile) {
log.debug(" Adding Exclude Filter File ")
String[] excludefilters = excludeFilterFile.split(SpotBugsInfo.COMMA)
excludefilters.each { excludeFilter ->
args << "-exclude"
args << resourceHelper.getResourceFile(excludeFilter.trim())
}
}
if (excludeFilterFiles) {
log.debug(" Adding Exclude Filter Files ")
excludeFilterFiles.each { excludeFilter ->
args << "-exclude"
args << resourceHelper.getResourceFile(excludeFilter.trim())
}
}
if (excludeBugsFile) {
log.debug(" Adding Exclude Bug File (Baselines)")
String[] excludeFiles = excludeBugsFile.split(SpotBugsInfo.COMMA)
excludeFiles.each() { excludeFile ->
args << "-excludeBugs"
args << resourceHelper.getResourceFile(excludeFile.trim())
}
}
if (excludeBugsFiles) {
log.debug(" Adding Exclude Bug Files (Baselines)")
excludeBugsFiles.each() { excludeFile ->
args << "-excludeBugs"
args << resourceHelper.getResourceFile(excludeFile.trim())
}
}
if (addSourceDirs) {
log.debug(" Adding Source directories (To process source exclusions)")
args << "-sourcepath"
String sourceRoots = ""
compileSourceRoots.each() { sourceRoots += it + File.pathSeparator }
if (includeTests) {
testSourceRoots.each() { sourceRoots + it + File.pathSeparator }
}
args << sourceRoots.substring(0, sourceRoots.length() -1)
}
if (maxRank) {
args << "-maxRank"
args << maxRank
}
if (classFilesDirectory.isDirectory()) {
log.debug(" Adding to Source Directory ->" + classFilesDirectory.absolutePath)
args << classFilesDirectory.absolutePath
}
if (testClassFilesDirectory.isDirectory() && includeTests) {
log.debug(" Adding to Source Directory ->" + testClassFilesDirectory.absolutePath)
args << testClassFilesDirectory.absolutePath
}
if (noClassOk) {
log.debug(" Adding no class ok")
args << "-noClassOk"
}
return args
}
/**
* Create the Spotbugs AuxClasspath file.
*
*/
private File createSpotbugsAuxClasspathFile() {
List auxClasspathElements
if (testClassFilesDirectory.isDirectory() && includeTests) {
auxClasspathElements = project.testClasspathElements
} else if (classFilesDirectory.isDirectory()) {
auxClasspathElements = project.compileClasspathElements
}
File auxClasspathFile = null
if (auxClasspathElements) {
auxClasspathFile = File.createTempFile("auxclasspath", ".tmp")
auxClasspathFile.deleteOnExit()
log.debug(" AuxClasspath Elements ->" + auxClasspathElements)
List auxClasspathList = auxClasspathElements.findAll { project.build.outputDirectory != it.toString() }
if (auxClasspathList.size() > 0) {
log.debug(" Last AuxClasspath is ->" + auxClasspathList[auxClasspathList.size() - 1])
auxClasspathList.each() { auxClasspathElement ->
log.debug(" Adding to AuxClasspath ->" + auxClasspathElement.toString())
auxClasspathFile << auxClasspathElement.toString() + SpotBugsInfo.EOL
}
}
}
return auxClasspathFile
}
/**
* For the file creation by creating the file AND folder if needed.
* The file created will be empty.
*
* @param file Destination file to create.
*/
private void forceFileCreation(File file) {
if (file.exists()) {
file.delete()
}
file.getParentFile().mkdirs()
file.createNewFile()
}
/**
* Set up and run the Spotbugs engine.
*
* @param outputFile
* the outputFile
*
*/
private void executeSpotbugs(File outputFile) {
log.debug("****** SpotBugsMojo executeSpotbugs *******")
File htmlTempFile = new File("${project.build.directory}/spotbugs.html")
if (htmlOutput) {
forceFileCreation(htmlTempFile)
}
File xmlTempFile = new File("${project.build.directory}/spotbugsTemp.xml")
forceFileCreation(xmlTempFile)
File sarifTempFile = new File("${project.build.directory}/spotbugsTempSarif.json")
if (sarifOutput) {
forceFileCreation(sarifTempFile)
}
outputEncoding = outputEncoding ?: 'UTF-8'
log.debug("****** Executing SpotBugsMojo *******")
resourceManager.addSearchPath(FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath())
resourceManager.addSearchPath(SpotBugsInfo.URL, "")
resourceManager.setOutputDirectory(new File(project.getBuild().getDirectory()))
if (log.isDebugEnabled()) {
log.debug("resourceManager.outputDirectory is ${resourceManager.outputDirectory}")
log.debug("Plugin Artifacts to be added -> ${pluginArtifacts.toString()}")
log.debug("outputFile is ${outputFile.getCanonicalPath()}")
log.debug("output Directory is ${spotbugsXmlOutputDirectory.getAbsolutePath()}")
if (htmlOutput) {
log.debug("HtmlTempFile is ${htmlTempFile.getCanonicalPath()}")
}
log.debug("XmlTempFile is ${xmlTempFile.getCanonicalPath()}")
if (sarifOutput) {
log.debug("SarifTempFile is ${sarifTempFile.getCanonicalPath()}")
}
}
AntBuilder ant = new AntBuilder()
log.info("Fork Value is ${fork}")
long startTime
if (log.isDebugEnabled()) {
startTime = System.nanoTime()
}
List spotbugsArgs = getSpotbugsArgs(htmlTempFile, xmlTempFile, sarifTempFile)
String effectiveEncoding = System.getProperty("file.encoding", "UTF-8")
if (sourceEncoding) {
effectiveEncoding = sourceEncoding
}
ant.java(classname: "edu.umd.cs.findbugs.FindBugs2", fork: "${fork}", failonerror: "true", clonevm: "false", timeout: "${timeout}", maxmemory: "${maxHeap}m") {
log.debug("File Encoding is " + effectiveEncoding)
sysproperty(key: "file.encoding", value: effectiveEncoding)
if (jvmArgs && fork) {
log.debug("Adding JVM Args => ${jvmArgs}")
String[] args = jvmArgs.split(SpotBugsInfo.BLANK)
args.each() { jvmArg ->
log.debug("Adding JVM Arg => ${jvmArg}")
jvmarg(value: jvmArg)
}
}
if (debug || trace) {
sysproperty(key: "findbugs.debug", value: Boolean.TRUE)
}
classpath() {
pluginArtifacts.each() { pluginArtifact ->
log.debug(" Adding to pluginArtifact ->" + pluginArtifact.file.toString())
pathelement(location: pluginArtifact.file)
}
}
spotbugsArgs.each { spotbugsArg ->
log.debug("Spotbugs arg is ${spotbugsArg}")
arg(value: spotbugsArg)
}
systemPropertyVariables.each { sysProp ->
log.debug("System property ${sysProp.key} is ${sysProp.value}")
sysproperty(key: sysProp.key, value: sysProp.value)
}
}
long duration
if (log.isDebugEnabled()) {
duration = (System.nanoTime() - startTime) / 1000000000.00
log.debug("SpotBugs duration is ${duration}")
}
log.info("Done SpotBugs Analysis....")
if (htmlTempFile.exists() && htmlOutput && htmlTempFile.size() > 0) {
// Do nothing more at this time
}
if (xmlTempFile.exists()) {
if (xmlTempFile.size() > 0) {
GPathResult path = new XmlSlurper().parse(xmlTempFile)
List allNodes = path.depthFirst().collect { it }
bugCount = allNodes.findAll { it.name() == 'BugInstance' }.size()
log.debug("BugInstance size is ${bugCount}")
errorCount = allNodes.findAll { it.name() == 'Error' }.size()
log.debug("Error size is ${errorCount}")
def xmlProject = path.Project
compileSourceRoots.each() { compileSourceRoot ->
xmlProject.appendNode { SrcDir(compileSourceRoot) }
}
if (testClassFilesDirectory.isDirectory() && includeTests) {
testSourceRoots.each() { testSourceRoot ->
xmlProject.appendNode { SrcDir(testSourceRoot) }
}
}
// Fixes visitor problem
path.SpotbugsResults.FindBugsSummary.'total_bugs' = bugCount
xmlProject.appendNode {
WrkDir(project.build.directory)
}
StreamingMarkupBuilder xmlBuilder = new StreamingMarkupBuilder()
if (outputFile.exists()) {
outputFile.delete()
}
outputFile.getParentFile().mkdirs()
outputFile.createNewFile()
BufferedWriter writer = Files.newBufferedWriter(outputFile.toPath(), Charset.forName(effectiveEncoding))
if (effectiveEncoding.equalsIgnoreCase("Cp1252")) {
writer.write ""
} else {
writer.write ""
}
writer.write SpotBugsInfo.EOL
writer << xmlBuilder.bind { mkp.yield path }
} else {
log.info("No bugs found")
}
if (!log.isDebugEnabled()) {
xmlTempFile.delete()
}
}
if (sarifTempFile && sarifOutput && sarifTempFile.size() > 0) {
def slurpedResult = new JsonSlurper().parse(sarifTempFile)
JsonBuilder builder = new JsonBuilder(slurpedResult)
// With -Dspotbugs.sarifFullPath=true
// The location uri will be replace by path relative to the root of project
// SomeFile.java => src/main/java/SomeFile.java
// This change is required for some tool including Github code scanning API
if (sarifFullPath) {
SourceFileIndexer indexer = new SourceFileIndexer()
indexer.buildListSourceFiles(getProject(),getSession())
for (result in slurpedResult.runs.results[0]) {
for (loc in result.locations) {
String originalFullPath = loc.physicalLocation.artifactLocation.uri
//We replace relative path to the complete path
String newFileName = indexer.searchActualFilesLocation(originalFullPath)
if (newFileName != null) {
if (getLog().isDebugEnabled()) {
getLog().info("$originalFullPath modified to $newFileName")
}
loc.physicalLocation.artifactLocation.uri = newFileName
} else {
getLog().warn("No source file found for $originalFullPath. " +
"The path include in the SARIF report could be incomplete.")
}
}
}
}
File sarifFinalFile = new File(sarifOutputDirectory, sarifOutputFilename)
forceFileCreation(sarifFinalFile)
sarifFinalFile.withWriter {
builder.writeTo(it)
}
if (!log.isDebugEnabled()) {
sarifTempFile.delete()
}
}
}
/**
* Returns the threshold parameter to use.
*
* @return A valid threshold parameter.
*
*/
protected String getThresholdParameter() {
log.debug("threshold is ${threshold}")
String thresholdParameter
switch (threshold) {
case "High":
thresholdParameter = "-high"
break
case "Exp":
thresholdParameter = "-experimental"
break
case "Low":
thresholdParameter = "-low"
break
case "high":
thresholdParameter = "-high"
break
default:
thresholdParameter = "-medium"
break
}
log.debug("thresholdParameter is ${thresholdParameter}")
return thresholdParameter
}
/**
* Set report output directory, allowed to be not read only as defined in AbstractMavenParent.
*
* @see AbstractMavenReport#setReportOutputDirectory(File)
*/
@Override
public void setReportOutputDirectory(File reportOutputDirectory) {
super.setReportOutputDirectory(reportOutputDirectory)
this.outputDirectory = reportOutputDirectory
}
}