mirror of
https://github.com/architectury/architectury-plugin.git
synced 2026-03-28 04:07:01 -05:00
Transform @ExpectPlatform
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
Architect is a gradle plugin to allow easier multi-modloader set-ups using a common module.
|
||||
|
||||
### Examples
|
||||
- [Hardcore Questing Mode](https://github.com/shedaniel/HQM/tree/002b5df265fd26b8df6a3b6b28cbc5bfe76573a6)
|
||||
- [Hardcore Questing Mode](https://github.com/lorddusk/HQM)
|
||||
- [Light Overlay](https://github.com/shedaniel/LightOverlay)
|
||||
|
||||
### Important Information
|
||||
@@ -11,6 +11,9 @@ Architect is a gradle plugin to allow easier multi-modloader set-ups using a com
|
||||
- No mixins in the common module.
|
||||
- You **MUST** run `gradlew build` or `gradlew :common:build` to update the MCP remapped version of `common`, or else the version of common module that the forge module is using will not update.
|
||||
|
||||
### Implementing Platform Specific APIs
|
||||

|
||||
|
||||
### How does it work
|
||||
Fabric Side:
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ plugins {
|
||||
}
|
||||
|
||||
group "me.shedaniel"
|
||||
version = "1.0." + (System.getenv("GITHUB_RUN_NUMBER") == null ? "9999" : System.getenv("GITHUB_RUN_NUMBER"))
|
||||
version = "1.1." + (System.getenv("GITHUB_RUN_NUMBER") == null ? (((short) new Random().nextInt()).abs() + 1000).toString() : System.getenv("GITHUB_RUN_NUMBER"))
|
||||
|
||||
logger.lifecycle(":building architect plugin v${version}")
|
||||
|
||||
sourceCompatibility = targetCompatibility = 1.8
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ import org.gradle.api.plugins.JavaPluginExtension
|
||||
|
||||
class ArchitectPlugin : Plugin<Project> {
|
||||
override fun apply(project: Project) {
|
||||
project.apply(mapOf(
|
||||
project.apply(
|
||||
mapOf(
|
||||
"plugin" to "java",
|
||||
"plugin" to "eclipse",
|
||||
"plugin" to "idea"
|
||||
))
|
||||
)
|
||||
)
|
||||
project.extensions.create("architect", ArchitectPluginExtension::class.java, project)
|
||||
|
||||
project.afterEvaluate {
|
||||
@@ -21,9 +23,17 @@ class ArchitectPlugin : Plugin<Project> {
|
||||
}
|
||||
}
|
||||
|
||||
project.tasks.register("remapMcp", RemapMCPTask::class.java)
|
||||
project.tasks.register("remapMcp", RemapMCPTask::class.java) {
|
||||
it.group = "Architect"
|
||||
}
|
||||
|
||||
project.tasks.register("remapMcpFakeMod", RemapMCPTask::class.java) {
|
||||
it.fakeMod = true
|
||||
it.group = "Architect"
|
||||
}
|
||||
|
||||
project.tasks.register("transformArchitectJar", TransformTask::class.java) {
|
||||
it.group = "Architect"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
package me.shedaniel.architect.plugin
|
||||
|
||||
import net.fabricmc.loom.task.RemapJarTask
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask
|
||||
|
||||
open class ArchitectPluginExtension(val project: Project) {
|
||||
var minecraft = ""
|
||||
@@ -10,38 +12,77 @@ open class ArchitectPluginExtension(val project: Project) {
|
||||
fun common() {
|
||||
project.configurations.create("mcp")
|
||||
project.configurations.create("mcpGenerateMod")
|
||||
project.configurations.create("transformed")
|
||||
|
||||
project.tasks.getByName("remapMcp") {
|
||||
val buildTask = project.tasks.getByName("build")
|
||||
val jarTask = project.tasks.getByName("jar") {
|
||||
it as AbstractArchiveTask
|
||||
it.archiveClassifier.set("dev")
|
||||
} as AbstractArchiveTask
|
||||
|
||||
val transformArchitectJarTask = project.tasks.getByName("transformArchitectJar") {
|
||||
it as TransformTask
|
||||
|
||||
it.archiveClassifier.set("transformed")
|
||||
it.input.set(jarTask.archiveFile.get())
|
||||
|
||||
project.artifacts.add("archives", it)
|
||||
it.dependsOn(jarTask)
|
||||
buildTask.dependsOn(it)
|
||||
it.outputs.upToDateWhen { false }
|
||||
} as TransformTask
|
||||
|
||||
val remapJarTask = project.tasks.getByName("remapJar") {
|
||||
it as RemapJarTask
|
||||
|
||||
it.archiveClassifier.set("")
|
||||
it.input.set(transformArchitectJarTask.archiveFile.get())
|
||||
it.dependsOn(transformArchitectJarTask)
|
||||
it.mustRunAfter(transformArchitectJarTask)
|
||||
} as RemapJarTask
|
||||
|
||||
val remapMCPTask = project.tasks.getByName("remapMcp") {
|
||||
it as RemapMCPTask
|
||||
|
||||
it.input.set(project.file("${project.buildDir}/libs/${project.properties["archivesBaseName"]}-${project.version}-dev.jar"))
|
||||
it.input.set(transformArchitectJarTask.archiveFile.get())
|
||||
it.archiveClassifier.set("mcp")
|
||||
it.dependsOn(project.tasks.getByName("jar"))
|
||||
project.tasks.getByName("build").dependsOn(it)
|
||||
it.dependsOn(transformArchitectJarTask)
|
||||
buildTask.dependsOn(it)
|
||||
it.outputs.upToDateWhen { false }
|
||||
}
|
||||
} as RemapMCPTask
|
||||
|
||||
project.tasks.getByName("remapMcpFakeMod") {
|
||||
val remapMCPFakeModTask = project.tasks.getByName("remapMcpFakeMod") {
|
||||
it as RemapMCPTask
|
||||
|
||||
it.input.set(project.file("${project.buildDir}/libs/${project.properties["archivesBaseName"]}-${project.version}-dev.jar"))
|
||||
it.input.set(transformArchitectJarTask.archiveFile.get())
|
||||
it.archiveClassifier.set("mcpGenerateMod")
|
||||
it.dependsOn(project.tasks.getByName("jar"))
|
||||
project.tasks.getByName("build").dependsOn(it)
|
||||
it.dependsOn(transformArchitectJarTask)
|
||||
buildTask.dependsOn(it)
|
||||
it.outputs.upToDateWhen { false }
|
||||
}
|
||||
} as RemapMCPTask
|
||||
|
||||
project.artifacts {
|
||||
it.add("mcp", mapOf(
|
||||
"file" to project.file("${project.buildDir}/libs/${project.properties["archivesBaseName"]}-${project.version}-mcp.jar"),
|
||||
it.add(
|
||||
"mcp", mapOf(
|
||||
"file" to remapMCPTask.archiveFile.get().asFile,
|
||||
"type" to "jar",
|
||||
"builtBy" to project.tasks.getByName("remapMcp")
|
||||
))
|
||||
it.add("mcpGenerateMod", mapOf(
|
||||
"file" to project.file("${project.buildDir}/libs/${project.properties["archivesBaseName"]}-${project.version}-mcpGenerateMod.jar"),
|
||||
"builtBy" to remapMCPTask
|
||||
)
|
||||
)
|
||||
it.add(
|
||||
"mcpGenerateMod", mapOf(
|
||||
"file" to remapMCPFakeModTask.archiveFile.get().asFile,
|
||||
"type" to "jar",
|
||||
"builtBy" to project.tasks.getByName("remapMcpFakeMod")
|
||||
))
|
||||
"builtBy" to remapMCPFakeModTask
|
||||
)
|
||||
)
|
||||
it.add(
|
||||
"transformed", mapOf(
|
||||
"file" to transformArchitectJarTask.archiveFile.get().asFile,
|
||||
"type" to "jar",
|
||||
"builtBy" to transformArchitectJarTask
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
package me.shedaniel.architect.plugin
|
||||
|
||||
import me.shedaniel.architect.plugin.utils.GradleSupport
|
||||
import net.fabricmc.loom.LoomGradleExtension
|
||||
import net.fabricmc.loom.util.GradleSupport
|
||||
import net.fabricmc.loom.util.TinyRemapperMappingsHelper
|
||||
import net.fabricmc.mapping.tree.TinyTree
|
||||
import net.fabricmc.tinyremapper.IMappingProvider
|
||||
@@ -32,7 +32,7 @@ open class RemapMCPTask : Jar() {
|
||||
private val fromM: String = "named"
|
||||
private val toM: String = "official"
|
||||
var fakeMod = false
|
||||
val input: RegularFileProperty = GradleSupport.getfileProperty(project)
|
||||
val input: RegularFileProperty = GradleSupport.getFileProperty(project)
|
||||
private val environmentClass = "net/fabricmc/api/Environment"
|
||||
|
||||
@TaskAction
|
||||
@@ -133,9 +133,9 @@ modId = "$fakeModId"
|
||||
val className = "generated/$fakeModId"
|
||||
val classWriter = ClassWriter(0)
|
||||
classWriter.visit(52, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null)
|
||||
val mixinAnnotation = classWriter.visitAnnotation("Lnet/minecraftforge/fml/common/Mod;", false)
|
||||
mixinAnnotation.visit("value", fakeModId)
|
||||
mixinAnnotation.visitEnd()
|
||||
val modAnnotation = classWriter.visitAnnotation("Lnet/minecraftforge/fml/common/Mod;", false)
|
||||
modAnnotation.visit("value", fakeModId)
|
||||
modAnnotation.visitEnd()
|
||||
classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, arrayOf()).also {
|
||||
it.visitVarInsn(Opcodes.ALOAD, 0)
|
||||
it.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)
|
||||
|
||||
142
src/main/kotlin/me/shedaniel/architect/plugin/TransformExpect.kt
Normal file
142
src/main/kotlin/me/shedaniel/architect/plugin/TransformExpect.kt
Normal file
@@ -0,0 +1,142 @@
|
||||
package me.shedaniel.architect.plugin
|
||||
|
||||
import org.objectweb.asm.Handle
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.tree.*
|
||||
import java.lang.invoke.CallSite
|
||||
import java.lang.invoke.MethodHandles
|
||||
import java.lang.invoke.MethodType
|
||||
|
||||
const val expectPlatform = "Lme/shedaniel/architectury/ExpectPlatform;"
|
||||
|
||||
fun transformExpectPlatform(): (ClassNode, (String, ByteArray) -> Unit) -> ClassNode = { clazz, classAdder ->
|
||||
if (clazz.access and Opcodes.ACC_INTERFACE == 0) {
|
||||
clazz.methods.filter { method -> method?.visibleAnnotations?.any { it.desc == expectPlatform } == true }
|
||||
.forEach { method ->
|
||||
if (method.access and Opcodes.ACC_STATIC == 0) {
|
||||
System.err.println("@ExpectPlatform can only apply to static methods!")
|
||||
} else {
|
||||
println("Found ${clazz.name}#${method.name}")
|
||||
method.instructions.clear()
|
||||
val endOfDesc = method.desc.lastIndexOf(')')
|
||||
val returnValue = method.desc.substring(endOfDesc + 1)
|
||||
val args = method.desc.substring(1, endOfDesc)
|
||||
var cursor = 0
|
||||
var inClass = false
|
||||
var index = 0
|
||||
while (cursor < args.length) {
|
||||
val char = args[cursor]
|
||||
if (inClass) {
|
||||
if (char == ';') {
|
||||
method.instructions.addLoad(char, index++)
|
||||
inClass = false
|
||||
}
|
||||
} else when (char) {
|
||||
'[' -> Unit
|
||||
'L' -> inClass = true
|
||||
else -> method.instructions.addLoad(char, index++)
|
||||
}
|
||||
cursor++
|
||||
}
|
||||
|
||||
val methodType = MethodType.methodType(
|
||||
CallSite::class.java,
|
||||
MethodHandles.Lookup::class.java,
|
||||
String::class.java,
|
||||
MethodType::class.java
|
||||
)
|
||||
|
||||
val handle = Handle(
|
||||
Opcodes.H_INVOKESTATIC,
|
||||
"me/shedaniel/architectury/PlatformMethods",
|
||||
"platform",
|
||||
methodType.toMethodDescriptorString(),
|
||||
false
|
||||
)
|
||||
|
||||
method.instructions.add(
|
||||
InvokeDynamicInsnNode(
|
||||
method.name,
|
||||
method.desc,
|
||||
handle
|
||||
)
|
||||
)
|
||||
|
||||
method.instructions.addReturn(returnValue.first { it != '[' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clazz
|
||||
}
|
||||
|
||||
private fun InsnList.addLoad(type: Char, index: Int) {
|
||||
when (type) {
|
||||
';' -> add(
|
||||
VarInsnNode(
|
||||
Opcodes.ALOAD,
|
||||
index
|
||||
)
|
||||
)
|
||||
'I', 'S', 'B', 'C', 'Z' -> add(
|
||||
VarInsnNode(
|
||||
Opcodes.ILOAD,
|
||||
index
|
||||
)
|
||||
)
|
||||
'F' -> add(
|
||||
VarInsnNode(
|
||||
Opcodes.FLOAD,
|
||||
index
|
||||
)
|
||||
)
|
||||
'J' -> add(
|
||||
VarInsnNode(
|
||||
Opcodes.LLOAD,
|
||||
index
|
||||
)
|
||||
)
|
||||
'D' -> add(
|
||||
VarInsnNode(
|
||||
Opcodes.DLOAD,
|
||||
index
|
||||
)
|
||||
)
|
||||
else -> throw IllegalStateException("Invalid Type: $type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun InsnList.addReturn(type: Char) {
|
||||
when (type) {
|
||||
'L' -> add(
|
||||
InsnNode(
|
||||
Opcodes.ARETURN
|
||||
)
|
||||
)
|
||||
'I', 'S', 'B', 'C', 'Z' -> add(
|
||||
InsnNode(
|
||||
Opcodes.IRETURN
|
||||
)
|
||||
)
|
||||
'F' -> add(
|
||||
InsnNode(
|
||||
Opcodes.FRETURN
|
||||
)
|
||||
)
|
||||
'J' -> add(
|
||||
InsnNode(
|
||||
Opcodes.LRETURN
|
||||
)
|
||||
)
|
||||
'D' -> add(
|
||||
InsnNode(
|
||||
Opcodes.DRETURN
|
||||
)
|
||||
)
|
||||
'V' -> add(
|
||||
InsnNode(
|
||||
Opcodes.RETURN
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
package me.shedaniel.architect.plugin
|
||||
|
||||
import me.shedaniel.architect.plugin.utils.GradleSupport
|
||||
import me.shedaniel.architect.plugin.utils.Transform
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.jvm.tasks.Jar
|
||||
import java.nio.file.Path
|
||||
|
||||
open class TransformTask : Jar() {
|
||||
val input: RegularFileProperty = GradleSupport.getFileProperty(project)
|
||||
|
||||
@TaskAction
|
||||
fun doTask() {
|
||||
val input: Path = this.input.asFile.get().toPath()
|
||||
val output: Path = this.archiveFile.get().asFile.toPath()
|
||||
|
||||
project.logger.lifecycle(":transforming " + input.fileName + " => " + output.fileName)
|
||||
Transform.transform(input, output, transformExpectPlatform())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package me.shedaniel.architect.plugin.utils
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
|
||||
object GradleSupport {
|
||||
fun getFileProperty(project: Project): RegularFileProperty {
|
||||
return try {
|
||||
getFilePropertyModern(project)
|
||||
} catch (var3: Exception) {
|
||||
try {
|
||||
getFilePropertyLegacy(project)
|
||||
} catch (var2: Exception) {
|
||||
throw RuntimeException("Failed to find file property", var2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFilePropertyModern(project: Project): RegularFileProperty {
|
||||
return getFilePropertyLegacyFromObject(project.objects)
|
||||
}
|
||||
|
||||
private fun getFilePropertyLegacy(project: Project): RegularFileProperty {
|
||||
return getFilePropertyLegacyFromObject(project.layout)
|
||||
}
|
||||
|
||||
private fun getFilePropertyLegacyFromObject(`object`: Any): RegularFileProperty {
|
||||
val method = `object`.javaClass.getDeclaredMethod("fileProperty")
|
||||
method.isAccessible = true
|
||||
return method.invoke(`object`) as RegularFileProperty
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package me.shedaniel.architect.plugin.utils
|
||||
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassWriter
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
object Transform {
|
||||
inline fun transform(input: Path, output: Path, transform: (ClassNode, (String, ByteArray) -> Unit) -> ClassNode) {
|
||||
output.toFile().delete()
|
||||
|
||||
if (!Files.exists(input)) {
|
||||
throw FileNotFoundException(input.toString())
|
||||
}
|
||||
|
||||
if (input.toFile().absolutePath.endsWith(".class")) {
|
||||
var allBytes = Files.newInputStream(input).readBytes()
|
||||
val reader = ClassReader(allBytes)
|
||||
if ((reader.access and Opcodes.ACC_MODULE) == 0) {
|
||||
val node = ClassNode(Opcodes.ASM8)
|
||||
reader.accept(node, ClassReader.EXPAND_FRAMES)
|
||||
val writer = ClassWriter(0)
|
||||
transform(node) { name, bytes ->
|
||||
File(output.toFile().parentFile, "$name.class").also {
|
||||
it.delete()
|
||||
it.writeBytes(bytes)
|
||||
}
|
||||
}.accept(writer)
|
||||
allBytes = writer.toByteArray()
|
||||
}
|
||||
output.toFile().writeBytes(allBytes)
|
||||
} else {
|
||||
val zipOutputStream = ZipOutputStream(output.toFile().outputStream())
|
||||
zipOutputStream.use {
|
||||
ZipInputStream(Files.newInputStream(input)).use {
|
||||
while (true) {
|
||||
val entry = it.nextEntry ?: break
|
||||
var allBytes = it.readBytes()
|
||||
if (entry.name.toString().endsWith(".class")) {
|
||||
val reader = ClassReader(allBytes)
|
||||
if ((reader.access and Opcodes.ACC_MODULE) == 0) {
|
||||
val node = ClassNode(Opcodes.ASM8)
|
||||
reader.accept(node, ClassReader.EXPAND_FRAMES)
|
||||
val writer = ClassWriter(0)
|
||||
transform(node) { name, bytes ->
|
||||
zipOutputStream.putNextEntry(ZipEntry(name))
|
||||
zipOutputStream.write(bytes)
|
||||
zipOutputStream.closeEntry()
|
||||
}.accept(writer)
|
||||
allBytes = writer.toByteArray()
|
||||
}
|
||||
}
|
||||
zipOutputStream.putNextEntry(ZipEntry(entry.name))
|
||||
zipOutputStream.write(allBytes)
|
||||
zipOutputStream.closeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user