Skip to content

Commit

Permalink
avoid deprecated "project.gradle.taskGraph.afterTask" api usage, inst…
Browse files Browse the repository at this point in the history
…ead use build service with task completion listener (plus, use doLast callback for precise report position for not failed quality tasks)
  • Loading branch information
xvik committed Jan 31, 2024
1 parent 9c4c87c commit 3f12640
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 71 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
* (BREAKING) Drop gradle 5 and 6 support
* Update spotbugs plugin to 5.2.5 (#94)
- Remove spotbugsShowStackTraces option because it's not used by spotbugs anymore
- Custom xsl file not used for html report - native html report generation used instead
- Custom xsl file is not used for html report - native html report generation used instead
* Update checkstyle 10.6.0 -> 10.12.7 (gradle metadata fix applied)
* Update spotbugs 4.7.3 -> 4.8.3
* Update pmd 6.54 -> 6.55 (java 20 support)
* Update codenarc 3.2.0 -> 3.4.0
* Remove deprecated gradle apis usage
- The plugin is still NOT compatible with configuration cache

NOTE: in strict mode (when a quality task fails when violations are found)
a console report may appear NOT STRICTLY BELOW the referenced quality task
(because old gradle api was deprecated and new api does not guarantee
immediate execution after the quality task)

### 4.9.0 (2023-02-18)
* Gradle 8 support (#77)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@ import org.gradle.api.execution.TaskExecutionGraph
import org.gradle.api.plugins.GroovyPlugin
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.quality.*
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceTask
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.TaskState
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.build.event.BuildEventsListenerRegistry
import org.gradle.process.CommandLineArgumentProvider
import ru.vyarus.gradle.plugin.quality.report.*
import ru.vyarus.gradle.plugin.quality.spotbugs.CustomSpotBugsPlugin
import ru.vyarus.gradle.plugin.quality.task.InitQualityConfigTask
import ru.vyarus.gradle.plugin.quality.util.CpdUtils
import ru.vyarus.gradle.plugin.quality.util.DurationFormatter
import ru.vyarus.gradle.plugin.quality.util.SpotbugsExclusionConfigProvider
import ru.vyarus.gradle.plugin.quality.util.SpotbugsUtils
import ru.vyarus.gradle.plugin.quality.service.TasksListenerService

import javax.inject.Inject

/**
* Quality plugin enables and configures quality plugins for java and groovy projects.
Expand Down Expand Up @@ -60,23 +62,33 @@ import ru.vyarus.gradle.plugin.quality.util.SpotbugsUtils
* @see de.aaschmid.gradle.plugins.cpd.CpdPlugin
*/
@CompileStatic
class QualityPlugin implements Plugin<Project> {
abstract class QualityPlugin implements Plugin<Project> {

private static final String QUALITY_TASK = 'checkQuality'
private static final String CODENARC_GROOVY4 = '-groovy-4.0'

private Provider<TasksListenerService> tasksListener

@Inject
abstract BuildEventsListenerRegistry getEventsListenerRegistry()

@Override
void apply(Project project) {
// activated only when java plugin is enabled
project.plugins.withType(JavaPlugin) {
QualityExtension extension = project.extensions.create('quality', QualityExtension, project)
addInitConfigTask(project)

tasksListener = project.getGradle().getSharedServices().registerIfAbsent(
"taskEvents", TasksListenerService.class, spec -> {})
getEventsListenerRegistry().onTaskCompletion(tasksListener)

project.afterEvaluate {
configureGroupingTasks(project)

Context context = createContext(project, extension)
ConfigLoader configLoader = new ConfigLoader(project)
tasksListener.get().init(configLoader, extension)

configureJavac(project, extension)
applyCheckstyle(project, extension, configLoader, context.registerJavaPlugins)
Expand Down Expand Up @@ -167,7 +179,7 @@ class QualityPlugin implements Plugin<Project> {
}
}

tasks.withType(Checkstyle).configureEach {
tasks.withType(Checkstyle).configureEach { task ->
doFirst {
if (extension.checkstyleBackport) {
project.logger.warn("WARNING: checkstyle-backport-jre8 (${extension.checkstyleVersion})" +
Expand All @@ -178,9 +190,11 @@ class QualityPlugin implements Plugin<Project> {
}
reports.xml.required.set(true)
reports.html.required.set(extension.htmlReports)

registerReporter(task, 'checkstyle')
}
}
configurePluginTasks(project, extension, Checkstyle, 'checkstyle', new CheckstyleReporter(configLoader))
configurePluginTasks(project, extension, Checkstyle, 'checkstyle')
}
}

Expand Down Expand Up @@ -208,16 +222,18 @@ class QualityPlugin implements Plugin<Project> {
+ 'supported only from gradle 5.6')
}
}
tasks.withType(Pmd).configureEach {
tasks.withType(Pmd).configureEach { task ->
doFirst {
configLoader.resolvePmdConfig()
applyExcludes(it, extension)
}
reports.xml.required.set(true)
reports.html.required.set(extension.htmlReports)

registerReporter(task, 'pmd')
}
}
configurePluginTasks(project, extension, Pmd, 'pmd', new PmdReporter())
configurePluginTasks(project, extension, Pmd, 'pmd')
}
}

Expand Down Expand Up @@ -269,10 +285,11 @@ class QualityPlugin implements Plugin<Project> {
required.set(extension.htmlReports)
}
}
registerReporter(task, 'spotbugs')
}
}

