SpotBugsGui.groovy
/*
* SPDX-License-Identifier: Apache-2.0
* See LICENSE file for details.
*
* Copyright 2005-2026 the original author or authors.
*/
package org.codehaus.mojo.spotbugs
import java.awt.GraphicsEnvironment
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import javax.inject.Inject
import org.apache.maven.artifact.Artifact
import org.apache.maven.execution.MavenSession
import org.apache.maven.plugin.AbstractMojo
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.toolchain.Toolchain
import org.apache.maven.toolchain.ToolchainManager
import org.codehaus.plexus.resource.ResourceManager
/**
* Launch the Spotbugs GUI.
* It will use all the parameters in the POM fle.
*
* @since 2.0
*
* @description Launch the Spotbugs GUI using the parameters in the POM fle.
*/
@Mojo(name = 'gui', requiresDependencyResolution = ResolutionScope.TEST, requiresProject = true)
class SpotBugsGui extends AbstractMojo implements SpotBugsPluginsTrait {
/** Locale to use for Resource bundle. */
static Locale locale = Locale.getDefault()
/** Directory containing the class files for Spotbugs to analyze. */
@Parameter(defaultValue = '${project.build.outputDirectory}', required = true)
File classFilesDirectory
/** Turn on Spotbugs debugging. */
@Parameter(defaultValue = 'false', property = 'spotbugs.debug')
boolean debug
/** List of artifacts this plugin depends on. Used for resolving the Spotbugs core plugin. */
@Parameter(property = 'plugin.artifacts', readonly = true, required = true)
List<Artifact> pluginArtifacts
/** Effort of the bug finders. Valid values are Min, Default and Max. */
@Parameter(defaultValue = 'Default', property = 'spotbugs.effort')
String effort
/** The plugin list to include in the report. This is a SpotbugsInfo.COMMA-delimited list. */
@Parameter(property = 'spotbugs.pluginList')
String pluginList
/**
* Collection of PluginArtifact to work on. (PluginArtifact contains groupId, artifactId, version, type, classifier.)
* See <a href="./usage.html#Using Detectors from a Repository">Usage</a> for details.
*
* @since 2.4.1
* @since 4.8.3.0 includes classifier
*/
@Parameter
List<PluginArtifact> plugins
/** Artifact resolver, needed to download the plugin jars. */
@Inject
org.eclipse.aether.RepositorySystem repositorySystem
/** Used to look up Artifacts in the remote repository. */
@Inject
org.apache.maven.repository.RepositorySystem factory
/** Toolchain manager used to retrieve the JDK toolchain. */
@Inject
ToolchainManager toolchainManager
/** Maven Session. */
@Parameter (defaultValue = '${session}', readonly = true, required = true)
MavenSession session
/** Specifies the directory where the Spotbugs native xml output will be generated. */
@Parameter(defaultValue = '${project.build.directory}', required = true)
File spotbugsXmlOutputDirectory
/**
* Set the name of the output XML file produced
*
* @since 3.1.12.2
*/
@Parameter(defaultValue = 'spotbugsXml.xml', property = 'spotbugs.outputXmlFilename')
String spotbugsXmlOutputFilename
/**
* The file encoding to use when reading the source files. If the property <code>project.build.sourceEncoding</code>
* is not set, the platform default encoding is used. <strong>Note:</strong> This parameter always overrides the
* property <code>charset</code> from Checkstyle's <code>TreeWalker</code> module.
*
* @since 2.2
*/
@Parameter(defaultValue = '${project.build.sourceEncoding}', property = 'encoding')
String encoding
/**
* Maximum Java heap size in megabytes (default=512).
*
* @since 2.2
*/
@Parameter(defaultValue = '512', property = 'spotbugs.maxHeap')
int maxHeap
/**
* Resource Manager.
*
* @since 2.0
*/
@Inject
ResourceManager resourceManager
@Override
void execute() {
List<String> auxClasspathElements = session.getCurrentProject().compileClasspathElements
if (debug && log.isInfoEnabled()) {
log.info(" Plugin Artifacts to be added -> ${pluginArtifacts}")
}
Charset effectiveEncoding
if (encoding) {
effectiveEncoding = Charset.forName(encoding)
} else {
effectiveEncoding = Charset.defaultCharset() ?: StandardCharsets.UTF_8
}
if (log.isInfoEnabled()) {
log.info('File Encoding is ' + effectiveEncoding.name())
}
if (GraphicsEnvironment.isHeadless()) {
log.warn('Skipping SpotBugs GUI launch in headless environment')
return
}
// options must be added before the spotbugsXml path
List<String> spotbugsArgs = []
spotbugsArgs << getEffortParameter()
if (pluginList || plugins) {
spotbugsArgs << '-pluginList'
spotbugsArgs << getSpotbugsPlugins()
}
Path spotbugsXml = spotbugsXmlOutputDirectory.toPath().resolve(spotbugsXmlOutputFilename)
if (Files.exists(spotbugsXml)) {
if (log.isDebugEnabled()) {
log.debug(" Found an SpotBugs XML at -> ${spotbugsXml}")
}
spotbugsArgs << spotbugsXml.toString()
}
Toolchain toolchain = toolchainManager?.getToolchainFromBuildContext('jdk', session)
String javaExecutable = 'java'
if (toolchain != null) {
String toolchainPath = toolchain.findTool('java')
if (toolchainPath != null) {
log.info("Toolchain in spotbugs-maven-plugin: ${toolchain}")
javaExecutable = toolchainPath
}
}
// Build classpath
List<String> classpathElements = pluginArtifacts.collect { Artifact pluginArtifact -> pluginArtifact.file.getAbsolutePath() }
String classpath = classpathElements.join(File.pathSeparator)
// Build command
List<String> command = []
command << javaExecutable
command << "-Xmx${maxHeap}m"
command << "-Dfile.encoding=${effectiveEncoding.name()}"
command << '-Dfindbugs.launchUI=gui2'
command << '-cp'
command << classpath
command << 'edu.umd.cs.findbugs.LaunchAppropriateUI'
command.addAll(spotbugsArgs)
List<String> sanitizedCommand = command.collect(Object::toString)
if (debug && log.isInfoEnabled()) {
log.info("Launching SpotBugs GUI with command: ${sanitizedCommand}")
}
ProcessBuilder pb = new ProcessBuilder(sanitizedCommand)
pb.directory(spotbugsXmlOutputDirectory)
pb.redirectErrorStream(true)
Process process = pb.start()
process.inputStream.withReader(effectiveEncoding.name()) { reader ->
reader.eachLine { line ->
if (log.isInfoEnabled()) {
log.info(line)
}
}
}
int exitCode = process.waitFor()
if (exitCode != 0) {
log.error("SpotBugs GUI exited with code: ${exitCode}")
}
}
}