Migrate Java code to Gradle (#3833)

* Create get_resource_file macro to get an embedded resource

If the tauri feature is enabled, the resource will be loaded from Tauri resources.
If the tauri feature is disabled, the resource will be extracted to a temp directory.

* Wrap process execution to inject system properties through stdin

* Pass the time values as ISO 8601 datetimes

* Remove entirely internal modrinth.process.uuid

* Redo Java version checking somewhat and fix a few bugs with it

* Fix game launch with early access versions of Java

* Format Java code

* Use Gradle to build Java code

* Make Gradle build reproducible

* Fix constant rebuilds

* Get rid of unnecessary rebuilds

* Fix modrinth.profile.modified being the same as modrinth.profile.created

* Make javac use all lints and treat them as errors

* Force Gradle color output

* Add Java formatting config

* Make gradlew executable

* Revert to manually extracting class files

* Switch to using update resource macro

* fix: make `app-lib` build again

---------

Co-authored-by: Alejandro González <me@alegon.dev>
This commit is contained in:
Josiah Glosson
2025-06-26 09:56:35 -05:00
committed by GitHub
parent f10e0f2bf1
commit 47af459f24
24 changed files with 633 additions and 151 deletions

View File

@@ -0,0 +1,21 @@
package com.modrinth.theseus;
public final class JavaInfo {
private static final String[] CHECKED_PROPERTIES = new String[] {"os.arch", "java.version"};
public static void main(String[] args) {
int returnCode = 0;
for (String key : CHECKED_PROPERTIES) {
String property = System.getProperty(key);
if (property != null) {
System.out.println(key + "=" + property);
} else {
returnCode = 1;
}
}
System.exit(returnCode);
}
}

View File

@@ -0,0 +1,118 @@
package com.modrinth.theseus;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public final class MinecraftLaunch {
public static void main(String[] args) throws IOException, ReflectiveOperationException {
final String mainClass = args[0];
final String[] gameArgs = Arrays.copyOfRange(args, 1, args.length);
System.setProperty("modrinth.process.args", String.join("\u001f", gameArgs));
parseInput();
relaunch(mainClass, gameArgs);
}
private static void parseInput() throws IOException {
final ByteArrayOutputStream line = new ByteArrayOutputStream();
while (true) {
final int b = System.in.read();
if (b < 0) {
throw new IllegalStateException("Stdin terminated while parsing");
}
if (b != '\n') {
line.write(b);
continue;
}
if (handleLine(line.toString("UTF-8"))) {
break;
}
line.reset();
}
}
private static boolean handleLine(String line) {
final String[] parts = line.split("\t", 2);
switch (parts[0]) {
case "property": {
final String[] keyValue = parts[1].split("\t", 2);
System.setProperty(keyValue[0], keyValue[1]);
return false;
}
case "launch":
return true;
}
System.err.println("Unknown input line " + line);
return false;
}
private static void relaunch(String mainClassName, String[] args) throws ReflectiveOperationException {
final int javaVersion = getJavaVersion();
final Class<?> mainClass = Class.forName(mainClassName);
if (javaVersion >= 25) {
Method mainMethod;
try {
mainMethod = findMainMethodJep512(mainClass);
} catch (ReflectiveOperationException e) {
System.err.println(
"[MODRINTH] Unable to call JDK findMainMethod. Falling back to pre-Java 25 main method finding.");
// If the above fails due to JDK implementation details changing
try {
mainMethod = findSimpleMainMethod(mainClass);
} catch (ReflectiveOperationException innerE) {
e.addSuppressed(innerE);
throw e;
}
}
if (mainMethod == null) {
throw new IllegalArgumentException("Could not find main() method");
}
Object thisObject = null;
if (!Modifier.isStatic(mainMethod.getModifiers())) {
thisObject = mainClass.getDeclaredConstructor().newInstance();
}
final Object[] parameters = mainMethod.getParameterCount() > 0 ? new Object[] {args} : new Object[] {};
mainMethod.invoke(thisObject, parameters);
} else {
findSimpleMainMethod(mainClass).invoke(null, new Object[] {args});
}
}
private static int getJavaVersion() {
String javaVersion = System.getProperty("java.version");
final int dotIndex = javaVersion.indexOf('.');
if (dotIndex != -1) {
javaVersion = javaVersion.substring(0, dotIndex);
}
final int dashIndex = javaVersion.indexOf('-');
if (dashIndex != -1) {
javaVersion = javaVersion.substring(0, dashIndex);
}
return Integer.parseInt(javaVersion);
}
private static Method findMainMethodJep512(Class<?> mainClass) throws ReflectiveOperationException {
// BEWARE BELOW: This code may break if JDK internals to find the main method
// change.
final Class<?> methodFinderClass = Class.forName("jdk.internal.misc.MethodFinder");
final Method methodFinderMethod = methodFinderClass.getDeclaredMethod("findMainMethod", Class.class);
final Object result = methodFinderMethod.invoke(null, mainClass);
return (Method) result;
}
private static Method findSimpleMainMethod(Class<?> mainClass) throws NoSuchMethodException {
return mainClass.getMethod("main", String[].class);
}
}