org.sonarqube.gradle.SonarQubePlugin Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sonarqube-gradle-plugin Show documentation
Show all versions of sonarqube-gradle-plugin Show documentation
Gradle plugin to help analyzing projects with SonarQube
/**
* SonarQube Gradle Plugin
* Copyright (C) 2015-2017 SonarSource
* [email protected]
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonarqube.gradle;
import com.android.build.gradle.api.BaseVariant;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.annotation.Nullable;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.UnknownTaskException;
import org.gradle.api.internal.ConventionMapping;
import org.gradle.api.internal.plugins.DslObject;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.plugins.GroovyBasePlugin;
import org.gradle.api.plugins.GroovyPlugin;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.api.tasks.testing.Test;
import org.gradle.testing.jacoco.plugins.JacocoPlugin;
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension;
import org.gradle.util.GradleVersion;
import org.sonarsource.scanner.api.Utils;
import static java.util.Arrays.asList;
/**
* A plugin for analyzing projects with the SonarQube Runner.
* When applied to a project, both the project itself and its subprojects will be analyzed (in a single run).
*/
public class SonarQubePlugin implements Plugin {
private static final Logger LOGGER = Logging.getLogger(SonarQubePlugin.class);
static final String SONAR_SOURCES_PROP = "sonar.sources";
static final String SONAR_TESTS_PROP = "sonar.tests";
static final String SONAR_JAVA_SOURCE_PROP = "sonar.java.source";
static final String SONAR_JAVA_TARGET_PROP = "sonar.java.target";
private static final Pattern TEST_RESULT_FILE_PATTERN = Pattern.compile("TESTS?-.*\\.xml");
// Project where sonarqube plugin is apply. Most of the time the root project.
private Project targetProject;
private static void evaluateSonarPropertiesBlocks(ActionBroadcast super SonarQubeProperties> propertiesActions, Map properties) {
SonarQubeProperties sqProperties = new SonarQubeProperties(properties);
propertiesActions.execute(sqProperties);
}
private static ActionBroadcast addBroadcaster(Map> actionBroadcastMap, Project project) {
ActionBroadcast actionBroadcast = new ActionBroadcast<>();
actionBroadcastMap.put(project, actionBroadcast);
return actionBroadcast;
}
private static boolean addTaskByName(Project p, String name, List allCompileTasks) {
try {
allCompileTasks.add(p.getTasks().getByName(name));
return true;
} catch (UnknownTaskException e) {
return false;
}
}
private static String capitalize(final String word) {
return Character.toUpperCase(word.charAt(0)) + word.substring(1);
}
private static boolean isAndroidProject(Project project) {
return project.getPlugins().hasPlugin("com.android.application") || project.getPlugins().hasPlugin("com.android.library") || project.getPlugins().hasPlugin("com.android.test");
}
private static void configureForJava(final Project project, final Map properties) {
project.getPlugins().withType(JavaBasePlugin.class, javaBasePlugin -> configureJdkSourceAndTarget(project, properties));
project.getPlugins().withType(JavaPlugin.class, javaPlugin -> {
boolean hasSourceOrTest = configureSourceDirsAndJavaClasspath(project, properties, false);
if (hasSourceOrTest) {
configureSourceEncoding(project, properties);
Task testTask = project.getTasks().getByName(JavaPlugin.TEST_TASK_NAME);
if (testTask instanceof Test) {
configureTestReports((Test) testTask, properties);
configureJaCoCoCoverageReport((Test) testTask, false, project, properties);
} else {
LOGGER.warn("Non standard test task: unable to automatically find test execution and coverage reports paths");
}
}
});
}
/**
* Groovy projects support joint compilation of a mix of Java and Groovy classes. That's why we set both
* sonar.java.* and sonar.groovy.* properties.
*/
private static void configureForGroovy(final Project project, final Map properties) {
project.getPlugins().withType(GroovyBasePlugin.class, groovyBasePlugin -> configureJdkSourceAndTarget(project, properties));
project.getPlugins().withType(GroovyPlugin.class, groovyPlugin -> {
boolean hasSourceOrTest = configureSourceDirsAndJavaClasspath(project, properties, true);
if (hasSourceOrTest) {
configureSourceEncoding(project, properties);
final Test testTask = (Test) project.getTasks().getByName(JavaPlugin.TEST_TASK_NAME);
configureTestReports(testTask, properties);
configureJaCoCoCoverageReport(testTask, true, project, properties);
}
});
}
private static void configureJaCoCoCoverageReport(final Test testTask, final boolean addForGroovy, Project project, final Map properties) {
project.getPlugins().withType(JacocoPlugin.class, jacocoPlugin -> {
JacocoTaskExtension jacocoTaskExtension = testTask.getExtensions().getByType(JacocoTaskExtension.class);
File destinationFile = jacocoTaskExtension.getDestinationFile();
if (destinationFile.exists()) {
properties.put("sonar.jacoco.reportPath", destinationFile);
appendProp(properties, "sonar.jacoco.reportPaths", destinationFile);
if (addForGroovy) {
properties.put("sonar.groovy.jacoco.reportPath", destinationFile);
}
}
});
}
private static void configureTestReports(Test testTask, Map properties) {
File testResultsDir = testTask.getReports().getJunitXml().getDestination();
// do not set a custom test reports path if it does not exists, otherwise SonarQube will emit an error
// do not set a custom test reports path if there are no files, otherwise SonarQube will emit a warning
if (testResultsDir.isDirectory()
&& asList(testResultsDir.list()).stream().anyMatch(file -> TEST_RESULT_FILE_PATTERN.matcher(file).matches())) {
appendProp(properties, "sonar.junit.reportPaths", testResultsDir);
// For backward compatibility
appendProp(properties, "sonar.junit.reportsPath", testResultsDir);
appendProp(properties, "sonar.surefire.reportsPath", testResultsDir);
}
}
private static boolean configureSourceDirsAndJavaClasspath(Project project, Map properties, final boolean addForGroovy) {
JavaPluginConvention javaPluginConvention = new DslObject(project).getConvention().getPlugin(JavaPluginConvention.class);
SourceSet main = javaPluginConvention.getSourceSets().getAt("main");
List sourceDirectories = nonEmptyOrNull(main.getAllJava().getSrcDirs().stream().filter(File::exists).collect(Collectors.toList()));
properties.put(SONAR_SOURCES_PROP, sourceDirectories);
SourceSet test = javaPluginConvention.getSourceSets().getAt("test");
List testDirectories = nonEmptyOrNull(test.getAllJava().getSrcDirs().stream().filter(File::exists).collect(Collectors.toList()));
properties.put(SONAR_TESTS_PROP, testDirectories);
Collection mainClassDirs = getOutputDirs(main);
Collection mainLibraries = getLibraries(main);
setMainClasspathProps(properties, addForGroovy, mainClassDirs, mainLibraries);
Collection testClassDirs = getOutputDirs(test);
Collection testLibraries = getLibraries(test);
setTestClasspathProps(properties, testClassDirs, testLibraries);
return sourceDirectories != null || testDirectories != null;
}
private static Collection getOutputDirs(SourceSet sourceSet) {
Collection result;
if (GradleVersion.version("4.0").compareTo(GradleVersion.current()) <= 0) {
result = sourceSet.getOutput().getClassesDirs().getFiles();
} else {
result = Arrays.asList(sourceSet.getOutput().getClassesDir());
}
return result.stream().filter(File::exists).collect(Collectors.toList());
}
static void setMainClasspathProps(Map properties, boolean addForGroovy, Collection mainClassDirs, Collection mainLibraries) {
appendProps(properties, "sonar.java.binaries", mainClassDirs);
if (addForGroovy) {
appendProps(properties, "sonar.groovy.binaries", mainClassDirs);
}
// Populate deprecated properties for backward compatibility
appendProps(properties, "sonar.binaries", mainClassDirs);
appendProps(properties, "sonar.java.libraries", mainLibraries);
// Populate deprecated properties for backward compatibility
appendProps(properties, "sonar.libraries", mainLibraries);
}
static void appendProps(Map properties, String key, Iterable> valuesToAppend) {
properties.putIfAbsent(key, new LinkedHashSet());
StreamSupport.stream(valuesToAppend.spliterator(), false)
.forEach(v -> ((Collection) properties.get(key)).add(v.toString()));
}
static void appendProp(Map properties, String key, Object valueToAppend) {
properties.putIfAbsent(key, new LinkedHashSet());
((Collection) properties.get(key)).add(valueToAppend.toString());
}
static void setTestClasspathProps(Map properties, Collection testClassDirs, Collection testLibraries) {
appendProps(properties, "sonar.java.test.binaries", testClassDirs);
appendProps(properties, "sonar.java.test.libraries", testLibraries);
}
private static void configureSourceEncoding(Project project, final Map properties) {
project.getTasks().withType(JavaCompile.class, compile -> {
String encoding = compile.getOptions().getEncoding();
if (encoding != null) {
properties.put("sonar.sourceEncoding", encoding);
}
});
}
private static void configureJdkSourceAndTarget(Project project, Map properties) {
JavaPluginConvention javaPluginConvention = new DslObject(project).getConvention().getPlugin(JavaPluginConvention.class);
properties.put(SONAR_JAVA_SOURCE_PROP, javaPluginConvention.getSourceCompatibility());
properties.put(SONAR_JAVA_TARGET_PROP, javaPluginConvention.getTargetCompatibility());
}
private static void addEnvironmentProperties(Map properties) {
for (Map.Entry