configurePluginTasks(project, extension, SpotBugsTask, 'spotbugs', new SpotbugsReporter(configLoader))
configurePluginTasks(project, extension, SpotBugsTask, 'spotbugs')
}
}

Expand All @@ -296,22 +313,24 @@ class QualityPlugin implements Plugin<Project> {
codenarc "org.codenarc:CodeNarc:${extension.codenarcVersion}$CODENARC_GROOVY4"
}
}
tasks.withType(CodeNarc).configureEach {
tasks.withType(CodeNarc).configureEach { task ->
doFirst {
configLoader.resolveCodenarcConfig()
applyExcludes(it, extension)
}
reports.xml.required.set(true)
reports.html.required.set(extension.htmlReports)

registerReporter(task, 'codenarc')
}
}
configurePluginTasks(project, extension, CodeNarc, 'codenarc', new CodeNarcReporter())
configurePluginTasks(project, extension, CodeNarc, 'codenarc')
}
}

@CompileStatic(TypeCheckingMode.SKIP)
private void configureAnimalSniffer(Project project, QualityExtension extension) {
project.plugins.withId('ru.vyarus.animalsniffer') {
project.plugins.withId('ru.vyarus.animalsniffer') {plugin ->
project.configure(project) {
animalsniffer {
ignoreFailures = !extension.strict
Expand All @@ -322,7 +341,7 @@ class QualityPlugin implements Plugin<Project> {
}
}
applyEnabledState(project, extension,
it.class.classLoader.loadClass('ru.vyarus.gradle.plugin.animalsniffer.AnimalSniffer'))
plugin.class.classLoader.loadClass('ru.vyarus.gradle.plugin.animalsniffer.AnimalSniffer'))
groupQualityTasks(project, 'animalsniffer')
}
}
Expand Down Expand Up @@ -380,8 +399,7 @@ class QualityPlugin implements Plugin<Project> {
configLoader.resolveCpdXsl()
}
// console reporting for each cpd task
applyReporter(prj, task.name, new CpdReporter(configLoader),
extension.consoleReporting, extension.htmlReports)
registerReporter(task, 'cpd', true)
}
// cpd plugin recommendation: module check must also run cpd (check module changes for duplicates)
// grouping tasks (checkQualityMain) are not affected because cpd applied to all source sets
Expand All @@ -395,31 +413,6 @@ class QualityPlugin implements Plugin<Project> {
}
}

private void applyReporter(Project project, String type, Reporter reporter,
boolean consoleReport, boolean htmlReport) {
boolean generatesHtmlReport = htmlReport && HtmlReportGenerator.isAssignableFrom(reporter.class)
if (!consoleReport && !generatesHtmlReport) {
// nothing to do at all
return
}
// in multi-project reporter registered for each project, but all gets called on task execution in any module
project.gradle.taskGraph.afterTask { Task task, TaskState state ->
if (task.name.startsWith(type) && project == task.project) {
// special case for cpd where single task used for all source sets
String taskType = task.name == type ? type : task.name[type.length()..-1].toLowerCase()
if (generatesHtmlReport) {
(reporter as HtmlReportGenerator).generateHtmlReport(task, taskType)
}
if (consoleReport) {
long start = System.currentTimeMillis()
reporter.report(task, taskType)
String duration = DurationFormatter.format(System.currentTimeMillis() - start)
task.project.logger.info("[plugin:quality] $type reporting executed in $duration")
}
}
}
}

