diff --git a/detekt-gradle-plugin/src/functionalTest/kotlin/dev/detekt/gradle/plugin/DetektBasePluginSpec.kt b/detekt-gradle-plugin/src/functionalTest/kotlin/dev/detekt/gradle/plugin/DetektBasePluginSpec.kt new file mode 100644 index 00000000000..629723823ca --- /dev/null +++ b/detekt-gradle-plugin/src/functionalTest/kotlin/dev/detekt/gradle/plugin/DetektBasePluginSpec.kt @@ -0,0 +1,190 @@ +package dev.detekt.gradle.plugin + +import io.gitlab.arturbosch.detekt.testkit.DslGradleRunner +import io.gitlab.arturbosch.detekt.testkit.ProjectLayout +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.condition.EnabledForJreRange +import org.junit.jupiter.api.condition.JRE +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +class DetektBasePluginSpec { + @Test + fun `generates source set tasks for JVM project`() { + val gradleRunner = DslGradleRunner( + projectLayout = ProjectLayout( + numberOfSourceFilesInRootPerSourceDir = 1, + srcDirs = listOf( + "src/main/kotlin", + "src/test/kotlin", + ), + ), + buildFileName = "build.gradle.kts", + mainBuildFileContent = """ + plugins { + id("io.gitlab.arturbosch.detekt") + kotlin("jvm") + } + + repositories { + mavenLocal() + mavenCentral() + } + """.trimIndent(), + dryRun = true, + ).also { + it.setupProject() + } + + gradleRunner.checkTask("main") + gradleRunner.checkTask("test") + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_17, disabledReason = "Android Gradle Plugin 8.0+ requires JDK 17 or newer") + fun `generates source set tasks for Android project`() { + val gradleRunner = DslGradleRunner( + projectLayout = ProjectLayout( + numberOfSourceFilesInRootPerSourceDir = 1, + srcDirs = listOf( + "src/main/kotlin", + "src/debug/kotlin", + "src/test/kotlin", + "src/androidTest/kotlin", + ), + ), + buildFileName = "build.gradle.kts", + mainBuildFileContent = """ + plugins { + id("io.gitlab.arturbosch.detekt") + id("com.android.library") + kotlin("android") + } + + repositories { + mavenLocal() + mavenCentral() + google() + } + + android { + compileSdk = 30 + namespace = "dev.detekt.gradle.plugin.app" + } + """.trimIndent(), + dryRun = true, + ).also { + it.setupProject() + } + + gradleRunner.checkTask("main") + gradleRunner.checkTask("debug") + gradleRunner.checkTask("test") + gradleRunner.checkTask("androidTest") + } + + @Nested + @EnabledForJreRange(min = JRE.JAVA_17, disabledReason = "Android Gradle Plugin 8.0+ requires JDK 17 or newer") + inner class `generates source set tasks for KMP project` { + val gradleRunner = DslGradleRunner( + projectLayout = ProjectLayout( + numberOfSourceFilesInRootPerSourceDir = 1, + srcDirs = listOf( + "src/commonMain/kotlin", + "src/commonTest/kotlin", + "src/androidMain/kotlin", + "src/androidUnitTest/kotlin", + "src/androidInstrumentedTest/kotlin", + "src/jvmMain/kotlin", + "src/jvmTest/kotlin", + "src/jsMain/kotlin", + "src/jsTest/kotlin", + "src/iosMain/kotlin", + "src/iosTest/kotlin", + "src/appleMain/kotlin", + "src/appleTest/kotlin", + "src/nativeMain/kotlin", + "src/nativeTest/kotlin", + ), + ), + buildFileName = "build.gradle.kts", + mainBuildFileContent = """ + plugins { + id("io.gitlab.arturbosch.detekt") + kotlin("multiplatform") + id("com.android.library") + } + + repositories { + mavenLocal() + mavenCentral() + google() + } + + kotlin { + androidTarget() + iosArm64() + iosSimulatorArm64() + jvm() + js { + browser() + nodejs() + } + } + + android { + compileSdk = 30 + namespace = "dev.detekt.gradle.plugin.app" + } + """.trimIndent(), + dryRun = true, + ).also { + it.setupProject() + } + + @ParameterizedTest + @ValueSource(strings = ["commonMain", "commonTest"]) + fun `generates source set tasks for common code`(sourceSetTaskName: String) { + gradleRunner.checkTask(sourceSetTaskName) + } + + @ParameterizedTest + @ValueSource(strings = ["androidMain", "androidUnitTest", "androidInstrumentedTest"]) + fun `generates source set tasks for Android`(sourceSetTaskName: String) { + gradleRunner.checkTask(sourceSetTaskName) + } + + @ParameterizedTest + @ValueSource(strings = ["jvmMain", "jvmTest"]) + fun `generates source set tasks for JVM`(sourceSetTaskName: String) { + gradleRunner.checkTask(sourceSetTaskName) + } + + @ParameterizedTest + @ValueSource(strings = ["jsMain", "jsTest"]) + fun `generates source set tasks for JS`(sourceSetTaskName: String) { + gradleRunner.checkTask(sourceSetTaskName) + } + + @ParameterizedTest + @ValueSource(strings = ["iosMain", "iosTest", "appleMain", "appleTest", "nativeMain", "nativeTest"]) + fun `generates source set tasks for iOS native`(sourceSetTaskName: String) { + gradleRunner.checkTask(sourceSetTaskName) + } + } + + private fun DslGradleRunner.checkTask(sourceSetTaskName: String) { + runTasksAndCheckResult(":detekt${sourceSetTaskName}SourceSet") { buildResult -> + assertThat(buildResult.output) + .containsPattern("""--input \S*[/\\]src[/\\]$sourceSetTaskName[/\\]kotlin""") + val xmlReportFile = projectFile("build/reports/detekt/$sourceSetTaskName.xml") + val sarifReportFile = projectFile("build/reports/detekt/$sourceSetTaskName.sarif") + val txtReportFile = projectFile("build/reports/detekt/$sourceSetTaskName.txt") + assertThat(buildResult.output).contains("--report xml:$xmlReportFile") + assertThat(buildResult.output).contains("--report sarif:$sarifReportFile") + assertThat(buildResult.output).contains("--report txt:$txtReportFile") + } + } +} diff --git a/detekt-gradle-plugin/src/functionalTest/kotlin/io/gitlab/arturbosch/detekt/DetektAndroidSpec.kt b/detekt-gradle-plugin/src/functionalTest/kotlin/io/gitlab/arturbosch/detekt/DetektAndroidSpec.kt index ec2e66ae9c5..50c1c9422f8 100644 --- a/detekt-gradle-plugin/src/functionalTest/kotlin/io/gitlab/arturbosch/detekt/DetektAndroidSpec.kt +++ b/detekt-gradle-plugin/src/functionalTest/kotlin/io/gitlab/arturbosch/detekt/DetektAndroidSpec.kt @@ -140,7 +140,8 @@ class DetektAndroidSpec { @DisplayName("task :app:detektMain") fun appDetektMain() { gradleRunner.runTasksAndExpectFailure(":app:detektMain") { result -> - assertThat(result.output).containsIgnoringCase("Task 'detektMain' not found in project") + assertThat(result.output) + .contains("Cannot locate tasks that match ':app:detektMain' as task 'detektMain' is ambiguous") } } @@ -148,7 +149,8 @@ class DetektAndroidSpec { @DisplayName("task :app:detektTest") fun appDetektTest() { gradleRunner.runTasksAndExpectFailure(":app:detektTest") { result -> - assertThat(result.output).containsIgnoringCase("Task 'detektTest' not found in project") + assertThat(result.output) + .contains("Cannot locate tasks that match ':app:detektTest' as task 'detektTest' is ambiguous") } } } diff --git a/detekt-gradle-plugin/src/main/kotlin/dev/detekt/gradle/plugin/DetektBasePlugin.kt b/detekt-gradle-plugin/src/main/kotlin/dev/detekt/gradle/plugin/DetektBasePlugin.kt index f8df0e724c9..79e7d53d946 100644 --- a/detekt-gradle-plugin/src/main/kotlin/dev/detekt/gradle/plugin/DetektBasePlugin.kt +++ b/detekt-gradle-plugin/src/main/kotlin/dev/detekt/gradle/plugin/DetektBasePlugin.kt @@ -1,12 +1,21 @@ package dev.detekt.gradle.plugin +import io.gitlab.arturbosch.detekt.DetektPlugin import io.gitlab.arturbosch.detekt.extensions.DetektExtension import io.gitlab.arturbosch.detekt.extensions.FailOnSeverity import io.gitlab.arturbosch.detekt.extensions.loadDetektVersion +import io.gitlab.arturbosch.detekt.internal.addVariantName +import io.gitlab.arturbosch.detekt.internal.existingVariantOrBaseFile +import io.gitlab.arturbosch.detekt.internal.registerCreateBaselineTask +import io.gitlab.arturbosch.detekt.internal.registerDetektTask +import io.gitlab.arturbosch.detekt.internal.setReportOutputConventions import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.ReportingBasePlugin import org.gradle.api.reporting.ReportingExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetContainer class DetektBasePlugin : Plugin { override fun apply(project: Project) { @@ -51,6 +60,40 @@ class DetektBasePlugin : Plugin { configuration.isCanBeResolved = true configuration.isCanBeConsumed = false } + + project.registerSourceSetTasks(extension) + } + + private fun Project.registerSourceSetTasks(extension: DetektExtension) { + project.plugins.withType(KotlinBasePlugin::class.java) { + project.extensions.getByType(KotlinSourceSetContainer::class.java) + .sourceSets + .withType(KotlinSourceSet::class.java) { sourceSet -> + val taskName = "${DetektPlugin.DETEKT_TASK_NAME}${sourceSet.name.capitalize()}SourceSet" + project.registerDetektTask(taskName, extension) { + source = sourceSet.kotlin + // If a baseline file is configured as input file, it must exist to be configured, otherwise the task fails. + // We try to find the configured baseline or alternatively a specific variant matching this task. + extension.baseline.asFile.orNull?.existingVariantOrBaseFile("${sourceSet.name}SourceSet") + ?.let { file -> + baseline.convention(project.layout.file(project.provider { file })) + } + setReportOutputConventions(reports, extension, sourceSet.name) + description = "Run detekt analysis for ${sourceSet.name} source set" + } + + val baseLineTaskName = "${DetektPlugin.BASELINE_TASK_NAME}${sourceSet.name.capitalize()}SourceSet" + project.registerCreateBaselineTask(baseLineTaskName, extension) { + source = sourceSet.kotlin + + val variantBaselineFile = + extension.baseline.asFile.orNull?.addVariantName("${sourceSet.name}SourceSet") + baseline.convention(project.layout.file(project.provider { variantBaselineFile })) + + description = "Creates detekt baseline for ${sourceSet.name} source set" + } + } + } } internal companion object {