mirror of
https://github.com/architectury/architectury-plugin.git
synced 2026-04-02 21:47:41 -05:00
Close #3 Allow the use of @ExpectPlatform without api
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package me.shedaniel.architect.plugin.callsite;
|
||||
|
||||
public class PlatformExpectedError extends Error {
|
||||
public PlatformExpectedError() {
|
||||
}
|
||||
|
||||
public PlatformExpectedError(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PlatformExpectedError(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public PlatformExpectedError(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public PlatformExpectedError(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package me.shedaniel.architect.plugin.callsite;
|
||||
|
||||
import java.lang.invoke.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PlatformMethods {
|
||||
public static CallSite platform(MethodHandles.Lookup lookup, String name, MethodType type) {
|
||||
Class<?> lookupClass = lookup.lookupClass();
|
||||
String lookupType = lookupClass.getName().replace("$", "") + "Impl";
|
||||
|
||||
String platformExpectedClass = lookupType.substring(0, lookupType.lastIndexOf('.')) + "." + getModLoader() + "." +
|
||||
lookupType.substring(lookupType.lastIndexOf('.') + 1);
|
||||
Class<?> newClass;
|
||||
try {
|
||||
newClass = Class.forName(platformExpectedClass, false, lookupClass.getClassLoader());
|
||||
} catch (ClassNotFoundException exception) {
|
||||
throw new PlatformExpectedError(lookupClass.getName() + "#" + name + " expected platform implementation in " + platformExpectedClass +
|
||||
"#" + name + ", but the class doesn't exist!", exception);
|
||||
}
|
||||
MethodHandle platformMethod;
|
||||
try {
|
||||
platformMethod = lookup.findStatic(newClass, name, type);
|
||||
} catch (NoSuchMethodException exception) {
|
||||
throw new PlatformExpectedError(lookupClass.getName() + "#" + name + " expected platform implementation in " + platformExpectedClass +
|
||||
"#" + name + ", but the method doesn't exist!", exception);
|
||||
} catch (IllegalAccessException exception) {
|
||||
throw new PlatformExpectedError(lookupClass.getName() + "#" + name + " expected platform implementation in " + platformExpectedClass +
|
||||
"#" + name + ", but the method's modifier doesn't match the access requirements!", exception);
|
||||
}
|
||||
return new ConstantCallSite(platformMethod);
|
||||
}
|
||||
|
||||
private static String modLoader = null;
|
||||
|
||||
public static String getModLoader() {
|
||||
try {
|
||||
return (String) Class.forName("me.shedaniel.architectury.platform.Platform").getDeclaredMethod("getModLoader").invoke(null);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
if (modLoader == null) {
|
||||
List<String> loader = new ArrayList<>();
|
||||
HashMap<String, String> MOD_LOADERS = new HashMap<>();
|
||||
MOD_LOADERS.put("net.fabricmc.loader.FabricLoader", "fabric");
|
||||
MOD_LOADERS.put("net.minecraftforge.fml.common.Mod", "forge");
|
||||
for (Map.Entry<String, String> entry : MOD_LOADERS.entrySet()) {
|
||||
try {
|
||||
Class.forName(entry.getKey(), false, null);
|
||||
loader.add(entry.getValue());
|
||||
break;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
}
|
||||
if (loader.isEmpty())
|
||||
throw new IllegalStateException("No detected mod loader!");
|
||||
if (loader.size() >= 2)
|
||||
System.err.println("Detected multiple mod loaders! Something is wrong on the classpath! " + String.join(", ", loader));
|
||||
modLoader = loader.get(0);
|
||||
}
|
||||
return modLoader;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,10 @@ open class ArchitectPluginExtension(val project: Project) {
|
||||
}
|
||||
|
||||
fun common(forgeEnabled: Boolean) {
|
||||
with(project.dependencies) {
|
||||
add("compileOnly", "me.shedaniel:architectury-annotations:+")
|
||||
}
|
||||
|
||||
if (forgeEnabled) {
|
||||
project.configurations.create("mcp")
|
||||
project.configurations.create("mcpGenerateMod")
|
||||
|
||||
@@ -1,17 +1,91 @@
|
||||
package me.shedaniel.architect.plugin
|
||||
|
||||
import me.shedaniel.architect.plugin.utils.ClassTransformer
|
||||
import me.shedaniel.architect.plugin.utils.Transform
|
||||
import org.gradle.api.Project
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassWriter
|
||||
import org.objectweb.asm.Handle
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.commons.ClassRemapper
|
||||
import org.objectweb.asm.commons.Remapper
|
||||
import org.objectweb.asm.tree.*
|
||||
import org.zeroturnaround.zip.ZipUtil
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.lang.invoke.CallSite
|
||||
import java.lang.invoke.MethodHandles
|
||||
import java.lang.invoke.MethodType
|
||||
import java.util.*
|
||||
import java.util.zip.ZipEntry
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
const val expectPlatform = "Lme/shedaniel/architectury/ExpectPlatform;"
|
||||
const val expectPlatformNew = "Lme/shedaniel/architectury/annotations/ExpectPlatform;"
|
||||
|
||||
fun transformExpectPlatform(): (ClassNode, (String, ByteArray) -> Unit) -> ClassNode = { clazz, classAdder ->
|
||||
clazz.methods.filter { method -> method?.visibleAnnotations?.any { it.desc == expectPlatform } == true }
|
||||
.forEach { method ->
|
||||
fun Project.projectUniqueIdentifier(): String {
|
||||
val cache = File(project.file(".gradle"), "architectury-cache")
|
||||
cache.mkdirs()
|
||||
val uniqueIdFile = File(cache, "projectID")
|
||||
var id by Delegates.notNull<String>()
|
||||
if (uniqueIdFile.exists()) {
|
||||
id = uniqueIdFile.readText()
|
||||
} else {
|
||||
id = UUID.randomUUID().toString().filterNot { it == '-' }
|
||||
uniqueIdFile.writeText(id)
|
||||
}
|
||||
var name = project.name
|
||||
if (project.rootProject != project) name = project.rootProject.name + "_" + name
|
||||
return "architectury_inject_${name}_$id".filter { Character.isJavaIdentifierPart(it) }
|
||||
}
|
||||
|
||||
fun transformExpectPlatform(project: Project): ClassTransformer {
|
||||
val projectUniqueIdentifier by lazy { project.projectUniqueIdentifier() }
|
||||
var injectedClass = false
|
||||
return { clazz, classAdder ->
|
||||
clazz.methods.mapNotNull { method ->
|
||||
when {
|
||||
method?.visibleAnnotations?.any { it.desc == expectPlatform } == true -> method to "me/shedaniel/architectury/PlatformMethods"
|
||||
method?.invisibleAnnotations?.any { it.desc == expectPlatformNew } == true -> {
|
||||
if (!injectedClass) {
|
||||
injectedClass = true
|
||||
Transform::class.java.getResourceAsStream("/annotations-inject/injection.jar").use { stream ->
|
||||
ZipUtil.iterate(stream) { input: InputStream, entry: ZipEntry ->
|
||||
if (entry.name.endsWith(".class")) {
|
||||
val newName = "$projectUniqueIdentifier/${
|
||||
entry.name.substringBeforeLast(".class").substringAfterLast('/')
|
||||
}"
|
||||
classAdder(newName, input.readBytes().let {
|
||||
val node = ClassNode(Opcodes.ASM8)
|
||||
ClassReader(it).accept(node, ClassReader.EXPAND_FRAMES)
|
||||
val writer = ClassWriter(ClassWriter.COMPUTE_MAXS)
|
||||
val remapper = ClassRemapper(writer, object : Remapper() {
|
||||
override fun map(internalName: String?): String {
|
||||
if (internalName?.startsWith("me/shedaniel/architect/plugin/callsite") == true) {
|
||||
return internalName.replace(
|
||||
"me/shedaniel/architect/plugin/callsite",
|
||||
projectUniqueIdentifier
|
||||
)
|
||||
}
|
||||
return super.map(internalName)
|
||||
}
|
||||
})
|
||||
node.apply {
|
||||
name = newName
|
||||
}.accept(remapper)
|
||||
|
||||
writer.toByteArray()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
method to "$projectUniqueIdentifier/PlatformMethods"
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}.forEach { (method, platformMethodsClass) ->
|
||||
if (method.access and Opcodes.ACC_STATIC == 0) {
|
||||
System.err.println("@ExpectPlatform can only apply to static methods!")
|
||||
} else {
|
||||
@@ -49,7 +123,7 @@ fun transformExpectPlatform(): (ClassNode, (String, ByteArray) -> Unit) -> Class
|
||||
|
||||
val handle = Handle(
|
||||
Opcodes.H_INVOKESTATIC,
|
||||
"me/shedaniel/architectury/PlatformMethods",
|
||||
platformMethodsClass,
|
||||
"platform",
|
||||
methodType.toMethodDescriptorString(),
|
||||
false
|
||||
@@ -68,7 +142,8 @@ fun transformExpectPlatform(): (ClassNode, (String, ByteArray) -> Unit) -> Class
|
||||
}
|
||||
}
|
||||
|
||||
clazz
|
||||
clazz
|
||||
}
|
||||
}
|
||||
|
||||
private fun InsnList.addLoad(type: Char, index: Int) {
|
||||
|
||||
@@ -70,7 +70,7 @@ open class TransformTask : Jar() {
|
||||
}
|
||||
|
||||
project.logger.lifecycle(":transforming " + input.fileName + " => " + intermediate.fileName)
|
||||
Transform.transform(intermediate, output, transformExpectPlatform())
|
||||
Transform.transform(intermediate, output, transformExpectPlatform(project))
|
||||
|
||||
Files.deleteIfExists(intermediate)
|
||||
|
||||
|
||||
@@ -12,8 +12,10 @@ import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
typealias ClassTransformer = (clazz: ClassNode, classAdder: (String, ByteArray) -> Unit) -> ClassNode
|
||||
|
||||
object Transform {
|
||||
inline fun transform(input: Path, output: Path, transform: (ClassNode, (String, ByteArray) -> Unit) -> ClassNode) {
|
||||
inline fun transform(input: Path, output: Path, transform: ClassTransformer) {
|
||||
output.toFile().delete()
|
||||
|
||||
if (!Files.exists(input)) {
|
||||
@@ -50,7 +52,7 @@ object Transform {
|
||||
reader.accept(node, ClassReader.EXPAND_FRAMES)
|
||||
val writer = ClassWriter(ClassWriter.COMPUTE_MAXS)
|
||||
transform(node) { name, bytes ->
|
||||
zipOutputStream.putNextEntry(ZipEntry(name))
|
||||
zipOutputStream.putNextEntry(ZipEntry("$name.class"))
|
||||
zipOutputStream.write(bytes)
|
||||
zipOutputStream.closeEntry()
|
||||
}.accept(writer)
|
||||
|
||||
Reference in New Issue
Block a user