From 61faea8e0112313dc5f8f421a048d0f7ba830582 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Mon, 23 Dec 2024 14:22:36 +0100 Subject: [PATCH] Very Legacy --- .../RecompileSourcesActionWithJDK.java | 2 +- .../actions/RemapSrgSourcesWithMcpAction.java | 112 +++++++++++++++ .../runtime/artifacts/ArtifactManager.java | 20 ++- .../runtime/artifacts/MavenMetadata.java | 87 ++++++++++++ .../runtime/cli/RunNeoFormCommand.java | 36 ++++- .../config/neoforge/NeoForgeConfig.java | 5 +- .../runtime/config/neoform/NeoFormConfig.java | 6 + .../neoform/runtime/engine/NeoFormEngine.java | 130 ++++++++++++------ .../runtime/engine/ProcessGeneration.java | 16 ++- .../runtime/graph/ExecutionNodeBuilder.java | 4 + .../runtime/utils/MavenCoordinate.java | 25 ++++ 11 files changed, 395 insertions(+), 48 deletions(-) create mode 100644 src/main/java/net/neoforged/neoform/runtime/actions/RemapSrgSourcesWithMcpAction.java create mode 100644 src/main/java/net/neoforged/neoform/runtime/artifacts/MavenMetadata.java diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/RecompileSourcesActionWithJDK.java b/src/main/java/net/neoforged/neoform/runtime/actions/RecompileSourcesActionWithJDK.java index 64c86212..28370892 100644 --- a/src/main/java/net/neoforged/neoform/runtime/actions/RecompileSourcesActionWithJDK.java +++ b/src/main/java/net/neoforged/neoform/runtime/actions/RecompileSourcesActionWithJDK.java @@ -40,7 +40,7 @@ public void run(ProcessingEnvironment environment) throws IOException, Interrupt try (var stream = Files.walk(sourceRoot).filter(Files::isRegularFile)) { stream.forEach(path -> { var filename = path.getFileName().toString(); - if (filename.endsWith(".java")) { + if (filename.endsWith(".java") && !filename.equals("package-info-template.java")) { sourcePaths.add(path); } else { nonSourcePaths.add(path); diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/RemapSrgSourcesWithMcpAction.java b/src/main/java/net/neoforged/neoform/runtime/actions/RemapSrgSourcesWithMcpAction.java new file mode 100644 index 00000000..55e2df33 --- /dev/null +++ b/src/main/java/net/neoforged/neoform/runtime/actions/RemapSrgSourcesWithMcpAction.java @@ -0,0 +1,112 @@ +package net.neoforged.neoform.runtime.actions; + +import net.neoforged.neoform.runtime.cache.CacheKeyBuilder; +import net.neoforged.neoform.runtime.engine.ProcessingEnvironment; +import net.neoforged.neoform.runtime.graph.ExecutionNodeAction; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +/** + * Takes a zip-file of Java sources and replaces SRG identifiers with + * identifiers from a mapping file. + * This version of {@link RemapSrgSourcesAction} uses MCP Mappings data for + * the intermediate->named mapping. + * The mappings are loaded from an MCP mappings ZIP, which contains CSV files with mappings for SRG->Named. + */ +public class RemapSrgSourcesWithMcpAction implements ExecutionNodeAction { + private static final Pattern SRG_FINDER = Pattern.compile("[fF]unc_\\d+_[a-zA-Z_]+|m_\\d+_|[fF]ield_\\d+_[a-zA-Z_]+|f_\\d+_"); + + private final Path mcpMappingsData; + + public RemapSrgSourcesWithMcpAction(Path mcpMappingsData) { + this.mcpMappingsData = mcpMappingsData; + } + + @Override + public void run(ProcessingEnvironment environment) throws IOException, InterruptedException { + var mappings = new HashMap(); + + try (var zf = new ZipFile(mcpMappingsData.toFile())) { + loadMappingsCsv(zf, "fields.csv", mappings); + loadMappingsCsv(zf, "methods.csv", mappings); + } + + var sourcesPath = environment.getRequiredInputPath("sources"); + var outputPath = environment.getOutputPath("output"); + + try (var zipIn = new ZipInputStream(new BufferedInputStream(Files.newInputStream(sourcesPath))); + var zipOut = new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(outputPath)))) { + for (var entry = zipIn.getNextEntry(); entry != null; entry = zipIn.getNextEntry()) { + zipOut.putNextEntry(entry); + + if (entry.getName().endsWith(".java")) { + var sourceCode = new String(zipIn.readAllBytes(), StandardCharsets.UTF_8); + var mappedSource = mapSourceCode(sourceCode, mappings); + zipOut.write(mappedSource.getBytes(StandardCharsets.UTF_8)); + } else { + zipIn.transferTo(zipOut); + } + zipOut.closeEntry(); + } + } + } + + // A very rudimentary parser for the CSV files inside the mappings ZIP. + // Ignores everything except the first two columns. + // Example: + // searge,name,side,desc + // field_100013_f,isPotionDurationMax,0,"True if potion effect duration is at maximum, false otherwise." + private void loadMappingsCsv(ZipFile zf, String filename, Map mappings) throws IOException { + var entry = zf.getEntry(filename); + if (entry == null) { + throw new IllegalStateException("MCP mappings ZIP file is missing entry " + filename); + } + + try (var reader = new BufferedReader(new InputStreamReader(zf.getInputStream(entry)))) { + var header = reader.readLine(); + if (!header.startsWith("searge,name,")) { + throw new IOException("Invalid header for Mappings CSV: " + filename); + } + String line; + while ((line = reader.readLine()) != null) { + var parts = line.split(",", 3); + if (parts.length < 2) { + continue; + } + String seargeName = parts[0]; + String name = parts[1]; + mappings.put(seargeName, name); + } + } + } + + @Override + public void computeCacheKey(CacheKeyBuilder ck) { + ExecutionNodeAction.super.computeCacheKey(ck); + ck.addPath("mcp mappings data", mcpMappingsData); + } + + private static String mapSourceCode(String sourceCode, Map srgNamesToOfficial) { + var m = SRG_FINDER.matcher(sourceCode); + return m.replaceAll(matchResult -> { + var matched = matchResult.group(); + // Some will be unmapped + var mapped = srgNamesToOfficial.getOrDefault(matched, matched); + return Matcher.quoteReplacement(mapped); + }); + } +} diff --git a/src/main/java/net/neoforged/neoform/runtime/artifacts/ArtifactManager.java b/src/main/java/net/neoforged/neoform/runtime/artifacts/ArtifactManager.java index 010f4df8..a36d8649 100644 --- a/src/main/java/net/neoforged/neoform/runtime/artifacts/ArtifactManager.java +++ b/src/main/java/net/neoforged/neoform/runtime/artifacts/ArtifactManager.java @@ -126,10 +126,28 @@ public Artifact get(MavenCoordinate mavenCoordinate) throws IOException { return externalArtifact; } + // Yet another special case: dynamic versions! + // Used in 1.12.1, for example. And yes, this will be very slow. + if (mavenCoordinate.isDynamicVersion()) { + var availableVersions = MavenMetadata.gatherVersions( + downloadManager, + repositoryBaseUrls, + mavenCoordinate.groupId(), + mavenCoordinate.artifactId() + ); + for (var availableVersion : availableVersions) { + if (mavenCoordinate.matchesVersion(availableVersion.version())) { + var concreteMavenCoordinate = mavenCoordinate.withVersion(availableVersion.version()); + return get(concreteMavenCoordinate, availableVersion.repositoryUrl()); + } + } + } + var finalLocation = artifactsCache.resolve(mavenCoordinate.toRelativeRepositoryPath()); // Special case: NeoForge reference libraries that are only available via the Mojang download server - if (mavenCoordinate.groupId().equals("com.mojang") && mavenCoordinate.artifactId().equals("logging")) { + if (mavenCoordinate.groupId().equals("com.mojang") && mavenCoordinate.artifactId().equals("logging") + || mavenCoordinate.groupId().equals("net.minecraft") && mavenCoordinate.artifactId().equals("launchwrapper")) { return get(mavenCoordinate, MINECRAFT_LIBRARIES_URI); } diff --git a/src/main/java/net/neoforged/neoform/runtime/artifacts/MavenMetadata.java b/src/main/java/net/neoforged/neoform/runtime/artifacts/MavenMetadata.java new file mode 100644 index 00000000..22fa3bc0 --- /dev/null +++ b/src/main/java/net/neoforged/neoform/runtime/artifacts/MavenMetadata.java @@ -0,0 +1,87 @@ +package net.neoforged.neoform.runtime.artifacts; + +import net.neoforged.neoform.runtime.downloads.DownloadManager; +import net.neoforged.neoform.runtime.utils.Logger; +import org.w3c.dom.Element; + +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +final class MavenMetadata { + private static final Logger LOG = Logger.create(); + + private MavenMetadata() { + } + + static List gatherVersions(DownloadManager downloadManager, + List repositoryBaseUrls, + String groupId, + String artifactId) throws IOException { + var versions = new ArrayList(); + for (var repositoryBaseUrl : repositoryBaseUrls) { + versions.addAll(gatherVersions(downloadManager, repositoryBaseUrl, groupId, artifactId)); + } + return versions; + } + + static List gatherVersions(DownloadManager downloadManager, + URI repositoryBaseUrl, + String groupId, + String artifactId) throws IOException { + var metadataUri = repositoryBaseUrl.toString(); + if (!metadataUri.endsWith("/")) { + metadataUri += "/"; + } + metadataUri += groupId.replace('.', '/') + "/" + artifactId + "/maven-metadata.xml"; + + byte[] metadataContent; + + var tempFile = Files.createTempFile("maven-metadata", ".xml"); + try { + Files.deleteIfExists(tempFile); // The downloader should assume it does not exist yet + downloadManager.download(URI.create(metadataUri), tempFile); + metadataContent = Files.readAllBytes(tempFile); + } catch (FileNotFoundException fnf) { + return List.of(); // Repository doesn't have artifact + } finally { + Files.deleteIfExists(tempFile); + } + + try (var in = new ByteArrayInputStream(metadataContent)) { + var result = new ArrayList(); + var documentBuilder = DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder(); + var document = documentBuilder.parse(in).getDocumentElement(); + var nodes = document.getChildNodes(); + for (var i = 0; i < nodes.getLength(); i++) { + if (nodes.item(i) instanceof Element versioningEl && "versioning".equals(versioningEl.getTagName())) { + for (var versions = versioningEl.getFirstChild(); versions != null; versions = versions.getNextSibling()) { + if (versions instanceof Element versionsEl && "versions".equals(versionsEl.getTagName())) { + for (var child = versionsEl.getFirstChild(); child != null; child = child.getNextSibling()) { + if (child instanceof Element childEl && "version".equals(childEl.getTagName())) { + result.add(new AvailableVersion( + repositoryBaseUrl, + childEl.getTextContent().trim() + )); + } + } + } + } + } + } + return result; + } catch (Exception e) { + LOG.println("Failed to parse Maven metadata from " + metadataUri + ": " + e); + throw new RuntimeException(e); + } + } + + record AvailableVersion(URI repositoryUrl, String version) { + } + +} diff --git a/src/main/java/net/neoforged/neoform/runtime/cli/RunNeoFormCommand.java b/src/main/java/net/neoforged/neoform/runtime/cli/RunNeoFormCommand.java index 10822654..b43de958 100644 --- a/src/main/java/net/neoforged/neoform/runtime/cli/RunNeoFormCommand.java +++ b/src/main/java/net/neoforged/neoform/runtime/cli/RunNeoFormCommand.java @@ -33,6 +33,7 @@ import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.function.Consumer; import java.util.jar.JarFile; @@ -68,8 +69,11 @@ public class RunNeoFormCommand extends NeoFormEngineCommand { @CommandLine.Option(names = "--parchment-conflict-prefix", description = "Setting this option enables automatic Parchment parameter conflict resolution and uses this prefix for parameter names that clash.") String parchmentConflictPrefix; + @CommandLine.Option(names = "--mcp-mapping-data", description = "Path or Maven coordinates of MCP mapping data to use for pre-1.17 Minecraft") + String mcpMappingData; + static class SourceArtifacts { - @CommandLine.ArgGroup(multiplicity = "1") + @CommandLine.ArgGroup NeoFormArtifact neoform; @CommandLine.Option(names = "--neoforge") String neoforge; @@ -86,6 +90,10 @@ static class NeoFormArtifact { protected void runWithNeoFormEngine(NeoFormEngine engine, List closables) throws IOException, InterruptedException { var artifactManager = engine.getArtifactManager(); + if (mcpMappingData != null) { + engine.setMcpMappingsData(artifactManager.get(mcpMappingData).path()); + } + if (sourceArtifacts.neoforge != null) { var neoforgeArtifact = artifactManager.get(sourceArtifacts.neoforge); var neoforgeZipFile = engine.addManagedResource(new JarFile(neoforgeArtifact.path().toFile())); @@ -93,10 +101,10 @@ protected void runWithNeoFormEngine(NeoFormEngine engine, List cl // Allow it to be overridden with local or remote data Path neoformArtifact; - if (sourceArtifacts.neoform.file != null) { + if (sourceArtifacts.neoform != null && sourceArtifacts.neoform.file != null) { LOG.println("Overriding NeoForm version " + neoforgeConfig.neoformArtifact() + " with NeoForm file " + sourceArtifacts.neoform.file); neoformArtifact = sourceArtifacts.neoform.file; - } else if (sourceArtifacts.neoform.artifact != null) { + } else if (sourceArtifacts.neoform != null && sourceArtifacts.neoform.artifact != null) { LOG.println("Overriding NeoForm version " + neoforgeConfig.neoformArtifact() + " with CLI argument " + sourceArtifacts.neoform.artifact); neoformArtifact = artifactManager.get(MavenCoordinate.parse(sourceArtifacts.neoform.artifact)).path(); } else { @@ -162,8 +170,28 @@ protected void runWithNeoFormEngine(NeoFormEngine engine, List cl } )); + // Source post-processors were used to post-process the decompiler output before applying the NF patches. + // Example version: 1.12.2. + var nfPatchesInputNode = "patch"; + var sourcePreProcessor = neoforgeConfig.sourcePreProcessor(); + if (sourcePreProcessor != null) { + transforms.add(new ReplaceNodeOutput( + "patch", "output", "applyUserdevSourcePreprocessor", + (builder, previousOutput) -> { + var newOutput = engine.applyFunctionToNode(Map.of( + // Provide the output of patch as the input + "input", "{patchOutput}" + ), NodeOutputType.ZIP, sourcePreProcessor, builder); + return Objects.requireNonNull(newOutput); + } + ) + ); + // Patches now need to use this node as input + nfPatchesInputNode = "applyUserdevSourcePreprocessor"; + } + // Append a patch step to the NeoForge patches - transforms.add(new ReplaceNodeOutput("patch", "output", "applyNeoforgePatches", + transforms.add(new ReplaceNodeOutput(nfPatchesInputNode, "output", "applyNeoforgePatches", (builder, previousOutput) -> { return PatchActionFactory.makeAction(builder, neoforgeArtifact.path(), diff --git a/src/main/java/net/neoforged/neoform/runtime/config/neoforge/NeoForgeConfig.java b/src/main/java/net/neoforged/neoform/runtime/config/neoforge/NeoForgeConfig.java index 0c3a9ca8..fa25c4af 100644 --- a/src/main/java/net/neoforged/neoform/runtime/config/neoforge/NeoForgeConfig.java +++ b/src/main/java/net/neoforged/neoform/runtime/config/neoforge/NeoForgeConfig.java @@ -4,6 +4,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonSyntaxException; import com.google.gson.annotations.SerializedName; +import net.neoforged.neoform.runtime.config.neoform.NeoFormFunction; import net.neoforged.neoform.runtime.utils.FilenameUtil; import net.neoforged.neoform.runtime.utils.MavenCoordinate; import org.jetbrains.annotations.Nullable; @@ -27,7 +28,9 @@ public record NeoForgeConfig( @SerializedName("patchesModifiedPrefix") @Nullable String modifiedPathPrefix, Map runs, List libraries, - List modules + List modules, + // This was used in older MC versions (i.e. 1.12.2) + @SerializedName("processor") @Nullable NeoFormFunction sourcePreProcessor ) { public static NeoForgeConfig from(ZipFile zipFile) throws IOException { byte[] configContent; diff --git a/src/main/java/net/neoforged/neoform/runtime/config/neoform/NeoFormConfig.java b/src/main/java/net/neoforged/neoform/runtime/config/neoform/NeoFormConfig.java index a0f54c8e..e236ab80 100644 --- a/src/main/java/net/neoforged/neoform/runtime/config/neoform/NeoFormConfig.java +++ b/src/main/java/net/neoforged/neoform/runtime/config/neoform/NeoFormConfig.java @@ -24,6 +24,12 @@ public record NeoFormConfig(int spec, Map functions, Map> libraries) { + public NeoFormConfig { + if (javaVersion == 0) { + javaVersion = 8; // Old versions didn't specify, but require Java 8 + } + } + public NeoFormDistConfig getDistConfig(String dist) { if (!steps.containsKey(dist)) { throw new IllegalArgumentException("This configuration does not include the distribution " diff --git a/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java b/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java index b54d085b..3a497eed 100644 --- a/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java +++ b/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java @@ -14,6 +14,7 @@ import net.neoforged.neoform.runtime.actions.RecompileSourcesActionWithECJ; import net.neoforged.neoform.runtime.actions.RecompileSourcesActionWithJDK; import net.neoforged.neoform.runtime.actions.RemapSrgSourcesAction; +import net.neoforged.neoform.runtime.actions.RemapSrgSourcesWithMcpAction; import net.neoforged.neoform.runtime.actions.SplitResourcesFromClassesAction; import net.neoforged.neoform.runtime.artifacts.ArtifactManager; import net.neoforged.neoform.runtime.cache.CacheKeyBuilder; @@ -38,6 +39,7 @@ import net.neoforged.neoform.runtime.utils.MavenCoordinate; import net.neoforged.neoform.runtime.utils.OsUtil; import net.neoforged.neoform.runtime.utils.StringUtil; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.io.PrintWriter; @@ -76,6 +78,7 @@ public class NeoFormEngine implements AutoCloseable { private final BuildOptions buildOptions = new BuildOptions(); private boolean verbose; private ProcessGeneration processGeneration; + private Path mcpMappingsData; /** * Nodes can reference certain configuration data (access transformers, patches, etc.) which come @@ -201,39 +204,71 @@ public void loadNeoFormProcess(NeoFormDistConfig distConfig) { // If we're running NeoForm for 1.20.1 or earlier, the sources after patches use // SRG method and field names, and need to be remapped. if (processGeneration.sourcesUseIntermediaryNames()) { - if (!graph.hasOutput("mergeMappings", "output") - || !graph.hasOutput("downloadClientMappings", "output")) { - throw new IllegalStateException("NFRT currently does not support MCP versions that did not make use of official Mojang mappings (pre 1.17)."); - } + if (processGeneration.needsMcpMappingData()) { + if (mcpMappingsData == null) { + throw new IllegalStateException("You need to provide MCP mappings data for this version of Minecraft"); + } - applyTransforms(List.of( - new ReplaceNodeOutput( - "patch", - "output", - "remapSrgSourcesToOfficial", - (builder, previousNodeOutput) -> { - builder.input("sources", previousNodeOutput.asInput()); - builder.input("mergedMappings", graph.getRequiredOutput("mergeMappings", "output").asInput()); - builder.input("officialMappings", graph.getRequiredOutput("downloadClientMappings", "output").asInput()); - var action = new RemapSrgSourcesAction(); - builder.action(action); - return builder.output("output", NodeOutputType.ZIP, "Sources with SRG method and field names remapped to official."); - } - ) - )); + applyTransforms(List.of( + new ReplaceNodeOutput( + "patch", + "output", + "remapSrgSourcesToOfficial", + (builder, previousNodeOutput) -> { + builder.input("sources", previousNodeOutput.asInput()); + var action = new RemapSrgSourcesWithMcpAction(mcpMappingsData); + builder.action(action); + return builder.output("output", NodeOutputType.ZIP, "Sources with SRG method and field names remapped to official."); + } + ) + )); + + // We also expose a few results for mappings in different formats + // var createMappings = graph.nodeBuilder("createMappings"); + // // official -> obf + // createMappings.inputFromNodeOutput("officialToObf", "downloadClientMappings", "output"); + // // obf -> srg + // createMappings.inputFromNodeOutput("obfToSrg", "mergeMappings", "output"); + // var action = new CreateLegacyMappingsAction(); + // createMappings.action(action); + // graph.setResult("namedToIntermediaryMapping", createMappings.output("officialToSrg", NodeOutputType.TSRG, "A mapping file that maps user-facing (Mojang, MCP) names to intermediary (SRG)")); + // graph.setResult("intermediaryToNamedMapping", createMappings.output("srgToOfficial", NodeOutputType.SRG, "A mapping file that maps intermediary (SRG) names to user-facing (Mojang, MCP) names")); + // graph.setResult("csvMapping", createMappings.output("csvMappings", NodeOutputType.ZIP, "A zip containing csv files with SRG to official mappings")); + // createMappings.build(); + } else { + if (!graph.hasOutput("mergeMappings", "output") || !graph.hasOutput("downloadClientMappings", "output")) { + throw new IllegalStateException("The NeoForm/MCP process has no nodes for mergeMappings/downloadClientMappings!"); + } + + applyTransforms(List.of( + new ReplaceNodeOutput( + "patch", + "output", + "remapSrgSourcesToOfficial", + (builder, previousNodeOutput) -> { + builder.input("sources", previousNodeOutput.asInput()); + builder.input("mergedMappings", graph.getRequiredOutput("mergeMappings", "output").asInput()); + builder.input("officialMappings", graph.getRequiredOutput("downloadClientMappings", "output").asInput()); + var action = new RemapSrgSourcesAction(); + builder.action(action); + return builder.output("output", NodeOutputType.ZIP, "Sources with SRG method and field names remapped to official."); + } + ) + )); - // We also expose a few results for mappings in different formats - var createMappings = graph.nodeBuilder("createMappings"); - // official -> obf - createMappings.inputFromNodeOutput("officialToObf", "downloadClientMappings", "output"); - // obf -> srg - createMappings.inputFromNodeOutput("obfToSrg", "mergeMappings", "output"); - var action = new CreateLegacyMappingsAction(); - createMappings.action(action); - graph.setResult("namedToIntermediaryMapping", createMappings.output("officialToSrg", NodeOutputType.TSRG, "A mapping file that maps user-facing (Mojang, MCP) names to intermediary (SRG)")); - graph.setResult("intermediaryToNamedMapping", createMappings.output("srgToOfficial", NodeOutputType.SRG, "A mapping file that maps intermediary (SRG) names to user-facing (Mojang, MCP) names")); - graph.setResult("csvMapping", createMappings.output("csvMappings", NodeOutputType.ZIP, "A zip containing csv files with SRG to official mappings")); - createMappings.build(); + // We also expose a few results for mappings in different formats + var createMappings = graph.nodeBuilder("createMappings"); + // official -> obf + createMappings.inputFromNodeOutput("officialToObf", "downloadClientMappings", "output"); + // obf -> srg + createMappings.inputFromNodeOutput("obfToSrg", "mergeMappings", "output"); + var action = new CreateLegacyMappingsAction(); + createMappings.action(action); + graph.setResult("namedToIntermediaryMapping", createMappings.output("officialToSrg", NodeOutputType.TSRG, "A mapping file that maps user-facing (Mojang, MCP) names to intermediary (SRG)")); + graph.setResult("intermediaryToNamedMapping", createMappings.output("srgToOfficial", NodeOutputType.SRG, "A mapping file that maps intermediary (SRG) names to user-facing (Mojang, MCP) names")); + graph.setResult("csvMapping", createMappings.output("csvMappings", NodeOutputType.ZIP, "A zip containing csv files with SRG to official mappings")); + createMappings.build(); + } } } @@ -373,17 +408,32 @@ private DataSource getRequiredDataSource(String dataId) { } private void applyFunctionToNode(NeoFormStep step, NeoFormFunction function, ExecutionNodeBuilder builder) { + var type = switch (step.type()) { + case "mergeMappings" -> NodeOutputType.TSRG; + default -> NodeOutputType.JAR; + }; + + applyFunctionToNode(step.values(), type, function, builder); + } + + @Nullable + public NodeOutput applyFunctionToNode(Map placeholders, + NodeOutputType outputType, + NeoFormFunction function, + ExecutionNodeBuilder builder) { var resolvedJvmArgs = new ArrayList<>(Objects.requireNonNullElse(function.jvmargs(), List.of())); var resolvedArgs = new ArrayList<>(Objects.requireNonNullElse(function.args(), List.of())); // Start by resolving the function->step indirection where functions can reference variables that // are defined in the step. Usually (but not always) these will just refer to further global variables. - for (var entry : step.values().entrySet()) { + for (var entry : placeholders.entrySet()) { UnaryOperator resolver = s -> s.replace("{" + entry.getKey() + "}", entry.getValue()); resolvedJvmArgs.replaceAll(resolver); resolvedArgs.replaceAll(resolver); } + NodeOutput[] mainOutput = new NodeOutput[1]; + // Now resolve the remaining placeholders. Consumer placeholderProcessor = text -> { var matcher = NeoFormInterpolator.TOKEN_PATTERN.matcher(text); @@ -393,12 +443,8 @@ private void applyFunctionToNode(NeoFormStep step, NeoFormFunction function, Exe // Handle the "magic" output variable. In NeoForm JSON, it's impossible to know which // variables are truly intended to be outputs. if ("output".equals(variable)) { - var type = switch (step.type()) { - case "mergeMappings" -> NodeOutputType.TSRG; - default -> NodeOutputType.JAR; - }; if (!builder.hasOutput(variable)) { - builder.output(variable, type, "Output of step " + step.type()); + mainOutput[0] = builder.output(variable, outputType, "Output of step"); } } else if (dataSources.containsKey(variable)) { // It likely refers to data from the NeoForm zip, this will be handled by the runtime later @@ -410,7 +456,7 @@ private void applyFunctionToNode(NeoFormStep step, NeoFormFunction function, Exe } else if (variable.equals("log")) { // This variable is used in legacy MCP config JSONs to signify the path to a logfile and is ignored here } else { - throw new IllegalArgumentException("Unsupported variable " + variable + " used by step " + step.getId()); + throw new IllegalArgumentException("Unsupported variable " + variable + " used by step " + builder.id()); } } }; @@ -421,7 +467,7 @@ private void applyFunctionToNode(NeoFormStep step, NeoFormFunction function, Exe try { toolArtifactCoordinate = MavenCoordinate.parse(function.toolArtifact()); } catch (Exception e) { - throw new IllegalArgumentException("Function for step " + step + " has invalid tool: " + function.toolArtifact()); + throw new IllegalArgumentException("Function for step " + builder.id() + " has invalid tool: " + function.toolArtifact()); } var action = new ExternalJavaToolAction(toolArtifactCoordinate); @@ -429,6 +475,8 @@ private void applyFunctionToNode(NeoFormStep step, NeoFormFunction function, Exe action.setJvmArgs(resolvedJvmArgs); action.setArgs(resolvedArgs); builder.action(action); + + return mainOutput[0]; } private void createDownloadFromVersionManifest(ExecutionNodeBuilder builder, String manifestEntry, NodeOutputType jar, String description) { @@ -759,4 +807,8 @@ public boolean isVerbose() { return verbose; } } + + public void setMcpMappingsData(Path mcpMappingsData) { + this.mcpMappingsData = mcpMappingsData; + } } diff --git a/src/main/java/net/neoforged/neoform/runtime/engine/ProcessGeneration.java b/src/main/java/net/neoforged/neoform/runtime/engine/ProcessGeneration.java index 4de0c6ea..90a08f28 100644 --- a/src/main/java/net/neoforged/neoform/runtime/engine/ProcessGeneration.java +++ b/src/main/java/net/neoforged/neoform/runtime/engine/ProcessGeneration.java @@ -59,9 +59,11 @@ public int compareTo(@NotNull ProcessGeneration.MinecraftReleaseVersion o) { private boolean sourcesUseIntermediaryNames; /** - * For (Neo)Forge 1.20.1 and below, we have to remap method and field names from - * SRG to official names for development. + * For Pre-1.17, we were not using official mappings and require an external source + * for the mapping from intermediate to named. This was done using "MCP Mappings". + * This field can only be true if sourcesUseIntermediaryNames is also true. */ + private boolean needsMcpMappingData; static ProcessGeneration fromMinecraftVersion(String minecraftVersion) { var releaseVersion = MinecraftReleaseVersion.parse(minecraftVersion); @@ -88,6 +90,9 @@ static ProcessGeneration fromMinecraftVersion(String minecraftVersion) { // In 1.20.2 and later, NeoForge switched to Mojmap at runtime and sources defined in Mojmap result.sourcesUseIntermediaryNames = isLessThanOrEqualTo(releaseVersion, MC_1_20_1); + // In 1.17.1 and later, Forge started using official Mojang mappings + result.needsMcpMappingData = isLessThanOrEqualTo(releaseVersion, MC_1_17_1); + return result; } @@ -105,6 +110,13 @@ public boolean sourcesUseIntermediaryNames() { return sourcesUseIntermediaryNames; } + /** + * Do we support and need MCP mappings data to get readable names in source? + */ + public boolean needsMcpMappingData() { + return needsMcpMappingData; + } + /** * Allows additional resources to be completely removed from Minecraft jars before processing them. */ diff --git a/src/main/java/net/neoforged/neoform/runtime/graph/ExecutionNodeBuilder.java b/src/main/java/net/neoforged/neoform/runtime/graph/ExecutionNodeBuilder.java index a8a17488..2185d26f 100644 --- a/src/main/java/net/neoforged/neoform/runtime/graph/ExecutionNodeBuilder.java +++ b/src/main/java/net/neoforged/neoform/runtime/graph/ExecutionNodeBuilder.java @@ -16,6 +16,10 @@ public ExecutionNodeBuilder(ExecutionGraph graph, String id) { this.id = Objects.requireNonNull(id, "id"); } + public String id() { + return id; + } + public boolean hasInput(String inputId) { return nodeInputs.containsKey(inputId); } diff --git a/src/main/java/net/neoforged/neoform/runtime/utils/MavenCoordinate.java b/src/main/java/net/neoforged/neoform/runtime/utils/MavenCoordinate.java index bd9425d1..b6169f82 100644 --- a/src/main/java/net/neoforged/neoform/runtime/utils/MavenCoordinate.java +++ b/src/main/java/net/neoforged/neoform/runtime/utils/MavenCoordinate.java @@ -128,4 +128,29 @@ public MavenCoordinate withClassifier(String classifier) { version ); } + + public MavenCoordinate withVersion(String version) { + return new MavenCoordinate( + groupId, + artifactId, + extension, + classifier, + version + ); + } + + public boolean isDynamicVersion() { + // We only support extremely simple cases right now. + return version.endsWith("+"); + } + + public boolean matchesVersion(String version) { + // "+" acts as a prefix match according to Gradle + // https://docs.gradle.org/current/userguide/dependency_versions.html#sec:single-version-declarations + if (this.version.endsWith("+")) { + return version.startsWith(this.version.substring(0, this.version.length() - 1)); + } else { + return this.version.equals(version); + } + } }