Switch to java-poet for codegen. This is a breaking change
ci/woodpecker/push/gradle Pipeline was successful Details
ci/woodpecker/push/pages Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2022-11-13 12:16:12 +01:00
parent 50574d5cd4
commit 20cd8e7d7f
Signed by: Johannes
GPG Key ID: E76429612C2929F4
8 changed files with 149 additions and 209 deletions

View File

@ -1,2 +1,2 @@
group = "io.gitlab.jfronny" group = "io.gitlab.jfronny"
version = "1.1-SNAPSHOT" version = "1.2-SNAPSHOT"

View File

@ -4,4 +4,5 @@ plugins {
dependencies { dependencies {
implementation("org.eclipse.jgit:org.eclipse.jgit:[6.0, 7.0)") implementation("org.eclipse.jgit:org.eclipse.jgit:[6.0, 7.0)")
implementation("com.squareup:javapoet:1.13.0")
} }

View File

@ -1,21 +1,154 @@
@file:Suppress("unused", "HasPlatformType")
package io.gitlab.jfronny.scripts package io.gitlab.jfronny.scripts
import io.gitlab.jfronny.scripts.codegen.ContentGenerator import com.squareup.javapoet.*
import org.gradle.api.Action import org.gradle.api.Action
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.SourceSet
import org.gradle.kotlin.dsl.extra import org.gradle.kotlin.dsl.extra
import java.lang.reflect.Type
import java.util.function.Supplier
import javax.lang.model.element.Element
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.Modifier
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.TypeMirror
import javax.lang.model.util.Types
val Project.codegenDir get() = buildDir.resolve("generated/sources/jfCodegen") val Project.codegenDir get() = buildDir.resolve("generated/sources/jfCodegen")
fun SourceSet.generate(project: Project, generate: Action<ContentGenerator>) { fun SourceSet.generate(project: Project, generate: Action<ContentGenerator>) {
val generators = project.extra["codeGenerators"] as LinkedHashMap<String, ContentGenerator.Generated> val generators = project.extra["codeGenerators"] as LinkedHashMap<String, ContentGenerator.Generated>
val exists = generators.containsKey(name) val exists = generators.containsKey(name)
val generator = ContentGenerator() val generator = ContentGenerator().run(generate)
generate.execute(generator)
generators[name] = if (exists) generators[name]!!.merge(generator.finalize()) else generator.finalize() generators[name] = if (exists) generators[name]!!.merge(generator.finalize()) else generator.finalize()
if (!exists) { if (!exists) {
java.srcDir(project.codegenDir.resolve("java/$name")) java.srcDir(project.codegenDir.resolve("java/$name"))
resources.srcDir(project.codegenDir.resolve("resources/$name")) resources.srcDir(project.codegenDir.resolve("resources/$name"))
} }
} }
class ContentGenerator {
private var finalized = false
data class Generated(val classes: Map<String, String>, val resources: Map<String, ByteArray>) {
fun merge(other: Generated): Generated = Generated(classes + other.classes, resources + other.resources)
}
private data class ImmutableMap<Key, Value>(private val inner: Map<Key, Value>): Map<Key, Value> by inner
fun finalize(): Generated {
val fin = Generated(ImmutableMap(classes), ImmutableMap(resources))
finalized = true
return fin
}
private fun <T> ensureMutable(action: () -> T): T {
if (finalized) throw IllegalAccessException("Attempted to access CodeGenerator after it was finalized")
return action()
}
private val classes: MutableMap<String, String> = LinkedHashMap()
private val resources: MutableMap<String, ByteArray> = LinkedHashMap()
fun `class`(`package`: String, name: String, generator: Action<TypeSpec.Builder>) = ensureMutable {
check(`package`, "package", packagePattern)
check(name, "class name", classNamePattern)
val builder = TypeSpec.classBuilder(ClassName.get(`package`, name))
generator.execute(builder)
val javaFile: JavaFile = JavaFile.builder(`package`, builder.build())
.skipJavaLangImports(true)
.indent(" ")
.addFileComment("Automatically generated through JfCodegen, do not edit!")
.build()
classes["${`package`.replace('.', '/')}/$name.java"] = javaFile.toString()
}
fun text(path: String, generator: Action<StringBuilder>) = ensureMutable {
check(path, "path", pathPattern)
val builder = StringBuilder()
generator.execute(builder)
resources[path] = builder.toString().toByteArray()
}
fun blob(path: String, generator: Supplier<ByteArray>) = ensureMutable {
check(path, "path", pathPattern)
resources[path] = generator.get()
}
companion object {
@JvmStatic private val packagePattern = Regex("(?:[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*)")
@JvmStatic private val classNamePattern = Regex("(?:[A-Z][A-Za-z0-9_\$]*)")
@JvmStatic private val pathPattern = Regex("(?:([a-z]+/)*[a-zA-Z][a-zA-Z0-9_.]*)")
@JvmStatic
private fun check(name: String, type: String, pattern: Regex) {
if (!pattern.matches(name)) throw IllegalArgumentException("$type \"$name\" is not a valid $type")
}
}
}
// Extensions for TypeSpec.Builder
fun TypeSpec.Builder.javadoc(`import`: String) = addJavadoc(import)
fun TypeSpec.Builder.javadoc(generate: Action<CodeBlock.Builder>) = addJavadoc(CodeBlock.builder().run(generate).build())
fun TypeSpec.Builder.annotation(annotation: Class<*>, generate: Action<AnnotationSpec.Builder>? = null) = addAnnotation(AnnotationSpec.builder(annotation).run(generate).build())
fun TypeSpec.Builder.annotation(annotation: ClassName, generate: Action<AnnotationSpec.Builder>? = null) = addAnnotation(AnnotationSpec.builder(annotation).run(generate).build())
fun TypeSpec.Builder.modifiers(vararg modifiers: Modifier) = addModifiers(*modifiers)
fun TypeSpec.Builder.typeVariable(typeVariable: TypeVariableName) = addTypeVariable(typeVariable)
fun TypeSpec.Builder.superInterface(superInterface: TypeName) = addSuperinterface(superInterface)
fun TypeSpec.Builder.superInterface(superInterface: Type) = addSuperinterface(superInterface)
fun TypeSpec.Builder.superInterface(superInterface: Type, avoidNestedTypeNameClashes: Boolean) = addSuperinterface(superInterface, avoidNestedTypeNameClashes)
fun TypeSpec.Builder.superInterface(superInterface: TypeMirror) = addSuperinterface(superInterface)
fun TypeSpec.Builder.superInterface(superInterface: TypeMirror, avoidNestedTypeNameClashes: Boolean) = addSuperinterface(superInterface, avoidNestedTypeNameClashes)
fun TypeSpec.Builder.enumConstant(name: String) = addEnumConstant(name)
fun TypeSpec.Builder.enumConstant(name: String, typeSpec: TypeSpec) = addEnumConstant(name, typeSpec)
fun TypeSpec.Builder.field(type: Type, name: String, vararg modifiers: Modifier, generate: Action<FieldSpec.Builder>? = null) = addField(FieldSpec.builder(type, name, *modifiers).run(generate).build())
fun TypeSpec.Builder.field(type: TypeName, name: String, vararg modifiers: Modifier, generate: Action<FieldSpec.Builder>? = null) = addField(FieldSpec.builder(type, name, *modifiers).run(generate).build())
fun TypeSpec.Builder.field(name: String, value: Boolean, vararg modifiers: Modifier) = field(Boolean::class.java, name, *modifiers) { initializer("$value") }
fun TypeSpec.Builder.field(name: String, value: Short, vararg modifiers: Modifier) = field(Short::class.java, name, *modifiers) { initializer("$value") }
fun TypeSpec.Builder.field(name: String, value: Int, vararg modifiers: Modifier) = field(Int::class.java, name, *modifiers) { initializer("$value") }
fun TypeSpec.Builder.field(name: String, value: Long, vararg modifiers: Modifier) = field(Long::class.java, name, *modifiers) { initializer("${value}L") }
fun TypeSpec.Builder.field(name: String, value: Float, vararg modifiers: Modifier) = field(Float::class.java, name, *modifiers) { initializer("${value}f") }
fun TypeSpec.Builder.field(name: String, value: Double, vararg modifiers: Modifier) = field(Double::class.java, name, *modifiers) { initializer("$value") }
fun TypeSpec.Builder.field(name: String, value: String, vararg modifiers: Modifier) = field(String::class.java, name, *modifiers) { initializer("\$S", value) }
fun TypeSpec.Builder.static(generate: Action<CodeBlock.Builder>) = addStaticBlock(CodeBlock.builder().run(generate).build())
fun TypeSpec.Builder.initializer(generate: Action<CodeBlock.Builder>) = addInitializerBlock(CodeBlock.builder().run(generate).build())
fun TypeSpec.Builder.method(name: String, generate: Action<MethodSpec.Builder>) = addMethod(MethodSpec.methodBuilder(name).run(generate).build())
fun TypeSpec.Builder.override(element: ExecutableElement, generate: Action<MethodSpec.Builder>) = addMethod(MethodSpec.overriding(element).run(generate).build())
fun TypeSpec.Builder.override(element: ExecutableElement, enclosing: DeclaredType, types: Types, generate: Action<MethodSpec.Builder>) = addMethod(MethodSpec.overriding(element, enclosing, types).run(generate).build())
fun TypeSpec.Builder.constructor(generate: Action<MethodSpec.Builder>) = addMethod(MethodSpec.constructorBuilder().run(generate).build())
fun TypeSpec.Builder.innerEnum(name: String, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.enumBuilder(name).run(generate).build())
fun TypeSpec.Builder.innerInterface(name: String, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.interfaceBuilder(name).run(generate).build())
fun TypeSpec.Builder.innerClass(name: String, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.classBuilder(name).run(generate).build())
fun TypeSpec.Builder.innerAnnotation(name: String, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.annotationBuilder(name).run(generate).build())
fun TypeSpec.Builder.innerEnum(name: ClassName, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.enumBuilder(name).run(generate).build())
fun TypeSpec.Builder.innerInterface(name: ClassName, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.interfaceBuilder(name).run(generate).build())
fun TypeSpec.Builder.innerClass(name: ClassName, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.classBuilder(name).run(generate).build())
fun TypeSpec.Builder.innerAnnotation(name: ClassName, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.annotationBuilder(name).run(generate).build())
fun TypeSpec.Builder.origin(origin: Element) = addOriginatingElement(origin)
// Extensions for AnnotationSpec.Builder
fun AnnotationSpec.Builder.member(name: String, format: String, vararg args: Any) = addMember(name, format, args)
fun AnnotationSpec.Builder.member(name: String, generate: Action<CodeBlock.Builder>) = addMember(name, CodeBlock.builder().run(generate).build())
// Extensions for MethodSpec.Builder
fun MethodSpec.Builder.javadoc(`import`: String) = addJavadoc(import)
fun MethodSpec.Builder.javadoc(generate: Action<CodeBlock.Builder>) = addJavadoc(CodeBlock.builder().run(generate).build())
fun MethodSpec.Builder.annotation(annotation: Class<*>, generate: Action<AnnotationSpec.Builder>? = null) = addAnnotation(AnnotationSpec.builder(annotation).run(generate).build())
fun MethodSpec.Builder.annotation(annotation: ClassName, generate: Action<AnnotationSpec.Builder>? = null) = addAnnotation(AnnotationSpec.builder(annotation).run(generate).build())
fun MethodSpec.Builder.modifiers(vararg modifiers: Modifier) = addModifiers(*modifiers)
fun MethodSpec.Builder.typeVariable(typeVariable: TypeVariableName) = addTypeVariable(typeVariable)
fun MethodSpec.Builder.parameter(type: Type, name: String, vararg modifiers: Modifier, generate: Action<ParameterSpec.Builder>) = addParameter(ParameterSpec.builder(type, name, *modifiers).run(generate).build())
fun MethodSpec.Builder.parameter(type: TypeName, name: String, vararg modifiers: Modifier, generate: Action<ParameterSpec.Builder>) = addParameter(ParameterSpec.builder(type, name, *modifiers).run(generate).build())
fun MethodSpec.Builder.exception(exception: Type) = addException(exception)
fun MethodSpec.Builder.exception(exception: TypeName) = addException(exception)
fun MethodSpec.Builder.code(generate: Action<CodeBlock.Builder>) = addCode(CodeBlock.builder().run(generate).build())
fun MethodSpec.Builder.defaultValue(generate: Action<CodeBlock.Builder>) = defaultValue(CodeBlock.builder().run(generate).build())
// Extensions for ParameterSpec.Builder
fun ParameterSpec.Builder.javadoc(`import`: String) = addJavadoc(import)
fun ParameterSpec.Builder.javadoc(generate: Action<CodeBlock.Builder>) = addJavadoc(CodeBlock.builder().run(generate).build())
fun ParameterSpec.Builder.annotation(annotation: Class<*>, generate: Action<AnnotationSpec.Builder>? = null) = addAnnotation(AnnotationSpec.builder(annotation).run(generate).build())
fun ParameterSpec.Builder.annotation(annotation: ClassName, generate: Action<AnnotationSpec.Builder>? = null) = addAnnotation(AnnotationSpec.builder(annotation).run(generate).build())
fun ParameterSpec.Builder.modifiers(vararg modifiers: Modifier) = addModifiers(*modifiers)

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.scripts package io.gitlab.jfronny.scripts
import org.gradle.api.Action
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.Task import org.gradle.api.Task
@ -26,4 +27,10 @@ val TaskContainer.deployRelease: Task get() = findByName("deployRelease") ?: reg
fun Project.prop(name: String, default: String? = null): String = fun Project.prop(name: String, default: String? = null): String =
if (default == null || hasProperty(name)) property(name).toString() if (default == null || hasProperty(name)) property(name).toString()
else default else default
// Utility to run actions on values
fun <T> T.run(action: Action<T>?): T {
action?.execute(this!!)
return this
}

View File

@ -1,129 +0,0 @@
package io.gitlab.jfronny.scripts.codegen
class ClassGenerator(`package`: String, val name: String, val indent: String = " ") : Generator<String>() {
override fun generateFinalized() = ensureBody { gen.appendLine("}").toString() }
private val gen: StringBuilder = StringBuilder("package $`package`;\n\n")
private var headerGenerated = false
private var extends: String? = null
private val implements: MutableList<String> = ArrayList()
private var hadImport = false
private fun <T> ensureHeader(action: () -> T): T = ensureMutable {
if (headerGenerated) throw IllegalAccessException("Attempted to generate pre-header statement while in body")
action()
}
private fun <T> ensureBody(action: () -> T): T = ensureMutable {
if (!headerGenerated) {
headerGenerated = true
if (hadImport) gen.appendLine()
gen.append("public class $name")
if (extends != null) gen.append(" extends $extends")
if (implements.isNotEmpty()) gen.append(" implements ").append(implements.joinToString(", ") { it })
gen.append(" {")
gen.appendLine()
}
action()
}
fun rawHead(toWrite: String): Unit = ensureHeader { gen.appendLine(toWrite.trimIndent().trim('\n').prependIndent(indent)) }
fun rawBody(toWrite: String): Unit = ensureBody { gen.appendLine(toWrite.trimIndent().trim('\n').prependIndent(indent)) }
fun import(`import`: String): Unit = ensureHeader {
check(import, "import", importPattern)
gen.appendLine("import $import;")
hadImport = true
}
fun extends(extends: String): Unit = ensureHeader {
check(extends, "extended class", classPattern)
if (this.extends != null) throw IllegalAccessException("Attempted to extend multiple classes")
this.extends = extends
}
fun implements(implements: String): Unit = ensureHeader {
check(implements, "implemented interface", classPattern)
this.implements.add(implements)
}
fun fieldRaw(name: String, type: String, valueRaw: String, modifiers: String = defaultModifiers): Unit = ensureBody {
check(name, "field", classEntryPattern)
check(type, "field type", classPattern)
check(modifiers, "set of modifiers", modifierPattern)
gen.appendLine("$indent$modifiers $type $name = $valueRaw;")
}
fun field(name: String, value: Boolean, modifiers: String = defaultModifiers) = fieldRaw(name, "boolean", value.toString(), modifiers)
fun field(name: String, value: Short, modifiers: String = defaultModifiers) = fieldRaw(name, "short", value.toString(), modifiers)
fun field(name: String, value: Int, modifiers: String = defaultModifiers) = fieldRaw(name, "int", value.toString(), modifiers)
fun field(name: String, value: Long, modifiers: String = defaultModifiers) = fieldRaw(name, "long", value.toString() + "L", modifiers)
fun field(name: String, value: Float, modifiers: String = defaultModifiers) = fieldRaw(name, "float", value.toString() + "f", modifiers)
fun field(name: String, value: Double, modifiers: String = defaultModifiers) = fieldRaw(name, "double", value.toString(), modifiers)
fun field(name: String, value: String, modifiers: String = defaultModifiers) = fieldRaw(name, "String", enquote(value), modifiers)
fun procedure(name: String, argument: Map<String, String>, modifiers: String = defaultModifiers, body: String, throws: String? = null): Unit = ensureBody {
check(name, "procedure", classEntryPattern)
if (argument.any { !classEntryPattern.matches(it.key) || !classPattern.matches(it.value) }) throw IllegalArgumentException("invalid argument on procedure")
check(modifiers, "set of modifiers", modifierPattern)
ensureBrackets(body)
if (throws != null) check(throws, "thrown exceptions list", classListPattern)
gen.append(indent)
gen.append(modifiers)
gen.append(" void ")
gen.append(name)
gen.append("(")
gen.append(argument.entries.joinToString(", ") { "${it.key} ${it.value}" })
gen.append(") ")
if (throws != null) gen.append(throws).append(' ')
gen.append('{')
gen.appendLine()
gen.appendLine(body.trimIndent().prependIndent("$indent "))
gen.appendLine("$indent}")
}
fun function(name: String, returnType: String, argument: Map<String, String>, modifiers: String = defaultModifiers, body: String, throws: String? = null): Unit = ensureBody {
check(name, "procedure", classEntryPattern)
check(returnType, "return type", classPattern)
if (argument.any { !classEntryPattern.matches(it.key) || !classPattern.matches(it.value) }) throw IllegalArgumentException("invalid argument on procedure")
check(modifiers, "set of modifiers", modifierPattern)
ensureBrackets(body)
if (throws != null) check(throws, "thrown exceptions list", classListPattern)
gen.append(indent)
gen.append(modifiers)
gen.append(" $returnType ")
gen.append(name)
gen.append("(")
gen.append(argument.entries.joinToString(", ") { "${it.key} ${it.value}" })
gen.append(") ")
if (throws != null) gen.append(throws).append(' ')
gen.append('{')
gen.appendLine()
gen.appendLine(body.trimIndent().prependIndent("$indent "))
gen.appendLine("$indent}")
}
companion object {
@JvmStatic private val defaultModifiers = "public static"
@JvmStatic private fun enquote(value: String): String = "\"${value.replace("\\", "\\\\").replace("\"", "\\\"")}\""
@JvmStatic private fun ensureBrackets(value: String): Unit {
var state = 0 // 0 -> outside of anything, 1 -> inside string, 2 -> inside block quote
var brackets = 0
value.forEachIndexed { i, c ->
when (state) {
0 -> {
if (c == '"') state = if (i + 2 < value.length && value[i+1] == '"' && value[i+2] == '"') 2 else 1
if (c == '{') brackets++
if (c == '}') brackets--
}
1 -> {
if (c == '"') state = 0
}
2 -> {
if (c == '"' && i + 2 < value.length && value[i+1] == '"' && value[i+2] == '"') state = 0
}
}
}
if (brackets != 0) throw IllegalArgumentException("Unclosed brackets in method body")
}
}
}

View File

@ -1,34 +0,0 @@
package io.gitlab.jfronny.scripts.codegen
import org.gradle.api.Action
import java.util.function.Supplier
class ContentGenerator : Generator<ContentGenerator.Generated>() {
private val classes: MutableMap<String, String> = LinkedHashMap()
private val resources: MutableMap<String, ByteArray> = LinkedHashMap()
override fun generateFinalized() = Generated(ImmutableMap(classes), ImmutableMap(resources))
data class Generated(val classes: Map<String, String>, val resources: Map<String, ByteArray>) {
fun merge(other: Generated): Generated = Generated(classes + other.classes, resources + other.resources)
}
fun `class`(`package`: String, name: String, generator: Action<ClassGenerator>) = ensureMutable {
if (!packagePattern.matches(`package`)) throw IllegalArgumentException("package \"$`package`\" is not a valid package")
if (!classNamePattern.matches(name)) throw IllegalArgumentException("class name \"$name\" is not a valid class name")
val builder = ClassGenerator(`package`, name)
generator.execute(builder)
classes["${`package`.replace('.', '/')}/$name.java"] = builder.finalize()
}
fun text(path: String, generator: Action<StringBuilder>) = ensureMutable {
if (!pathPattern.matches(path)) throw IllegalArgumentException("path \"$path\" is not a valid path")
val builder = StringBuilder()
generator.execute(builder)
resources[path] = builder.toString().toByteArray()
}
fun blob(path: String, generator: Supplier<ByteArray>) = ensureMutable {
if (!pathPattern.matches(path)) throw IllegalArgumentException("path \"$path\" is not a valid path")
resources[path] = generator.get()
}
}

