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 maximum gradle version to 8.5
increase maximum ktlint version to 1.1.0
increase maximum kotlin version to 1.9.21

fixes #702
based on work in #703
  • Loading branch information
wakingrufus committed Dec 20, 2023
1 parent a826dcd commit caf668f
Show file tree
Hide file tree
Showing 13 changed files with 214 additions and 41 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

- fix detection of android kotlin source directories in AGP >= 7 [#733](https://github.com/JLLeitschuh/ktlint-gradle/pull/733)

## [12.0.3] - 2023-12-11

- fix: apply configuration for source sets and targets that are added after the plugin is
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ Minimal supported [Kotlin](https://kotlinlang.org) version: `1.4`

Minimal supported [ktlint](https://github.com/pinterest/ktlint) version: `0.47.1`

Minimal supported [Android Gradle plugin](https://developer.android.com/build) version: `4.1.0`

### Ktlint plugin

#### Simple setup
Expand Down
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 17, so set it to 17 for tests
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(17)
}
}

val relocateShadowJar = tasks.register<ConfigureShadowRelocation>("relocateShadowJar")
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,89 @@
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, input.kotlinVersion, input.ktlintVersion)
) {
withCleanSources("src/main/java/CleanSource.kt")
build(CHECK_PARENT_TASK_NAME) {
assertThat(task(":$mainSourceSetCheckTaskName")).hasOutcome(TaskOutcome.SUCCESS)
assertThat(task(":$kotlinScriptCheckTaskName")).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, input.kotlinVersion, input.ktlintVersion)
) {
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(":$kotlinScriptCheckTaskName")).hasOutcome(TaskOutcome.SUCCESS)
assertThat(task(":$CHECK_PARENT_TASK_NAME")).hasOutcome(TaskOutcome.SUCCESS)
}
} else {
build(CHECK_PARENT_TASK_NAME) {
assertThat(task(":$mainSourceSetCheckTaskName")).hasOutcome(TaskOutcome.SUCCESS)
assertThat(task(":$kotlinScriptCheckTaskName")).hasOutcome(TaskOutcome.SUCCESS)
assertThat(task(":$CHECK_PARENT_TASK_NAME")).hasOutcome(TaskOutcome.SUCCESS)
}
}
}
}

enum class AndroidTestInput(
val gradleVersion: GradleVersion,
val agpVersion: String,
val kotlinVersion: String,
val ktlintVersion: String? =null
) {
MIN(
GradleVersion.version(TestVersions.minSupportedGradleVersion),
TestVersions.minAgpVersion,
"1.5.20", // AGP 4.1 requires kotlin 1.5.20
"0.47.1" // old AGP doesn't properly set variant info which ktlint >= 1 requires
),
GRADLE_7_5_AGP_7_4(
GradleVersion.version("7.5"), // AGP 7.4 requires Gradle 7.5
"7.4.2",
"1.5.20", // AGP 4.1 requires kotlin 1.5.20
"0.47.1" // old AGP doesn't properly set variant info which ktlint >= 1 requires
),
MAX_GRADLE_MIN_AGP(
GradleVersion.version(TestVersions.maxSupportedGradleVersion),
TestVersions.minAgpVersion,
"1.5.20", // AGP 4.1 requires kotlin 1.5.20
),
MAX(
GradleVersion.version(TestVersions.maxSupportedGradleVersion),
TestVersions.maxAgpVersion,
TestVersions.maxSupportedKotlinPluginVersion
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.jlleitschuh.gradle.ktlint.testdsl

import net.swiftzer.semver.SemVer
import java.io.File

fun androidProjectSetup(
agpVersion: String,
kotlinPluginVersion: String,
ktlintVersion: String? = null
): (File) -> Unit = {
val ktLintOverride = ktlintVersion?.let { "ktlint { version = \"$it\" }\n" } ?: ""
val setNamespace = if ((SemVer.parse(agpVersion) >= SemVer(7))) {
" namespace = \"com.example.myapp\"\n"
} else {
""
}
//language=Groovy
it.resolve("build.gradle").writeText(
"""
|plugins {
| id("com.android.application")
| id("org.jetbrains.kotlin.android")
| id("org.jlleitschuh.gradle.ktlint")
|}
|
|repositories {
| mavenCentral()
|}
|android {
| compileSdk = 33
|$setNamespace}
|$ktLintOverride
""".trimMargin()
)

// before 4.2.0, AGP did not properly publish metadata for id resolution
val oldAgpHack = if ((SemVer.parse(agpVersion) < SemVer(4, 2))) {
"""
| resolutionStrategy {
| eachPlugin {
| when (requested.id.id) {
| "com.android.application" -> useModule("com.android.tools.build:gradle:$agpVersion")
| }
| }
| }
|
""".trimMargin()
} else {
""
}
val newAgp = if ((SemVer.parse(agpVersion) < SemVer(4, 2))) {
""
} else {
" id(\"com.android.application\") version \"$agpVersion\"\n "
}

//language=Groovy
it.resolve("settings.gradle.kts").writeText(
"""
|pluginManagement {
| repositories {
| mavenLocal()
| gradlePluginPortal()
| google()
| mavenCentral()
| }
|
| plugins {
| id("org.jetbrains.kotlin.android") version("$kotlinPluginVersion")
| id("org.jlleitschuh.gradle.ktlint") version("${TestVersions.pluginVersion}")
| $newAgp}
|$oldAgpHack}
|
""".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 maxSupportedKotlinPluginVersion = "1.9.21"
const val minAgpVersion = "4.1.0"
const val maxAgpVersion = "8.2.0"
}

@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
10 changes: 1 addition & 9 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,14 @@ pluginManagement {
plugins {
id("org.jetbrains.kotlin.jvm") version "1.8.22"
id("org.jetbrains.kotlin.js") version "1.8.22"
id("com.android.application") version "4.2.2"
}

repositories {
gradlePluginPortal()
google()
maven("https://dl.bintray.com/jetbrains/kotlin-native-dependencies")
}

resolutionStrategy {
eachPlugin {
when (requested.id.id) {
"com.android.application" ->
useModule("com.android.tools.build:gradle:4.1.3")
}
}
}
}

enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
Expand Down

0 comments on commit caf668f

Please sign in to comment.