diff --git a/README.md b/README.md index a17df2d5..83dd3bd6 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ ktlint { ktlintVersion = "1.0.0-SNAPSHOT" chunkSize = 50 baselineFile.set(file("config/ktlint_baseline.xml")) + ignoreKspGeneratedSources = true } ``` @@ -81,7 +82,8 @@ ktlint { provided `ktlint` version isn't compatible with the latest ktlint apis. - `chunkSize` - defines how many files will be processed by a single gradle worker in parallel -- `baselineFile` - points at location of baseline file containing _known_ offenses that will be ignored during `lintKotlin` task execution +- `baselineFile` - points at location of baseline file containing _known_ offenses that will be ignored during `lintKotlin` task execution +- `ignoreKspGeneratedSources` - indicates if the plugin should automatically register tasks for KSP generated sources (hence triggering compilation tasks). This is an incubating, rather primitive workaround for the issue reported: https://github.com/google/ksp/issues/1261. ### Customizing Tasks diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 68c25fd8..d1410f32 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,7 @@ gradle-starter = "0.64.0" gradle-pluginpublish = "1.2.1" gradle-doctor = "0.8.1" google-agp = "8.1.1" +google-ksp = "1.9.10-1.0.13" maven-junit = "5.10.0" maven-assertj = "3.24.2" maven-ktlint = "1.0.0" @@ -19,6 +20,7 @@ junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.re junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "maven-junit" } assertj-core = { module = "org.assertj:assertj-core", version.ref = "maven-assertj" } commons-io = { module = "commons-io:commons-io", version.ref = "maven-commons" } +google-ksp-gradle = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "google-ksp"} [plugins] starter-config = { id = "com.starter.config", version.ref = "gradle-starter" } diff --git a/ktlint-gradle-plugin/build.gradle b/ktlint-gradle-plugin/build.gradle index cd58d687..6a294e8f 100644 --- a/ktlint-gradle-plugin/build.gradle +++ b/ktlint-gradle-plugin/build.gradle @@ -41,6 +41,7 @@ dependencies { testRuntimeDependencies(libs.kotlin.gradle) testRuntimeDependencies(libs.agp.gradle) + testRuntimeDependencies(libs.google.ksp.gradle) } kotlin { diff --git a/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/KtlintGradleExtension.kt b/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/KtlintGradleExtension.kt index 05c3b540..57bd26fd 100644 --- a/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/KtlintGradleExtension.kt +++ b/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/KtlintGradleExtension.kt @@ -3,6 +3,7 @@ package io.github.usefulness import io.github.usefulness.support.versionProperties import io.github.usefulness.tasks.listProperty import io.github.usefulness.tasks.property +import org.gradle.api.Incubating import org.gradle.api.file.RegularFileProperty import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty @@ -21,17 +22,20 @@ public open class KtlintGradleExtension internal constructor( val DEFAULT_DISABLED_RULES = emptyList() } - public var ignoreFailures: Property = objectFactory.property(default = DEFAULT_IGNORE_FAILURES) + public val ignoreFailures: Property = objectFactory.property(default = DEFAULT_IGNORE_FAILURES) - public var reporters: ListProperty = objectFactory.listProperty(default = emptyList()) + public val reporters: ListProperty = objectFactory.listProperty(default = emptyList()) - public var experimentalRules: Property = objectFactory.property(default = DEFAULT_EXPERIMENTAL_RULES) + public val experimentalRules: Property = objectFactory.property(default = DEFAULT_EXPERIMENTAL_RULES) - public var disabledRules: ListProperty = objectFactory.listProperty(default = DEFAULT_DISABLED_RULES) + public val disabledRules: ListProperty = objectFactory.listProperty(default = DEFAULT_DISABLED_RULES) - public var ktlintVersion: Property = objectFactory.property(providerFactory.provider { versionProperties.ktlintVersion() }) + public val ktlintVersion: Property = objectFactory.property(providerFactory.provider { versionProperties.ktlintVersion() }) - public var chunkSize: Property = objectFactory.property(default = DEFAULT_CHUNK_SIZE) + public val chunkSize: Property = objectFactory.property(default = DEFAULT_CHUNK_SIZE) - public var baselineFile: RegularFileProperty = objectFactory.fileProperty() + public val baselineFile: RegularFileProperty = objectFactory.fileProperty() + + @Incubating + public val ignoreKspGeneratedSources: Property = objectFactory.property(default = true) } diff --git a/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/KtlintGradlePlugin.kt b/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/KtlintGradlePlugin.kt index 53e6dabd..9b441890 100644 --- a/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/KtlintGradlePlugin.kt +++ b/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/KtlintGradlePlugin.kt @@ -45,7 +45,7 @@ public class KtlintGradlePlugin : Plugin { task.chunkSize.set(pluginExtension.chunkSize) } - sourceResolver.applyToAll(this) { id, resolvedSources -> + sourceResolver.applyToAll(this, pluginExtension) { id, resolvedSources -> val checkWorker = tasks.register( "lintKotlin${id.capitalize()}", LintTask::class.java, diff --git a/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/SourceSetApplier.kt b/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/SourceSetApplier.kt index f348f877..3f3d799e 100644 --- a/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/SourceSetApplier.kt +++ b/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/SourceSetApplier.kt @@ -7,5 +7,5 @@ import org.gradle.api.provider.Provider internal typealias SourceSetAction = (String, Provider) -> Unit internal interface SourceSetApplier { - fun applyToAll(project: Project, action: SourceSetAction) + fun applyToAll(project: Project, extension: KtlintGradleExtension, action: SourceSetAction) } diff --git a/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/pluginapplier/AndroidSourceSetApplier.kt b/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/pluginapplier/AndroidSourceSetApplier.kt index 1c762c52..5967b8c9 100644 --- a/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/pluginapplier/AndroidSourceSetApplier.kt +++ b/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/pluginapplier/AndroidSourceSetApplier.kt @@ -3,6 +3,7 @@ package io.github.usefulness.pluginapplier import com.android.build.api.dsl.AndroidSourceDirectorySet import com.android.build.api.dsl.AndroidSourceSet import com.android.build.api.variant.AndroidComponentsExtension +import io.github.usefulness.KtlintGradleExtension import io.github.usefulness.SourceSetAction import io.github.usefulness.SourceSetApplier import io.github.usefulness.id @@ -11,7 +12,7 @@ import org.gradle.api.file.FileTree internal object AndroidSourceSetApplier : SourceSetApplier { - override fun applyToAll(project: Project, action: SourceSetAction) { + override fun applyToAll(project: Project, extension: KtlintGradleExtension, action: SourceSetAction) { val android = project.extensions.findByName("androidComponents") as? AndroidComponentsExtension<*, *, *> ?: return android.finalizeDsl { commonExtension -> commonExtension.sourceSets.configureEach { sourceSet -> diff --git a/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/pluginapplier/KotlinSourceSetApplier.kt b/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/pluginapplier/KotlinSourceSetApplier.kt index e8ea622f..fefac952 100644 --- a/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/pluginapplier/KotlinSourceSetApplier.kt +++ b/ktlint-gradle-plugin/src/main/kotlin/io/github/usefulness/pluginapplier/KotlinSourceSetApplier.kt @@ -1,5 +1,6 @@ package io.github.usefulness.pluginapplier +import io.github.usefulness.KtlintGradleExtension import io.github.usefulness.SourceSetAction import io.github.usefulness.SourceSetApplier import io.github.usefulness.id @@ -7,13 +8,12 @@ import org.gradle.api.Project import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension internal object KotlinSourceSetApplier : SourceSetApplier { - override fun applyToAll(project: Project, action: SourceSetAction) { - getSourceSets(project).configureEach { sourceSet -> - sourceSet.kotlin.let { directorySet -> - action(directorySet.name.id, project.provider { directorySet }) + + override fun applyToAll(project: Project, extension: KtlintGradleExtension, action: SourceSetAction) { + project.extensions.getByType(KotlinProjectExtension::class.java).sourceSets.configureEach { sourceSet -> + if (!sourceSet.name.startsWith("generatedByKsp") || !extension.ignoreKspGeneratedSources.get()) { + action(sourceSet.kotlin.name.id, project.provider { sourceSet.kotlin }) } } } - - private fun getSourceSets(project: Project) = project.extensions.getByType(KotlinProjectExtension::class.java).sourceSets } diff --git a/ktlint-gradle-plugin/src/test/kotlin/io/github/usefulness/functional/ReportersTest.kt b/ktlint-gradle-plugin/src/test/kotlin/io/github/usefulness/functional/ReportersTest.kt index 5ea21a64..d9cf0058 100644 --- a/ktlint-gradle-plugin/src/test/kotlin/io/github/usefulness/functional/ReportersTest.kt +++ b/ktlint-gradle-plugin/src/test/kotlin/io/github/usefulness/functional/ReportersTest.kt @@ -6,7 +6,6 @@ import io.github.usefulness.functional.utils.settingsFile import org.assertj.core.api.Assertions.assertThat import org.gradle.testkit.runner.TaskOutcome import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.condition.DisabledOnOs import org.junit.jupiter.api.condition.OS @@ -98,7 +97,6 @@ class ReportersTest : WithGradleTest.Kotlin() { } @Test - @Disabled("ktlint doesn't maintain binary compatibility for some reason 🤷‍♂️") fun `uses reporters from overridden ktlint version`() { projectRoot.resolve("build.gradle") { // language=groovy diff --git a/ktlint-gradle-plugin/src/test/kotlin/io/github/usefulness/functional/ThirdPartyPlugins.kt b/ktlint-gradle-plugin/src/test/kotlin/io/github/usefulness/functional/ThirdPartyPlugins.kt new file mode 100644 index 00000000..d5cba5b7 --- /dev/null +++ b/ktlint-gradle-plugin/src/test/kotlin/io/github/usefulness/functional/ThirdPartyPlugins.kt @@ -0,0 +1,163 @@ +package io.github.usefulness.functional + +import io.github.usefulness.functional.utils.editorConfig +import io.github.usefulness.functional.utils.kotlinClass +import io.github.usefulness.functional.utils.resolve +import io.github.usefulness.functional.utils.settingsFile +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class ThirdPartyPlugins : WithGradleTest.Android() { + + @Test + fun kspKotlin() { + testProjectDir.apply { + resolve("settings.gradle") { writeText(settingsFile) } + resolve(".editorconfig") { writeText(editorConfig) } + resolve("build.gradle") { + // language=groovy + writeText( + """ + plugins { + id 'org.jetbrains.kotlin.jvm' + id 'com.google.devtools.ksp' + id 'io.github.usefulness.ktlint-gradle-plugin' + } + + repositories.mavenCentral() + + dependencies { + ksp "com.google.dagger:dagger-compiler:2.48" + } + + """.trimIndent(), + ) + } + resolve("src/main/kotlin/KotlinClass.kt") { + writeText(kotlinClass("KotlinClass")) + } + } + + val result = build("lintKotlin") + + assertThat(result.tasks.map { it.path }).containsExactlyInAnyOrder( + ":lintKotlinTest", + ":lintKotlinMain", + ":lintKotlin", + ) + testProjectDir.resolve("build.gradle") { + appendText( + // language=groovy + """ + + ktlint { + ignoreKspGeneratedSources = false + } + + """.trimIndent(), + ) + } + + val onlyMain = build("lintKotlin") + + assertThat(onlyMain.tasks.map { it.path }).containsAll( + listOf( + ":compileKotlin", + ":compileJava", + ":lintKotlinGeneratedByKspTestKotlin", + ":lintKotlinTest", + ":lintKotlinMain", + ":lintKotlin", + ), + ) + } + + @Test + fun kspAndroid() { + testProjectDir.apply { + resolve("settings.gradle") { writeText(settingsFile) } + resolve(".editorconfig") { writeText(editorConfig) } + resolve("build.gradle") { + // language=groovy + writeText( + """ + plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' + id 'com.google.devtools.ksp' + id 'io.github.usefulness.ktlint-gradle-plugin' + } + + android { + namespace 'io.github.usefulness' + compileSdk 33 + defaultConfig { + minSdkVersion 23 + } + } + + + repositories.mavenCentral() + + dependencies { + ksp "com.google.dagger:dagger-compiler:2.48" + } + + """.trimIndent(), + ) + } + resolve("src/main/kotlin/KotlinClass.kt") { + writeText(kotlinClass("KotlinClass")) + } + } + + val result = build("lintKotlin") + + assertThat(result.tasks.map { it.path }).containsExactlyInAnyOrder( + ":lintKotlinTestFixturesRelease", + ":lintKotlinTestDebug", + ":lintKotlinAndroidTest", + ":lintKotlinTestFixtures", + ":lintKotlinTestFixturesDebug", + ":lintKotlinRelease", + ":lintKotlinTest", + ":lintKotlinTestRelease", + ":lintKotlinMain", + ":lintKotlinDebug", + ":lintKotlinAndroidTestRelease", + ":lintKotlinAndroidTestDebug", + ":lintKotlin", + ) + + testProjectDir.resolve("build.gradle") { + appendText( + // language=groovy + """ + + ktlint { + ignoreKspGeneratedSources = false + } + + """.trimIndent(), + ) + } + + val onlyMain = build("lintKotlin") + + assertThat(onlyMain.tasks.map { it.path }).containsExactlyInAnyOrder( + ":lintKotlinTestFixturesRelease", + ":lintKotlinTestDebug", + ":lintKotlinAndroidTest", + ":lintKotlinTestFixtures", + ":lintKotlinTestFixturesDebug", + ":lintKotlinRelease", + ":lintKotlinTest", + ":lintKotlinTestRelease", + ":lintKotlinMain", + ":lintKotlinDebug", + ":lintKotlinAndroidTestRelease", + ":lintKotlinAndroidTestDebug", + ":lintKotlin", + ) + } +} diff --git a/ktlint-gradle-plugin/src/test/kotlin/io/github/usefulness/functional/utils/Factories.kt b/ktlint-gradle-plugin/src/test/kotlin/io/github/usefulness/functional/utils/Factories.kt index 66036076..0a645225 100644 --- a/ktlint-gradle-plugin/src/test/kotlin/io/github/usefulness/functional/utils/Factories.kt +++ b/ktlint-gradle-plugin/src/test/kotlin/io/github/usefulness/functional/utils/Factories.kt @@ -22,6 +22,8 @@ internal val androidManifest = internal val editorConfig = """ + root = true + [*.kt] """.trimIndent()