Skip to content

Commit

Permalink
Legacy Support (#36)
Browse files Browse the repository at this point in the history
- NFRT needs the ability to supply a specific JDK home for recompilation
and supplying the JRE libraries for Vineflower / Remapping. (It runs
under 21 itself but the 1.20.1 decompiler crashes with J21 libs)
- Patches in 1.20.1 had a different prefix (not a/, b/), so I added the
required support for specifying the patch prefix. 1.12 specified the
different prefixes in its "userdev" config, so support for that is just
added.
- Properly process the NeoForm/MCP java version in the recompile options
(was harcoded to 21 before)
- NFRT gains support to remap the sources from SRG->Mojang via the
Regular Expression based remapper
- Add a task that generates mappings in different formats and directions
(named <-> intermediary, TSRG/SRG/CSV) for use in pre 1.20.1 toolchains
- Support for a few nitpicky changes in the Userdev JSON

---------

Co-authored-by: Matyrobbrt <matyrobbrt@gmail.com>
  • Loading branch information
shartte and Matyrobbrt authored Sep 8, 2024
1 parent bb4d9db commit 77bfe1a
Show file tree
Hide file tree
Showing 21 changed files with 636 additions and 66 deletions.
16 changes: 16 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'org.eclipse.jdt:ecj:3.38.0'
implementation 'net.fabricmc:fabric-loom-native:0.2.1'
implementation 'net.neoforged:srgutils:1.0.9'
annotationProcessor 'info.picocli:picocli-codegen:4.7.6'

testImplementation platform('org.junit:junit-bom:5.10.0')
Expand Down Expand Up @@ -190,6 +191,21 @@ idea {
programParameters = "run --dist joined --neoform net.neoforged:neoform:1.20.6-20240429.153634@zip --write-result=compiled:build/minecraft.jar --write-result=clientResources:build/client-extra.jar --write-result=sources:build/minecraft-sources.jar"
moduleRef(project, sourceSets.main)
}
"Run NeoForge 1.20.1 (joined)"(Application) {
mainClass = mainClassName
programParameters = "run --dist joined --neoforge net.neoforged:forge:1.20.1-47.1.54:userdev --parchment-data=org.parchmentmc.data:parchment-1.20.1:2023.09.03@zip --parchment-conflict-prefix=p_ --write-result=namedToIntermediaryMapping:build/1.20.1.tsrg --write-result=compiledWithNeoForge:build/1.20.1-minecraft.jar --write-result=clientResources:build/1.20.1-client-extra.jar --write-result=sourcesWithNeoForge:build/1.20.1-minecraft-sources.jar"
moduleRef(project, sourceSets.main)
}
"Run Forge 1.12.2 (joined)"(Application) {
mainClass = mainClassName
programParameters = "run --dist joined --add-repository https://maven.minecraftforge.net --neoforge net.minecraftforge:forge:1.12.2-14.23.5.2860:userdev3 --write-result=compiledWithNeoForge:build/1.12.2-minecraft.jar --write-result=clientResources:build/1.12.2-client-extra.jar --write-result=sourcesWithNeoForge:build/1.12.2-minecraft-sources.jar"
moduleRef(project, sourceSets.main)
}
"Run Forge 1.17.1 (joined)"(Application) {
mainClass = mainClassName
programParameters = "run --dist joined --add-repository https://maven.minecraftforge.net --neoforge net.minecraftforge:forge:1.17.1-37.1.1:userdev --write-result=compiledWithNeoForge:build/1.17.1-minecraft.jar --write-result=clientResources:build/1.17.1-client-extra.jar --write-result=sourcesWithNeoForge:build/1.17.1-minecraft-sources.jar"
moduleRef(project, sourceSets.main)
}
"Run Neoform 1.19.2 (joined)"(Application) {
mainClass = mainClassName
programParameters = "run --dist joined --neoform de.oceanlabs.mcp:mcp_config:1.19.2@zip --write-result=compiled:build/minecraft.jar --write-result=clientResources:build/client-extra.jar --write-result=sources:build/minecraft-sources.jar"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package net.neoforged.neoform.runtime.actions;

import net.neoforged.neoform.runtime.engine.ProcessingEnvironment;
import net.neoforged.neoform.runtime.graph.ExecutionNodeAction;
import net.neoforged.srgutils.IMappingFile;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
* Creates different mapping files used by the legacy toolchain:
* <ul>
* <li>a {@code officialToSrg} TSRGv1 file that maps official names to SRG names</li>
* <li>a {@code srgToOfficial} SRG file (to please Mixin) that maps SRG names to official names</li>
* <li>a {@code csvMappings} zip file containing 2 csv files that map SRG names to official names</li>
* </ul>
*/
public class CreateLegacyMappingsAction implements ExecutionNodeAction {
@Override
public void run(ProcessingEnvironment environment) throws IOException, InterruptedException {
var first = environment.getRequiredInputPath("officialToObf");
var second = environment.getRequiredInputPath("obfToSrg");

var firstMappings = IMappingFile.load(first.toFile());
var secondMappings = IMappingFile.load(second.toFile());

var officialToSrg = firstMappings.chain(secondMappings);
officialToSrg.write(environment.getOutputPath("officialToSrg"), IMappingFile.Format.TSRG, false);

var srgToOfficial = officialToSrg.reverse();
srgToOfficial.write(environment.getOutputPath("srgToOfficial"), IMappingFile.Format.SRG, false);

try (var zipCsv = new ZipOutputStream(Files.newOutputStream(environment.getOutputPath("csvMappings")))) {
writeCsv(zipCsv, "methods.csv", srgToOfficial.getClasses().stream()
.flatMap(c -> c.getMethods().stream()).filter(c -> c.getOriginal().startsWith("m_")));
writeCsv(zipCsv, "fields.csv", srgToOfficial.getClasses().stream()
.flatMap(c -> c.getFields().stream()).filter(c -> c.getOriginal().startsWith("f_")));
}
}

private static void writeCsv(ZipOutputStream stream, String name, Stream<? extends IMappingFile.INode> nodes) throws IOException {
stream.putNextEntry(new ZipEntry(name));
stream.write(("searge,name,side,desc\n" + nodes.map(n -> n.getOriginal() + "," + n.getMapped() + ",0,").collect(Collectors.joining("\n"))).getBytes(StandardCharsets.UTF_8));
stream.closeEntry();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,28 @@ public class ExternalJavaToolAction implements ExecutionNodeAction {
private List<String> jvmArgs = new ArrayList<>();
private List<String> args = new ArrayList<>();

/**
* Tools that are referenced by the NeoForm/MCP process files usually are only guaranteed to run
* with the Java version that was current at the time.
* NFRT offers a --java-executable option to set an appropriate (older) Java version.
* Some tools, however, are referenced by NFRT itself and added to the graph, and those will usually
* only run with the Java that NFRT itself is running with.
* This option should be used for such tools.
*/
private final boolean useHostJavaExecutable;

public ExternalJavaToolAction(MavenCoordinate toolArtifactId) {
this.toolArtifactId = toolArtifactId;
// Tools referenced by maven coordinate come from the MCP/NeoForm config file and will usually only
// be tested against the Java version used by that Minecraft version.
this.useHostJavaExecutable = false;
}

public ExternalJavaToolAction(ToolCoordinate toolCoordinate) {
this.toolArtifactId = toolCoordinate.version();
// Tools referenced by tool coordinate are internal tools that are verified to run with the Java
// version that NFRT itself can run with.
this.useHostJavaExecutable = true;
}

@Override
Expand All @@ -57,10 +73,15 @@ public void run(ProcessingEnvironment environment) throws IOException, Interrupt
toolArtifact = environment.getArtifactManager().get(toolArtifactId);
}

var javaExecutablePath = ProcessHandle.current()
.info()
.command()
.orElseThrow();
String javaExecutablePath;
if (useHostJavaExecutable) {
javaExecutablePath = environment.getJavaExecutable();
} else {
javaExecutablePath = ProcessHandle.current()
.info()
.command()
.orElseThrow();
}

var workingDir = environment.getWorkspace();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
Expand All @@ -26,10 +27,22 @@ public class InjectFromZipFileSource implements InjectSource {
* Folder within the ZIP we're copying from
*/
private final String sourcePath;
/**
* Optional regex for filtering which entries from this source will be injected.
* The relative path in the ZIP file will be matched against this regular expression.
* If the pattern is null, all entries are included.
*/
@Nullable
private final Pattern includeFilterPattern;

public InjectFromZipFileSource(ZipFile zf, String sourcePath) {
this(zf, sourcePath, null);
}

public InjectFromZipFileSource(ZipFile zf, String sourcePath, @Nullable Pattern includeFilterPattern) {
this.zf = zf;
this.sourcePath = sanitizeSourcePath(sourcePath);
this.includeFilterPattern = includeFilterPattern;
}

private static String sanitizeSourcePath(String sourcePath) {
Expand All @@ -55,7 +68,7 @@ public CacheKey.AnnotatedValue getCacheKey(FileHashService fileHashService) thro
var entries = zf.entries();
while (entries.hasMoreElements()) {
var entry = entries.nextElement();
if (sourcePath.isEmpty() || entry.getName().startsWith(sourcePath)) {
if ((sourcePath.isEmpty() || entry.getName().startsWith(sourcePath)) && matchesIncludeFilter(entry)) {
digestStream.write(entry.getName().getBytes());
try (var in = zf.getInputStream(entry)) {
in.transferTo(digestStream);
Expand All @@ -65,7 +78,7 @@ public CacheKey.AnnotatedValue getCacheKey(FileHashService fileHashService) thro

return new CacheKey.AnnotatedValue(
HexFormat.of().formatHex(digest.digest()),
sourcePath + " from " + zf.getName()
sourcePath + " from " + zf.getName() + (includeFilterPattern == null ? "" : " matching " + includeFilterPattern.pattern())
);
}

Expand All @@ -85,7 +98,7 @@ public void copyTo(ZipOutputStream out) throws IOException {
var entries = zf.entries();
while (entries.hasMoreElements()) {
var entry = entries.nextElement();
if (sourcePath.isEmpty() || entry.getName().startsWith(sourcePath)) {
if ((sourcePath.isEmpty() || entry.getName().startsWith(sourcePath)) && matchesIncludeFilter(entry)) {
try (var in = zf.getInputStream(entry)) {
// Relocate the entry
var copiedEntry = new ZipEntry(entry.getName().substring(sourcePath.length()));
Expand All @@ -108,4 +121,8 @@ public void copyTo(ZipOutputStream out) throws IOException {
}
}
}

private boolean matchesIncludeFilter(ZipEntry entry) {
return includeFilterPattern == null || includeFilterPattern.matcher(entry.getName()).matches();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
* Injects additional content from {@linkplain InjectSource configurable sources} into a Zip (or Jar) file.
*/
public class InjectZipContentAction extends BuiltInAction {
private List<InjectSource> injectedSources;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ public final class PatchActionFactory {
public static NodeOutput makeAction(ExecutionNodeBuilder builder,
Path patchesArchive,
String sourcePathInArchive,
NodeOutput sources) {
NodeOutput sources,
String basePathPrefix,
String modifiedPathPrefix) {
var patchesInZip = Objects.requireNonNull(sourcePathInArchive, "patches");
builder.input("input", sources.asInput());
var mainOutput = builder.output("output", NodeOutputType.ZIP, "ZIP file containing the patched sources");
Expand All @@ -30,7 +32,9 @@ public static NodeOutput makeAction(ExecutionNodeBuilder builder,
"--log-level", "WARN",
"--mode", "OFFSET",
"--archive-rejects", "ZIP",
"--reject", "{outputRejects}"
"--reject", "{outputRejects}",
"--base-path-prefix", basePathPrefix,
"--modified-path-prefix", modifiedPathPrefix
));

builder.action(action);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@
import java.util.List;

public abstract class RecompileSourcesAction extends BuiltInAction implements ExecutionNodeAction {

private final ExtensibleClasspath classpath = new ExtensibleClasspath();
private final ExtensibleClasspath sourcepath = new ExtensibleClasspath();
private int targetJavaVersion = 21;

@Override
public void computeCacheKey(CacheKeyBuilder ck) {
super.computeCacheKey(ck);
classpath.computeCacheKey("compile classpath", ck);
sourcepath.computeCacheKey("compile sourcepath", ck);
ck.add("target java version", String.valueOf(targetJavaVersion));
}

protected final List<Path> getEffectiveClasspath(ProcessingEnvironment environment) throws IOException {
Expand Down Expand Up @@ -53,4 +56,12 @@ public ExtensibleClasspath getClasspath() {
public ExtensibleClasspath getSourcepath() {
return sourcepath;
}

public int getTargetJavaVersion() {
return targetJavaVersion;
}

public void setTargetJavaVersion(int targetJavaVersion) {
this.targetJavaVersion = targetJavaVersion;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class RecompileSourcesActionWithECJ extends RecompileSourcesAction {
@Override
public void run(ProcessingEnvironment environment) throws IOException, InterruptedException {
var sources = environment.getRequiredInputPath("sources");
var javaRelease = String.valueOf(getTargetJavaVersion());

// Merge the original Minecraft classpath with the libs required by additional patches that we made
var classpathPaths = getEffectiveClasspath(environment);
Expand All @@ -69,7 +70,7 @@ public void run(ProcessingEnvironment environment) throws IOException, Interrupt
true,
new AccessRuleSet(new AccessRule[0], AccessRestriction.COMMAND_LINE, library.getFileName().toString()),
"none",
"21"
javaRelease
));
}

Expand All @@ -81,9 +82,9 @@ public void run(ProcessingEnvironment environment) throws IOException, Interrupt
var policy = DefaultErrorHandlingPolicies.exitOnFirstError();

var options = new CompilerOptions(Map.of(
CompilerOptions.OPTION_Source, "21",
CompilerOptions.OPTION_Compliance, "21",
CompilerOptions.OPTION_TargetPlatform, "21",
CompilerOptions.OPTION_Source, javaRelease,
CompilerOptions.OPTION_Compliance, javaRelease,
CompilerOptions.OPTION_TargetPlatform, javaRelease,
CompilerOptions.OPTION_ReportDeprecation, CompilerOptions.IGNORE,
CompilerOptions.OPTION_ReportDeprecationInDeprecatedCode, CompilerOptions.DISABLED,
CompilerOptions.OPTION_ReportDeprecationWhenOverridingDeprecatedMethod, CompilerOptions.DISABLED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,6 @@
* Uses the current JDKs java compiler interface to recompile the sources.
*/
public class RecompileSourcesActionWithJDK extends RecompileSourcesAction {
private final List<String> compilerOptions = new ArrayList<>();

public RecompileSourcesActionWithJDK() {
compilerOptions.add("--release");
compilerOptions.add("21");
compilerOptions.add("-proc:none"); // No annotation processing on Minecraft sources
compilerOptions.add("-nowarn"); // We have no influence on Minecraft sources, so no warnings
compilerOptions.add("-g"); // Gradle compiles with debug by default, so we replicate this
compilerOptions.add("-XDuseUnsharedTable=true"); // Gradle also adds this unconditionally
compilerOptions.add("-implicit:none"); // Prevents source files from the source-path from being emitted
}

@Override
public void run(ProcessingEnvironment environment) throws IOException, InterruptedException {
var sources = environment.getRequiredInputPath("sources");
Expand Down Expand Up @@ -84,7 +72,7 @@ public void report(Diagnostic<? extends JavaFileObject> d) {
fileManager.setLocationFromPaths(StandardLocation.SOURCE_PATH, sourcepath);

var sourceJavaFiles = fileManager.getJavaFileObjectsFromPaths(sourcePaths);
var task = compiler.getTask(null, fileManager, diagnostics, compilerOptions, null, sourceJavaFiles);
var task = compiler.getTask(null, fileManager, diagnostics, getCompilerOptions(), null, sourceJavaFiles);
if (!task.call()) {
throw new IOException("Compilation failed");
}
Expand All @@ -106,6 +94,18 @@ public void report(Diagnostic<? extends JavaFileObject> d) {
public void computeCacheKey(CacheKeyBuilder ck) {
super.computeCacheKey(ck);
ck.add("compiler type", "javac");
ck.addStrings("compiler options", compilerOptions);
ck.addStrings("compiler options", getCompilerOptions());
}

private List<String> getCompilerOptions() {
return List.of(
"--release",
String.valueOf(getTargetJavaVersion()),
"-proc:none", // No annotation processing on Minecraft sources
"-nowarn", // We have no influence on Minecraft sources, so no warnings
"-g", // Gradle compiles with debug by default, so we replicate this
"-XDuseUnsharedTable=true", // Gradle also adds this unconditionally
"-implicit:none" // Prevents source files from the source-path from being emitted
);
}
}
Loading

0 comments on commit 77bfe1a

Please sign in to comment.