From cc369068ffbf95087b86e2dac1908b836651dec7 Mon Sep 17 00:00:00 2001 From: Olivier Lemasle Date: Fri, 6 Apr 2018 11:38:58 +0200 Subject: [PATCH] Add dependencies for gradle plugin (for custom rules) Following the feature request https://github.com/arturbosch/detekt/issues/795, this commit implements a way to configure Detekt plugins using Gradle plugin. --- README.md | 23 ++- .../arturbosch/detekt/DetektCheckTask.kt | 14 +- .../detekt/DetektCreateBaselineTask.kt | 15 +- .../detekt/DetektGenerateConfigTask.kt | 17 +-- .../gitlab/arturbosch/detekt/DetektPlugin.kt | 2 + .../detekt/extensions/DetektExtension.kt | 14 ++ .../arturbosch/detekt/FunctionalTest.kt | 133 ++++++++++++++++++ 7 files changed, 197 insertions(+), 21 deletions(-) create mode 100644 detekt-gradle-plugin/src/test/kotlin/io/gitlab/arturbosch/detekt/FunctionalTest.kt diff --git a/README.md b/README.md index adde3af3f..da8ee52d0 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ It operates on the abstract syntax tree provided by the Kotlin compiler. 3. [in android projects](#gradleandroid) 4. [plugin tasks](#tasks) 5. [detekt-closure](#closure) + 6. [custom rules](#gradleCustomRules) 3. [Standalone gradle task](#gradle) 4. [Standalone maven task](#maventask) 5. [Rule sets](#rulesets) @@ -298,6 +299,26 @@ detekt { For more information on using idea as a headless formatting/inspection tool see [here](https://www.jetbrains.com/help/idea/working-with-intellij-idea-features-from-command-line.html). +##### Using custom rules with Gradle plugin + +When your _detekt_ custom rules are located in a module of your Gradle project (e.g. `:detekt-extensions`), you can +enable them with the following syntax: + +```groovy +dependencies { + detekt project(':detekt-extensions') +} + +detekt { + profile("main") { + input = "$projectDir/src/main/kotlin" + } +} +``` + +More generally, you can use dependencies on `detekt` configuration; these dependencies will be resolved, built if +needed, and added to _detekt_ classpath. + #### Using _detekt_ in custom gradle projects 1. Add following lines to your build.gradle file. @@ -597,7 +618,7 @@ If you contributed to detekt but your name is not in the list, please feel free - [Svyatoslav Chatchenko](https://github.com/MyDogTom) - Active on Issues, NamingConventions and UnusedImport fixes - [Sean Flanigan](https://github.com/seanf) - Config from classpath resource - [Sebastian Schuberth](https://github.com/sschuberth) - Active on Issues, Windows support -- [Olivier Lemasle](https://github.com/olivierlemasle) - NP-Bugfix +- [Olivier Lemasle](https://github.com/olivierlemasle) - NP-Bugfix, fix TooGenericExceptionCaught, Gradle plugin improvement - [Marc Prengemann](https://github.com/winterDroid) - Support for custom output formats, prototyped Rule-Context-Issue separation - [Sebastiano Poggi](https://github.com/rock3r) - Enhanced milestone report script, Magic number fixes - [Ilya Tretyakov](https://github.com/jvilya) - Sonar runs should not auto correct formatting. diff --git a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektCheckTask.kt b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektCheckTask.kt index 7a9c6b17c..059e19ba6 100644 --- a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektCheckTask.kt +++ b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektCheckTask.kt @@ -4,7 +4,7 @@ import io.gitlab.arturbosch.detekt.extensions.DetektExtension import io.gitlab.arturbosch.detekt.extensions.INPUT_PARAMETER import io.gitlab.arturbosch.detekt.extensions.OUTPUT_PARAMETER import org.gradle.api.DefaultTask -import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency +import org.gradle.api.file.FileCollection import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction @@ -13,6 +13,7 @@ import java.io.File /** * @author Artur Bosch * @author Marvin Ramin + * @author Olivier Lemasle */ open class DetektCheckTask : DefaultTask() { @@ -22,6 +23,8 @@ open class DetektCheckTask : DefaultTask() { @OutputDirectory val output: File? + private val classpath: FileCollection + init { description = "Analyze your kotlin code with detekt." group = "verification" @@ -33,19 +36,18 @@ open class DetektCheckTask : DefaultTask() { val outputIndex = arguments.indexOf(OUTPUT_PARAMETER) output = File(arguments[outputIndex + 1]) + + classpath = detektExtension.resolveClasspath(project) + dependsOn(classpath) } @TaskAction fun check() { val detektExtension = project.extensions.getByName("detekt") as DetektExtension - val configuration = project.buildscript.configurations.maybeCreate("detektCheck") - project.buildscript.dependencies.add(configuration.name, DefaultExternalModuleDependency( - "io.gitlab.arturbosch.detekt", "detekt-cli", detektExtension.version)) - project.javaexec { it.main = "io.gitlab.arturbosch.detekt.cli.Main" - it.classpath = configuration + it.classpath = classpath it.args(detektExtension.resolveArguments(project)) } } 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 5ac3c243f..e09c19ee6 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 @@ -2,17 +2,24 @@ package io.gitlab.arturbosch.detekt import io.gitlab.arturbosch.detekt.extensions.DetektExtension import org.gradle.api.DefaultTask -import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency +import org.gradle.api.file.FileCollection import org.gradle.api.tasks.TaskAction /** * @author Artur Bosch + * @author Olivier Lemasle */ open class DetektCreateBaselineTask : DefaultTask() { + private val classpath: FileCollection + init { description = "Creates a detekt baseline on the given --baseline path." group = "verification" + + val detektExtension = project.extensions.getByName("detekt") as DetektExtension + classpath = detektExtension.resolveClasspath(project) + dependsOn(classpath) } private val createBaseline = "--create-baseline" @@ -21,13 +28,9 @@ open class DetektCreateBaselineTask : DefaultTask() { fun baseline() { val detektExtension = project.extensions.getByName("detekt") as DetektExtension - val configuration = project.buildscript.configurations.maybeCreate("detektBaseline") - project.buildscript.dependencies.add(configuration.name, DefaultExternalModuleDependency( - "io.gitlab.arturbosch.detekt", "detekt-cli", detektExtension.version)) - project.javaexec { it.main = "io.gitlab.arturbosch.detekt.cli.Main" - it.classpath = configuration + it.classpath = classpath it.args(detektExtension.resolveArguments(project).plus(createBaseline)) } } 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 300e817a0..3d965e65e 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 @@ -2,30 +2,31 @@ package io.gitlab.arturbosch.detekt import io.gitlab.arturbosch.detekt.extensions.DetektExtension import org.gradle.api.DefaultTask -import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency +import org.gradle.api.file.FileCollection import org.gradle.api.tasks.TaskAction /** * @author Artur Bosch + * @author Olivier Lemasle */ open class DetektGenerateConfigTask : DefaultTask() { + private val classpath: FileCollection + init { description = "Generate a detekt configuration file inside your project." group = "verification" + + val detektExtension = project.extensions.getByName("detekt") as DetektExtension + classpath = detektExtension.resolveClasspath(project) + dependsOn(classpath) } @TaskAction fun generateConfig() { - val detektExtension = project.extensions.getByName("detekt") as DetektExtension - - val configuration = project.buildscript.configurations.maybeCreate("detektConfig") - project.buildscript.dependencies.add(configuration.name, DefaultExternalModuleDependency( - "io.gitlab.arturbosch.detekt", "detekt-cli", detektExtension.version)) - project.javaexec { it.main = "io.gitlab.arturbosch.detekt.cli.Main" - it.classpath = configuration + it.classpath = classpath it.args("--input", project.projectDir.absolutePath, "--generate-config") } } 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 0df34ce85..467f9bcc6 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 @@ -9,6 +9,8 @@ import org.gradle.api.Project class DetektPlugin : Plugin { override fun apply(project: Project) { + project.configurations.create("detekt") + val profilesContainer = project.container(ProfileExtension::class.java) project.extensions.add(PROFILES_EXTENSION_NAME, profilesContainer) profilesContainer.all { ProfileStorage.add(it) } diff --git a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/extensions/DetektExtension.kt b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/extensions/DetektExtension.kt index 832d09443..4551e2036 100644 --- a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/extensions/DetektExtension.kt +++ b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/extensions/DetektExtension.kt @@ -2,10 +2,13 @@ package io.gitlab.arturbosch.detekt.extensions import org.gradle.api.Action import org.gradle.api.Project +import org.gradle.api.file.FileCollection +import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency /** * @author Artur Bosch * @author Said Tahsin Dane + * @author Olivier Lemasle */ open class DetektExtension(open var version: String = SUPPORTED_DETEKT_VERSION, open var debug: Boolean = DEFAULT_DEBUG_VALUE, @@ -34,6 +37,17 @@ open class DetektExtension(open var version: String = SUPPORTED_DETEKT_VERSION, } } + fun resolveClasspath(project: Project): FileCollection = project + .configurations + .getByName("detekt") + .withDependencies { + it.add( + DefaultExternalModuleDependency( + "io.gitlab.arturbosch.detekt", "detekt-cli", version + ) + ) + } + fun resolveArguments(project: Project): List { return with(extractArguments()) { if (!contains(INPUT_PARAMETER)) { diff --git a/detekt-gradle-plugin/src/test/kotlin/io/gitlab/arturbosch/detekt/FunctionalTest.kt b/detekt-gradle-plugin/src/test/kotlin/io/gitlab/arturbosch/detekt/FunctionalTest.kt new file mode 100644 index 000000000..b71e9493e --- /dev/null +++ b/detekt-gradle-plugin/src/test/kotlin/io/gitlab/arturbosch/detekt/FunctionalTest.kt @@ -0,0 +1,133 @@ +package io.gitlab.arturbosch.detekt + +import org.assertj.core.api.Assertions.assertThat +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.describe +import org.jetbrains.spek.api.dsl.it +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import java.io.File + +/** + * @author Olivier Lemasle + */ +internal class FunctionalTest : Spek({ + + describe("The Detekt Gradle plugin") { + + it("uses built-in rules") { + val rootDir = createTempDir(prefix = "withoutCustomRules") + writeFiles(rootDir) + + // Using a custom "project-cache-dir" to avoid a Gradle error on Windows + val result = GradleRunner.create() + .withProjectDir(rootDir) + .withArguments("--project-cache-dir", createTempDir(prefix = "cache").absolutePath, "detektCheck") + .withPluginClasspath() + .build() + + assertThat(result.output).contains("number of classes: 1") + assertThat(result.output).contains("Ruleset: comments") + assertThat(result.task(":detektCheck")?.outcome).isEqualTo(TaskOutcome.SUCCESS) + + // Asserts that the "custom" module is not built, and that custom ruleset is not enabled + assertThat(result.output).doesNotContain("Ruleset: test-custom") + assertThat(File(rootDir, "custom/build")).doesNotExist() + } + + it("can use custom rules from a project's module") { + val rootDir = createTempDir(prefix = "withCustomRules") + writeFiles(rootDir) + + File(rootDir, "build.gradle").appendText( + """ + |dependencies { + | detekt project(':custom') + |} + """.trimMargin() + ) + + val result = GradleRunner.create() + .withProjectDir(rootDir) + .withArguments("--project-cache-dir", createTempDir(prefix = "cache").absolutePath, "detektCheck") + .withPluginClasspath() + .build() + + assertThat(result.output).contains("number of classes: 1") + assertThat(result.output).contains("Ruleset: comments") + assertThat(result.task(":detektCheck")?.outcome).isEqualTo(TaskOutcome.SUCCESS) + + // Asserts that the "custom" module is built, and that custom ruleset is enabled + assertThat(result.output).contains("Ruleset: test-custom") + assertThat(File(rootDir, "custom/build")).exists() + } + } +}) + +// build.gradle +private val buildFileContent = """ + |plugins { + | id 'io.gitlab.arturbosch.detekt' + |} + |repositories { + | jcenter() + |} + |detekt { + | profile('main') { + | input = "${"$"}projectDir/src/main/kotlin" + | } + |} + | + """.trimMargin() + +// settings.gradle +private const val settingsFileContent = """include ":custom"""" + +// src/main/kotlin/MyClass.kt +private val ktFileContent = """ + |class MyClass + | + """.trimMargin() + +// custom/build.gradle +private val customBuildFileContent = """ + |plugins { + | id "org.jetbrains.kotlin.jvm" version "1.2.31" + |} + |repositories { + | jcenter() + |} + |dependencies { + | implementation "io.gitlab.arturbosch.detekt:detekt-api:1.0.0.RC6-4" + |} + """.trimMargin() + +// custom/src/main/kotlin/RulesProvider.kt +private val customRulesProviderContent = """ + |import io.gitlab.arturbosch.detekt.api.Config + |import io.gitlab.arturbosch.detekt.api.RuleSet + |import io.gitlab.arturbosch.detekt.api.RuleSetProvider + | + |class RulesProvider : RuleSetProvider { + | override val ruleSetId: String = "test-custom" + | override fun instance(config: Config) = RuleSet(ruleSetId, listOf()) + |} + | + """.trimMargin() + +// custom/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider +private const val ruleSetProviderSpiContent = "RulesProvider" + +private fun writeFiles(root: File) { + File(root, "build.gradle").writeText(buildFileContent) + File(root, "settings.gradle").writeText(settingsFileContent) + File(root, "src/main/kotlin").mkdirs() + File(root, "src/main/kotlin/MyClass.kt").writeText(ktFileContent) + File(root, "custom").mkdirs() + File(root, "custom/build.gradle").writeText(customBuildFileContent) + File(root, "custom/src/main/kotlin").mkdirs() + File(root, "custom/src/main/kotlin/RulesProvider.kt").writeText(customRulesProviderContent) + File(root, "custom/src/main/resources/META-INF/services").mkdirs() + File(root, "custom/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider") + .writeText(ruleSetProviderSpiContent) +}