diff --git a/build.gradle b/build.gradle index 4c7f9cbf..a1b1fed9 100644 --- a/build.gradle +++ b/build.gradle @@ -209,11 +209,6 @@ idea { 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=gameJarWithNeoForge:build/1.20.1-minecraft.jar --write-result=clientResources:build/1.20.1-client-extra.jar --write-result=gameSourcesWithNeoForge:build/1.20.1-minecraft-sources.jar --problems-report=build/1.20.1-problems.json" 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=gameJarWithNeoForge:build/1.12.2-minecraft.jar --write-result=clientResources:build/1.12.2-client-extra.jar --write-result=gameSourcesWithNeoForge: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=gameJarWithNeoForge:build/1.17.1-minecraft.jar --write-result=clientResources:build/1.17.1-client-extra.jar --write-result=gameSourcesWithNeoForge:build/1.17.1-minecraft-sources.jar" diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/CreateLegacyMappingsAction.java b/src/main/java/net/neoforged/neoform/runtime/actions/CreateLegacyMappingsAction.java index 2d4468dc..7e0c2423 100644 --- a/src/main/java/net/neoforged/neoform/runtime/actions/CreateLegacyMappingsAction.java +++ b/src/main/java/net/neoforged/neoform/runtime/actions/CreateLegacyMappingsAction.java @@ -37,10 +37,13 @@ public void run(ProcessingEnvironment environment) throws IOException, Interrupt srgToOfficial.write(environment.getOutputPath("srgToOfficial"), IMappingFile.Format.SRG, false); try (var zipCsv = new ZipOutputStream(Files.newOutputStream(environment.getOutputPath("csvMappings")))) { + // Match both modern intermediary names (m_/f_, 1.17+) and legacy SRG names (func_/field_, pre-1.17) writeCsv(zipCsv, "methods.csv", srgToOfficial.getClasses().stream() - .flatMap(c -> c.getMethods().stream()).filter(c -> c.getOriginal().startsWith("m_"))); + .flatMap(c -> c.getMethods().stream()) + .filter(c -> c.getOriginal().startsWith("m_") || c.getOriginal().startsWith("func_"))); writeCsv(zipCsv, "fields.csv", srgToOfficial.getClasses().stream() - .flatMap(c -> c.getFields().stream()).filter(c -> c.getOriginal().startsWith("f_"))); + .flatMap(c -> c.getFields().stream()) + .filter(c -> c.getOriginal().startsWith("f_") || c.getOriginal().startsWith("field_"))); } } diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/ExtractNeoFormDataAction.java b/src/main/java/net/neoforged/neoform/runtime/actions/ExtractNeoFormDataAction.java new file mode 100644 index 00000000..157506b3 --- /dev/null +++ b/src/main/java/net/neoforged/neoform/runtime/actions/ExtractNeoFormDataAction.java @@ -0,0 +1,31 @@ +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.IOException; +import java.nio.file.Files; + +/** + * Copies a NeoForm data source (a file embedded in the NeoForm config zip) to a node output, + * making it available as an input to downstream nodes in the execution graph. + */ +public class ExtractNeoFormDataAction implements ExecutionNodeAction { + private final String dataKey; + + public ExtractNeoFormDataAction(String dataKey) { + this.dataKey = dataKey; + } + + @Override + public void run(ProcessingEnvironment environment) throws IOException, InterruptedException { + Files.copy(environment.extractData(dataKey), environment.getOutputPath("output")); + } + + @Override + public void computeCacheKey(CacheKeyBuilder ck) { + ExecutionNodeAction.super.computeCacheKey(ck); + ck.add("data key", dataKey); + } +} diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/RecompileSourcesActionWithECJ.java b/src/main/java/net/neoforged/neoform/runtime/actions/RecompileSourcesActionWithECJ.java index 92c5e509..94b7ed34 100644 --- a/src/main/java/net/neoforged/neoform/runtime/actions/RecompileSourcesActionWithECJ.java +++ b/src/main/java/net/neoforged/neoform/runtime/actions/RecompileSourcesActionWithECJ.java @@ -146,7 +146,7 @@ public void acceptResult(CompilationResult result) { while (entries.hasMoreElements()) { var entry = entries.nextElement(); if (!entry.isDirectory()) { - if (entry.getName().endsWith(".java")) { + if (entry.getName().endsWith(".java") && !entry.getName().equals("package-info-template.java")) { futures.add(executor.submit(() -> { // TODO This is copy heavy and should be optimized try (var in = sourcesZip.getInputStream(entry)) { 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/artifacts/ArtifactManager.java b/src/main/java/net/neoforged/neoform/runtime/artifacts/ArtifactManager.java index 8c7fbead..92aa08c5 100644 --- a/src/main/java/net/neoforged/neoform/runtime/artifacts/ArtifactManager.java +++ b/src/main/java/net/neoforged/neoform/runtime/artifacts/ArtifactManager.java @@ -125,10 +125,30 @@ 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()); + } + } + + throw new FileNotFoundException("Could not find " + mavenCoordinate + " in any repository."); + } + 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); } @@ -258,11 +278,22 @@ public interface DownloadAction { } private Artifact getFromExternalManifest(MavenCoordinate artifactCoordinate) { + // Try direct match first var artifact = externallyProvided.get(artifactCoordinate); if (artifact != null) { return artifact; } + // Find any manifest entry for the same group/artifact/classifier and evaluate if it matches a dynamic version constraint + if (artifactCoordinate.isDynamicVersion()) { + for (var entry : externallyProvided.entrySet()) { + if (artifactCoordinate.equalsWithoutVersion(entry.getKey()) + && artifactCoordinate.matchesVersion(entry.getKey().version())) { + return entry.getValue(); + } + } + } + // Fall back to looking up a wildcard version for dependency replacement in includeBuild scenarios if (!"*".equals(artifactCoordinate.version())) { artifact = externallyProvided.get(artifactCoordinate.withVersion("*")); 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..88048364 --- /dev/null +++ b/src/main/java/net/neoforged/neoform/runtime/artifacts/MavenMetadata.java @@ -0,0 +1,92 @@ +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; + +/** + * Support class for querying maven metadata from a remote repository. + * The format is documented here: https://maven.apache.org/repositories/metadata.html + * We only deal with A-level metadata since we're interested in listing the versions of + * a specific artifact. + */ +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) { + } +} \ No newline at end of file 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 89c32d49..b6cabf9d 100644 --- a/src/main/java/net/neoforged/neoform/runtime/cli/RunNeoFormCommand.java +++ b/src/main/java/net/neoforged/neoform/runtime/cli/RunNeoFormCommand.java @@ -45,6 +45,7 @@ import java.util.Base64; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.function.Consumer; import java.util.jar.JarFile; @@ -220,6 +221,15 @@ private static void applyNeoForgeProcessTransforms(NeoFormEngine engine, JarFile // The MCF sources have a bogus MANIFEST that should be ignored Pattern.compile("^(?!META-INF/MANIFEST.MF$).*") )); + // Older Forge versions ship pre-compiled dev-only classes and service registrations + // (e.g. ILaunchHandlerService) under inject/ in the userdev jar. + if (neoforgeConfig.injectFolder() != null) { + action.getInjectedSources().add(new InjectFromZipFileSource( + neoforgeZipFile, + neoforgeConfig.injectFolder(), + Pattern.compile("^(?!.*\\.class$).*") + )); + } } )); } @@ -263,8 +273,28 @@ private static void applyNeoForgeProcessTransforms(NeoFormEngine engine, JarFile } } + // 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) { + engine.applyTransform(new ReplaceNodeOutput( + "patch", "output", "applyUserdevSourcePreprocessor", + (builder, previousOutput) -> { + var newOutput = engine.applyFunctionToNode(neoforgeConfig.libraries(), 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 - engine.applyTransform(new ReplaceNodeOutput("patch", "output", "applyNeoforgePatches", + engine.applyTransform(new ReplaceNodeOutput(nfPatchesInputNode, "output", "applyNeoforgePatches", (builder, previousOutput) -> { return PatchActionFactory.makeAction(builder, new DataSource(neoforgeZipFile, neoforgeConfig.patchesFolder(), engine.getFileHashingService()), 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 a27c2b99..1d4ea273 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 @@ -5,6 +5,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; @@ -28,10 +29,13 @@ public record NeoForgeConfig( @SerializedName("universal") String universalArtifact, @SerializedName("patchesOriginalPrefix") @Nullable String basePathPrefix, @SerializedName("patchesModifiedPrefix") @Nullable String modifiedPathPrefix, + @SerializedName("inject") @Nullable String injectFolder, Map runs, List libraries, List modules, - @SerializedName("sass") List sideAnnotationStrippers + @SerializedName("sass") List sideAnnotationStrippers, + // 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..897bcac4 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 @@ -23,6 +23,11 @@ public record NeoFormConfig(int spec, Map> steps, Map functions, Map> libraries) { + public NeoFormConfig { + if (javaVersion == 0) { + javaVersion = 8; // Older versions did not explicitly specify 8 + } + } public NeoFormDistConfig getDistConfig(String dist) { if (!steps.containsKey(dist)) { 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 e0732c40..f6aaa3ba 100644 --- a/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java +++ b/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java @@ -6,6 +6,7 @@ import net.neoforged.neoform.runtime.actions.DownloadLauncherManifestAction; import net.neoforged.neoform.runtime.actions.DownloadVersionManifestAction; import net.neoforged.neoform.runtime.actions.ExternalJavaToolAction; +import net.neoforged.neoform.runtime.actions.ExtractNeoFormDataAction; import net.neoforged.neoform.runtime.actions.InjectFromZipFileSource; import net.neoforged.neoform.runtime.actions.InjectZipContentAction; import net.neoforged.neoform.runtime.actions.MergeWithSourcesAction; @@ -220,9 +221,14 @@ 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 (!graph.hasOutput("mergeMappings", "output")) { + if (!processGeneration.hasProguardMappings()) { + throw new IllegalStateException("MCP versions predating Mojang's ProGuard mappings (before 1.14.4) are not supported."); + } + // 1.14.4–1.16.5: ProGuard mappings exist in the version manifest but the MCP + // pipeline predates the mergeMappings/downloadClientMappings steps. Synthesize + // equivalent nodes so the unified path below works for all versions. + synthesizeMojmapNodes(); } applyTransforms(List.of( @@ -270,6 +276,27 @@ public void loadNeoFormProcess(NeoFormDistConfig distConfig) { } } + /** + * Synthesizes the {@code downloadClientMappings} and {@code mergeMappings} graph nodes for + * MCP configs that predate those pipeline steps (1.14.4–1.16.5). ProGuard mappings are + * available from the version manifest for these versions, so the same unified remapping path + * used for 1.17+ can be applied after synthesis. + */ + private void synthesizeMojmapNodes() { + var dlBuilder = graph.nodeBuilder("downloadClientMappings"); + dlBuilder.inputFromNodeOutput("versionManifest", "downloadJson", "output"); + dlBuilder.output("output", NodeOutputType.TXT, "Official mappings for the Minecraft client jar-file."); + dlBuilder.action(new DownloadFromVersionManifestAction(artifactManager, "client_mappings")); + dlBuilder.build(); + + // The MCP config TSRG (config/joined.tsrg) is already in obf→srg format, identical to + // what the native mergeMappings step produces. Expose it as a node output. + var mergeBuilder = graph.nodeBuilder("mergeMappings"); + mergeBuilder.output("output", NodeOutputType.TSRG, "Obfuscated-to-SRG mappings from MCP config."); + mergeBuilder.action(new ExtractNeoFormDataAction("mappings")); + mergeBuilder.build(); + } + private NodeOutput addRecompileStep(NeoFormDistConfig distConfig, NodeOutput sourcesOutput) { // Add a recompile step var builder = graph.nodeBuilder("recompile"); @@ -422,13 +449,28 @@ private DataSource getRequiredDataSource(String dataId) { } private void applyFunctionToNode(NeoFormDistConfig config, NeoFormStep step, NeoFormFunction function, ExecutionNodeBuilder builder) { + var type = switch (step.type()) { + case "mergeMappings" -> NodeOutputType.TSRG; + case "generateSplitManifest" -> NodeOutputType.JAR_MANIFEST; + default -> NodeOutputType.JAR; + }; + + applyFunctionToNode(config.libraries(), step.values(), type, function, builder); + } + + @Nullable + public NodeOutput applyFunctionToNode(List libraries, + 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())); // At runtime, {placeholder} in the function arguments can refer to node inputs, node outputs or data (see ProcessingEnvironment#interpolateString). // Variables assigned to outputs of other nodes have already been added as node inputs and can be used directly. // Any constants or references to data need to be eagerly resolved since those are not supported as node inputs. - for (var entry : step.values().entrySet()) { + for (var entry : placeholders.entrySet()) { if (builder.hasInput(entry.getKey())) { continue; // This placeholder was already declared as a node input (and referes to the output of another node) } @@ -437,6 +479,8 @@ private void applyFunctionToNode(NeoFormDistConfig config, NeoFormStep step, Neo resolvedArgs.replaceAll(resolver); } + NodeOutput[] mainOutput = new NodeOutput[1]; + // Now resolve the remaining placeholders. Set dataSourcesUsed = new HashSet<>(); boolean[] usesListLibraries = new boolean[]{false}; @@ -448,13 +492,8 @@ private void applyFunctionToNode(NeoFormDistConfig config, NeoFormStep step, Neo // 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; - case "generateSplitManifest" -> NodeOutputType.JAR_MANIFEST; - 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 " + builder.id()); } } else if (dataSources.containsKey(variable)) { // It likely refers to data from the NeoForm zip, this will be handled by the runtime later @@ -472,14 +511,15 @@ private void applyFunctionToNode(NeoFormDistConfig config, NeoFormStep step, Neo } else if (builder.hasInput(variable)) { // The variable was already set by the step and added as an input to the node, so we can safely use it } else { - throw new IllegalArgumentException("Unsupported variable " + variable + " used by step " + step.getId()); + throw new IllegalArgumentException("Unsupported variable " + variable + " used by step " + builder.id()); } } }; resolvedJvmArgs.forEach(placeholderProcessor); resolvedArgs.forEach(placeholderProcessor); - var action = new ExternalJavaToolAction(getFunctionClasspath(step, function), function.mainClass()); + var action = new ExternalJavaToolAction(getFunctionClasspath(builder.id(), function), function.mainClass()); + action.setRepositoryUrl(function.repository()); action.setJvmArgs(resolvedJvmArgs); action.setArgs(resolvedArgs); @@ -494,12 +534,14 @@ private void applyFunctionToNode(NeoFormDistConfig config, NeoFormStep step, Neo builder.inputFromNodeOutput("versionManifest", "downloadJson", "output"); var listLibraries = new CreateLibrariesOptionsFile(); listLibraries.getClasspath().setOverriddenClasspath(buildOptions.getOverriddenCompileClasspath()); - listLibraries.getClasspath().addMavenLibraries(config.libraries()); + listLibraries.getClasspath().addMavenLibraries(libraries); action.setListLibraries(listLibraries); } + + return mainOutput[0]; } - private static List getFunctionClasspath(NeoFormStep step, NeoFormFunction function) { + private static List getFunctionClasspath(String step, NeoFormFunction function) { List toolClasspath = new ArrayList<>(); if (function.toolArtifact() != null) { if (function.classpath() != null || function.mainClass() != null) { @@ -878,4 +920,5 @@ public ProblemReporter getProblemReporter() { return problemReporter; } } + } 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 004b0275..156a609f 100644 --- a/src/main/java/net/neoforged/neoform/runtime/engine/ProcessGeneration.java +++ b/src/main/java/net/neoforged/neoform/runtime/engine/ProcessGeneration.java @@ -41,6 +41,7 @@ public int compareTo(@NotNull ProcessGeneration.MinecraftReleaseVersion o) { } } + private static final MinecraftReleaseVersion MC_1_14_4 = new MinecraftReleaseVersion(1, 14, 4); private static final MinecraftReleaseVersion MC_1_17_1 = new MinecraftReleaseVersion(1, 17, 1); private static final MinecraftReleaseVersion MC_1_20_1 = new MinecraftReleaseVersion(1, 20, 1); private static final MinecraftReleaseVersion MC_1_21_6 = new MinecraftReleaseVersion(1, 21, 6); @@ -59,6 +60,12 @@ public int compareTo(@NotNull ProcessGeneration.MinecraftReleaseVersion o) { */ private boolean sourcesUseIntermediaryNames; + /** + * Indicates that Mojang publishes ProGuard mappings for this version. + * ProGuard mappings have been published since Minecraft 1.14.4. + */ + private boolean hasProguardMappings; + /** * SAS was used in Forge 1.20.1 and earlier to remove the "OnlyIn" annotation from client-only classes * that we'd want to be able to use on the server as well. @@ -101,6 +108,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); + // Mojang started publishing ProGuard mappings with 1.14.4 + result.hasProguardMappings = isGreaterThanOrEqualTo(releaseVersion, MC_1_14_4); + // In 1.21.6 and later, manifest entries should be generated as they may be used instead of RuntimeDistCleaner result.generateDistSourceManifest = isGreaterThanOrEqualTo(releaseVersion, MC_1_21_6); @@ -130,6 +140,13 @@ public boolean sourcesUseIntermediaryNames() { return sourcesUseIntermediaryNames; } + /** + * Does Mojang publish ProGuard mappings for this Minecraft version? + */ + public boolean hasProguardMappings() { + return hasProguardMappings; + } + /** * We only support side annotation stripping for Forge 1.20.1 and earlier. */ 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..5f12368e 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 this.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 94902767..6d031f5f 100644 --- a/src/main/java/net/neoforged/neoform/runtime/utils/MavenCoordinate.java +++ b/src/main/java/net/neoforged/neoform/runtime/utils/MavenCoordinate.java @@ -151,4 +151,19 @@ public boolean equalsWithoutVersion(MavenCoordinate other) { && Objects.equals(classifier, other.classifier) && Objects.equals(extension, other.extension); } + + 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); + } + } }