Skip to content

Commit

Permalink
Refactor compilation analysis task setup (#7016)
Browse files Browse the repository at this point in the history
* Use new method to setup compilation tasks

* Remove unused code

* Refactor

* Correct the logic to link test baseline tasks to detektBaselineTest

* Throw exception when KotlinTargetsContainer extension isn't found

This should never happen on a KMP project unless KGP API interfaces are
changed unexpectedly.
  • Loading branch information
3flex committed Mar 5, 2024
1 parent 1912058 commit 92269e6
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 383 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ class DetektMultiplatformSpec {

@Test
fun `configures baseline task`() {
gradleRunner.runTasks(":shared:detektBaselineMainMetadata")
gradleRunner.runTasks(":shared:detektBaselineCommonMainSourceSet")
}

@Test
fun `configures detekt task without type resolution`() {
gradleRunner.runTasksAndCheckResult(":shared:detektMainMetadata") {
gradleRunner.runTasksAndCheckResult(":shared:detektCommonMainSourceSet") {
assertThat(it.output).containsPattern("""--baseline \S*[/\\]detekt-baseline.xml """)
assertThat(it.output).containsPattern("""--report xml:\S*[/\\]mainMetadata.xml""")
assertThat(it.output).containsPattern("""--report xml:\S*[/\\]commonMainSourceSet.xml""")
assertDetektWithoutClasspath(it)
}
}
Expand Down Expand Up @@ -264,20 +264,20 @@ class DetektMultiplatformSpec {

@Test
fun `configures baseline task`() {
gradleRunner.runTasks(":shared:detektBaselineMainJs")
gradleRunner.runTasks(":shared:detektBaselineTestJs")
gradleRunner.runTasks(":shared:detektBaselineJsMainSourceSet")
gradleRunner.runTasks(":shared:detektBaselineJsTestSourceSet")
}

@Test
fun `configures detekt task without type resolution`() {
gradleRunner.runTasksAndCheckResult(":shared:detektMainJs") {
gradleRunner.runTasksAndCheckResult(":shared:detektJsMainSourceSet") {
assertThat(it.output).containsPattern("""--baseline \S*[/\\]detekt-baseline.xml """)
assertThat(it.output).containsPattern("""--report xml:\S*[/\\]mainJs.xml""")
assertThat(it.output).containsPattern("""--report xml:\S*[/\\]jsMainSourceSet.xml""")
assertDetektWithoutClasspath(it)
}
gradleRunner.runTasksAndCheckResult(":shared:detektTestJs") {
gradleRunner.runTasksAndCheckResult(":shared:detektJsTestSourceSet") {
assertThat(it.output).containsPattern("""--baseline \S*[/\\]detekt-baseline.xml """)
assertThat(it.output).containsPattern("""--report xml:\S*[/\\]testJs.xml""")
assertThat(it.output).containsPattern("""--report xml:\S*[/\\]jsTestSourceSet.xml""")
assertDetektWithoutClasspath(it)
}
}
Expand Down Expand Up @@ -320,32 +320,32 @@ class DetektMultiplatformSpec {

@Test
fun `configures baseline task`() {
gradleRunner.runTasks(":shared:detektBaselineMainIosArm64")
gradleRunner.runTasks(":shared:detektBaselineTestIosArm64")
gradleRunner.runTasks(":shared:detektBaselineMainIosX64")
gradleRunner.runTasks(":shared:detektBaselineTestIosX64")
gradleRunner.runTasks(":shared:detektBaselineIosArm64MainSourceSet")
gradleRunner.runTasks(":shared:detektBaselineIosArm64TestSourceSet")
gradleRunner.runTasks(":shared:detektBaselineIosX64MainSourceSet")
gradleRunner.runTasks(":shared:detektBaselineIosX64TestSourceSet")
}

@Test
fun `configures detekt task without type resolution`() {
gradleRunner.runTasksAndCheckResult(":shared:detektMainIosArm64") {
gradleRunner.runTasksAndCheckResult(":shared:detektIosArm64MainSourceSet") {
assertThat(it.output).containsPattern("""--baseline \S*[/\\]detekt-baseline.xml """)
assertThat(it.output).containsPattern("""--report xml:\S*[/\\]mainIosArm64.xml""")
assertThat(it.output).containsPattern("""--report xml:\S*[/\\]iosArm64MainSourceSet.xml""")
assertDetektWithoutClasspath(it)
}
gradleRunner.runTasksAndCheckResult(":shared:detektTestIosArm64") {
gradleRunner.runTasksAndCheckResult(":shared:detektIosArm64TestSourceSet") {
assertThat(it.output).containsPattern("""--baseline \S*[/\\]detekt-baseline.xml """)
assertThat(it.output).containsPattern("""--report xml:\S*[/\\]testIosArm64.xml""")
assertThat(it.output).containsPattern("""--report xml:\S*[/\\]iosArm64TestSourceSet.xml""")
assertDetektWithoutClasspath(it)
}
gradleRunner.runTasksAndCheckResult(":shared:detektMainIosX64") {
gradleRunner.runTasksAndCheckResult(":shared:detektIosX64MainSourceSet") {
assertThat(it.output).containsPattern("""--baseline \S*[/\\]detekt-baseline.xml """)
assertThat(it.output).containsPattern("""--report xml:\S*[/\\]mainIosX64.xml""")
assertThat(it.output).containsPattern("""--report xml:\S*[/\\]iosX64MainSourceSet.xml""")
assertDetektWithoutClasspath(it)
}
gradleRunner.runTasksAndCheckResult(":shared:detektTestIosX64") {
gradleRunner.runTasksAndCheckResult(":shared:detektIosX64TestSourceSet") {
assertThat(it.output).containsPattern("""--baseline \S*[/\\]detekt-baseline.xml """)
assertThat(it.output).containsPattern("""--report xml:\S*[/\\]testIosX64.xml""")
assertThat(it.output).containsPattern("""--report xml:\S*[/\\]iosX64TestSourceSet.xml""")
assertDetektWithoutClasspath(it)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
@file:Suppress("DEPRECATION")

package dev.detekt.gradle.plugin.internal

import com.android.build.gradle.AppExtension
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.TestExtension
import com.android.build.gradle.api.BaseVariant
import com.android.build.gradle.internal.api.TestedVariant
import io.gitlab.arturbosch.detekt.DetektPlugin
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.DomainObjectSet
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension

internal object DetektAndroidCompilations {
fun registerTasks(project: Project, extension: DetektExtension) {
project.extensions.getByType(KotlinAndroidProjectExtension::class.java).target.compilations.all { compilation ->
project.registerJvmCompilationDetektTask(extension, compilation)
project.registerJvmCompilationCreateBaselineTask(extension, compilation)
}
}

private fun DetektExtension.matchesIgnoredConfiguration(variant: BaseVariant): Boolean =
ignoredVariants.get().contains(variant.name) ||
ignoredBuildTypes.get().contains(variant.buildType.name) ||
ignoredFlavors.get().contains(variant.flavorName)

fun linkTasks(project: Project, extension: DetektExtension) {
val mainTaskProvider =
project.tasks.register("${DetektPlugin.DETEKT_TASK_NAME}Main") {
it.group = "verification"
it.description = "EXPERIMENTAL: Run detekt analysis for production classes across " +
"all variants with type resolution"
}

val testTaskProvider =
project.tasks.register("${DetektPlugin.DETEKT_TASK_NAME}Test") {
it.group = "verification"
it.description = "EXPERIMENTAL: Run detekt analysis for test classes across " +
"all variants with type resolution"
}

val mainBaselineTaskProvider =
project.tasks.register("${DetektPlugin.BASELINE_TASK_NAME}Main") {
it.group = "verification"
it.description = "EXPERIMENTAL: Creates detekt baseline files for production classes across " +
"all variants with type resolution"
}

val testBaselineTaskProvider =
project.tasks.register("${DetektPlugin.BASELINE_TASK_NAME}Test") {
it.group = "verification"
it.description = "EXPERIMENTAL: Creates detekt baseline files for test classes across " +
"all variants with type resolution"
}

fun variants(extension: BaseExtension): DomainObjectSet<out BaseVariant>? = when (extension) {
is AppExtension -> extension.applicationVariants
is LibraryExtension -> extension.libraryVariants
is TestExtension -> extension.applicationVariants
else -> null
}

fun testVariants(baseVariant: BaseVariant): List<BaseVariant> = if (baseVariant is TestedVariant) {
listOfNotNull(baseVariant.testVariant, baseVariant.unitTestVariant)
} else {
emptyList()
}

// There is not a single Android plugin, but each registers an extension based on BaseExtension,
// so we catch them all by looking for this one
project.extensions.findByType(BaseExtension::class.java)?.let { baseExtension ->
variants(baseExtension)
?.matching { !extension.matchesIgnoredConfiguration(it) }
?.all { variant ->
mainTaskProvider.configure {
it.dependsOn(DetektPlugin.DETEKT_TASK_NAME + variant.name.capitalize())
}
mainBaselineTaskProvider.configure {
it.dependsOn(DetektPlugin.BASELINE_TASK_NAME + variant.name.capitalize())
}
testVariants(variant)
.filter { !extension.matchesIgnoredConfiguration(it) }
.forEach { testVariant ->
testTaskProvider.configure {
it.dependsOn(DetektPlugin.DETEKT_TASK_NAME + testVariant.name.capitalize())
}
testBaselineTaskProvider.configure {
it.dependsOn(DetektPlugin.BASELINE_TASK_NAME + testVariant.name.capitalize())
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dev.detekt.gradle.plugin.internal

import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension

internal object DetektJvmCompilations {
fun registerTasks(project: Project, extension: DetektExtension) {
project.extensions.getByType(KotlinJvmProjectExtension::class.java).target.compilations.all { compilation ->
project.registerJvmCompilationDetektTask(extension, compilation)
project.registerJvmCompilationCreateBaselineTask(extension, compilation)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dev.detekt.gradle.plugin.internal

import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.androidJvm
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.jvm
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetsContainer

internal object DetektKmpJvmCompilations {
fun registerTasks(project: Project, extension: DetektExtension) {
val kotlinExtension = project.extensions.getByType(KotlinTargetsContainer::class.java)

kotlinExtension.targets.matching { it.platformType in setOf(jvm, androidJvm) }.all { target ->
target.compilations.all { compilation ->
project.registerJvmCompilationDetektTask(extension, compilation, target)
project.registerJvmCompilationCreateBaselineTask(extension, compilation, target)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package dev.detekt.gradle.plugin.internal

import io.gitlab.arturbosch.detekt.DetektPlugin
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
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 org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile

internal fun Project.registerJvmCompilationDetektTask(
extension: DetektExtension,
compilation: KotlinCompilation<KotlinCommonOptions>,
target: KotlinTarget? = null,
) {
val taskSuffix = if (target != null) compilation.name + target.name.capitalize() else compilation.name
registerDetektTask(DetektPlugin.DETEKT_TASK_NAME + taskSuffix.capitalize(), extension) {
val siblingTask = compilation.compileTaskProvider.get() as KotlinJvmCompile

setSource(siblingTask.sources)
classpath.setFrom(compilation.output.classesDirs, siblingTask.libraries)

// 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(compilation.name)?.let { baselineFile ->
baseline.convention(layout.file(provider { baselineFile }))
}
description = if (target != null) {
"EXPERIMENTAL: Run detekt analysis for compilation ${compilation.name} on target " +
"${compilation.target.name} with type resolution"
} else {
"EXPERIMENTAL: Run detekt analysis for ${compilation.name} classes with type resolution"
}
}
}

internal fun Project.registerJvmCompilationCreateBaselineTask(
extension: DetektExtension,
compilation: KotlinCompilation<KotlinCommonOptions>,
target: KotlinTarget? = null,
) {
val taskSuffix = if (target != null) compilation.name + target.name.capitalize() else compilation.name
registerCreateBaselineTask(DetektPlugin.BASELINE_TASK_NAME + taskSuffix.capitalize(), extension) {
val siblingTask = compilation.compileTaskProvider.get() as KotlinJvmCompile

setSource(siblingTask.sources)
classpath.setFrom(compilation.output.classesDirs, siblingTask.libraries)

val variantBaselineFile = extension.baseline.asFile.orNull?.addVariantName(compilation.name)
baseline.convention(layout.file(provider { variantBaselineFile }))
description = if (target != null) {
"EXPERIMENTAL: Creates detekt baseline for compilation ${compilation.name} on target " +
"${compilation.target.name} with type resolution"
} else {
"EXPERIMENTAL: Creates detekt baseline for ${compilation.name} classes with type resolution"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import dev.detekt.gradle.plugin.CONFIGURATION_DETEKT_PLUGINS
import dev.detekt.gradle.plugin.DetektBasePlugin
import dev.detekt.gradle.plugin.DetektBasePlugin.Companion.CONFIG_DIR_NAME
import dev.detekt.gradle.plugin.DetektBasePlugin.Companion.CONFIG_FILE
import dev.detekt.gradle.plugin.internal.DetektAndroidCompilations
import dev.detekt.gradle.plugin.internal.DetektJvmCompilations
import dev.detekt.gradle.plugin.internal.DetektKmpJvmCompilations
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import io.gitlab.arturbosch.detekt.internal.DetektAndroid
import io.gitlab.arturbosch.detekt.internal.DetektJvm
import io.gitlab.arturbosch.detekt.internal.DetektMultiplatform
import io.gitlab.arturbosch.detekt.internal.DetektPlain
import org.gradle.api.Incubating
import org.gradle.api.Plugin
Expand Down Expand Up @@ -38,19 +38,20 @@ class DetektPlugin : Plugin<Project> {

private fun Project.registerDetektJvmTasks(extension: DetektExtension) {
plugins.withId("org.jetbrains.kotlin.jvm") {
DetektJvm(this).registerTasks(extension)
DetektJvmCompilations.registerTasks(project, extension)
}
}

private fun Project.registerDetektMultiplatformTasks(extension: DetektExtension) {
plugins.withId("org.jetbrains.kotlin.multiplatform") {
DetektMultiplatform(this).registerTasks(extension)
DetektKmpJvmCompilations.registerTasks(project, extension)
}
}

private fun Project.registerDetektAndroidTasks(extension: DetektExtension) {
plugins.withId("kotlin-android") {
DetektAndroid(this).registerTasks(extension)
DetektAndroidCompilations.registerTasks(project, extension)
DetektAndroidCompilations.linkTasks(project, extension)
}
}

Expand Down

0 comments on commit 92269e6

Please sign in to comment.