View File

@ -1,36 +0,0 @@
package io.gitlab.jfronny.scripts.codegen
abstract class Generator<T> {
private var finalized = false
protected abstract fun generateFinalized(): T
fun finalize(): T {
val fin = generateFinalized()
finalized = true
return fin
}
protected fun <T> ensureMutable(action: () -> T): T {
if (finalized) throw IllegalAccessException("Attempted to access CodeGenerator after it was finalized")
return action()
}
protected data class ImmutableMap<Key, Value>(private val inner: Map<Key, Value>): Map<Key, Value> by inner
companion object {
@JvmStatic protected val primitivePattern = Regex("(?:byte|short|int|long|float|double|boolean|char)")
@JvmStatic protected val singleModifierPattern = Regex("(?:public|private|protected|static|final|abstract|transient|synchronized|volatile)")
@JvmStatic protected val modifierPattern = Regex("(?:$singleModifierPattern(?: $singleModifierPattern)*)")
@JvmStatic protected val packagePattern = Regex("(?:[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*)")
@JvmStatic protected val classNamePattern = Regex("(?:[A-Z][A-Za-z0-9_\$]*)")
@JvmStatic protected val pathPattern = Regex("(?:([a-z]+/)*[a-zA-Z][a-zA-Z0-9_.]*)")
@JvmStatic protected val classPattern = Regex("(?:(?:(?:$packagePattern\\.)?$classNamePattern)|$primitivePattern)")
@JvmStatic protected val importPattern = Regex("(?:$packagePattern\\.(?:\\*|$classNamePattern))")
@JvmStatic protected val classEntryPattern = Regex("(?:[a-zA-Z][a-zA-Z_\$0-9]*)")
@JvmStatic protected val classListPattern = Regex("(?:$classPattern(?:, $classPattern)*)")
@JvmStatic protected fun check(name: String, type: String, pattern: Regex): Unit {
if (!pattern.matches(name)) throw IllegalArgumentException("$type \"$name\" is not a valid $type")
}
}
}

View File

@ -1,16 +1,14 @@
import gradle.kotlin.dsl.accessors._72efc76fad8c8cf3476d335fb6323bde.*
import io.gitlab.jfronny.scripts.codegen.ContentGenerator.Generated
import io.gitlab.jfronny.scripts.* import io.gitlab.jfronny.scripts.*
plugins { plugins {
java java
} }
extra["codeGenerators"] = LinkedHashMap<String, Generated>() extra["codeGenerators"] = LinkedHashMap<String, ContentGenerator.Generated>()
val jfCodegen by tasks.registering { val jfCodegen by tasks.registering {
doLast { doLast {
val generators = project.extra["codeGenerators"] as LinkedHashMap<String, Generated> val generators = project.extra["codeGenerators"] as LinkedHashMap<String, ContentGenerator.Generated>
if (codegenDir.exists()) codegenDir.deleteRecursively() if (codegenDir.exists()) codegenDir.deleteRecursively()
generators.forEach { (name, generated) -> generators.forEach { (name, generated) ->
generated.classes.forEach { (filePath, content) -> generated.classes.forEach { (filePath, content) ->