Skip to content

Commit

Permalink
Merge pull request #187 from usefulness/updates
Browse files Browse the repository at this point in the history
Improve configuration-cache compatibility, validate editorconfigs
  • Loading branch information
mateuszkwiecinski authored Mar 3, 2024
2 parents 2c8c348 + 9be2578 commit 54fc37d
Show file tree
Hide file tree
Showing 13 changed files with 427 additions and 29 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ ktlint {
- `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
- `ignoreFilesUnderBuildDir` - This allows to ignore generated sources. This is a workaround for https://youtrack.jetbrains.com/issue/KT-45161. Setting the value to `false` restores default behavior and will run ktlint against all sources returned by KGP
- `editorConfigValidation` - One of `None`, `PrintWarningLogs`, `BuildFailure`.
- Currently, this only validates if any of the _typical_ editorconfig location contain `root=true` entry. This is highly recommended to ensure the builds are deterministic across machines

### Customizing Tasks

Expand Down
4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ maven-junit = "5.10.2"
maven-assertj = "3.25.3"
maven-ktlint = "1.2.1"
maven-commons = "2.15.1"
maven-binarycompatibilityvalidator = "0.14.0"

[libraries]
agp-gradle = { module = "com.android.tools.build:gradle", version.ref = "google-agp" }
Expand All @@ -21,11 +22,12 @@ 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"}
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" }
starter-versioning = { id = "com.starter.versioning", version.ref = "gradle-starter" }
starter-library-kotlin = { id = "com.starter.library.kotlin", version.ref = "gradle-starter" }
gradle-pluginpublish = { id = "com.gradle.plugin-publish", version.ref = "gradle-pluginpublish" }
osacky-doctor = { id = "com.osacky.doctor", version.ref = "gradle-doctor" }
kotlinx-binarycompatibilityvalidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "maven-binarycompatibilityvalidator" }
78 changes: 78 additions & 0 deletions ktlint-gradle-plugin/api/ktlint-gradle-plugin.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
public final class io/github/usefulness/EditorConfigValidationMode : java/lang/Enum {
public static final field BuildFailure Lio/github/usefulness/EditorConfigValidationMode;
public static final field None Lio/github/usefulness/EditorConfigValidationMode;
public static final field PrintWarningLogs Lio/github/usefulness/EditorConfigValidationMode;
public static fun valueOf (Ljava/lang/String;)Lio/github/usefulness/EditorConfigValidationMode;
public static fun values ()[Lio/github/usefulness/EditorConfigValidationMode;
}

public class io/github/usefulness/KtlintGradleExtension {
public static final field DEFAULT_CHUNK_SIZE I
public static final field DEFAULT_EXPERIMENTAL_RULES Z
public static final field DEFAULT_IGNORE_FAILURES Z
public final fun editorConfigValidation (Ljava/lang/Object;)V
public final fun getBaselineFile ()Lorg/gradle/api/file/RegularFileProperty;
public final fun getChunkSize ()Lorg/gradle/api/provider/Property;
public final fun getDisabledRules ()Lorg/gradle/api/provider/ListProperty;
public final fun getEditorConfigValidation ()Lorg/gradle/api/provider/Property;
public final fun getExperimentalRules ()Lorg/gradle/api/provider/Property;
public final fun getIgnoreFailures ()Lorg/gradle/api/provider/Property;
public final fun getIgnoreFilesUnderBuildDir ()Lorg/gradle/api/provider/Property;
public final fun getIgnoreKspGeneratedSources ()Lorg/gradle/api/provider/Property;
public final fun getKtlintVersion ()Lorg/gradle/api/provider/Property;
public final fun getReporters ()Lorg/gradle/api/provider/ListProperty;
}

public final class io/github/usefulness/KtlintGradlePlugin : org/gradle/api/Plugin {
public fun <init> ()V
public synthetic fun apply (Ljava/lang/Object;)V
public fun apply (Lorg/gradle/api/Project;)V
}

public class io/github/usefulness/tasks/CheckEditorConfigTask : org/gradle/api/DefaultTask {
public fun <init> (Lorg/gradle/api/model/ObjectFactory;)V
public final fun getMode ()Lorg/gradle/api/provider/Property;
public final fun run ()V
}

public class io/github/usefulness/tasks/FormatTask : io/github/usefulness/tasks/KtlintWorkTask {
public fun <init> (Lorg/gradle/workers/WorkerExecutor;Lorg/gradle/api/model/ObjectFactory;Lorg/gradle/api/file/ProjectLayout;)V
public final fun run (Lorg/gradle/work/InputChanges;)V
}

public abstract class io/github/usefulness/tasks/KtlintWorkTask : org/gradle/api/DefaultTask, org/gradle/api/tasks/util/PatternFilterable {
public fun <init> (Lorg/gradle/workers/WorkerExecutor;Lorg/gradle/api/file/ProjectLayout;Lorg/gradle/api/model/ObjectFactory;Lorg/gradle/api/tasks/util/PatternFilterable;)V
public synthetic fun <init> (Lorg/gradle/workers/WorkerExecutor;Lorg/gradle/api/file/ProjectLayout;Lorg/gradle/api/model/ObjectFactory;Lorg/gradle/api/tasks/util/PatternFilterable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun exclude (Lgroovy/lang/Closure;)Lorg/gradle/api/tasks/util/PatternFilterable;
public fun exclude (Ljava/lang/Iterable;)Lorg/gradle/api/tasks/util/PatternFilterable;
public fun exclude (Lorg/gradle/api/specs/Spec;)Lorg/gradle/api/tasks/util/PatternFilterable;
public fun exclude ([Ljava/lang/String;)Lorg/gradle/api/tasks/util/PatternFilterable;
public final fun getBaselineFile ()Lorg/gradle/api/file/RegularFileProperty;
public final fun getChunkSize ()Lorg/gradle/api/provider/Property;
public final fun getDisabledRules ()Lorg/gradle/api/provider/ListProperty;
public fun getExcludes ()Ljava/util/Set;
public final fun getExperimentalRules ()Lorg/gradle/api/provider/Property;
public final fun getIgnoreFailures ()Lorg/gradle/api/provider/Property;
public fun getIncludes ()Ljava/util/Set;
public final fun getJvmArgs ()Lorg/gradle/api/provider/ListProperty;
public final fun getKtlintClasspath ()Lorg/gradle/api/file/ConfigurableFileCollection;
public final fun getReportersConfiguration ()Lorg/gradle/api/file/ConfigurableFileCollection;
public final fun getReports ()Lorg/gradle/api/provider/MapProperty;
public final fun getRuleSetsClasspath ()Lorg/gradle/api/file/ConfigurableFileCollection;
public final fun getSource ()Lorg/gradle/api/file/FileCollection;
public final fun getWorkerMaxHeapSize ()Lorg/gradle/api/provider/Property;
public fun include (Lgroovy/lang/Closure;)Lorg/gradle/api/tasks/util/PatternFilterable;
public fun include (Ljava/lang/Iterable;)Lorg/gradle/api/tasks/util/PatternFilterable;
public fun include (Lorg/gradle/api/specs/Spec;)Lorg/gradle/api/tasks/util/PatternFilterable;
public fun include ([Ljava/lang/String;)Lorg/gradle/api/tasks/util/PatternFilterable;
public fun setExcludes (Ljava/lang/Iterable;)Lorg/gradle/api/tasks/util/PatternFilterable;
public fun setIncludes (Ljava/lang/Iterable;)Lorg/gradle/api/tasks/util/PatternFilterable;
public final fun setSource (Ljava/lang/Object;)V
public final fun source ([Ljava/lang/Object;)Lio/github/usefulness/tasks/KtlintWorkTask;
}

public class io/github/usefulness/tasks/LintTask : io/github/usefulness/tasks/KtlintWorkTask {
public fun <init> (Lorg/gradle/workers/WorkerExecutor;Lorg/gradle/api/model/ObjectFactory;Lorg/gradle/api/file/ProjectLayout;)V
public final fun run (Lorg/gradle/work/InputChanges;)V
}

10 changes: 5 additions & 5 deletions ktlint-gradle-plugin/build.gradle
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import kotlin.Suppress
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapperKt

plugins {
id("java-gradle-plugin")
id("com.starter.publishing")
alias(libs.plugins.gradle.pluginpublish)
alias(libs.plugins.starter.library.kotlin)
alias(libs.plugins.kotlinx.binarycompatibilityvalidator)
}

description = "Lint and formatting for Kotlin using ktlint with configuration-free setup on JVM and Android projects"
Expand Down Expand Up @@ -65,9 +65,9 @@ tasks.named("processResources") {
}

tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
apiVersion = "1.8"
languageVersion = "1.8"
compilerOptions {
apiVersion = KotlinVersion.KOTLIN_1_8
languageVersion = KotlinVersion.KOTLIN_1_8
}
}
tasks.withType(Test).configureEach {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.github.usefulness

import org.gradle.api.Incubating

@Incubating
public enum class EditorConfigValidationMode {
None,
PrintWarningLogs,
BuildFailure,
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.usefulness

import io.github.usefulness.EditorConfigValidationMode.PrintWarningLogs
import io.github.usefulness.support.versionProperties
import io.github.usefulness.tasks.listProperty
import io.github.usefulness.tasks.property
Expand Down Expand Up @@ -42,4 +43,13 @@ public open class KtlintGradleExtension internal constructor(
@Deprecated(message = "Will be removed in the next version", replaceWith = ReplaceWith(expression = "ignoreFilesUnderBuildDir"))
@Incubating
public val ignoreKspGeneratedSources: Property<Boolean> = ignoreFilesUnderBuildDir

@Incubating
public val editorConfigValidation: Property<EditorConfigValidationMode> = objectFactory.property(default = PrintWarningLogs)

@Incubating
public fun editorConfigValidation(any: Any) {
val value = EditorConfigValidationMode.values().firstOrNull { it.name.equals(any.toString(), ignoreCase = true) }
editorConfigValidation.set(checkNotNull(value) { "Has to be one of ${EditorConfigValidationMode.values()}, was=$any" })
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.github.usefulness

import io.github.usefulness.EditorConfigValidationMode.None
import io.github.usefulness.pluginapplier.AndroidSourceSetApplier
import io.github.usefulness.pluginapplier.KotlinSourceSetApplier
import io.github.usefulness.support.ReporterType
import io.github.usefulness.tasks.CheckEditorConfigTask
import io.github.usefulness.tasks.FormatTask
import io.github.usefulness.tasks.KtlintWorkTask
import io.github.usefulness.tasks.LintTask
Expand Down Expand Up @@ -32,6 +34,14 @@ public class KtlintGradlePlugin : Plugin<Project> {
val ktlintConfiguration = createKtlintConfiguration(pluginExtension)
val ruleSetConfiguration = createRuleSetConfiguration(ktlintConfiguration)
val reportersConfiguration = createReportersConfiguration(ktlintConfiguration)
val recognisedEditorConfigs = generateSequence(project) { it.parent }
.map { it.layout.projectDirectory.file(".editorconfig").asFile }
.toList()

tasks.register("validateEditorConfigForKtlint", CheckEditorConfigTask::class.java) {
it.editorConfigFiles.from(recognisedEditorConfigs)
it.mode.set(pluginExtension.editorConfigValidation)
}

extendablePlugins.forEach { (pluginId, sourceResolver) ->
pluginManager.withPlugin(pluginId) {
Expand All @@ -43,6 +53,7 @@ public class KtlintGradlePlugin : Plugin<Project> {
task.ruleSetsClasspath.setFrom(ruleSetConfiguration)
task.reportersConfiguration.setFrom(reportersConfiguration)
task.chunkSize.set(pluginExtension.chunkSize)
task.editorConfigFiles.from(recognisedEditorConfigs)
}

sourceResolver.applyToAll(this, pluginExtension) { id, resolvedSources ->
Expand Down Expand Up @@ -87,6 +98,15 @@ public class KtlintGradlePlugin : Plugin<Project> {

formatKotlin.configure { it.dependsOn(formatWorker) }
}

tasks.named("lintKotlin") { task ->
if (pluginExtension.editorConfigValidation.get() == None) return@named
task.dependsOn("validateEditorConfigForKtlint")
}
tasks.named("formatKotlin") { task ->
if (pluginExtension.editorConfigValidation.get() == None) return@named
task.dependsOn("validateEditorConfigForKtlint")
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import org.ec4j.core.model.Property
import org.ec4j.core.model.PropertyType
import org.ec4j.core.model.PropertyType.PropertyValueParser.IDENTITY_VALUE_PARSER
import org.ec4j.core.model.Section
import org.gradle.api.file.ProjectLayout
import java.io.File

internal fun editorConfigOverride(disabledRules: List<String>) = getPropertiesForDisabledRules(disabledRules)
Expand Down Expand Up @@ -69,19 +68,7 @@ private fun getKtlintRulePropertyName(ruleName: String) = if (ruleName.contains(
"ktlint_standard_$ruleName"
}

internal fun ProjectLayout.findApplicableEditorConfigFiles(): Sequence<File> {
val projectEditorConfig = projectDirectory.file(".editorconfig").asFile

return generateSequence(seed = projectEditorConfig) { editorconfig ->
if (editorconfig.isRootEditorConfig) {
null
} else {
editorconfig.parentFile?.parentFile?.resolve(".editorconfig")
}
}
}

private val File.isRootEditorConfig: Boolean
internal val File.isRootEditorConfig: Boolean
get() {
if (!isFile || !canRead()) return false

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package io.github.usefulness.tasks

import io.github.usefulness.EditorConfigValidationMode
import io.github.usefulness.EditorConfigValidationMode.BuildFailure
import io.github.usefulness.EditorConfigValidationMode.None
import io.github.usefulness.EditorConfigValidationMode.PrintWarningLogs
import io.github.usefulness.support.isRootEditorConfig
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import javax.inject.Inject

@CacheableTask
public open class CheckEditorConfigTask @Inject constructor(objectFactory: ObjectFactory) : DefaultTask() {

@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val editorConfigFiles = objectFactory.fileCollection()

@Input
public val mode: Property<EditorConfigValidationMode> = objectFactory.property<EditorConfigValidationMode>(default = null)

@get:OutputFile
internal val resultsFile = project.layout.buildDirectory.file("reports/ktlintValidation/result.txt")

@TaskAction
public fun run() {
val files = editorConfigFiles.files
val messageFn = {
"None of the recognised `.editorconfig` files contain `root=true` entry, this may result in non-deterministic builds. " +
"Please add `root=true` entry to the top-most editorconfig file\n" +
"`.editorconfig` files:\n${files.joinToString(separator = "\n")}"
}
if (files.none { it.isRootEditorConfig }) {
resultsFile.get().asFile.writeText("Failure")
when (mode.get()) {
None -> Unit
PrintWarningLogs -> logger.warn(messageFn())

BuildFailure,
null,
-> throw GradleException(messageFn())
}
} else {
resultsFile.get().asFile.writeText("OK")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import io.github.usefulness.KtlintGradleExtension.Companion.DEFAULT_DISABLED_RUL
import io.github.usefulness.KtlintGradleExtension.Companion.DEFAULT_EXPERIMENTAL_RULES
import io.github.usefulness.KtlintGradleExtension.Companion.DEFAULT_IGNORE_FAILURES
import io.github.usefulness.support.KtlintRunMode
import io.github.usefulness.support.findApplicableEditorConfigFiles
import io.github.usefulness.tasks.workers.ConsoleReportWorker
import io.github.usefulness.tasks.workers.GenerateReportsWorker
import io.github.usefulness.tasks.workers.KtlintWorker
Expand Down Expand Up @@ -70,9 +69,7 @@ public abstract class KtlintWorkTask(
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:Incremental
internal val editorconfigFiles = objectFactory.fileCollection().apply {
from(projectLayout.findApplicableEditorConfigFiles().toList())
}
internal val editorConfigFiles = objectFactory.fileCollection()

@Input
public val workerMaxHeapSize: Property<String> = objectFactory.property(default = "256m")
Expand Down Expand Up @@ -192,10 +189,10 @@ internal inline fun <reified K, reified V> ObjectFactory.mapProperty(default: Ma
}

internal fun KtlintWorkTask.getChangedEditorconfigFiles(inputChanges: InputChanges) =
inputChanges.getFileChanges(editorconfigFiles).map(FileChange::getFile)
inputChanges.getFileChanges(editorConfigFiles).map(FileChange::getFile)

internal fun KtlintWorkTask.getChangedSources(inputChanges: InputChanges) =
if (inputChanges.isIncremental && inputChanges.getFileChanges(editorconfigFiles).none()) {
if (inputChanges.isIncremental && inputChanges.getFileChanges(editorConfigFiles).none()) {
inputChanges.getFileChanges(source)
.asSequence()
.filter { it.fileType == FileType.FILE && it.changeType != ChangeType.REMOVED }
Expand Down
Loading

0 comments on commit 54fc37d

Please sign in to comment.