diff options
author | Harsh Shandilya <me@msfjarvis.dev> | 2024-08-14 00:18:51 +0530 |
---|---|---|
committer | Harsh Shandilya <me@msfjarvis.dev> | 2024-08-14 00:18:51 +0530 |
commit | b699b4db71d08b574cc31daf4ee8c9b849ebaa11 (patch) | |
tree | 50dd1fddb1c2c270a28d5819af2bbe570fcbb289 | |
parent | 3af68b45c4cfd815b9975108fa09cb3872fbab8b (diff) |
feat(build): replace homebrew ktfmt formatter with Spotless
Spotless has fixed their Gradle Configuration Cache woes in the past
couple months which gets rid of my primary complaint.
15 files changed, 65 insertions, 243 deletions
diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 71995b37..ecbc5a60 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -68,6 +68,16 @@ "datasourceTemplate": "maven", "depNameTemplate": "androidx.compose.compiler:compiler", "registryUrlTemplate": "https://maven.google.com", - } + }, + { + "fileMatch": [ + "build-logic/src/main/kotlin/app/passwordstore/gradle/SpotlessPlugin.kt" + ], + "matchStrings": [ + "KTFMT_VERSION = \"(?<currentValue>.*)\"" + ], + "datasourceTemplate": "maven", + "depNameTemplate": "com.facebook:ktfmt" + } ] } diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f5879ab7..242065f6 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -20,7 +20,7 @@ jobs: - name: Check codestyle shell: bash - run: ./gradlew ktfmtCheck + run: ./gradlew spotlessCheck - name: Upload Kotlin build report if: "${{ always() }}" diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index 0b3bee8f..e3d57b79 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -6,7 +6,7 @@ <resources> <plurals name="delete_title"> <item quantity="one">Wybrano %d element</item> - <item quantity="few">Wybrano %d elementy + <item quantity="few">Wybrano %d elementy (If: 2,3,4)</item> <item quantity="many">Wybrano %d elementów</item> <item quantity="other">Liczba wybranych elementów: %d</item> diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 4917fc8c..1f98dde8 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -41,10 +41,6 @@ gradlePlugin { id = "com.github.android-password-store.kotlin-jvm-library" implementationClass = "app.passwordstore.gradle.KotlinJVMLibrary" } - register("ktfmt") { - id = "com.github.android-password-store.ktfmt" - implementationClass = "app.passwordstore.gradle.KtfmtPlugin" - } register("published-android-library") { id = "com.github.android-password-store.published-android-library" implementationClass = "app.passwordstore.gradle.PublishedAndroidLibraryPlugin" @@ -61,6 +57,10 @@ gradlePlugin { id = "com.github.android-password-store.sentry" implementationClass = "app.passwordstore.gradle.SentryPlugin" } + register("spotless") { + id = "com.github.android-password-store.spotless" + implementationClass = "app.passwordstore.gradle.SpotlessPlugin" + } register("versioning") { id = "com.github.android-password-store.versioning-plugin" implementationClass = "app.passwordstore.gradle.versioning.VersioningPlugin" @@ -79,7 +79,6 @@ dependencies { implementation(libs.build.download) implementation(libs.build.javapoet) implementation(libs.build.kotlin) - implementation(libs.build.ktfmt) implementation(libs.build.mavenpublish) implementation(libs.build.metalava) implementation(libs.build.moshi) @@ -88,6 +87,7 @@ dependencies { implementation(libs.build.r8) implementation(libs.build.semver) implementation(libs.build.sentry) + implementation(libs.build.spotless) implementation(libs.build.vcu) implementation(libs.kotlinx.coroutines.core) diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/KtfmtPlugin.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/KtfmtPlugin.kt deleted file mode 100644 index 7aac48b5..00000000 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/KtfmtPlugin.kt +++ /dev/null @@ -1,37 +0,0 @@ -package app.passwordstore.gradle - -import app.passwordstore.gradle.ktfmt.KtfmtCheckTask -import app.passwordstore.gradle.ktfmt.KtfmtFormatTask -import com.facebook.ktfmt.format.FormattingOptions -import java.util.concurrent.Callable -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.kotlin.dsl.register - -class KtfmtPlugin : Plugin<Project> { - - override fun apply(target: Project) { - val input = Callable { - target.layout.projectDirectory.asFileTree.filter { file -> - file.extension == "kt" || file.extension == "kts" && !file.canonicalPath.contains("build/") - } - } - target.tasks.register<KtfmtFormatTask>("ktfmtFormat") { source(input) } - target.tasks.register<KtfmtCheckTask>("ktfmtCheck") { - source(input) - projectDirectory.set(target.layout.projectDirectory) - } - } - - companion object { - val DEFAULT_FORMATTING_OPTIONS = - FormattingOptions( - maxWidth = FormattingOptions.DEFAULT_MAX_WIDTH, - blockIndent = 2, - continuationIndent = 2, - removeUnusedImports = true, - debuggingPrintOpsAfterFormatting = false, - manageTrailingCommas = true, - ) - } -} diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/SpotlessPlugin.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/SpotlessPlugin.kt new file mode 100644 index 00000000..6f33f922 --- /dev/null +++ b/build-logic/src/main/kotlin/app/passwordstore/gradle/SpotlessPlugin.kt @@ -0,0 +1,44 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package app.passwordstore.gradle + +import com.diffplug.gradle.spotless.SpotlessExtension +import com.diffplug.gradle.spotless.SpotlessPlugin +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.getByType + +@Suppress("Unused") +class SpotlessPlugin : Plugin<Project> { + + override fun apply(project: Project) { + project.pluginManager.apply(SpotlessPlugin::class) + project.extensions.getByType<SpotlessExtension>().run { + kotlin { + ktfmt(KTFMT_VERSION).googleStyle() + target("**/*.kt") + targetExclude("**/build/") + } + kotlinGradle { + ktfmt(KTFMT_VERSION).googleStyle() + target("**/*.kts") + targetExclude("**/build/") + } + format("xml") { + target("**/*.xml") + targetExclude("**/build/", ".idea/") + trimTrailingWhitespace() + indentWithSpaces() + endWithNewline() + } + } + } + + private companion object { + private const val KTFMT_VERSION = "0.51" + } +} diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtCheckTask.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtCheckTask.kt deleted file mode 100644 index 58ef432f..00000000 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtCheckTask.kt +++ /dev/null @@ -1,63 +0,0 @@ -package app.passwordstore.gradle.ktfmt - -import app.passwordstore.gradle.KtfmtPlugin -import com.facebook.ktfmt.format.Formatter -import java.io.File -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.runBlocking -import org.gradle.api.GradleException -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.FileCollection -import org.gradle.api.tasks.IgnoreEmptyDirectories -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.PathSensitive -import org.gradle.api.tasks.PathSensitivity -import org.gradle.api.tasks.SourceTask -import org.gradle.api.tasks.TaskAction - -@OptIn(ExperimentalCoroutinesApi::class) -abstract class KtfmtCheckTask : SourceTask() { - - @get:PathSensitive(PathSensitivity.RELATIVE) - @get:InputFiles - @get:IgnoreEmptyDirectories - protected val inputFiles: FileCollection - get() = super.getSource() - - @get:Internal abstract val projectDirectory: DirectoryProperty - - @TaskAction - fun execute() { - runBlocking(Dispatchers.IO.limitedParallelism(PARALLEL_TASK_LIMIT)) { - coroutineScope { - val results = inputFiles.map { async { checkFile(it) } }.awaitAll() - if (results.any { (notFormatted, _) -> notFormatted }) { - val prettyDiff = - results - .map { (_, diffs) -> diffs } - .flatten() - .joinToString(separator = "\n") { diff -> diff.toString() } - throw GradleException("[ktfmt] Found unformatted files\n${prettyDiff}") - } - } - } - } - - private fun checkFile(input: File): Pair<Boolean, List<KtfmtDiffEntry>> { - val originCode = input.readText() - val formattedCode = Formatter.format(KtfmtPlugin.DEFAULT_FORMATTING_OPTIONS, originCode) - val pathNormalizer = { file: File -> file.toRelativeString(projectDirectory.asFile.get()) } - return (originCode != formattedCode) to - KtfmtDiffer.computeDiff(input, formattedCode, pathNormalizer) - } - - companion object { - - private const val PARALLEL_TASK_LIMIT = 4 - } -} diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtDiffEntry.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtDiffEntry.kt deleted file mode 100644 index ca01b5e9..00000000 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtDiffEntry.kt +++ /dev/null @@ -1,8 +0,0 @@ -package app.passwordstore.gradle.ktfmt - -data class KtfmtDiffEntry(val input: String, val lineNumber: Int, val message: String) { - - override fun toString(): String { - return "$input:$lineNumber - $message" - } -} diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtDiffer.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtDiffer.kt deleted file mode 100644 index f1332923..00000000 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtDiffer.kt +++ /dev/null @@ -1,28 +0,0 @@ -package app.passwordstore.gradle.ktfmt - -import com.github.difflib.DiffUtils -import com.github.difflib.patch.ChangeDelta -import com.github.difflib.patch.DeleteDelta -import com.github.difflib.patch.InsertDelta -import java.io.File - -object KtfmtDiffer { - fun computeDiff( - inputFile: File, - formattedCode: String, - pathNormalizer: (File) -> String, - ): List<KtfmtDiffEntry> { - val originCode = inputFile.readText() - return DiffUtils.diff(originCode, formattedCode, null).deltas.map { - val line = it.source.position + 1 - val message: String = - when (it) { - is ChangeDelta -> "Line changed: ${it.source.lines.first()}" - is DeleteDelta -> "Line deleted" - is InsertDelta -> "Line added" - else -> "" - } - KtfmtDiffEntry(pathNormalizer(inputFile), line, message) - } - } -} diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtFormatTask.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtFormatTask.kt deleted file mode 100644 index c76b5c89..00000000 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtFormatTask.kt +++ /dev/null @@ -1,46 +0,0 @@ -package app.passwordstore.gradle.ktfmt - -import javax.inject.Inject -import org.gradle.api.GradleException -import org.gradle.api.file.ProjectLayout -import org.gradle.api.tasks.SourceTask -import org.gradle.api.tasks.TaskAction -import org.gradle.internal.exceptions.MultiCauseException -import org.gradle.workers.WorkerExecutor -import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty - -abstract class KtfmtFormatTask -@Inject -constructor(private val workerExecutor: WorkerExecutor, private val projectLayout: ProjectLayout) : - SourceTask() { - - @TaskAction - fun execute() { - val result = - with(workerExecutor.noIsolation()) { - submit(KtfmtWorkerAction::class.java) { - name.set("ktfmt-worker") - files.from(source) - projectDirectory.set(projectLayout.projectDirectory.asFile) - } - runCatching { await() } - } - - result.exceptionOrNull()?.workErrorCauses<Exception>()?.ifNotEmpty { - forEach { logger.error(it.message, it.cause) } - throw GradleException("error formatting sources for $name") - } - } - - private inline fun <reified T : Throwable> Throwable.workErrorCauses(): List<Throwable> { - return when (this) { - is MultiCauseException -> this.causes.map { it.cause } - else -> listOf(this.cause) - } - .filter { - // class instance comparison doesn't work due to different classloaders - it?.javaClass?.canonicalName == T::class.java.canonicalName - } - .filterNotNull() - } -} diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtWorkerAction.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtWorkerAction.kt deleted file mode 100644 index c955adbe..00000000 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtWorkerAction.kt +++ /dev/null @@ -1,38 +0,0 @@ -package app.passwordstore.gradle.ktfmt - -import app.passwordstore.gradle.KtfmtPlugin -import com.facebook.ktfmt.format.Formatter -import java.io.File -import org.gradle.api.logging.LogLevel -import org.gradle.api.logging.Logger -import org.gradle.api.logging.Logging -import org.gradle.internal.logging.slf4j.DefaultContextAwareTaskLogger -import org.gradle.workers.WorkAction - -abstract class KtfmtWorkerAction : WorkAction<KtfmtWorkerParameters> { - private val logger: Logger = - DefaultContextAwareTaskLogger(Logging.getLogger(KtfmtFormatTask::class.java)) - private val files: List<File> = parameters.files.toList() - private val projectDirectory: File = parameters.projectDirectory.asFile.get() - private val name: String = parameters.name.get() - - override fun execute() { - try { - files.forEach { file -> - val sourceText = file.readText() - val relativePath = file.toRelativeString(projectDirectory) - - logger.log(LogLevel.DEBUG, "$name checking format: $relativePath") - - val formattedText = Formatter.format(KtfmtPlugin.DEFAULT_FORMATTING_OPTIONS, sourceText) - - if (!formattedText.contentEquals(sourceText)) { - logger.log(LogLevel.QUIET, "${file.toRelativeString(projectDirectory)}: Format fixed") - file.writeText(formattedText) - } - } - } catch (t: Throwable) { - throw Exception("format worker execution error", t) - } - } -} diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtWorkerParameters.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtWorkerParameters.kt deleted file mode 100644 index 1c550de8..00000000 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtWorkerParameters.kt +++ /dev/null @@ -1,12 +0,0 @@ -package app.passwordstore.gradle.ktfmt - -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.Property -import org.gradle.workers.WorkParameters - -interface KtfmtWorkerParameters : WorkParameters { - val name: Property<String> - val files: ConfigurableFileCollection - val projectDirectory: RegularFileProperty -} diff --git a/build.gradle.kts b/build.gradle.kts index aac6cf2b..8e6bedb4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,6 @@ plugins { id("com.github.android-password-store.git-hooks") id("com.github.android-password-store.kotlin-common") - id("com.github.android-password-store.ktfmt") + id("com.github.android-password-store.spotless") id("com.github.android-password-store.versions") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 947c2983..45f63a68 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,7 +39,6 @@ build-diffutils = "io.github.java-diff-utils:java-diff-utils:4.12" build-download = "de.undercouch:gradle-download-task:5.6.0" build-javapoet = "com.squareup:javapoet:1.13.0" build-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } -build-ktfmt = "com.facebook:ktfmt:0.51" build-mavenpublish = "com.vanniktech:gradle-maven-publish-plugin:0.29.0" build-metalava = "me.tylerbwong.gradle.metalava:plugin:0.3.5" build-moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" } @@ -48,6 +47,7 @@ build-okhttp = "com.squareup.okhttp3:okhttp:5.0.0-alpha.14" build-r8 = "com.android.tools:r8:8.4.6-dev" build-semver = "com.github.zafarkhaja:java-semver:0.10.2" build-sentry = "io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin:4.11.0" +build-spotless = "com.diffplug.spotless:spotless-plugin-gradle:6.25.0" build-vcu = "nl.littlerobots.version-catalog-update:nl.littlerobots.version-catalog-update.gradle.plugin:0.8.4" compose-bom = "androidx.compose:compose-bom:2024.06.00" compose-foundation-core = { module = "androidx.compose.foundation:foundation" } diff --git a/scripts/pre-push-hook.sh b/scripts/pre-push-hook.sh index 65ea0682..e228815c 100644 --- a/scripts/pre-push-hook.sh +++ b/scripts/pre-push-hook.sh @@ -13,6 +13,6 @@ while read -r local_ref local_oid remote_ref remote_oid; do _=$remote_ref _=$remote_oid if [ "${local_oid}" != "${ZERO}" ]; then - CI=true "${GRADLE_EXEC}" metalavaCheckCompatibilityRelease lint ktfmtCheck test -PslimTests + CI=true "${GRADLE_EXEC}" metalavaCheckCompatibilityRelease lint spotlessCheck test -PslimTests fi done |