diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/KtfmtPlugin.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/KtfmtPlugin.kt index 74e8ba95..c7ff2f06 100644 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/KtfmtPlugin.kt +++ b/build-logic/src/main/kotlin/app/passwordstore/gradle/KtfmtPlugin.kt @@ -2,6 +2,7 @@ package app.passwordstore.gradle import app.passwordstore.gradle.ktfmt.KtfmtCheckTask import app.passwordstore.gradle.ktfmt.KtfmtFormatTask +import java.util.concurrent.Callable import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.register @@ -9,23 +10,14 @@ import org.gradle.kotlin.dsl.register class KtfmtPlugin : Plugin { override fun apply(target: Project) { - target.tasks.register("ktfmtFormat") { - source = - project.layout.projectDirectory.asFileTree - .filter { file -> - file.extension == "kt" || - file.extension == "kts" && !file.canonicalPath.contains("build") - } - .asFileTree + val input = Callable { + target.layout.projectDirectory.asFileTree.filter { file -> + file.extension == "kt" || file.extension == "kts" && !file.canonicalPath.contains("build") + } } + target.tasks.register("ktfmtFormat") { source(input) } target.tasks.register("ktfmtCheck") { - source = - project.layout.projectDirectory.asFileTree - .filter { file -> - file.extension == "kt" || - file.extension == "kts" && !file.canonicalPath.contains("build") - } - .asFileTree + source(input) projectDirectory.set(target.layout.projectDirectory) } } 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 index 82ce4ca3..934d6171 100644 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtFormatTask.kt +++ b/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtFormatTask.kt @@ -1,52 +1,53 @@ package app.passwordstore.gradle.ktfmt -import com.facebook.ktfmt.format.Formatter -import com.facebook.ktfmt.format.FormattingOptions -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.file.FileCollection -import org.gradle.api.tasks.IgnoreEmptyDirectories -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.PathSensitive -import org.gradle.api.tasks.PathSensitivity +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 -@OptIn(ExperimentalCoroutinesApi::class) -abstract class KtfmtFormatTask : SourceTask() { +abstract class KtfmtFormatTask +@Inject +constructor( + private val workerExecutor: WorkerExecutor, + private val projectLayout: ProjectLayout, +) : SourceTask() { - @get:PathSensitive(PathSensitivity.RELATIVE) - @get:InputFiles - @get:IgnoreEmptyDirectories - protected val inputFiles: FileCollection - get() = super.getSource() + init { + outputs.upToDateWhen { false } + } @TaskAction fun execute() { - runBlocking(Dispatchers.IO.limitedParallelism(PARALLEL_TASK_LIMIT)) { - coroutineScope { inputFiles.map { async { formatFile(it) } }.awaitAll() } + val result = + with(workerExecutor.noIsolation()) { + submit(KtfmtWorkerAction::class.java) { + name.set("foofoo") + files.from(source) + projectDirectory.set(projectLayout.projectDirectory.asFile) + } + runCatching { await() } + } + + result.exceptionOrNull()?.workErrorCauses()?.ifNotEmpty { + forEach { logger.error(it.message, it.cause) } + throw GradleException("error formatting sources for $name") } } - private fun formatFile(input: File) { - val originCode = input.readText() - val formattedCode = - Formatter.format( - FormattingOptions( - style = FormattingOptions.Style.GOOGLE, - maxWidth = 100, - continuationIndent = 2, - ), - originCode - ) - if (originCode != formattedCode) { - input.writeText(formattedCode) - } + private inline fun Throwable.workErrorCauses(): List { + 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() } companion object { 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 new file mode 100644 index 00000000..d9b265b7 --- /dev/null +++ b/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtWorkerAction.kt @@ -0,0 +1,46 @@ +package app.passwordstore.gradle.ktfmt + +import com.facebook.ktfmt.format.Formatter +import com.facebook.ktfmt.format.FormattingOptions +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 { + private val logger: Logger = + DefaultContextAwareTaskLogger(Logging.getLogger(KtfmtFormatTask::class.java)) + private val files: List = 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( + FormattingOptions( + style = FormattingOptions.Style.GOOGLE, + maxWidth = 100, + continuationIndent = 2, + ), + 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 new file mode 100644 index 00000000..9d4a3e92 --- /dev/null +++ b/build-logic/src/main/kotlin/app/passwordstore/gradle/ktfmt/KtfmtWorkerParameters.kt @@ -0,0 +1,13 @@ +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 + val files: ConfigurableFileCollection + val projectDirectory: RegularFileProperty + val output: RegularFileProperty +}