refactor: use worker API for KtfmtFormatTask

This commit is contained in:
Harsh Shandilya 2023-03-04 02:01:15 +05:30
parent 3a694c7255
commit 3e56fa2e12
No known key found for this signature in database
4 changed files with 104 additions and 52 deletions

View file

@ -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<Project> {
override fun apply(target: Project) {
target.tasks.register<KtfmtFormatTask>("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<KtfmtFormatTask>("ktfmtFormat") { source(input) }
target.tasks.register<KtfmtCheckTask>("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)
}
}

View file

@ -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<Exception>()?.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 <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()
}
companion object {

View file

@ -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<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(
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)
}
}
}

View file

@ -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<String>
val files: ConfigurableFileCollection
val projectDirectory: RegularFileProperty
val output: RegularFileProperty
}