Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SourceTask in Gradle plugin #1797

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,65 +1,63 @@
package com.saveourtool.diktat.plugin.gradle

import org.gradle.api.Action
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.VerificationTask
import org.gradle.api.tasks.util.PatternFilterable
import org.gradle.api.tasks.util.PatternSet
import java.io.File

/**
* An extension to configure diktat in build.gradle(.kts) file
*
* @param patternSet
*/
open class DiktatExtension(
private val patternSet: PatternSet
) {
abstract class DiktatExtension {
/**
* Boolean flag to support `ignoreFailures` property of [VerificationTask].
*/
var ignoreFailures: Boolean = false

/**
* Flag that indicates whether to turn debug logging on
*/
var debug = false
abstract val ignoreFailures: Property<Boolean>

/**
* Property that will be used if you need to publish the report to GitHub
*/
var githubActions = false
abstract val githubActions: Property<Boolean> // = false

/**
* Type of the reporter to use
*/
var reporter: String = ""
abstract val reporter: Property<String>

/**
* Destination for reporter. If empty, will write to stdout.
*/
var output: String = ""
abstract val output: RegularFileProperty

/**
* Baseline file, containing a list of errors that will be ignored.
* If this file doesn't exist, it will be created on the first invocation.
*/
var baseline: String? = null
abstract val baseline: RegularFileProperty

/**
* Path to diktat yml config file. Can be either absolute or relative to project's root directory.
* Default value: `diktat-analysis.yml` in rootDir.
*/
@get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE)
lateinit var diktatConfigFile: File
abstract val diktatConfigFile: RegularFileProperty

/**
* @return [PatternFilterable] to configure input files for diktat task
*/
@Nested
abstract fun getInputs(): PatternFilterable

/**
* Configure input files for diktat task
*
* @param action configuration lambda for `PatternFilterable`
* @param action configuration lambda for [PatternFilterable]
*/
fun inputs(action: PatternFilterable.() -> Unit) {
action(patternSet)
}
fun inputs(action: Action<in PatternFilterable>) = action.execute(getInputs())
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.saveourtool.diktat.plugin.gradle.tasks.DiktatFixTask.Companion.regist
import com.saveourtool.diktat.plugin.gradle.tasks.configureMergeReportsTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.util.PatternSet

/**
* Plugin that configures diktat and registers tasks to run diktat
Expand All @@ -17,17 +16,15 @@ class DiktatGradlePlugin : Plugin<Project> {
*/
@Suppress("TOO_LONG_FUNCTION")
override fun apply(project: Project) {
val patternSet = PatternSet()
val diktatExtension = project.extensions.create(
DIKTAT_EXTENSION,
DiktatExtension::class.java,
patternSet
).apply {
diktatConfigFile = project.rootProject.file("diktat-analysis.yml")
diktatConfigFile.set(project.rootProject.file("diktat-analysis.yml"))
}

project.registerDiktatCheckTask(diktatExtension, patternSet)
project.registerDiktatFixTask(diktatExtension, patternSet)
project.registerDiktatCheckTask(diktatExtension)
project.registerDiktatFixTask(diktatExtension)
project.configureMergeReportsTask(diktatExtension)
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,24 @@ fun <T> Any.closureOf(action: T.() -> Unit): Closure<Any?> =
* @return CLI flag as string
*/
fun Project.getReporterType(diktatExtension: DiktatExtension): String {
val name = diktatExtension.reporter.trim()
val reporter = diktatExtension.reporter.getOrElse("")
val validReporters = listOf("sarif", "plain", "json", "html")
val reporterType = when {
diktatExtension.githubActions -> {
if (diktatExtension.reporter.isNotEmpty()) {
logger.warn("`diktat.githubActions` is set to true, so custom reporter [$name] will be ignored and SARIF reporter will be used")
diktatExtension.githubActions.getOrElse(false) -> {
if (reporter.isNotEmpty()) {
logger.warn("`diktat.githubActions` is set to true, so custom reporter [$reporter] will be ignored and SARIF reporter will be used")
}
"sarif"
}
name.isEmpty() -> {
reporter.isEmpty() -> {
logger.info("Reporter name was not set. Using 'plain' reporter")
"plain"
}
name !in validReporters -> {
logger.warn("Reporter name is invalid (provided value: [$name]). Falling back to 'plain' reporter")
reporter !in validReporters -> {
logger.warn("Reporter name is invalid (provided value: [$reporter]). Falling back to 'plain' reporter")
"plain"
}
else -> name
else -> reporter
}

return reporterType
Expand All @@ -78,15 +78,14 @@ fun Project.getReporterType(diktatExtension: DiktatExtension): String {
* @return destination [File] or null if stdout is used
*/
internal fun Project.getOutputFile(diktatExtension: DiktatExtension): File? = when {
diktatExtension.githubActions -> project.layout.buildDirectory
diktatExtension.githubActions.getOrElse(false) -> project.layout.buildDirectory
.file("reports/diktat/diktat.sarif")
.get()
.asFile
.also {
Files.createDirectories(it.parentFile.toPath())
}
diktatExtension.output.isNotEmpty() -> file(diktatExtension.output)
else -> null
else -> diktatExtension.output.map { it.asFile }.orNull
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import com.saveourtool.diktat.plugin.gradle.DiktatExtension
import com.saveourtool.diktat.plugin.gradle.DiktatGradlePlugin
import org.gradle.api.Project
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.util.PatternFilterable
import org.gradle.api.tasks.util.PatternSet
import javax.inject.Inject

Expand All @@ -15,8 +14,7 @@ import javax.inject.Inject
*/
abstract class DiktatCheckTask @Inject constructor(
extension: DiktatExtension,
inputs: PatternFilterable
) : DiktatTaskBase(extension, inputs) {
) : DiktatTaskBase(extension) {
override fun doRun(
runner: DiktatRunner,
args: DiktatRunnerArguments
Expand All @@ -30,11 +28,10 @@ abstract class DiktatCheckTask @Inject constructor(
*/
fun Project.registerDiktatCheckTask(
diktatExtension: DiktatExtension,
patternSet: PatternSet
): TaskProvider<DiktatCheckTask> =
tasks.register(
DiktatGradlePlugin.DIKTAT_CHECK_TASK, DiktatCheckTask::class.java,
diktatExtension, patternSet
diktatExtension,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@ import com.saveourtool.diktat.plugin.gradle.DiktatExtension
import com.saveourtool.diktat.plugin.gradle.DiktatGradlePlugin
import org.gradle.api.Project
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.util.PatternFilterable
import org.gradle.api.tasks.util.PatternSet
import javax.inject.Inject

/**
* A task to check source code by diktat
*/
abstract class DiktatFixTask @Inject constructor(
extension: DiktatExtension,
inputs: PatternFilterable
) : DiktatTaskBase(extension, inputs) {
) : DiktatTaskBase(extension) {
override fun doRun(
runner: DiktatRunner,
args: DiktatRunnerArguments
Expand All @@ -27,16 +24,14 @@ abstract class DiktatFixTask @Inject constructor(
companion object {
/**
* @param diktatExtension [DiktatExtension] with some values for task configuration
* @param patternSet [PatternSet] to discover files for diktat fix
* @return a [TaskProvider]
*/
fun Project.registerDiktatFixTask(
diktatExtension: DiktatExtension,
patternSet: PatternSet
): TaskProvider<DiktatFixTask> =
tasks.register(
DiktatGradlePlugin.DIKTAT_FIX_TASK, DiktatFixTask::class.java,
diktatExtension, patternSet
diktatExtension,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,59 +14,22 @@ import com.saveourtool.diktat.ruleset.rules.DiktatRuleConfigReaderImpl
import com.saveourtool.diktat.ruleset.rules.DiktatRuleSetFactoryImpl

import generated.DIKTAT_VERSION
import generated.KTLINT_VERSION
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.IgnoreEmptyDirectories
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.SourceTask
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.VerificationTask
import org.gradle.api.tasks.util.PatternFilterable

import java.nio.file.Path

/**
* A base task to run `diktat`
*
* @param inputs
* @property extension
*/
@Suppress("WRONG_NEWLINES", "Deprecation")
abstract class DiktatTaskBase(
@get:Internal internal val extension: DiktatExtension,
private val inputs: PatternFilterable
) : DefaultTask(), VerificationTask, com.saveourtool.diktat.plugin.gradle.DiktatJavaExecTaskBase {
/**
* Files that will be analyzed by diktat
*/
@get:IgnoreEmptyDirectories
@get:SkipWhenEmpty
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:InputFiles
val actualInputs: FileCollection by lazy {
if (inputs.includes.isEmpty() && inputs.excludes.isEmpty()) {
inputs.include("src/**/*.kt")
}
project.objects.fileCollection().from(
project.fileTree("${project.projectDir}").apply {
exclude("${project.buildDir}")
}
.matching(inputs)
)
}

/**
* Whether diktat should be executed
*/
@get:Internal
internal val shouldRun: Boolean by lazy {
!actualInputs.isEmpty
}
) : SourceTask(), VerificationTask {
private val diktatRunnerFactory by lazy {
DiktatRunnerFactory(
diktatRuleConfigReader = DiktatRuleConfigReaderImpl(),
Expand All @@ -78,10 +41,10 @@ abstract class DiktatTaskBase(
}
private val diktatRunnerArguments by lazy {
DiktatRunnerArguments(
configFile = extension.diktatConfigFile.toPath(),
configFile = extension.diktatConfigFile.get().asFile.toPath(),
sourceRootDir = project.projectDir.toPath(),
files = actualInputs.files.map { it.toPath() },
baselineFile = extension.baseline?.let { project.file(it).toPath() },
files = source.files.map { it.toPath() },
baselineFile = extension.baseline.map { it.asFile.toPath() }.orNull,
reporterType = project.getReporterType(extension),
reporterOutput = project.getOutputFile(extension)?.outputStream(),
loggingListener = object : DiktatProcessorListener {
Expand All @@ -105,7 +68,15 @@ abstract class DiktatTaskBase(
}

init {
ignoreFailures = extension.ignoreFailures
ignoreFailures = extension.ignoreFailures.getOrElse(false)
extension.getInputs().run {
if (includes.isEmpty() && excludes.isEmpty()) {
patternSet.include("src/**/*.kt")
} else {
patternSet.setIncludes(includes)
patternSet.setExcludes(excludes)
}
}
}

/**
Expand All @@ -115,14 +86,8 @@ abstract class DiktatTaskBase(
*/
@TaskAction
fun run() {
if (extension.debug) {
project.logger.lifecycle("Running diktat $DIKTAT_VERSION with ktlint $KTLINT_VERSION")
}
if (!shouldRun) {
/*
If ktlint receives empty patterns, it implicitly uses &#42;&#42;/*.kt, **/*.kts instead.
This can lead to diktat analyzing gradle buildscripts and so on. We want to prevent it.
*/
project.logger.lifecycle("Running diktat $DIKTAT_VERSION")
if (source.isEmpty) {
project.logger.warn("Inputs for $name do not exist, will not run diktat")
project.logger.info("Skipping diktat execution")
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ internal fun Project.configureMergeReportsTask(diktatExtension: DiktatExtension)
reportMergeTask.shouldRunAfter(tasks.withType(DiktatTaskBase::class.java))
}
}
tasks.withType(DiktatTaskBase::class.java).configureEach { diktatJavaExecTaskBase ->
diktatJavaExecTaskBase.finalizedBy(rootProject.tasks.withType(SarifReportMergeTask::class.java))
tasks.withType(DiktatTaskBase::class.java).configureEach { diktatTaskBase ->
diktatTaskBase.finalizedBy(rootProject.tasks.withType(SarifReportMergeTask::class.java))
}
}
Loading