All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.aaschmid.gradle.plugins.cpd.CpdPlugin Maven / Gradle / Ivy

package de.aaschmid.gradle.plugins.cpd;

import java.io.File;
import java.util.Optional;

import org.gradle.api.Incubating;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.execution.TaskExecutionGraph;
import org.gradle.api.file.ProjectLayout;
import org.gradle.api.internal.ConventionMapping;
import org.gradle.api.internal.IConventionAware;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.plugins.JvmEcosystemPlugin;
import org.gradle.api.plugins.ReportingBasePlugin;
import org.gradle.api.provider.ProviderFactory;
import org.gradle.api.reporting.ReportingExtension;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.language.base.plugins.LifecycleBasePlugin;

import static java.util.Collections.reverseOrder;

/**
 * A plugin for the finding duplicate code using CPD source code analyzer (which is
 * a part of PMD).
 * 

* Creates and registers a {@code cpd} extension with the default task options for every task of type {@link Cpd}. *

* Declares a {@code cpd} configuration which needs to be configured with the * PMD library containing the * CPD library to be used. *

* A {@link Cpd} task named {@code cpd} is created and configured with default options. It can be further configured to analyze the source * code you want, e.g. {@code source = project.files('src')}. *

* The created {@link Cpd} task is added to the {@code check} lifecycle task of {@link LifecycleBasePlugin} if it is also applied, e.g. * using {@link org.gradle.api.plugins.JavaPlugin}. *

* Sample: * *

 * apply plugin: 'cpd'
 *
 * repositories{
 *     mavenCentral()
 * }
 *
 * cpd {
 *     minimumTokenCount = 25
 *     // As PMD was split with v5.2.0 and CPD has moved to 'pmd-core', 'toolVersion' is just available for 5.2.0 and higher
 *     toolVersion = 5.2.1
 * }
 *
 * tasks.cpd {
 *     allprojects.findAll{ p -> p.hasProperty('sourceSets') }.each{ p ->
 *         p.sourceSets.all{ sourceSet -> source sourceSet.allJava }
 *     }
 * }
 * 
* * @see CpdExtension * @see Cpd */ @Incubating public class CpdPlugin implements Plugin { private static final Logger logger = Logging.getLogger(CpdPlugin.class); private static final String TASK_NAME_CPD_CHECK = "cpdCheck"; @Override public void apply(Project project) { project.getPlugins().apply(ReportingBasePlugin.class); project.getPlugins().apply(JvmEcosystemPlugin.class); CpdExtension extension = createExtension(project); createConfiguration(project, extension); setupTaskDefaults(project, extension); createTask(project); checkThatCpdCheckWasAutomaticallyAddedToTaskGraphOrWarn(project); } private CpdExtension createExtension(Project project) { CpdExtension extension = project.getExtensions().create("cpd", CpdExtension.class); extension.setToolVersion(Cpd.defaultCPDVersion()); return extension; } private void createConfiguration(Project project, CpdExtension extension) { Configuration configuration = project.getConfigurations().create("cpd"); configuration.setDescription("The CPD libraries to be used for this project."); configuration.setTransitive(true); configuration.setVisible(false); configuration.defaultDependencies(d -> d.add(project.getDependencies().create("net.sourceforge.pmd:pmd-dist:" + extension.getToolVersion()))); } /** Set up task defaults for every created {@link Cpd} task. */ private void setupTaskDefaults(Project project, CpdExtension extension) { project.getTasks().withType(Cpd.class).configureEach(task -> { ConventionMapping taskMapping = task.getConventionMapping(); taskMapping.map("encoding", extension::getEncoding); taskMapping.map("failOnError", extension::isFailOnError); taskMapping.map("failOnViolation", extension::isFailOnViolation); taskMapping.map("ignoreAnnotations", extension::isIgnoreAnnotations); taskMapping.map("ignoreIdentifiers", extension::isIgnoreIdentifiers); taskMapping.map("ignoreFailures", extension::isIgnoreFailures); taskMapping.map("ignoreLiterals", extension::isIgnoreLiterals); taskMapping.map("language", extension::getLanguage); taskMapping.map("minimumTokenCount", extension::getMinimumTokenCount); taskMapping.map("pmdClasspath", () -> project.getConfigurations().findByName("cpd")); taskMapping.map("skipDuplicateFiles", extension::isSkipDuplicateFiles); taskMapping.map("skipBlocks", extension::isSkipBlocks); taskMapping.map("skipBlocksPattern", extension::getSkipBlocksPattern); ConventionMapping extensionMapping = ((IConventionAware) extension).getConventionMapping(); extensionMapping.map("reportsDir", () -> project.getExtensions().getByType(ReportingExtension.class).file("cpd")); ProjectLayout layout = project.getLayout(); ProviderFactory providers = project.getProviders(); task.getReports().all(report -> { report.getRequired().convention("xml".equals(report.getName())); report.getOutputLocation().convention(layout.getProjectDirectory().file(providers.provider(() -> new File(extension.getReportsDir(), task.getName() + "." + report.getName()).getAbsolutePath()))); }); }); } private void createTask(Project project) { TaskProvider taskProvider = project.getTasks().register(TASK_NAME_CPD_CHECK, Cpd.class, task -> { task.setDescription("Run CPD analysis for all sources"); project.getAllprojects().forEach(p -> p.getPlugins().withType(JavaBasePlugin.class, plugin -> p.getExtensions().getByType(JavaPluginExtension.class).getSourceSets().all(sourceSet -> sourceSet.getAllJava().getSrcDirs().forEach(task::source) ) ) ); }); project.getPlugins().withType(LifecycleBasePlugin.class, plugin -> project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).configure(t -> t.dependsOn(taskProvider))); } private void checkThatCpdCheckWasAutomaticallyAddedToTaskGraphOrWarn(Project project) { project.getGradle().getTaskGraph().whenReady(graph -> { String projectPath = (project.getRootProject() == project) ? project.getPath() : project.getPath() + ":"; if (!graph.hasTask(projectPath + TASK_NAME_CPD_CHECK)) { logWarningIfCheckTaskOnTaskGraph(project, graph); } }); } private void logWarningIfCheckTaskOnTaskGraph(Project project, TaskExecutionGraph graph) { if (logger.isWarnEnabled()) { Optional lastCheckTask = graph.getAllTasks().stream().sorted(reverseOrder()) .filter(t -> t.getName().endsWith(LifecycleBasePlugin.CHECK_TASK_NAME)).findFirst(); if (lastCheckTask.isPresent()) { // it is possible to just execute a task before "check", e.g. "compileJava" Task task = lastCheckTask.get(); String message = "\n" + "WARNING: Due to the absence of '" + LifecycleBasePlugin.class.getSimpleName() + "' on " + project + " the task ':" + TASK_NAME_CPD_CHECK + "' could not be added to task graph. Therefore CPD will not be executed. To prevent this, manually add a task dependency of ':" + TASK_NAME_CPD_CHECK + "' to a '" + LifecycleBasePlugin.CHECK_TASK_NAME + "' task of a subproject.\n" + "1) Directly to " + task.getProject() + ":\n" + " " + task.getName() + ".dependsOn(':" + TASK_NAME_CPD_CHECK + "')\n" + "2) Indirectly, e.g. via " + project + ":\n" + " project('" + task.getProject().getPath() + "') {\n" + " plugins.withType(LifecycleBasePlugin) { // <- just required if 'java' plugin is applied within subproject\n" + " " + task.getName() + ".dependsOn(" + TASK_NAME_CPD_CHECK + ")\n" + " }\n" + " }\n"; logger.warn(message); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy