diff options
author | Linnea Gräf <nea@nea.moe> | 2024-10-30 00:00:54 +0100 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2024-10-30 00:00:54 +0100 |
commit | 410f6a0dd1e5288df7c3fc90bd3937a97b2e6385 (patch) | |
tree | cc3db28a82d28dd59528aa3580878cf4fff8980a /gradle-plugin | |
download | mcautotranslations-410f6a0dd1e5288df7c3fc90bd3937a97b2e6385.tar.gz mcautotranslations-410f6a0dd1e5288df7c3fc90bd3937a97b2e6385.tar.bz2 mcautotranslations-410f6a0dd1e5288df7c3fc90bd3937a97b2e6385.zip |
Init
Diffstat (limited to 'gradle-plugin')
4 files changed, 248 insertions, 0 deletions
diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts new file mode 100644 index 0000000..caef680 --- /dev/null +++ b/gradle-plugin/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + kotlin("jvm") + id("java-gradle-plugin") +} + +dependencies { + implementation(kotlin("gradle-plugin-api")) + implementation(kotlin("stdlib")) + implementation("com.google.code.gson:gson:2.11.0") + implementation("org.ow2.asm:asm:9.7.1") + implementation(project(":annotations")) +} + +gradlePlugin { + plugins { + create("mcAutoTranslations") { + id = "moe.nea.mc-auto-translations" + displayName = "MC Auto Translation File Generation" + implementationClass = "moe.nea.mcautotranslations.gradle.MCAutoTranslationsGradlePlugin" + } + } + +} diff --git a/gradle-plugin/src/main/kotlin/moe/nea/mcautotranslations/gradle/MCAutoTranslationsExtension.kt b/gradle-plugin/src/main/kotlin/moe/nea/mcautotranslations/gradle/MCAutoTranslationsExtension.kt new file mode 100644 index 0000000..7367056 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/moe/nea/mcautotranslations/gradle/MCAutoTranslationsExtension.kt @@ -0,0 +1,7 @@ +package moe.nea.mcautotranslations.gradle + +abstract class MCAutoTranslationsExtension { + + fun translationFunction(name: String) {} // TODO: actual config + +} diff --git a/gradle-plugin/src/main/kotlin/moe/nea/mcautotranslations/gradle/MCAutoTranslationsGradlePlugin.kt b/gradle-plugin/src/main/kotlin/moe/nea/mcautotranslations/gradle/MCAutoTranslationsGradlePlugin.kt new file mode 100644 index 0000000..d954e64 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/moe/nea/mcautotranslations/gradle/MCAutoTranslationsGradlePlugin.kt @@ -0,0 +1,37 @@ +package moe.nea.mcautotranslations.gradle + +import moe.nea.mcautotranslation.`gradle-plugin`.BuildConfig +import org.gradle.api.Project +import org.gradle.api.provider.Provider +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin +import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact +import org.jetbrains.kotlin.gradle.plugin.SubpluginOption + +class MCAutoTranslationsGradlePlugin : KotlinCompilerPluginSupportPlugin { + override fun apply(target: Project) { + target.extensions.create("mcAutoTranslations", MCAutoTranslationsExtension::class.java) + } + + override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider<List<SubpluginOption>> { + val project = kotlinCompilation.target.project + val extension = project.extensions.getByType(MCAutoTranslationsExtension::class.java) + return project.provider { + listOf() // TODO: add plugin options from extension in here + } + } + + override fun getCompilerPluginId(): String { + return BuildConfig.KOTLIN_PLUGIN_ID + } + + override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact( + groupId = BuildConfig.KOTLIN_PLUGIN_GROUP, + artifactId = BuildConfig.KOTLIN_PLUGIN_ARTIFACT, + version = BuildConfig.KOTLIN_PLUGIN_VERSION, + ) + + override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean { + return true + } +}
\ No newline at end of file diff --git a/gradle-plugin/src/main/kotlin/moe/nea/mcautotranslations/gradle/MergeTranslations.kt b/gradle-plugin/src/main/kotlin/moe/nea/mcautotranslations/gradle/MergeTranslations.kt new file mode 100644 index 0000000..44cea35 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/moe/nea/mcautotranslations/gradle/MergeTranslations.kt @@ -0,0 +1,181 @@ +package moe.nea.mcautotranslations.gradle + +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import moe.nea.mcautotranslations.annotations.GatheredTranslation +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction +import org.gradle.work.Incremental +import org.gradle.work.InputChanges +import org.objectweb.asm.AnnotationVisitor +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.Type +import java.io.File + +abstract class CollectTranslations : DefaultTask() { + @get:InputFiles + @get:Incremental + abstract val baseTranslations: ConfigurableFileCollection + + @get:InputFiles + @get:Incremental + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val classes: ConfigurableFileCollection + + @get:OutputFile + abstract val outputFile: RegularFileProperty + + class Translations { + var baseTranslation: HashMap<String, HashMap<String, String>> = HashMap() + var inlineTranslations: HashMap<String, HashMap<String, String>> = HashMap() + } + + companion object { + val gson = Gson() + val mapType: TypeToken<HashMap<String, String>> = object : TypeToken<HashMap<String, String>>() {} + } + + @TaskAction + fun execute(inputs: InputChanges) { + val baseTranslationsDirty = inputs.getFileChanges(baseTranslations).any() + val outFile = outputFile.get().asFile + val outputExists = outFile.exists() + val canBeIncremental = outputExists && !baseTranslationsDirty + val baseTranslations: Translations = if (canBeIncremental) { + gson.fromJson(outFile.readText(), Translations::class.java) + } else { + val t = Translations() + baseTranslations.associateTo(t.baseTranslation) { + it.toString() to gson.fromJson(outFile.readText(), mapType) + } + t + } + val files: List<FileChange> = if (canBeIncremental) { + inputs.getFileChanges(classes).map { FileChange(it.file, it.normalizedPath) } + } else { + buildList { + classes.asFileTree.visit { + add(FileChange(it.file, it.path)) + } + } + } + files + .asSequence() + .filter { checkFile(it.file) } + .forEach { + val className = getClassName(it.relativePath) + if (it.file.exists()) { + parseClassAnnotations(it.file) + } else { + baseTranslations.inlineTranslations.remove(className) + } + } + outFile.writeText(gson.toJson(baseTranslations)) + } + + + private class KVVisitor(val map: MutableMap<String, String>) : AnnotationVisitor(Opcodes.ASM9) { + var value: String? = null + var key: String? = null + override fun visit(name: String, value: Any) { + when (name) { + "key" -> this.key = value as String + "value" -> this.value = value as String + else -> error("Unknown annotation element $name") + } + } + + override fun visitAnnotation(name: String?, descriptor: String?): AnnotationVisitor { + error("Unknown annotation element $name") + } + + override fun visitArray(name: String?): AnnotationVisitor { + error("Unknown annotation element $name") + } + + override fun visitEnum(name: String?, descriptor: String?, value: String?) { + error("Unknown annotation element $name") + } + + override fun visitEnd() { + map[key ?: error("Missing key")] = value ?: error("Missing value") + } + } + + private class RepeatableVisitor(val map: MutableMap<String, String>) : AnnotationVisitor(Opcodes.ASM9) { + override fun visitArray(name: String?): AnnotationVisitor { + require(name == "value") { "Unknown annotation element $name" } + foundArray = true + return KVVisitor(map) + } + + var foundArray = false + + override fun visitEnd() { + require(foundArray) { "Missing array" } + } + + override fun visitAnnotation(name: String?, descriptor: String?): AnnotationVisitor { + error("Unknown annotation element $name") + } + + override fun visit(name: String?, value: Any?) { + error("Unknown annotation element $name") + } + + override fun visitEnum(name: String?, descriptor: String?, value: String?) { + error("Unknown annotation element $name") + } + } + + private class AnnotationCollector(val map: MutableMap<String, String>) : ClassVisitor(Opcodes.ASM9) { + override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? { + // TODO: inject our own annotations into the classpath + if (Type.getType(GatheredTranslation::class.java).descriptor.equals(descriptor)) { + return KVVisitor(map) + } + if (Type.getType(GatheredTranslation.Repeatable::class.java).descriptor.equals(descriptor)) { + return RepeatableVisitor(map) + } + // TODO: remove print log + println("Ignoring descriptor $descriptor") + return null + } + } + + private fun parseClassAnnotations(file: File): HashMap<String, String> { + val map = HashMap<String, String>() + kotlin.runCatching { + ClassReader(file.readBytes()) + .accept(AnnotationCollector(map), + ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES) + }.onFailure { + throw RuntimeException("Could not parse annotations in $file", it) + } + return map + } + + private fun getClassName(relativePath: String): String { + return relativePath.replace("/", ".").removeSuffix(".class") + } + + data class FileChange(val file: File, val relativePath: String) + + private fun checkFile(file: File): Boolean { + if (file.isDirectory) return false + val extension = file.extension + if (extension == "kt" || extension == "java") + error("Cannot collect translations from source files. Please attach the CollectTranslations task to a compile output") + if (extension != "class") return false + return true + } + +}
\ No newline at end of file |