migrate buildSrc plugins to includedBuild plugins

This commit is contained in:
Adam 2022-09-18 20:49:32 +02:00
parent 805d4c8170
commit f12a505d22
40 changed files with 159 additions and 112 deletions

View file

@ -0,0 +1,81 @@
package kafkatorio.tasks
import kafkatorio.extensions.filesChecksum
import javax.inject.Inject
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.LogLevel
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.property
import org.gradle.process.ExecOperations
import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
@CacheableTask
@Suppress("UnstableApiUsage")
abstract class DockerComposeExec @Inject constructor(
private val executor: ExecOperations,
private val objects: ObjectFactory,
private val providers: ProviderFactory,
) : DefaultTask() {
@get:InputDirectory
@get:SkipWhenEmpty
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:NormalizeLineEndings
abstract val dockerComposeDir: DirectoryProperty
@get:Input
abstract val command: Property<String>
@get:Input
val dockerIsActive: Property<Boolean> = objects.property<Boolean>().convention(
providers.exec {
commandLine = parseSpaceSeparatedArgs("docker info")
isIgnoreExitValue = true
}.result.map {
it.exitValue == 0
}
)
@get:OutputFile
val stateFile: RegularFileProperty = objects.fileProperty().convention {
temporaryDir.resolve("docker-files.md5")
}
init {
group = DOCKER_COMPOSE_GROUP
logging.captureStandardOutput(LogLevel.LIFECYCLE)
super.onlyIf { task ->
require(task is DockerComposeExec)
task.dockerIsActive.getOrElse(false)
}
}
@TaskAction
fun exec() {
executor.exec {
workingDir = dockerComposeDir.asFile.get()
commandLine = parseSpaceSeparatedArgs(command.get())
}
val inputChecksum = dockerComposeDir.get().filesChecksum()
stateFile.get().asFile.writeText("$inputChecksum")
}
companion object {
const val DOCKER_COMPOSE_GROUP: String = "docker-compose"
}
}

View file

@ -0,0 +1,27 @@
package kafkatorio.tasks
import javax.inject.Inject
import kafkatorio.extensions.DOCKER_COMPOSE_TASK_GROUP
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.LogLevel
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.WriteProperties
@CacheableTask
open class DockerEnvUpdateTask @Inject constructor(
private val objects: ObjectFactory
) : WriteProperties() {
@Internal
val dotEnvFile: RegularFileProperty = objects.fileProperty()
init {
group = DOCKER_COMPOSE_TASK_GROUP
super.setOutputFile(dotEnvFile)
super.getLogging().captureStandardOutput(LogLevel.LIFECYCLE)
}
}

View file

@ -0,0 +1,37 @@
package kafkatorio.tasks
import javax.inject.Inject
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.work.DisableCachingByDefault
@DisableCachingByDefault(because = "caching makes regenerating more difficult")
abstract class GenerateTypeScriptTask @Inject constructor(
private val fileOps: FileSystemOperations,
) : JavaExec() {
@get:OutputDirectory
abstract val output: DirectoryProperty
init {
group = "kt2ts"
}
@TaskAction
fun generate() {
fileOps.delete { delete(temporaryDir) }
temporaryDir.mkdirs()
super.exec()
fileOps.sync {
from(temporaryDir)
into(output)
include("**/*.ts")
}
}
}

View file

