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