org.sonarqube.gradle.SonarQubePlugin.java Source code

Java tutorial

Introduction

Here is the source code for org.sonarqube.gradle.SonarQubePlugin.java

Source

/**
 * SonarQube Gradle Plugin
 * Copyright (C) 2015-2016 SonarSource
 * sonarqube@googlegroups.com
 *
 * 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.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import org.gradle.api.Action;
import org.gradle.api.Nullable;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.internal.ConventionMapping;
import org.gradle.api.internal.plugins.DslObject;
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.internal.jvm.Jvm;
import org.gradle.listener.ActionBroadcast;
import org.gradle.testing.jacoco.plugins.JacocoPlugin;
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension;

/**
 * A plugin for analyzing projects with the <a href="http://redirect.sonarsource.com/doc/analyzing-with-sq-gradle.html">SonarQube Runner</a>.
 * When applied to a project, both the project itself and its subprojects will be analyzed (in a single run).
 * Please see the SonarQube RunnerPlugin? chapter of the Gradle User Guide for more information.
 */
public class SonarQubePlugin implements Plugin<Project> {

    private static final Predicate<File> FILE_EXISTS = new Predicate<File>() {
        @Override
        public boolean apply(File input) {
            return input.exists();
        }
    };
    private static final Predicate<File> IS_DIRECTORY = new Predicate<File>() {
        @Override
        public boolean apply(File input) {
            return input.isDirectory();
        }
    };
    private static final Predicate<File> IS_FILE = new Predicate<File>() {
        @Override
        public boolean apply(File input) {
            return input.isFile();
        }
    };
    private static final Joiner COMMA_JOINER = Joiner.on(",");
    public static final String SONAR_SOURCES_PROP = "sonar.sources";

    private Project targetProject;

    private static void evaluateSonarPropertiesBlocks(
            ActionBroadcast<? super SonarQubeProperties> propertiesActions, Map<String, Object> properties) {
        SonarQubeProperties sqProperties = new SonarQubeProperties(properties);
        propertiesActions.execute(sqProperties);
    }

    @Override
    public void apply(Project project) {
        targetProject = project;

        final Map<Project, ActionBroadcast<SonarQubeProperties>> actionBroadcastMap = Maps.newHashMap();
        createTask(project, actionBroadcastMap);

        ActionBroadcast<SonarQubeProperties> actionBroadcast = addBroadcaster(actionBroadcastMap, project);
        project.subprojects(new Action<Project>() {
            @Override
            public void execute(Project project) {
                ActionBroadcast<SonarQubeProperties> actionBroadcast = addBroadcaster(actionBroadcastMap, project);
                project.getExtensions().create(SonarQubeExtension.SONARQUBE_EXTENSION_NAME,
                        SonarQubeExtension.class, actionBroadcast);
            }
        });
        project.getExtensions().create(SonarQubeExtension.SONARQUBE_EXTENSION_NAME, SonarQubeExtension.class,
                actionBroadcast);
    }

    private static ActionBroadcast<SonarQubeProperties> addBroadcaster(
            Map<Project, ActionBroadcast<SonarQubeProperties>> actionBroadcastMap, Project project) {
        ActionBroadcast<SonarQubeProperties> actionBroadcast = new ActionBroadcast<>();
        actionBroadcastMap.put(project, actionBroadcast);
        return actionBroadcast;
    }

    private SonarQubeTask createTask(final Project project,
            final Map<Project, ActionBroadcast<SonarQubeProperties>> actionBroadcastMap) {
        SonarQubeTask sonarQubeTask = project.getTasks().create(SonarQubeExtension.SONARQUBE_TASK_NAME,
                SonarQubeTask.class);
        sonarQubeTask.setDescription("Analyzes " + project + " and its subprojects with SonarQube.");

        ConventionMapping conventionMapping = new DslObject(sonarQubeTask).getConventionMapping();
        conventionMapping.map("properties", new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                Map<String, Object> properties = Maps.newLinkedHashMap();
                computeSonarProperties(project, properties, actionBroadcastMap, "");
                return properties;
            }
        });

        sonarQubeTask.dependsOn(new Callable<Iterable<? extends Task>>() {
            @Override
            public Iterable<? extends Task> call() throws Exception {
                Iterable<Project> applicableProjects = Iterables.filter(project.getAllprojects(),
                        new Predicate<Project>() {
                            @Override
                            public boolean apply(Project input) {
                                return input.getPlugins().hasPlugin(JavaPlugin.class) && !input.getExtensions()
                                        .getByType(SonarQubeExtension.class).isSkipProject();
                            }
                        });

                return Iterables.transform(applicableProjects, new Function<Project, Task>() {
                    @Nullable
                    @Override
                    public Task apply(Project input) {
                        return input.getTasks().getByName(JavaPlugin.TEST_TASK_NAME);
                    }
                });
            }
        });

        return sonarQubeTask;
    }

    public void computeSonarProperties(Project project, Map<String, Object> properties,
            Map<Project, ActionBroadcast<SonarQubeProperties>> sonarPropertiesActionBroadcastMap, String prefix) {
        SonarQubeExtension extension = project.getExtensions().getByType(SonarQubeExtension.class);
        if (extension.isSkipProject()) {
            return;
        }

        Map<String, Object> rawProperties = Maps.newLinkedHashMap();
        addGradleDefaults(project, rawProperties);
        evaluateSonarPropertiesBlocks(sonarPropertiesActionBroadcastMap.get(project), rawProperties);
        if (project.equals(targetProject)) {
            addSystemProperties(rawProperties);
        }

        convertProperties(rawProperties, prefix, properties);

        List<Project> enabledChildProjects = Lists
                .newLinkedList(Iterables.filter(project.getChildProjects().values(), new Predicate<Project>() {
                    @Override
                    public boolean apply(Project input) {
                        return !input.getExtensions().getByType(SonarQubeExtension.class).isSkipProject();
                    }
                }));

        if (enabledChildProjects.isEmpty()) {
            return;
        }

        List<String> moduleIds = new ArrayList<>();

        for (Project childProject : enabledChildProjects) {
            String moduleId = childProject.getPath();
            moduleIds.add(moduleId);
            String modulePrefix = (prefix.length() > 0) ? (prefix + "." + moduleId) : moduleId;
            computeSonarProperties(childProject, properties, sonarPropertiesActionBroadcastMap, modulePrefix);
        }
        properties.put(convertKey("sonar.modules", prefix), COMMA_JOINER.join(moduleIds));
    }

    private void addGradleDefaults(final Project project, final Map<String, Object> properties) {

        // IMPORTANT: Whenever changing the properties/values here, ensure that the Gradle User Guide chapter on this is still in sync.

        properties.put("sonar.projectName", project.getName());
        properties.put("sonar.projectDescription", project.getDescription());
        properties.put("sonar.projectVersion", project.getVersion());
        properties.put("sonar.projectBaseDir", project.getProjectDir());

        if (project.equals(targetProject)) {
            // Root project
            properties.put("sonar.projectKey", getProjectKey(project));
            properties.put("sonar.working.directory", new File(project.getBuildDir(), "sonar"));
        } else {
            properties.put("sonar.moduleKey", getProjectKey(project));
        }

        configureForJava(project, properties);
        configureForGroovy(project, properties);

        if (properties.get(SONAR_SOURCES_PROP) == null) {
            properties.put(SONAR_SOURCES_PROP, "");
        }
    }

    private void configureForJava(final Project project, final Map<String, Object> properties) {
        project.getPlugins().withType(JavaBasePlugin.class, new Action<JavaBasePlugin>() {
            @Override
            public void execute(JavaBasePlugin javaBasePlugin) {
                configureJdkSourceAndTarget(project, properties);
            }
        });

        project.getPlugins().withType(JavaPlugin.class, new Action<JavaPlugin>() {
            @Override
            public void execute(JavaPlugin javaPlugin) {
                boolean hasSourceOrTest = configureSourceDirsAndJavaClasspath(project, properties);
                if (hasSourceOrTest) {
                    configureSourceEncoding(project, properties);
                    final Test testTask = (Test) project.getTasks().getByName(JavaPlugin.TEST_TASK_NAME);
                    configureTestReports(testTask, properties);
                    configureJaCoCoCoverageReport(testTask, false, project, properties);
                }
            }
        });
    }

    /**
     * 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 void configureForGroovy(final Project project, final Map<String, Object> properties) {
        project.getPlugins().withType(GroovyBasePlugin.class, new Action<GroovyBasePlugin>() {
            @Override
            public void execute(GroovyBasePlugin groovyBasePlugin) {
                configureJdkSourceAndTarget(project, properties);
            }
        });

        project.getPlugins().withType(GroovyPlugin.class, new Action<GroovyPlugin>() {
            @Override
            public void execute(GroovyPlugin groovyPlugin) {
                boolean hasSourceOrTest = configureSourceDirsAndJavaClasspath(project, properties);
                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 void configureJaCoCoCoverageReport(final Test testTask, final boolean addForGroovy, Project project,
            final Map<String, Object> properties) {
        project.getPlugins().withType(JacocoPlugin.class, new Action<JacocoPlugin>() {
            @Override
            public void execute(JacocoPlugin jacocoPlugin) {
                JacocoTaskExtension jacocoTaskExtension = testTask.getExtensions()
                        .getByType(JacocoTaskExtension.class);
                File destinationFile = jacocoTaskExtension.getDestinationFile();
                if (destinationFile.exists()) {
                    properties.put("sonar.jacoco.reportPath", destinationFile);
                    if (addForGroovy) {
                        properties.put("sonar.groovy.jacoco.reportPath", destinationFile);
                    }
                }
            }
        });
    }

    private static void configureTestReports(Test testTask, Map<String, Object> properties) {
        File testResultsDir = testTask.getReports().getJunitXml().getDestination();
        // create the test results folder to prevent SonarQube from emitting
        // a warning if a project does not contain any tests
        try {
            Files.createDirectories(testResultsDir.toPath());
        } catch (IOException e) {
            throw new IllegalStateException("Unable to create test report directory", e);
        }

        properties.put("sonar.junit.reportsPath", testResultsDir);
        // For backward compatibility
        properties.put("sonar.surefire.reportsPath", testResultsDir);
    }

    private boolean configureSourceDirsAndJavaClasspath(Project project, Map<String, Object> properties) {
        JavaPluginConvention javaPluginConvention = new DslObject(project).getConvention()
                .getPlugin(JavaPluginConvention.class);

        SourceSet main = javaPluginConvention.getSourceSets().getAt("main");
        List<File> sourceDirectories = nonEmptyOrNull(
                Iterables.filter(main.getAllSource().getSrcDirs(), FILE_EXISTS));
        properties.put(SONAR_SOURCES_PROP, sourceDirectories);
        SourceSet test = javaPluginConvention.getSourceSets().getAt("test");
        List<File> testDirectories = nonEmptyOrNull(
                Iterables.filter(test.getAllSource().getSrcDirs(), FILE_EXISTS));
        properties.put("sonar.tests", testDirectories);

        List<File> mainClasspath = nonEmptyOrNull(Iterables.filter(main.getRuntimeClasspath(), IS_DIRECTORY));
        Collection<File> mainLibraries = getLibraries(main);
        properties.put("sonar.java.binaries", mainClasspath);
        properties.put("sonar.java.libraries", mainLibraries);
        List<File> testClasspath = nonEmptyOrNull(Iterables.filter(test.getRuntimeClasspath(), IS_DIRECTORY));
        Collection<File> testLibraries = getLibraries(test);
        properties.put("sonar.java.test.binaries", testClasspath);
        properties.put("sonar.java.test.libraries", testLibraries);

        // Populate deprecated properties for backward compatibility
        properties.put("sonar.binaries", mainClasspath);
        properties.put("sonar.libraries", mainLibraries);

        return sourceDirectories != null || testDirectories != null;
    }

    private void configureSourceEncoding(Project project, final Map<String, Object> properties) {
        project.getTasks().withType(JavaCompile.class, new Action<JavaCompile>() {
            @Override
            public void execute(final JavaCompile compile) {
                String encoding = compile.getOptions().getEncoding();
                if (encoding != null) {
                    properties.put("sonar.sourceEncoding", encoding);
                }
            }
        });
    }

    private void configureJdkSourceAndTarget(Project project, Map<String, Object> properties) {
        JavaPluginConvention javaPluginConvention = new DslObject(project).getConvention()
                .getPlugin(JavaPluginConvention.class);
        properties.put("sonar.java.source", javaPluginConvention.getSourceCompatibility());
        properties.put("sonar.java.target", javaPluginConvention.getTargetCompatibility());
    }

    private static String getProjectKey(Project project) {
        Project rootProject = project.getRootProject();
        String rootProjectName = rootProject.getName();
        String rootGroup = rootProject.getGroup().toString();
        String rootKey = rootGroup.isEmpty() ? rootProjectName : (rootGroup + ":" + rootProjectName);
        if (project == rootProject) {
            return rootKey;
        }
        return rootKey + project.getPath();
    }

    private static void addSystemProperties(Map<String, Object> properties) {
        for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
            String key = entry.getKey().toString();
            if (key.startsWith("sonar")) {
                properties.put(key, entry.getValue());
            }
        }
    }

    private static Collection<File> getLibraries(SourceSet main) {
        List<File> libraries = Lists.newLinkedList(Iterables.filter(main.getRuntimeClasspath(), IS_FILE));
        File runtimeJar = Jvm.current().getRuntimeJar();
        if (runtimeJar != null) {
            libraries.add(runtimeJar);
        }

        return libraries;
    }

    private static void convertProperties(Map<String, Object> rawProperties, final String projectPrefix,
            final Map<String, Object> properties) {
        for (Map.Entry<String, Object> entry : rawProperties.entrySet()) {
            String value = convertValue(entry.getValue());
            if (value != null) {
                properties.put(convertKey(entry.getKey(), projectPrefix), value);
            }
        }
    }

    private static String convertKey(String key, final String projectPrefix) {
        return projectPrefix.isEmpty() ? key : (projectPrefix + "." + key);
    }

    private static String convertValue(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Iterable<?>) {
            Iterable<String> flattened = Iterables.transform((Iterable<?>) value, new Function<Object, String>() {
                @Override
                public String apply(Object input) {
                    return convertValue(input);
                }
            });

            Iterable<String> filtered = Iterables.filter(flattened, Predicates.notNull());
            String joined = COMMA_JOINER.join(filtered);
            return joined.isEmpty() ? null : joined;
        } else {
            return value.toString();
        }
    }

    @Nullable
    public static <T> List<T> nonEmptyOrNull(Iterable<T> iterable) {
        ImmutableList<T> list = ImmutableList.copyOf(iterable);
        return list.isEmpty() ? null : list;
    }

}