@ -0,0 +1,59 @@
package kafkatorio.tasks
import kafkatorio.tasks.UpdatePackageJson.Companion.foldParseToJsonObject
import kafkatorio.tasks.UpdatePackageJson.Companion.parseToJsonObject
import kotlinx.serialization.json.JsonObject
import org.gradle.api.Task
import org.gradle.api.internal.specs.ExplainingSpec
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
object JsonPropertiesUpToDateSpec : ExplainingSpec<Task> {
private val logger: Logger = Logging.getLogger(this::class.java)
override fun isSatisfiedBy(element: Task?): Boolean {
val reason = whyUnsatisfied(element)
if (reason != null) {
logger.lifecycle("JsonProperties not up to date:\n$reason")
}
return reason == null
}
// Returns a description explaining why the task is outdated,
// or null if the task is up to date
override fun whyUnsatisfied(task: Task?): String? {
require(task is UpdatePackageJson) { "$task is not a UpdatePackageJson task" }
val current = task.packageJsonFile.asFile.orNull?.parseToJsonObject()
?: return "no package.json file"
val updates = task.expectedJsonUpdates.orNull?.toList()?.foldParseToJsonObject()
?: return "no package.json updates"
val expected = JsonObject(current + updates)
if (current != expected) {
return (current.keys + expected.keys)
.asSequence()
.distinct()
.sorted()
.mapNotNull {
val currentVal = current[it]
val expectedVal = expected[it]
if (currentVal == expectedVal) {
null
} else {
"'$it' - expected:$expectedVal, actual:$currentVal"
}
}.joinToString(separator = "\n\t", prefix = "package.json is not up to date.\n\t")
}
return null
}
}

View file

@ -0,0 +1,77 @@
package kafkatorio.tasks
import kafkatorio.extensions.execCapture
import org.gradle.api.DefaultTask
import org.gradle.api.logging.LogLevel
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
abstract class KafkaConsumerGroupsTask : DefaultTask() {
@get:Input
val bootstrapServer: String = "localhost:9092"
@get:Input
val kafkaDockerContainerName: String = "kafka"
@get:Internal
protected val bootstrapArg = "--bootstrap-server $bootstrapServer"
abstract class ResetAll : KafkaConsumerGroupsTask() {
@get:Input
@Language("Shell Script")
val resetCommand: String =
"""
/kafka/bin/kafka-consumer-groups.sh --execute --reset-offsets --all-topics --to-earliest $bootstrapArg
""".trimIndent()
@TaskAction
fun reset() {
getAllGroups().forEach { group ->
logger.lifecycle("Resetting group $group\n---")
dockerExec("$resetCommand --group $group")
logger.lifecycle("---")
}
}
}
abstract class DeleteAll : KafkaConsumerGroupsTask() {
@get:Input
@Language("Shell Script")
val deleteCommand: String =
"""
/kafka/bin/kafka-consumer-groups.sh --delete $bootstrapArg
""".trimIndent()
@TaskAction
fun reset() {
val groups = getAllGroups().joinToString(" ") { group -> "--group $group" }
dockerExec("$deleteCommand $groups")
}
}
@Internal
protected fun getAllGroups(): List<String> = dockerExec(
"""
/kafka/bin/kafka-consumer-groups.sh --list $bootstrapArg
""".trimIndent()
).lines()
.filter { it.trim().isNotBlank() }
// fun reset(applicationId: String) = """
// /kafka/bin/kafka-streams-application-reset.sh --application-id $applicationId --force --to-earliest
// """.trimIndent()
protected fun dockerExec(
@Language("Shell Script")
cmd: String
): String = project.execCapture {
logging.captureStandardOutput(LogLevel.LIFECYCLE)
commandLine = parseSpaceSeparatedArgs(""" docker exec $kafkaDockerContainerName $cmd """)
}
}

View file

@ -0,0 +1,39 @@
package kafkatorio.tasks
import java.io.ByteArrayOutputStream
import org.gradle.api.Task
import org.gradle.api.internal.specs.ExplainingSpec
import org.gradle.api.tasks.Exec
import org.gradle.process.ExecOperations
/** Checks if a process is running. Windows only. */
class ProcessRunningSpec(
private val executor: ExecOperations,
private val process: String,
private val ignoreCase: Boolean = true,
) : ExplainingSpec<Task> {
override fun isSatisfiedBy(element: Task?): Boolean = whyUnsatisfied(element) == null
override fun whyUnsatisfied(element: Task?): String? {
return when (element) {
null -> "task is null"
!is Exec -> "task ${this::class.simpleName} is not ${Exec::class.simpleName}"
else -> {
return ByteArrayOutputStream().use { outputStream ->
executor.exec {
commandLine("tasklist") // Windows only for now...
standardOutput = outputStream
}
if (outputStream.toString().contains(process, ignoreCase)) {
null
} else {
"found $process in tasklist"
}
}
}
}
}
}

View file

