From 65ccafa5338288e15010a7832306a49cabaa6cb9 Mon Sep 17 00:00:00 2001 From: wakingrufus Date: Wed, 20 Dec 2023 07:13:20 -0600 Subject: [PATCH] fix detection of android kotlin source directories in AGP >= 7 increase minimum supported AGP version to 4.2.2 because the plugin coordinates/id changed between 4.1 and 4.2 increase maximum gradle version to 8.5 increase maximum ktlint version to 1.1.0 fixes #702 based on work in #703 --- plugin/VERSION_LATEST_RELEASE.txt | 2 +- plugin/build.gradle.kts | 5 ++ plugin/gradle/libs.versions.toml | 2 +- .../jlleitschuh/gradle/ktlint/KtlintPlugin.kt | 2 +- .../ktlint/android/AndroidPluginsApplier.kt | 44 ++++++------- .../kotlin/org/assertj/core/api/Assertions.kt | 10 +++ .../ktlint/KtLintSupportedVersionsTest.kt | 3 +- .../ktlint/android/KtlintPluginAndroidTest.kt | 62 +++++++++++++++++++ .../gradle/ktlint/testdsl/AndroidSetup.kt | 48 ++++++++++++++ .../gradle/ktlint/testdsl/TestAnnotations.kt | 4 +- .../gradle/ktlint/testdsl/TestDsl.kt | 4 +- 11 files changed, 154 insertions(+), 32 deletions(-) create mode 100644 plugin/src/test/kotlin/org/assertj/core/api/Assertions.kt create mode 100644 plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/android/KtlintPluginAndroidTest.kt create mode 100644 plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/AndroidSetup.kt diff --git a/plugin/VERSION_LATEST_RELEASE.txt b/plugin/VERSION_LATEST_RELEASE.txt index 1a31377d..470ce40f 100644 --- a/plugin/VERSION_LATEST_RELEASE.txt +++ b/plugin/VERSION_LATEST_RELEASE.txt @@ -1 +1 @@ -11.6.1 +12.0.3 diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index eab4e7f6..d998fe7f 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -195,6 +195,11 @@ tasks.withType { maxFailures.set(10) } } + + // AGP requires java 11, so set it to 11 for tests + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(11) + } } val relocateShadowJar = tasks.register("relocateShadowJar") diff --git a/plugin/gradle/libs.versions.toml b/plugin/gradle/libs.versions.toml index 878e46f8..02ef99eb 100644 --- a/plugin/gradle/libs.versions.toml +++ b/plugin/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "1.5.31" ktlint = "0.47.1" # last version prior to API changes. we can increase this more once we drop support for the old API. -androidPlugin = "4.1.0" +androidPlugin = "4.2.2" semver = "1.1.1" jgit = "5.6.0.201912101111-r" sl4fj = "1.7.30" diff --git a/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/KtlintPlugin.kt b/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/KtlintPlugin.kt index b5151ec9..8699412c 100644 --- a/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/KtlintPlugin.kt +++ b/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/KtlintPlugin.kt @@ -29,7 +29,7 @@ open class KtlintPlugin : Plugin { private fun PluginHolder.addKtLintTasksToKotlinPlugin() { target.plugins.withId("kotlin", applyKtLint()) target.plugins.withId("org.jetbrains.kotlin.js", applyKtLint()) - target.plugins.withId("kotlin-android", applyKtLintToAndroid()) + target.plugins.withId("org.jetbrains.kotlin.android", applyKtLintToAndroid()) target.plugins.withId( "org.jetbrains.kotlin.multiplatform", applyKtlintMultiplatform() diff --git a/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/android/AndroidPluginsApplier.kt b/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/android/AndroidPluginsApplier.kt index c8c2d7ec..6130cb31 100644 --- a/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/android/AndroidPluginsApplier.kt +++ b/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/android/AndroidPluginsApplier.kt @@ -1,14 +1,8 @@ package org.jlleitschuh.gradle.ktlint.android +import com.android.build.api.dsl.AndroidSourceDirectorySet import com.android.build.api.dsl.AndroidSourceSet -import com.android.build.api.dsl.BuildFeatures -import com.android.build.api.dsl.BuildType import com.android.build.api.dsl.CommonExtension -import com.android.build.api.dsl.DefaultConfig -import com.android.build.api.dsl.ProductFlavor -import com.android.build.api.dsl.SigningConfig -import com.android.build.api.variant.Variant -import com.android.build.api.variant.VariantProperties import com.android.build.gradle.internal.api.DefaultAndroidSourceDirectorySet import org.gradle.api.Plugin import org.gradle.api.file.FileCollection @@ -21,6 +15,7 @@ import org.jlleitschuh.gradle.ktlint.createGenerateReportsTask import org.jlleitschuh.gradle.ktlint.setCheckTaskDependsOnGenerateReportsTask import org.jlleitschuh.gradle.ktlint.tasks.GenerateReportsTask import java.util.concurrent.Callable +import kotlin.reflect.full.memberProperties internal fun KtlintPlugin.PluginHolder.applyKtLintToAndroid(): (Plugin) -> Unit { return { @@ -43,33 +38,32 @@ internal fun KtlintPlugin.PluginHolder.applyKtLintToAndroid(): (Plugin) } } -@Suppress("UnstableApiUsage") -private typealias AndroidCommonExtension = CommonExtension< - AndroidSourceSet, - BuildFeatures, - BuildType, - DefaultConfig, - ProductFlavor, - SigningConfig, - Variant, - VariantProperties - > - /* * Variant manager returns all sources for variant, * so most probably main source set maybe checked several times. * This approach creates one check tasks per one source set. */ -@Suppress("UNCHECKED_CAST", "UnstableApiUsage") +@Suppress("UnstableApiUsage") private fun androidPluginConfigureAction( pluginHolder: KtlintPlugin.PluginHolder ): (Plugin) -> Unit = { pluginHolder.target.extensions.configure(CommonExtension::class.java) { - val androidCommonExtension = this as AndroidCommonExtension - - androidCommonExtension.sourceSets.all { - // https://issuetracker.google.com/u/1/issues/170650362 - val androidSourceSet = java as DefaultAndroidSourceDirectorySet + // kotlin property exists in AGP >= 7 + val kotlinProperty = AndroidSourceSet::class.memberProperties.firstOrNull { it.name == "kotlin" } + if (kotlinProperty == null) { + pluginHolder.target.logger.warn( + buildString { + append("In AGP <7 kotlin source directories are not auto-detected.") + append("In order to lint kotlin sources, manually add the directory to the source set. ") + append("""For example: sourceSets.getByName("main").java.srcDirs("src/main/kotlin/")""") + } + ) + } + val sourceMember: AndroidSourceSet.() -> AndroidSourceDirectorySet = { + kotlinProperty?.get(this) as AndroidSourceDirectorySet? ?: this.java + } + sourceSets.all { + val androidSourceSet = sourceMember(this) as DefaultAndroidSourceDirectorySet // Passing Callable, so returned FileCollection, will lazy evaluate it // only when task will need it. // Solves the problem of having additional source dirs in diff --git a/plugin/src/test/kotlin/org/assertj/core/api/Assertions.kt b/plugin/src/test/kotlin/org/assertj/core/api/Assertions.kt new file mode 100644 index 00000000..72b9eb80 --- /dev/null +++ b/plugin/src/test/kotlin/org/assertj/core/api/Assertions.kt @@ -0,0 +1,10 @@ +package org.assertj.core.api + +import org.assertj.core.internal.Objects +import org.gradle.testkit.runner.BuildTask +import org.gradle.testkit.runner.TaskOutcome + +fun ObjectAssert.hasOutcome(outcome: TaskOutcome) { + Objects.instance().assertNotNull(this.info, this.actual) + Objects.instance().assertEqual(this.info, this.actual!!.outcome, outcome) +} diff --git a/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/KtLintSupportedVersionsTest.kt b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/KtLintSupportedVersionsTest.kt index 9ecd938e..af283e69 100644 --- a/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/KtLintSupportedVersionsTest.kt +++ b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/KtLintSupportedVersionsTest.kt @@ -181,7 +181,8 @@ class KtLintSupportedVersionsTest : AbstractPluginTest() { "0.49.1", "0.50.0", "1.0.0", - "1.0.1" + "1.0.1", + "1.1.0" ) override fun provideArguments( diff --git a/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/android/KtlintPluginAndroidTest.kt b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/android/KtlintPluginAndroidTest.kt new file mode 100644 index 00000000..1eb49efa --- /dev/null +++ b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/android/KtlintPluginAndroidTest.kt @@ -0,0 +1,62 @@ +package org.jlleitschuh.gradle.ktlint.android + +import net.swiftzer.semver.SemVer +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.hasOutcome +import org.gradle.testkit.runner.TaskOutcome +import org.gradle.util.GradleVersion +import org.jlleitschuh.gradle.ktlint.AbstractPluginTest +import org.jlleitschuh.gradle.ktlint.CHECK_PARENT_TASK_NAME +import org.jlleitschuh.gradle.ktlint.testdsl.TestVersions +import org.jlleitschuh.gradle.ktlint.testdsl.androidProjectSetup +import org.jlleitschuh.gradle.ktlint.testdsl.build +import org.jlleitschuh.gradle.ktlint.testdsl.project +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +class KtlintPluginAndroidTest : AbstractPluginTest() { + + @ParameterizedTest + @EnumSource(AndroidTestInput::class) + fun `ktlint pass src java`(input: AndroidTestInput) { + project( + input.gradleVersion, + projectSetup = androidProjectSetup(input.agpVersion) + ) { + withCleanSources("src/main/java/CleanSource.kt") + build(CHECK_PARENT_TASK_NAME) { + assertThat(task(":$mainSourceSetCheckTaskName")).hasOutcome(TaskOutcome.SUCCESS) + assertThat(task(":$CHECK_PARENT_TASK_NAME")).hasOutcome(TaskOutcome.SUCCESS) + } + } + } + + @ParameterizedTest + @EnumSource(AndroidTestInput::class) + fun `ktlint pass src kotlin`(input: AndroidTestInput) { + project( + input.gradleVersion, + projectSetup = androidProjectSetup(input.agpVersion) + ) { + withCleanSources("src/main/kotlin/CleanSource.kt") + if (SemVer.parse(input.agpVersion) < SemVer(7)) { + build(CHECK_PARENT_TASK_NAME) { + assertThat(output).contains("In AGP <7 kotlin source directories are not auto-detected.") + assertThat(task(":$mainSourceSetCheckTaskName")).hasOutcome(TaskOutcome.SKIPPED) + assertThat(task(":$CHECK_PARENT_TASK_NAME")).hasOutcome(TaskOutcome.UP_TO_DATE) + } + } else { + build(CHECK_PARENT_TASK_NAME) { + assertThat(task(":$mainSourceSetCheckTaskName")).hasOutcome(TaskOutcome.SUCCESS) + assertThat(task(":$CHECK_PARENT_TASK_NAME")).hasOutcome(TaskOutcome.SUCCESS) + } + } + } + } + + enum class AndroidTestInput(val gradleVersion: GradleVersion, val agpVersion: String) { + MIN(GradleVersion.version(TestVersions.minSupportedGradleVersion), TestVersions.minAgpVersion), + MAX_GRADLE_MIN_AGP(GradleVersion.version(TestVersions.minSupportedGradleVersion), TestVersions.minAgpVersion), + MAX(GradleVersion.version(TestVersions.maxSupportedGradleVersion), TestVersions.maxAgpVersion) + } +} diff --git a/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/AndroidSetup.kt b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/AndroidSetup.kt new file mode 100644 index 00000000..10de4fca --- /dev/null +++ b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/AndroidSetup.kt @@ -0,0 +1,48 @@ +package org.jlleitschuh.gradle.ktlint.testdsl + +import java.io.File + +fun androidProjectSetup( + agpVersion: String +): (File) -> Unit = { + val kotlinPluginVersion = TestVersions.maxSupportedKotlinPluginVersion + //language=Groovy + it.resolve("build.gradle").writeText( + """ + |plugins { + | id("org.jetbrains.kotlin.android") + | id("org.jlleitschuh.gradle.ktlint") + | id("com.android.application") + |} + | + |repositories { + | mavenCentral() + |} + |android { + | compileSdk = 33 + |} + | + """.trimMargin() + ) + + //language=Groovy + it.resolve("settings.gradle").writeText( + """ + |pluginManagement { + | repositories { + | mavenLocal() + | gradlePluginPortal() + | google() + | mavenCentral() + | } + | + | plugins { + | id 'org.jetbrains.kotlin.android' version '$kotlinPluginVersion' + | id 'org.jlleitschuh.gradle.ktlint' version '${TestVersions.pluginVersion}' + | id("com.android.application") version "$agpVersion" + | } + |} + | + """.trimMargin() + ) +} diff --git a/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/TestAnnotations.kt b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/TestAnnotations.kt index e3416888..ae9b05cc 100644 --- a/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/TestAnnotations.kt +++ b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/TestAnnotations.kt @@ -12,10 +12,12 @@ import kotlin.streams.asStream object TestVersions { const val minSupportedGradleVersion = KtlintBasePlugin.LOWEST_SUPPORTED_GRADLE_VERSION - const val maxSupportedGradleVersion = "8.4" + const val maxSupportedGradleVersion = "8.5" val pluginVersion = File("VERSION_CURRENT.txt").readText().trim() const val minSupportedKotlinPluginVersion = "1.4.32" const val maxSupportedKotlinPluginVersion = "1.9.10" + const val minAgpVersion = "4.2.2" + const val maxAgpVersion = "7.4.2" } @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) diff --git a/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/TestDsl.kt b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/TestDsl.kt index 2594d3ff..0e4533ef 100644 --- a/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/TestDsl.kt +++ b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/testdsl/TestDsl.kt @@ -40,9 +40,9 @@ class TestProject( val settingsGradle get() = projectPath.resolve("settings.gradle") val editorConfig get() = projectPath.resolve(".editorconfig") - fun withCleanSources() { + fun withCleanSources(filePath: String = CLEAN_SOURCES_FILE) { createSourceFile( - CLEAN_SOURCES_FILE, + filePath, """ |val foo = "bar" |