From d74149f5bf1900fc86776042eb51b92de4c078dc Mon Sep 17 00:00:00 2001 From: Stieglitz Date: Tue, 26 Apr 2022 01:58:19 +0200 Subject: [PATCH] Android Gradle: add javac intermediates to classpath (#4723) * add javac intermediates to classpath * use javac intermediates directly * use java compile path * fix registerAndroidDetektTask classpath * revert unchanged code * remove unused import * implement review notes --- .../arturbosch/detekt/DetektAndroidSpec.kt | 97 ++++++++++++++++++- .../detekt/internal/DetektAndroid.kt | 31 +++++- 2 files changed, 121 insertions(+), 7 deletions(-) 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 d46637ff373..c5fc0fab5ad 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 @@ -420,6 +420,53 @@ class DetektAndroidSpec { ) } } + + @Nested + inner class `configures android tasks android tasks have javac intermediates on classpath` { + val projectLayout = ProjectLayout( + numberOfSourceFilesInRootPerSourceDir = 0, + ).apply { + addSubmodule( + name = "app", + numberOfSourceFilesPerSourceDir = 0, + numberOfCodeSmells = 0, + buildFileContent = """ + $APP_PLUGIN_BLOCK + $ANDROID_BLOCK_WITH_VIEW_BINDING + """.trimIndent(), + srcDirs = listOf("src/main/java"), + ) + } + val gradleRunner = createGradleRunnerAndSetupProject(projectLayout, dryRun = false).also { + it.projectFile("app/src/main/java").mkdirs() + it.projectFile("app/src/main/res/layout").mkdirs() + it.writeProjectFile("app/src/main/AndroidManifest.xml", manifestContent()) + it.writeProjectFile( + "app/src/main/res/layout/activity_sample.xml", + SAMPLE_ACTIVITY_LAYOUT + ) + it.writeProjectFile( + "app/src/main/java/SampleActivity.kt", + SAMPLE_ACTIVITY_USING_VIEW_BINDING + ) + } + + @Test + @DisplayName("task :app:detektMain has javac intermediates on the classpath") + fun libDetektMain() { + gradleRunner.runTasksAndCheckResult(":app:detektMain") { buildResult -> + assertThat(buildResult.output).doesNotContain("UnreachableCode") + } + } + + @Test + @DisplayName("task :app:detektTest has javac intermediates on the classpath") + fun libDetektTest() { + gradleRunner.runTasksAndCheckResult(":app:detektTest") { buildResult -> + assertThat(buildResult.output).doesNotContain("UnreachableCode") + } + } + } } } @@ -475,6 +522,19 @@ private val ANDROID_BLOCK_WITH_FLAVOR = """ } """.trimIndent() +private val ANDROID_BLOCK_WITH_VIEW_BINDING = """ + android { + compileSdkVersion(30) + defaultConfig { + applicationId = "io.gitlab.arturbosch.detekt.app" + minSdkVersion(24) + } + buildFeatures { + viewBinding = true + } + } +""".trimIndent() + private val DETEKT_REPORTS_BLOCK = """ tasks.withType().configureEach { reports { @@ -483,7 +543,40 @@ private val DETEKT_REPORTS_BLOCK = """ } """.trimIndent() -private fun createGradleRunnerAndSetupProject(projectLayout: ProjectLayout) = DslGradleRunner( +private val SAMPLE_ACTIVITY_LAYOUT = """ + + +""".trimIndent() + +private val SAMPLE_ACTIVITY_USING_VIEW_BINDING = """ + package io.gitlab.arturbosch.detekt.app + + import android.app.Activity + import android.os.Bundle + import android.view.LayoutInflater + import io.gitlab.arturbosch.detekt.app.databinding.ActivitySampleBinding + + class SampleActivity : Activity() { + + private lateinit var binding: ActivitySampleBinding + + override fun onCreate(savedInstanceState: Bundle?) { + binding = ActivitySampleBinding.inflate(LayoutInflater.from(this)) + binding.sampleView ?: return + setContentView(binding.root) + } + } +""".trimIndent() + "\n" // new line at end of file rule + +private fun createGradleRunnerAndSetupProject( + projectLayout: ProjectLayout, + dryRun: Boolean = true, +) = DslGradleRunner( projectLayout = projectLayout, buildFileName = "build.gradle.kts", mainBuildFileContent = """ @@ -495,5 +588,5 @@ private fun createGradleRunnerAndSetupProject(projectLayout: ProjectLayout) = Ds } } """.trimIndent(), - dryRun = true + dryRun = dryRun, ).also { it.setupProject() } diff --git a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/internal/DetektAndroid.kt b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/internal/DetektAndroid.kt index be44a7a28cf..0cdfe0b2d37 100644 --- a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/internal/DetektAndroid.kt +++ b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/internal/DetektAndroid.kt @@ -14,6 +14,7 @@ import io.gitlab.arturbosch.detekt.extensions.DetektExtension import org.gradle.api.DomainObjectSet import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileCollection import org.gradle.api.tasks.TaskProvider @@ -71,9 +72,10 @@ internal class DetektAndroid(private val project: Project) { baseExtension.variants ?.matching { !extension.matchesIgnoredConfiguration(it) } ?.all { variant -> - project.registerAndroidDetektTask(bootClasspath, extension, variant).also { provider -> - mainTaskProvider.dependsOn(provider) - } + project.registerAndroidDetektTask(bootClasspath, extension, variant) + .also { provider -> + mainTaskProvider.dependsOn(provider) + } project.registerAndroidCreateBaselineTask(bootClasspath, extension, variant) .also { provider -> mainBaselineTaskProvider.dependsOn(provider) @@ -114,7 +116,11 @@ internal fun Project.registerAndroidDetektTask( registerDetektTask(taskName, extension) { setSource(variant.sourceSets.map { it.javaDirectories + it.kotlinDirectories }) extraInputSource?.let { source(it) } - classpath.setFrom(variant.getCompileClasspath(null).filter { it.exists() } + bootClasspath) + classpath.setFrom( + variant.getCompileClasspath(null).filter { it.exists() }, + bootClasspath, + javaCompileDestination(variant), + ) // 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?.existingVariantOrBaseFile(variant.name)?.let { baselineFile -> @@ -134,8 +140,23 @@ internal fun Project.registerAndroidCreateBaselineTask( registerCreateBaselineTask(taskName, extension) { setSource(variant.sourceSets.map { it.javaDirectories + it.kotlinDirectories }) extraInputSource?.let { source(it) } - classpath.setFrom(variant.getCompileClasspath(null).filter { it.exists() } + bootClasspath) + classpath.setFrom( + variant.getCompileClasspath(null).filter { it.exists() }, + bootClasspath, + javaCompileDestination(variant), + ) val variantBaselineFile = extension.baseline?.addVariantName(variant.name) baseline.set(project.layout.file(project.provider { variantBaselineFile })) description = "EXPERIMENTAL: Creates detekt baseline for ${variant.name} classes with type resolution" } + +private fun Project.javaCompileDestination(variant: BaseVariant): DirectoryProperty? { + val javaCompile = variant.javaCompileProvider.orNull + if (javaCompile == null) { + logger.warn( + "Unable to find Java compiler on variant '{}'. Detekt analysis can show false negatives.", + variant.name, + ) + } + return javaCompile?.destinationDirectory +}