@ -0,0 +1,58 @@
package kafkatorio.tasks
import com.github.gradle.node.npm.task.NpmTask
import javax.inject.Inject
import org.gradle.api.file.ConfigurableFileTree
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.IgnoreEmptyDirectories
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.TaskAction
import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
@CacheableTask
abstract class TypescriptToLuaTask @Inject constructor(
private val fs: FileSystemOperations
) : NpmTask() {
@get:InputDirectory
@get:SkipWhenEmpty
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:NormalizeLineEndings
@get:IgnoreEmptyDirectories
abstract val sourceFiles: DirectoryProperty
@get:OutputDirectory
abstract val outputDirectory: DirectoryProperty
init {
super.setGroup(BasePlugin.BUILD_GROUP)
super.setDescription("Convert Typescript To Lua")
super.execOverrides { standardOutput = System.out }
super.npmCommand.set(listOf("run", "build"))
super.ignoreExitValue.set(false)
super.args.set(parseSpaceSeparatedArgs("-- --outDir $temporaryDir"))
}
@TaskAction
fun tstl() {
fs.delete { delete(temporaryDir) }
temporaryDir.mkdirs()
super.exec()
fs.sync {
from(temporaryDir)
into(outputDirectory)
}
}
}

View file

@ -0,0 +1,116 @@
package kafkatorio.tasks
import com.github.gradle.node.NodePlugin
import kafkatorio.*
import java.io.File
import javax.inject.Inject
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonObjectBuilder
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.jsonObject
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.support.useToRun
import org.jetbrains.kotlin.util.suffixIfNot
@CacheableTask
abstract class UpdatePackageJson @Inject constructor(
private val providers: ProviderFactory,
) : DefaultTask(),
ProviderFactory by providers {
/** Cumulative updates - will be combined one by one, then applied to [packageJsonFile] */
@get:Input
internal abstract val expectedJsonUpdates: ListProperty<String>
@get:OutputFile
abstract val packageJsonFile: RegularFileProperty
init {
group = NodePlugin.NPM_GROUP
description = "Update package.json properties"
outputs.upToDateWhen(JsonPropertiesUpToDateSpec)
}
@TaskAction
fun exec() {
val packageJsonFile = packageJsonFile.asFile.get()
val expectedJsonUpdates: List<String> = expectedJsonUpdates.get().toList()
logger.lifecycle("updating package.json ${packageJsonFile.canonicalFile}")
val expectedJson = expectedJsonUpdates.fold(JsonObject(emptyMap())) { acc, update ->
val updateJson = jsonMapper.parseToJsonElement(update).jsonObject
JsonObject(acc + updateJson)
}
val updatedPackageJson = JsonObject(
packageJsonFile.parseToJsonObject() + expectedJson
)
val packageJsonContentFormatted =
jsonMapper
.encodeToString(updatedPackageJson)
// change empty arrays/objects, because `npm install` also formats package.json,
// but slightly differently to kxs.
.replace(": {\n }", ": {}")
.replace(": [\n ]", ": []")
.suffixIfNot("\n")
logger.info(packageJsonContentFormatted)
packageJsonFile.writer().useToRun {
write(packageJsonContentFormatted)
}
}
/** Update [expectedJson] by merging [updateJsonProvider] with the existing value. */
fun updateExpectedJson(
expectedJsonUpdate: JsonObjectBuilder.() -> Unit
) {
val expectedJsonUpdateProvider = provider {
buildJsonObject(expectedJsonUpdate).toString()
}
expectedJsonUpdates.add(expectedJsonUpdateProvider)
}
companion object {
private val jsonMapper = Json {
prettyPrint = true
prettyPrintIndent = " "
}
internal fun File.parseToJsonObject(): JsonObject =
jsonMapper.parseToJsonElement(readText()).jsonObject
internal fun Property<String>.parseToJsonObject(): JsonObject =
jsonMapper.parseToJsonElement(get()).jsonObject
internal fun List<String>.foldParseToJsonObject(): JsonObject =
fold(JsonObject(emptyMap())) { acc, update ->
val updateJson = jsonMapper.parseToJsonElement(update).jsonObject
JsonObject(acc + updateJson)
}
}
}