/**
* Detects available source folders in configured source sets to understand
* what sources are available: groovy, java or both. Based on that knowledge
Expand Down Expand Up @@ -467,18 +460,19 @@ class QualityPlugin implements Plugin<Project> {
}
}

void registerReporter(Task task, String type, boolean useFullTaskName = false) {
tasksListener.get().register(task, type, useFullTaskName)
}

/**
* Applies reporter, enabled state control and checkQuality* grouping tasks.
* Applies enabled state control and checkQuality* grouping tasks.
*
* @param project project instance
* @param extension extension instance
* @param taskType task class
* @param task task base name
* @param reporter plugin specific reporter instance
*/
private void configurePluginTasks(Project project, QualityExtension extension,
Class taskType, String task, Reporter reporter) {
applyReporter(project, task, reporter, extension.consoleReporting, extension.htmlReports)
void configurePluginTasks(Project project, QualityExtension extension, Class taskType, String task) {
applyEnabledState(project, extension, taskType)
groupQualityTasks(project, task)
}
Expand All @@ -496,7 +490,7 @@ class QualityPlugin implements Plugin<Project> {
* @param extension extension instance
* @param task quality plugin task class
*/
private void applyEnabledState(Project project, QualityExtension extension, Class task) {
void applyEnabledState(Project project, QualityExtension extension, Class task) {
if (!extension.enabled) {
project.gradle.taskGraph.whenReady { TaskExecutionGraph graph ->
project.tasks.withType(task).configureEach { Task t ->
Expand All @@ -517,7 +511,7 @@ class QualityPlugin implements Plugin<Project> {
* @param task quality task
* @param extension extension instance
*/
private void applyExcludes(SourceTask task, QualityExtension extension) {
void applyExcludes(SourceTask task, QualityExtension extension) {
if (extension.excludeSources) {
// directly excluded sources
task.source = task.source - extension.excludeSources
Expand All @@ -537,7 +531,7 @@ class QualityPlugin implements Plugin<Project> {
* @param task task base name
*/
@CompileStatic(TypeCheckingMode.SKIP)
private void groupQualityTasks(Project project, String task) {
void groupQualityTasks(Project project, String task) {
// each quality plugin generate separate tasks for each source set
// assign plugin tasks to source set grouping quality task
project.sourceSets.each {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import groovy.transform.CompileStatic
import groovy.transform.TypeCheckingMode
import groovy.xml.XmlParser
import org.gradle.api.plugins.quality.Checkstyle
import ru.vyarus.gradle.plugin.quality.ConfigLoader

/**
* Prints checkstyle errors (from xml report) into console.
Expand All @@ -16,12 +15,6 @@ import ru.vyarus.gradle.plugin.quality.ConfigLoader
@CompileStatic
class CheckstyleReporter implements Reporter<Checkstyle> {

ConfigLoader configLoader

CheckstyleReporter(ConfigLoader configLoader) {
this.configLoader = configLoader
}

@Override
@CompileStatic(TypeCheckingMode.SKIP)
void report(Checkstyle task, String type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ interface Reporter<T extends Task> {
*/
String NL = String.format('%n')

/**
* In some cases, reporter would be executed outside of gradle threads and so any access to
* project configurations would not be allowed ("was resolved from a thread not managed by Gradle" error).
* To avoid this error, all initialization must be performed before actual processing.
* <p>
* IMPORTANT: all quality tasks are lazy and this method would be called only when quality task configuration
* was triggered (ideally, before actual task execution).
* <p>
* Method would be called for EACH quality task.
*
* @param task
*/
default void init(T task) {}

/**
* Called after quality tool task to report violations.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import groovy.transform.CompileStatic
import groovy.transform.TypeCheckingMode
import groovy.xml.XmlParser
import org.gradle.api.Project
import ru.vyarus.gradle.plugin.quality.ConfigLoader
import ru.vyarus.gradle.plugin.quality.util.FileUtils

/**
Expand All @@ -18,10 +17,14 @@ import ru.vyarus.gradle.plugin.quality.util.FileUtils
class SpotbugsReporter implements Reporter<SpotBugsTask> {
private static final String XML = 'xml'

ConfigLoader configLoader
Map<String, String> pluginChecks

SpotbugsReporter(ConfigLoader configLoader) {
this.configLoader = configLoader
@Override
synchronized void init(SpotBugsTask task) {
if (pluginChecks == null) {
// there could not be tasks from different projects because quality plugin would be applied to each one
pluginChecks = resolvePluginsChecks(task.project)
}
}

@Override
Expand All @@ -45,7 +48,6 @@ class SpotbugsReporter implements Reporter<SpotBugsTask> {

Map<String, String> desc = buildDescription(result)
Map<String, String> cat = buildCategories(result)
Map<String, String> plugins = resolvePluginsChecks(task.project)
result.BugInstance.each { bug ->
Node msg = bug.LongMessage[0]
Node src = bug.SourceLine[0]
Expand All @@ -55,7 +57,7 @@ class SpotbugsReporter implements Reporter<SpotBugsTask> {
String classname = src.@classname
String pkg = classname[0..classname.lastIndexOf('.')]
String cls = src.@sourcefile
String plugin = plugins[bugType] ?: ''
String plugin = pluginChecks[bugType] ?: ''
// part in braces recognized by intellij IDEA and shown as link
task.logger.error "[${plugin}${cat[bug.@category]} | ${bugType}] $pkg(${cls}:${srcPosition}) " +
"[priority ${bug.@priority} / rank ${bug.@rank}]" +
Expand Down
Loading

0 comments on commit 3f12640

Please sign in to comment.