diff --git a/detekt-gradle-plugin/build.gradle.kts b/detekt-gradle-plugin/build.gradle.kts index 5538a971b71..c3af5fcb98e 100644 --- a/detekt-gradle-plugin/build.gradle.kts +++ b/detekt-gradle-plugin/build.gradle.kts @@ -69,6 +69,7 @@ dependencies { compileOnly(libs.kotlin.gradle) compileOnly(libs.kotlin.gradlePluginApi) implementation(libs.sarif4k) + compileOnly("io.gitlab.arturbosch.detekt:detekt-cli:1.22.0") testKitRuntimeOnly(libs.kotlin.gradle) testKitJava11RuntimeOnly(libs.android.gradle.maxSupported) diff --git a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/Detekt.kt b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/Detekt.kt index 5b6c99816c1..113d33b2592 100644 --- a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/Detekt.kt +++ b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/Detekt.kt @@ -16,6 +16,7 @@ import io.gitlab.arturbosch.detekt.invoke.CustomReportArgument import io.gitlab.arturbosch.detekt.invoke.DebugArgument import io.gitlab.arturbosch.detekt.invoke.DefaultReportArgument import io.gitlab.arturbosch.detekt.invoke.DetektInvoker +import io.gitlab.arturbosch.detekt.invoke.DetektWorkAction import io.gitlab.arturbosch.detekt.invoke.DisableDefaultRuleSetArgument import io.gitlab.arturbosch.detekt.invoke.InputArgument import io.gitlab.arturbosch.detekt.invoke.JdkHomeArgument @@ -32,6 +33,7 @@ import org.gradle.api.file.RegularFileProperty import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory import org.gradle.api.reporting.ReportingExtension import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Classpath @@ -53,12 +55,15 @@ import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.VerificationTask import org.gradle.api.tasks.options.Option import org.gradle.language.base.plugins.LifecycleBasePlugin +import org.gradle.workers.WorkerExecutor import java.io.File import javax.inject.Inject @CacheableTask abstract class Detekt @Inject constructor( - private val objects: ObjectFactory + private val objects: ObjectFactory, + private val workerExecutor: WorkerExecutor, + private val providers: ProviderFactory, ) : SourceTask(), VerificationTask { @get:Classpath @@ -249,12 +254,28 @@ abstract class Detekt @Inject constructor( @TaskAction fun check() { - DetektInvoker.create(task = this, isDryRun = isDryRun.orNull.toBoolean()).invokeCli( - arguments = arguments, - ignoreFailures = ignoreFailures, - classpath = detektClasspath.plus(pluginClasspath), - taskName = name - ) + if (providers.gradleProperty(USE_WORKER_API).getOrElse("false") == "true") { + logger.info("Executing $name using Worker API") + val workQueue = workerExecutor.processIsolation { workerSpec -> + workerSpec.classpath.from(detektClasspath) + workerSpec.classpath.from(pluginClasspath) + } + + workQueue.submit(DetektWorkAction::class.java) { workParameters -> + workParameters.arguments.set(arguments) + workParameters.ignoreFailures.set(ignoreFailures) + workParameters.dryRun.set(isDryRun.orNull.toBoolean()) + workParameters.taskName.set(name) + } + } else { + logger.info("Executing $name using DetektInvoker") + DetektInvoker.create(isDryRun = isDryRun.orNull.toBoolean()).invokeCli( + arguments = arguments, + ignoreFailures = ignoreFailures, + classpath = detektClasspath.plus(pluginClasspath), + taskName = name + ) + } } private fun convertCustomReportsToArguments(): List = reports.custom.map { diff --git a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektCreateBaselineTask.kt b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektCreateBaselineTask.kt index c0eebdf7522..ac01beb3942 100644 --- a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektCreateBaselineTask.kt +++ b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektCreateBaselineTask.kt @@ -11,6 +11,7 @@ import io.gitlab.arturbosch.detekt.invoke.ConfigArgument import io.gitlab.arturbosch.detekt.invoke.CreateBaselineArgument import io.gitlab.arturbosch.detekt.invoke.DebugArgument import io.gitlab.arturbosch.detekt.invoke.DetektInvoker +import io.gitlab.arturbosch.detekt.invoke.DetektWorkAction import io.gitlab.arturbosch.detekt.invoke.DisableDefaultRuleSetArgument import io.gitlab.arturbosch.detekt.invoke.InputArgument import io.gitlab.arturbosch.detekt.invoke.JvmTargetArgument @@ -20,6 +21,7 @@ import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileTree import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property +import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.Console @@ -36,9 +38,14 @@ import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.api.tasks.SourceTask import org.gradle.api.tasks.TaskAction import org.gradle.language.base.plugins.LifecycleBasePlugin +import org.gradle.workers.WorkerExecutor +import javax.inject.Inject @CacheableTask -abstract class DetektCreateBaselineTask : SourceTask() { +abstract class DetektCreateBaselineTask @Inject constructor( + private val workerExecutor: WorkerExecutor, + private val providers: ProviderFactory, +) : SourceTask() { init { description = "Creates a detekt baseline on the given --baseline path." @@ -139,11 +146,26 @@ abstract class DetektCreateBaselineTask : SourceTask() { @TaskAction fun baseline() { - DetektInvoker.create(task = this).invokeCli( - arguments = arguments, - ignoreFailures = ignoreFailures.getOrElse(false), - classpath = detektClasspath.plus(pluginClasspath), - taskName = name - ) + if (providers.gradleProperty(USE_WORKER_API).getOrElse("false") == "true") { + logger.info("Executing $name using Worker API") + val workQueue = workerExecutor.processIsolation { workerSpec -> + workerSpec.classpath.from(detektClasspath) + workerSpec.classpath.from(pluginClasspath) + } + + workQueue.submit(DetektWorkAction::class.java) { workParameters -> + workParameters.arguments.set(arguments) + workParameters.ignoreFailures.set(ignoreFailures) + workParameters.taskName.set(name) + } + } else { + logger.info("Executing $name using DetektInvoker") + DetektInvoker.create().invokeCli( + arguments = arguments, + ignoreFailures = ignoreFailures.getOrElse(false), + classpath = detektClasspath.plus(pluginClasspath), + taskName = name + ) + } } } diff --git a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektGenerateConfigTask.kt b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektGenerateConfigTask.kt index 74d58a605f3..b33854b53b7 100644 --- a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektGenerateConfigTask.kt +++ b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektGenerateConfigTask.kt @@ -5,11 +5,13 @@ import io.gitlab.arturbosch.detekt.DetektPlugin.Companion.CONFIG_FILE import io.gitlab.arturbosch.detekt.invoke.CliArgument import io.gitlab.arturbosch.detekt.invoke.ConfigArgument import io.gitlab.arturbosch.detekt.invoke.DetektInvoker +import io.gitlab.arturbosch.detekt.invoke.DetektWorkAction import io.gitlab.arturbosch.detekt.invoke.GenerateConfigArgument import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.ProviderFactory import org.gradle.api.services.BuildService import org.gradle.api.services.BuildServiceParameters import org.gradle.api.tasks.CacheableTask @@ -18,13 +20,16 @@ import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import org.gradle.language.base.plugins.LifecycleBasePlugin +import org.gradle.workers.WorkerExecutor import java.io.File import java.nio.file.Files import javax.inject.Inject @CacheableTask abstract class DetektGenerateConfigTask @Inject constructor( - objects: ObjectFactory + objects: ObjectFactory, + private val workerExecutor: WorkerExecutor, + private val providers: ProviderFactory, ) : DefaultTask() { init { @@ -71,11 +76,25 @@ abstract class DetektGenerateConfigTask @Inject constructor( Files.createDirectories(configFile.get().asFile.parentFile.toPath()) - DetektInvoker.create(task = this).invokeCli( - arguments = arguments, - classpath = detektClasspath.plus(pluginClasspath), - taskName = name, - ) + if (providers.gradleProperty(USE_WORKER_API).getOrElse("false") == "true") { + logger.info("Executing $name using Worker API") + val workQueue = workerExecutor.processIsolation { workerSpec -> + workerSpec.classpath.from(detektClasspath) + workerSpec.classpath.from(pluginClasspath) + } + + workQueue.submit(DetektWorkAction::class.java) { workParameters -> + workParameters.arguments.set(arguments) + workParameters.taskName.set(name) + } + } else { + logger.info("Executing $name using DetektInvoker") + DetektInvoker.create().invokeCli( + arguments = arguments, + classpath = detektClasspath.plus(pluginClasspath), + taskName = name, + ) + } } @Suppress("UnnecessaryAbstractClass") diff --git a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektPlugin.kt b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektPlugin.kt index 00469b18574..b524eb199f0 100644 --- a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektPlugin.kt +++ b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektPlugin.kt @@ -136,3 +136,4 @@ class DetektPlugin : Plugin { const val CONFIGURATION_DETEKT = "detekt" const val CONFIGURATION_DETEKT_PLUGINS = "detektPlugins" +const val USE_WORKER_API = "detekt.use.worker.api" diff --git a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/invoke/DetektInvoker.kt b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/invoke/DetektInvoker.kt index 502811e2858..9fd18c07d05 100644 --- a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/invoke/DetektInvoker.kt +++ b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/invoke/DetektInvoker.kt @@ -3,9 +3,12 @@ package io.gitlab.arturbosch.detekt.invoke import io.gitlab.arturbosch.detekt.internal.ClassLoaderCache import io.gitlab.arturbosch.detekt.internal.GlobalClassLoaderCache import org.gradle.api.GradleException -import org.gradle.api.Task +import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.FileCollection -import org.gradle.api.logging.Logger +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters import java.io.PrintStream import java.lang.reflect.InvocationTargetException @@ -20,15 +23,57 @@ internal interface DetektInvoker { companion object { - fun create(task: Task, isDryRun: Boolean = false): DetektInvoker = + fun create(isDryRun: Boolean = false): DetektInvoker = if (isDryRun) { - DryRunInvoker(task.logger) + DryRunInvoker() } else { DefaultCliInvoker() } } } +interface DetektWorkParameters : WorkParameters { + val arguments: ListProperty + val ignoreFailures: Property + val dryRun: Property + val taskName: Property + val classpath: ConfigurableFileCollection +} + +abstract class DetektWorkAction : WorkAction { + @Suppress("SwallowedException", "TooGenericExceptionCaught") + override fun execute() { + if (parameters.dryRun.getOrElse(false)) { + DryRunInvoker().invokeCli( + parameters.arguments.get(), + parameters.classpath, + parameters.taskName.get(), + parameters.ignoreFailures.getOrElse(false) + ) + return + } + + try { + @Suppress("DEPRECATION") + val runner = io.gitlab.arturbosch.detekt.cli.buildRunner( + parameters.arguments.get().toTypedArray(), + System.out, + System.err + ) + runner.execute() + } catch (e: Exception) { + if (isBuildFailure(e.message) && parameters.ignoreFailures.get()) { + return + } else { + throw GradleException(e.message ?: "There was a problem running detekt.") + } + } + } + + private fun isBuildFailure(msg: String?) = + msg != null && "Build failed with" in msg && "issues" in msg +} + internal class DefaultCliInvoker( private val classLoaderCache: ClassLoaderCache = GlobalClassLoaderCache ) : DetektInvoker { @@ -61,7 +106,7 @@ internal class DefaultCliInvoker( private fun isAnalysisFailure(msg: String) = "Analysis failed with" in msg && "issues" in msg } -private class DryRunInvoker(private val logger: Logger) : DetektInvoker { +private class DryRunInvoker : DetektInvoker { override fun invokeCli( arguments: List, @@ -69,10 +114,10 @@ private class DryRunInvoker(private val logger: Logger) : DetektInvoker { taskName: String, ignoreFailures: Boolean ) { - logger.info("Invoking detekt with dry-run.") - logger.info("Task: $taskName") - logger.info("Arguments: ${arguments.joinToString(" ")}") - logger.info("Classpath: ${classpath.files}") - logger.info("Ignore failures: $ignoreFailures") + println("Invoking detekt with dry-run.") + println("Task: $taskName") + println("Arguments: ${arguments.joinToString(" ")}") + println("Classpath: ${classpath.files}") + println("Ignore failures: $ignoreFailures") } }