diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java b/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java index 0f2c01db..d81fdec0 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java @@ -154,8 +154,21 @@ public void client() { getType().set("client"); } + /** + * Equivalent to setting {@code type = "clientData"}. + * + *

Should only be used for Minecraft versions starting from 1.21.4. + * (The first snapshot that supports this is 24w45a). + */ + public void clientData() { + getType().set("clientData"); + } + /** * Equivalent to setting {@code type = "data"}. + * + *

Should only be used for Minecraft versions up to 1.21.3 included. + * (The last snapshot that supports this is 24w44a). */ public void data() { getType().set("data"); @@ -168,6 +181,16 @@ public void server() { getType().set("server"); } + /** + * Equivalent to setting {@code type = "serverData"}. + * + *

Should only be used for Minecraft versions starting from 1.21.4. + * (The first snapshot that supports this is 24w45a). + */ + public void serverData() { + getType().set("serverData"); + } + /** * Gets the Gradle tasks that should be run before running this run. */ diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java index ebc5fb33..7c32e909 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java @@ -364,7 +364,7 @@ public void apply(Project project) { spec.setCanBeConsumed(false); spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get()); spec.attributes(attributes -> { - attributes.attributeProvider(ATTRIBUTE_DISTRIBUTION, type.map(t -> t.equals("client") || t.equals("data") ? "client" : "server")); + attributes.attributeProvider(ATTRIBUTE_DISTRIBUTION, type.map(t -> t.equals("client") || t.equals("data") || t.equals("clientData") ? "client" : "server")); attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME)); }); spec.withDependencies(set -> { @@ -402,6 +402,7 @@ public void apply(Project project) { task.getProgramArguments().set(run.getProgramArguments()); task.getJvmArguments().set(run.getJvmArguments()); task.getGameLogLevel().set(run.getLogLevel()); + task.getNeoFormVersion().set(extension.getNeoFormVersion()); task.dependsOn(run.getTasksBefore()); }); prepareRunTasks.put(run, prepareRunTask); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java b/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java index f04d6ea7..0e4aea43 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java @@ -3,6 +3,7 @@ import net.neoforged.moddevgradle.internal.utils.FileUtils; import net.neoforged.moddevgradle.internal.utils.OperatingSystem; import net.neoforged.moddevgradle.internal.utils.StringUtils; +import net.neoforged.moddevgradle.internal.utils.VersionUtils; import org.gradle.api.DefaultTask; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; @@ -28,6 +29,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -82,6 +84,14 @@ abstract class PrepareRunOrTest extends DefaultTask { @Input public abstract Property getGameLogLevel(); + /** + * Only used when {@link #getRunTypeTemplatesSource()} is empty, + * to know whether the associated Minecraft version requires one or two data runs. + */ + @Optional + @Input + public abstract Property getNeoFormVersion(); + private final ProgramArgsFormat programArgsFormat; protected PrepareRunOrTest(ProgramArgsFormat programArgsFormat) { @@ -138,17 +148,32 @@ private UserDevConfig getSimulatedUserDevConfigForVanilla() { var clientArgs = List.of("--gameDir", ".", "--assetIndex", "{asset_index}", "--assetsDir", "{assets_root}", "--accessToken", "NotValid", "--version", "ModDevGradle"); var commonArgs = List.of(); - return new UserDevConfig("", "", "", List.of(), List.of(), Map.of( - "client", new UserDevRunType( - true, "net.minecraft.client.main.Main", clientArgs, List.of(), true, false, false, false, Map.of(), Map.of() - ), - "server", new UserDevRunType( - true, "net.minecraft.server.Main", commonArgs, List.of(), false, true, false, false, Map.of(), Map.of() - ), - "data", new UserDevRunType( - true, "net.minecraft.data.Main", commonArgs, List.of(), false, false, true, false, Map.of(), Map.of() - ) + var runTypes = new LinkedHashMap(); + runTypes.put("client", new UserDevRunType( + true, "net.minecraft.client.main.Main", clientArgs, List.of(), Map.of(), Map.of() )); + runTypes.put("server", new UserDevRunType( + true, "net.minecraft.server.Main", commonArgs, List.of(), Map.of(), Map.of() + )); + + var splitData = getNeoFormVersion() + .map(VersionUtils::hasSplitDataRuns) + .orElse(false) // Default to single run for backwards compatibility + .get(); + if (splitData) { + runTypes.put("clientData", new UserDevRunType( + true, "net.minecraft.client.data.Main", commonArgs, List.of(), Map.of(), Map.of() + )); + runTypes.put("serverData", new UserDevRunType( + true, "net.minecraft.data.Main", commonArgs, List.of(), Map.of(), Map.of() + )); + } else { + runTypes.put("data", new UserDevRunType( + true, "net.minecraft.data.Main", commonArgs, List.of(), Map.of(), Map.of() + )); + } + + return new UserDevConfig(runTypes); } private void writeJvmArguments(UserDevRunType runConfig) throws IOException { diff --git a/src/main/java/net/neoforged/moddevgradle/internal/UserDevConfig.java b/src/main/java/net/neoforged/moddevgradle/internal/UserDevConfig.java index 364cff3e..4327c8d6 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/UserDevConfig.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/UserDevConfig.java @@ -6,11 +6,12 @@ import java.io.File; import java.io.Serializable; import java.nio.file.Files; -import java.util.List; import java.util.Map; -public record UserDevConfig(String mcp, String sources, String universal, List libraries, List modules, - Map runs) implements Serializable { +/** + * Sourced from the userdev config json. The run templates are the only thing that we use. + */ +public record UserDevConfig(Map runs) implements Serializable { public static UserDevConfig from(File userDevFile) { try (var reader = Files.newBufferedReader(userDevFile.toPath())) { return new Gson().fromJson(reader, UserDevConfig.class); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/UserDevRunType.java b/src/main/java/net/neoforged/moddevgradle/internal/UserDevRunType.java index 27ffb86f..ff8a3c9f 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/UserDevRunType.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/UserDevRunType.java @@ -4,6 +4,5 @@ import java.util.Map; public record UserDevRunType(boolean singleInstance, String main, List args, List jvmArgs, - boolean client, boolean server, boolean dataGenerator, boolean gameTest, Map env, Map props) { } diff --git a/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionUtils.java b/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionUtils.java new file mode 100644 index 00000000..cd106808 --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionUtils.java @@ -0,0 +1,38 @@ +package net.neoforged.moddevgradle.internal.utils; + +import java.util.Objects; +import java.util.regex.Pattern; + +public final class VersionUtils { + private VersionUtils() {} + + private static final Pattern RELEASE_PATTERN = Pattern.compile("1\\.(\\d+)(?:.(\\d+))?(?:-.*)?$"); + + /** + * Checks whether the provided NeoForm version should have split client and server data runs. + */ + public static boolean hasSplitDataRuns(String neoFormVersion) { + // Snapshots starting from 24w45a + if (neoFormVersion.length() >= 5 && neoFormVersion.charAt(2) == 'w') { + try { + var year = Integer.parseInt(neoFormVersion.substring(0, 2)); + var week = Integer.parseInt(neoFormVersion.substring(3, 5)); + + return year > 24 || (year == 24 && week >= 45); + } catch (NumberFormatException ignored) {} + } + // Releases starting from 1.21.4 + var matcher = RELEASE_PATTERN.matcher(neoFormVersion); + if (matcher.find()) { + try { + int minor = Integer.parseInt(matcher.group(1)); + // If there is no patch version, the second group has a null value + int patch = Integer.parseInt(Objects.requireNonNullElse(matcher.group(2), "0")); + + return minor > 21 || (minor == 21 && patch >= 4); + } catch (NumberFormatException ignored) {} + } + // Assume other version patterns are newer and therefore split + return true; + } +} diff --git a/src/test/java/net/neoforged/moddevgradle/internal/VersionUtilsTest.java b/src/test/java/net/neoforged/moddevgradle/internal/VersionUtilsTest.java new file mode 100644 index 00000000..c497fe45 --- /dev/null +++ b/src/test/java/net/neoforged/moddevgradle/internal/VersionUtilsTest.java @@ -0,0 +1,49 @@ +package net.neoforged.moddevgradle.internal; + +import net.neoforged.moddevgradle.internal.utils.VersionUtils; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThat; + +public class VersionUtilsTest { + @ParameterizedTest() + @CsvSource({ + "1.21.4,true", + "1.21.4-pre1-20241120.190508,true", + "1.21.3,false", + "24w45a,true", + "24w44a,false", + "1.21.3-pre1,false", + "25w01a,true", + "23w07a,false", + "1.20,false", + "1.20-pre1,false", + "1.21,false", + "1.21-pre1-20240529.150918,false", + "1.21-pre1,false", + "1.22,true", + "1.22-pre1,true" + }) + public void testSplitDataRunsCorrectness(String neoFormVersion, boolean splitDataRuns) { + assertThat(VersionUtils.hasSplitDataRuns(neoFormVersion)) + .isEqualTo(splitDataRuns); + } + + @ParameterizedTest + @CsvSource({ + "1", + "1.", + "1.21.", + "test", + "24w", + "24w5", + "24w50", + "2aw50", + "24242", + }) + public void testSplitDataRunsDoesNotCrash(String neoFormVersion) { + assertThat(VersionUtils.hasSplitDataRuns(neoFormVersion)) + .isTrue(); + } +}