Skip to content

Commit

Permalink
fix detection of android kotlin source directories in AGP >= 7
Browse files Browse the repository at this point in the history
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
  • Loading branch information
wakingrufus committed Dec 20, 2023
1 parent a826dcd commit 65ccafa
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 32 deletions.
2 changes: 1 addition & 1 deletion plugin/VERSION_LATEST_RELEASE.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
11.6.1
12.0.3
5 changes: 5 additions & 0 deletions plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ tasks.withType<Test> {
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<ConfigureShadowRelocation>("relocateShadowJar")
Expand Down
2 changes: 1 addition & 1 deletion plugin/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ open class KtlintPlugin : Plugin<Project> {
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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<in Any>) -> Unit {
return {
Expand All @@ -43,33 +38,32 @@ internal fun KtlintPlugin.PluginHolder.applyKtLintToAndroid(): (Plugin<in Any>)
}
}

@Suppress("UnstableApiUsage")
private typealias AndroidCommonExtension = CommonExtension<
AndroidSourceSet,
BuildFeatures,
BuildType,
DefaultConfig,
ProductFlavor,
SigningConfig,
Variant<VariantProperties>,
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<Any>) -> 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
Expand Down
10 changes: 10 additions & 0 deletions plugin/src/test/kotlin/org/assertj/core/api/Assertions.kt
Original file line number Diff line number Diff line change
@@ -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<BuildTask?>.hasOutcome(outcome: TaskOutcome) {
Objects.instance().assertNotNull(this.info, this.actual)
Objects.instance().assertEqual(this.info, this.actual!!.outcome, outcome)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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()
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
|
Expand Down

0 comments on commit 65ccafa

Please sign in to comment.