diff --git a/build.gradle b/build.gradle index b42d555c..4a17e2c0 100644 --- a/build.gradle +++ b/build.gradle @@ -163,6 +163,11 @@ idea { programParameters = "--help" moduleRef(project, sourceSets.main) } + "Run Neoforge 1.21.6 (joined) + Parchment"(Application) { + mainClass = mainClassName + programParameters = "run --dist joined --neoforge net.neoforged:neoforge:21.6.1-beta:userdev --add-repository=https://maven.parchmentmc.org --parchment-data=org.parchmentmc.data:parchment-1.21.5:2025.06.15@zip --parchment-conflict-prefix=p_ --write-result=compiled:build/minecraft-1.21.6.jar --write-result=clientResources:build/client-extra-1.21.6.jar --write-result=sources:build/minecraft-sources-1.21.6.jar" + moduleRef(project, sourceSets.main) + } "Run Neoforge 1.21 (joined) + Parchment"(Application) { mainClass = mainClassName programParameters = "run --dist joined --neoforge net.neoforged:neoforge:21.0.0-beta:userdev --add-repository=https://maven.parchmentmc.org --parchment-data=org.parchmentmc.data:parchment-1.21:2024.06.23@zip --parchment-conflict-prefix=p_ --write-result=compiled:build/minecraft-1.21.jar --write-result=clientResources:build/client-extra-1.21.jar --write-result=sources:build/minecraft-sources-1.21.jar" diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/SplitResourcesFromClassesAction.java b/src/main/java/net/neoforged/neoform/runtime/actions/SplitResourcesFromClassesAction.java index 2a09b852..57618533 100644 --- a/src/main/java/net/neoforged/neoform/runtime/actions/SplitResourcesFromClassesAction.java +++ b/src/main/java/net/neoforged/neoform/runtime/actions/SplitResourcesFromClassesAction.java @@ -2,32 +2,74 @@ import net.neoforged.neoform.runtime.cache.CacheKeyBuilder; import net.neoforged.neoform.runtime.engine.ProcessingEnvironment; +import net.neoforged.srgutils.IMappingFile; +import org.jetbrains.annotations.Nullable; -import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.function.Predicate; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; +import java.util.jar.Attributes; +import java.util.jar.JarFile; import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; /** * Copies a Jar file while applying a filename filter. + *
Optionally, this also {@link #generateSplitManifest creates and injects} a {@code MANIFEST.MF} file that details files that are exclusive
+ * to the distribution of Minecraft being processed by this action.
*/
public final class SplitResourcesFromClassesAction extends BuiltInAction {
+
+ /**
+ * @see #generateSplitManifest
+ */
+ public static final String INPUT_OTHER_DIST_JAR = "otherDistJar";
+ /**
+ * @see #generateSplitManifest
+ */
+ public static final String INPUT_MAPPINGS = "mappings";
+
+ /**
+ * Use a fixed timestamp for the manifest entry.
+ */
+ private static final LocalDateTime MANIFEST_TIME = LocalDateTime.of(2000, 1, 1, 0, 0, 0, 0);
+
/**
* Patterns for filenames that should not be written to either output jar.
*/
private final List This adds required inputs {@link #INPUT_MAPPINGS} and {@link #INPUT_OTHER_DIST_JAR} to this action.
+ * Common values for distributions are {@code client} and {@code server}.
+ *
+ * @param distId The name for the distribution that the main input file is from. It is used in the
+ * generated manifest for files that are only present in the main input, but not in the
+ * {@linkplain #INPUT_OTHER_DIST_JAR jar file of the other distribution}.
+ * @param otherDistId The name for the Minecraft distribution for the jar file given in {@link #INPUT_OTHER_DIST_JAR}.
+ * It is used in the generated manifest for files that are only present in that jar file.
+ */
+ public void generateSplitManifest(String distId, String otherDistId) {
+ generateDistManifestSettings = new GenerateDistManifestSettings(
+ Objects.requireNonNull(distId, "distId"),
+ Objects.requireNonNull(otherDistId, "otherDistId")
+ );
+ }
+
@Override
public void computeCacheKey(CacheKeyBuilder ck) {
super.computeCacheKey(ck);
ck.addStrings("deny patterns", denyListPatterns.stream().map(Pattern::pattern).toList());
+ if (generateDistManifestSettings != null) {
+ ck.add("generate dist manifest - our dist", generateDistManifestSettings.distId);
+ ck.add("generate dist manifest - other dist", generateDistManifestSettings.otherDistId);
+ }
+ }
+
+ private record GenerateDistManifestSettings(
+ String distId,
+ String otherDistId
+ ) {
}
}
diff --git a/src/main/java/net/neoforged/neoform/runtime/config/neoform/NeoFormDistConfig.java b/src/main/java/net/neoforged/neoform/runtime/config/neoform/NeoFormDistConfig.java
index a87198f5..6f786449 100644
--- a/src/main/java/net/neoforged/neoform/runtime/config/neoform/NeoFormDistConfig.java
+++ b/src/main/java/net/neoforged/neoform/runtime/config/neoform/NeoFormDistConfig.java
@@ -21,6 +21,10 @@ public NeoFormDistConfig(NeoFormConfig config, String dist) {
this.dist = dist;
}
+ public String dist() {
+ return dist;
+ }
+
public int javaVersion() {
return config.javaVersion();
}
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 0439518d..79f11d90 100644
--- a/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java
+++ b/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java
@@ -324,6 +324,26 @@ private void addNodeForStep(ExecutionGraph graph, NeoFormDistConfig config, NeoF
var action = new SplitResourcesFromClassesAction();
// The Minecraft jar contains nothing of interest in META-INF, and the signature files are useless.
action.addDenyPatterns("META-INF/.*");
+
+ // When generating Minecraft artifacts that join the client and server, we generate a MANIFEST.MF that
+ // indicates files exclusive to one or the other. This started in Minecraft 1.21.6.
+ if (processGeneration.generateDistSourceManifest() && config.dist().equals("joined")) {
+ if ("stripClient".equals(step.getId())) {
+ // Prefer the already extracted server, otherwise download it
+ var serverJarInput = graph.hasOutput("extractServer", "output") ?
+ graph.getRequiredOutput("extractServer", "output").asInput()
+ : graph.getRequiredOutput("downloadServer", "output").asInput();
+
+ action.generateSplitManifest("client", "server");
+ builder.input(SplitResourcesFromClassesAction.INPUT_OTHER_DIST_JAR, serverJarInput);
+ builder.input(SplitResourcesFromClassesAction.INPUT_MAPPINGS, graph.getRequiredOutput("mergeMappings", "output").asInput());
+ } else if ("stripServer".equals(step.getId())) {
+ action.generateSplitManifest("server", "client");
+ builder.input(SplitResourcesFromClassesAction.INPUT_OTHER_DIST_JAR, graph.getRequiredOutput("downloadClient", "output").asInput());
+ builder.input(SplitResourcesFromClassesAction.INPUT_MAPPINGS, graph.getRequiredOutput("mergeMappings", "output").asInput());
+ }
+ }
+
processGeneration.getAdditionalDenyListForMinecraftJars().forEach(action::addDenyPatterns);
builder.action(action);
}
@@ -401,6 +421,7 @@ private void applyFunctionToNode(NeoFormStep step, NeoFormFunction function, Exe
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)) {
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 b8bbd0ac..004b0275 100644
--- a/src/main/java/net/neoforged/neoform/runtime/engine/ProcessGeneration.java
+++ b/src/main/java/net/neoforged/neoform/runtime/engine/ProcessGeneration.java
@@ -43,6 +43,7 @@ public int compareTo(@NotNull ProcessGeneration.MinecraftReleaseVersion o) {
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);
/**
* Indicates whether the Minecraft server jar file contains third party
@@ -64,6 +65,12 @@ public int compareTo(@NotNull ProcessGeneration.MinecraftReleaseVersion o) {
*/
private boolean supportsSideAnnotationStripping;
+ /**
+ * Enables generation of the MANIFEST.MF in the client and server resource files that
+ * indicates which distribution each file came from. Only applies to joined distributions.
+ */
+ private boolean generateDistSourceManifest;
+
/**
* For (Neo)Forge 1.20.1 and below, we have to remap method and field names from
* SRG to official names for development.
@@ -94,6 +101,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.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);
+
result.supportsSideAnnotationStripping = isLessThanOrEqualTo(releaseVersion, MC_1_20_1);
return result;
@@ -106,6 +116,13 @@ private static boolean isLessThanOrEqualTo(@Nullable MinecraftReleaseVersion rel
return releaseVersion.compareTo(version) <= 0;
}
+ private static boolean isGreaterThanOrEqualTo(@Nullable MinecraftReleaseVersion releaseVersion, MinecraftReleaseVersion version) {
+ if (releaseVersion == null) {
+ return true; // We're working with a snapshot version, which we always use the latest processes for
+ }
+ return releaseVersion.compareTo(version) >= 0;
+ }
+
/**
* Does the Minecraft source code that MCP/NeoForm creates use SRG names?
*/
@@ -120,6 +137,14 @@ public boolean supportsSideAnnotationStripping() {
return supportsSideAnnotationStripping;
}
+ /**
+ * Does the FML version on that MC generation support use of MANIFEST.MF entries
+ * for filtering out dist-specific classes in dev? (When using the joined distribution)
+ */
+ public boolean generateDistSourceManifest() {
+ return generateDistSourceManifest;
+ }
+
/**
* Allows additional resources to be completely removed from Minecraft jars before processing them.
*/
diff --git a/src/main/java/net/neoforged/neoform/runtime/graph/NodeOutputType.java b/src/main/java/net/neoforged/neoform/runtime/graph/NodeOutputType.java
index b7e3ecb5..b07a34e1 100644
--- a/src/main/java/net/neoforged/neoform/runtime/graph/NodeOutputType.java
+++ b/src/main/java/net/neoforged/neoform/runtime/graph/NodeOutputType.java
@@ -2,6 +2,7 @@
public enum NodeOutputType {
JAR(".jar"),
+ JAR_MANIFEST(".MF"),
TXT(".txt"),
ZIP(".zip"),
JSON(".json"),