Skip to content
This repository has been archived by the owner on Feb 11, 2022. It is now read-only.

Integrate SpotBugs #209

Merged
merged 11 commits into from Oct 30, 2019
5 changes: 3 additions & 2 deletions README.md
Expand Up @@ -22,6 +22,7 @@ The plugin supports various static analysis tools for Java, Kotlin and Android p
* [`Checkstyle`](docs/tools/checkstyle.md)
* [`PMD`](docs/tools/pmd.md)
* [`FindBugs`](docs/tools/findbugs.md)
* [`SpotBugs`](docs/tools/spotbugs.md)
* [`Detekt`](docs/tools/detekt.md)
* [`Android Lint`](docs/tools/android_lint.md)
* [`KtLint`](docs/tools/ktlint.md)
Expand All @@ -31,7 +32,6 @@ Please note that the tools availability depends on the project the plugin is app

### Tools in-consideration

* `Spotbugs` [#142](https://github.com/novoda/gradle-static-analysis-plugin/issues/142)
* `CPD (Duplicate Code Detection) ` [#150](https://github.com/novoda/gradle-static-analysis-plugin/issues/150)
* `error-prone` [#151](https://github.com/novoda/gradle-static-analysis-plugin/issues/151)
* `Jetbrains IDEA Inspections` [#152](https://github.com/novoda/gradle-static-analysis-plugin/issues/152)
Expand Down Expand Up @@ -80,6 +80,7 @@ staticAnalysis {
checkstyle { }
pmd { }
findbugs { }
spotbugs { }
detekt { }
lintOptions { }
}
Expand All @@ -89,7 +90,7 @@ This will enable all the tools with their default settings and create `evaluateV
[advanced usage](docs/advanced-usage.md) and to the [supported tools](docs/supported-tools.md) pages.

## Sample app
There are two sample Android projects available, one consisting of a regular app - available [here](https://github.com/novoda/gradle-static-analysis-plugin/tree/master/sample) - and the other comprising a multi-module setup available [here](https://github.com/novoda/gradle-static-analysis-plugin/tree/master/sample-multi-module). Both sample projects showcase a setup featuring Checkstyle, FindBugs, PMD, Lint and Detekt.
There are two sample Android projects available, one consisting of a regular app - available [here](https://github.com/novoda/gradle-static-analysis-plugin/tree/master/sample) - and the other comprising a multi-module setup available [here](https://github.com/novoda/gradle-static-analysis-plugin/tree/master/sample-multi-module). Both sample projects showcase a setup featuring Checkstyle, FindBugs, SpotBugs, PMD, Lint, Ktlint and Detekt.

## Snapshots
[![CI status](https://ci.novoda.com/buildStatus/icon?job=gradle-static-analysis-plugin-snapshot)](https://ci.novoda.com/job/gradle-static-analysis-plugin-snapshot/lastBuild/console) [![Download from Bintray](https://api.bintray.com/packages/novoda-oss/snapshots/gradle-static-analysis-plugin/images/download.svg)](https://bintray.com/novoda-oss/snapshots/gradle-static-analysis-plugin/_latestVersion)
Expand Down
3 changes: 3 additions & 0 deletions docs/supported-tools.md
Expand Up @@ -8,6 +8,7 @@ Tool | Java | Android<br/>(Java) | Kotlin | Android<br/>(Kotlin)
[`Checkstyle`](https://checkstyle.sourceforge.net) | :white_check_mark: | :white_check_mark: | — | —
[`PMD`](https://pmd.github.io) | :white_check_mark: | :white_check_mark: | — | —
[`FindBugs`](http://findbugs.sourceforge.net/) | :white_check_mark: | :white_check_mark: | — | —
[`SpotBugs`](https://spotbugs.github.io/) | :white_check_mark: | :white_check_mark: | — | —
[`Detekt`](https://github.com/arturbosch/detekt) | — | — | :white_check_mark: | :white_check_mark:
[`Android Lint`](https://developer.android.com/studio/write/lint.html) | — | :white_check_mark:️ | — | :white_check_mark:
[`KtLint`](https://github.com/shyiko/ktlint) | — | — | :white_check_mark:️ | :white_check_mark:
Expand All @@ -22,6 +23,7 @@ For additional informations and tips on how to obtain advanced behaviours with t
* [Checkstyle](tools/checkstyle.md)
* [PMD](tools/pmd.md)
* [Findbugs](tools/findbugs.md)
* [SpotBugs](tools/spotbugs.md)
* [Android Lint](tools/android_lint.md)
* [KtLint](tools/ktlint.md)
* [Example configurations](#example-configurations)
Expand All @@ -40,6 +42,7 @@ staticAnalysis {
checkstyle {}
pmd {}
findbugs {}
spotbugs {}
lintOptions {}
detekt {}
ktlint {}
Expand Down
29 changes: 29 additions & 0 deletions docs/tools/spotbugs.md
@@ -0,0 +1,29 @@
# SpotBugs
[SpotBugs](https://spotbugs.github.io/) is a static analysis tool that looks for potential bugs in Java code. It does not support Kotlin.
It can be used in both pure Java, and Android Java projects. It then only makes sense to have SpotBugs enabled if you have Java code in your project.
The plugin only runs SpotBugs on projects that contain the Java or the Android plugin.

## Table of contents
* [Configure SpotBugs](#configure-spotbugs)
* [SpotBugs in mixed-language projects](#spotbugs-in-mixed-language-projects)

---

## Configure SpotBugs
Enabling and configuring SpotBugs for a project is done through the `spotbugs` closure:

```gradle
spotbugs {
toolVersion // Optional string, the latest SpotBugs release (currently 4.0.0-beta4)
excludeFilter // A file containing the SpotBugs exclusions, e.g., teamPropsFile('static-analysis/spotbugs-excludes.xml')
htmlReportEnabled true // Control whether html report generation should be enabled. `true` by default.
includeVariants { variant -> ... } // A closure to determine which variants (only for Android) to include
}
```

(assuming you're using the Novoda scaffolding system, see [Example configurations](#example-configurations) for more details)

For more information about SpotBugs rules, refer to the [official website](https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html).

## SpotBugs in mixed-language projects
If your project mixes Java and Kotlin code, you will need to exclude your Kotlin files by using `excludeFilter`
Expand Up @@ -7,6 +7,7 @@ import com.novoda.staticanalysis.internal.findbugs.FindbugsConfigurator
import com.novoda.staticanalysis.internal.ktlint.KtlintConfigurator
import com.novoda.staticanalysis.internal.lint.LintConfigurator
import com.novoda.staticanalysis.internal.pmd.PmdConfigurator
import com.novoda.staticanalysis.internal.spotbugs.SpotBugsConfigurator
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Plugin
import org.gradle.api.Project
Expand Down Expand Up @@ -42,6 +43,7 @@ class StaticAnalysisPlugin implements Plugin<Project> {
CheckstyleConfigurator.create(project, violationsContainer, evaluateViolations),
PmdConfigurator.create(project, violationsContainer, evaluateViolations),
FindbugsConfigurator.create(project, violationsContainer, evaluateViolations),
SpotBugsConfigurator.create(project, violationsContainer, evaluateViolations),
DetektConfigurator.create(project, violationsContainer, evaluateViolations),
KtlintConfigurator.create(project, violationsContainer, evaluateViolations),
LintConfigurator.create(project, violationsContainer, evaluateViolations)
Expand Down
@@ -0,0 +1,171 @@
package com.novoda.staticanalysis.internal.spotbugs

import com.novoda.staticanalysis.StaticAnalysisExtension
import com.novoda.staticanalysis.Violations
import com.novoda.staticanalysis.internal.Configurator
import com.novoda.staticanalysis.internal.VariantFilter
import com.novoda.staticanalysis.internal.findbugs.CollectFindbugsViolationsTask
import com.novoda.staticanalysis.internal.findbugs.GenerateFindBugsHtmlReport
import org.gradle.api.*
import org.gradle.api.tasks.SourceTask

import static com.novoda.staticanalysis.internal.Exceptions.handleException
import static com.novoda.staticanalysis.internal.TasksCompat.createTask

class SpotBugsConfigurator implements Configurator {

private static final String SPOTBUGS_PLUGIN = 'com.github.spotbugs'
private static final String SPOTBUGS_NOT_APPLIED = "The SpotBugs plugin is configured but not applied. Please apply the plugin: $SPOTBUGS_PLUGIN in your build script."
private static final String SPOTBUGS_CONFIGURATION_ERROR = "A problem occurred while configuring SpotBugs."

private final Project project
private final Violations violations
private final Task evaluateViolations
private final VariantFilter variantFilter
protected boolean htmlReportEnabled = true
protected boolean configured = false

static SpotBugsConfigurator create(Project project,
NamedDomainObjectContainer<Violations> violationsContainer,
Task evaluateViolations) {
Violations violations = violationsContainer.maybeCreate('SpotBugs')
return new SpotBugsConfigurator(project, violations, evaluateViolations)
}

SpotBugsConfigurator(Project project, Violations violations, Task evaluateViolations) {
this.project = project
this.violations = violations
this.evaluateViolations = evaluateViolations
this.variantFilter = new VariantFilter(project)
}

@Override
void execute() {
project.extensions.findByType(StaticAnalysisExtension).ext.spotbugs = { Closure config ->
if (!project.plugins.hasPlugin(SPOTBUGS_PLUGIN)) {
throw new GradleException(SPOTBUGS_NOT_APPLIED)
}

configureSpotBugsExtension(config)

project.plugins.withId('com.android.application') {
configureAndroidWithVariants(variantFilter.filteredApplicationVariants)
}
project.plugins.withId('com.android.library') {
configureAndroidWithVariants(variantFilter.filteredLibraryVariants)
}
project.plugins.withId('java') {
configureJavaProject()
}
}
}

private void configureSpotBugsExtension(Closure config) {
try {
def spotbugs = project.spotbugs
spotbugs.ext.includeVariants = { Closure<Boolean> filter ->
variantFilter.includeVariantsFilter = filter
}
spotbugs.ext.htmlReportEnabled = { boolean enabled -> this.htmlReportEnabled = enabled }
config.delegate = spotbugs
config.resolveStrategy = Closure.DELEGATE_FIRST
config()
} catch (Exception exception) {
handleException(SPOTBUGS_CONFIGURATION_ERROR, exception)
}
}

protected void configureAndroidWithVariants(DomainObjectSet variants) {
if (configured) return

variants.all { configureVariant(it) }
variantFilter.filteredTestVariants.all { configureVariant(it) }
variantFilter.filteredUnitTestVariants.all { configureVariant(it) }
configured = true
}

private void configureVariant(variant) {
createToolTaskForAndroid(variant)
def collectViolations = createCollectViolations(getToolTaskNameFor(variant), violations)
evaluateViolations.dependsOn collectViolations
}

private void createToolTaskForAndroid(variant) {
createTask(project, getToolTaskNameFor(variant), Class.forName('com.github.spotbugs.SpotBugsTask')) { SourceTask task ->
def javaCompile = javaCompile(variant)
def androidSourceDirs = variant.sourceSets.collect {
it.javaDirectories
}.flatten()
task.description = "Run SpotBugs analysis for ${variant.name} classes"
task.setSource(androidSourceDirs)
task.classpath = javaCompile.classpath
task.extraArgs '-auxclasspath', androidJar
task.conventionMapping.map("classes") {
project.fileTree(javaCompile.destinationDir)
}
task.dependsOn javaCompile
}
}

private void configureJavaProject() {
if (configured) return

project.sourceSets.all { sourceSet ->
def collectViolations = createCollectViolations(getToolTaskNameFor(sourceSet), violations)
evaluateViolations.dependsOn collectViolations
}
configured = true
}

private def createCollectViolations(String taskName, Violations violations) {
if (htmlReportEnabled) {
createHtmlReportTask(taskName)
}
createTask(project, "collect${taskName.capitalize()}Violations", CollectFindbugsViolationsTask) { task ->
def spotbugs = project.tasks[taskName] as SourceTask
configureToolTask(spotbugs)
task.xmlReportFile = spotbugs.reports.xml.destination
task.violations = violations

if (htmlReportEnabled) {
task.dependsOn project.tasks["generate${taskName.capitalize()}HtmlReport"]
} else {
task.dependsOn spotbugs
}
}
}

private void createHtmlReportTask(String taskName) {
createTask(project, "generate${taskName.capitalize()}HtmlReport", GenerateFindBugsHtmlReport) { GenerateFindBugsHtmlReport task ->
def spotbugs = project.tasks[taskName]
task.xmlReportFile = spotbugs.reports.xml.destination
task.htmlReportFile = new File(task.xmlReportFile.absolutePath - '.xml' + '.html')
task.classpath = spotbugs.spotbugsClasspath
task.dependsOn spotbugs
}
}

private static void configureToolTask(SourceTask task) {
task.group = 'verification'
task.exclude '**/*.kt'
task.ignoreFailures = true
task.reports.xml.enabled = true
task.reports.html.enabled = false
}

private static String getToolTaskNameFor(named) {
"spotbugs${named.name.capitalize()}"
}

private static def javaCompile(variant) {
if (variant.hasProperty('javaCompileProvider')) {
variant.javaCompileProvider.get()
} else {
variant.javaCompile
}
}

private def getAndroidJar() {
"${project.android.sdkDirectory}/platforms/${project.android.compileSdkVersion}/android.jar"
}
}