diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java b/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java index 4da9e5e..9bf0637 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java @@ -156,8 +156,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"); @@ -170,6 +183,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"); + } + /** * Equivalent to setting {@code ideName = ""} */ diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java index 726491e..1231903 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java @@ -309,7 +309,8 @@ public void apply(Project project) { userDevConfigOnly, modulePath -> modulePath.getDependencies().addLater(modulePathDependency), legacyClassPath -> legacyClassPath.extendsFrom(additionalClasspath), - downloadAssets.flatMap(DownloadAssets::getAssetPropertiesFile) + downloadAssets.flatMap(DownloadAssets::getAssetPropertiesFile), + extension.getNeoFormVersion() ); setupJarJar(project); @@ -478,7 +479,8 @@ static void setupRuns(Project project, Object runTemplatesSourceFile, Consumer configureModulePath, Consumer configureLegacyClasspath, - Provider assetPropertiesFile + Provider assetPropertiesFile, + Provider neoFormVersion ) { var ideIntegration = IdeIntegration.of(project, branding); @@ -499,7 +501,8 @@ static void setupRuns(Project project, configureModulePath, configureLegacyClasspath, assetPropertiesFile, - devLaunchConfig + devLaunchConfig, + neoFormVersion ); prepareRunTasks.put(run, prepareRunTask); }); @@ -521,7 +524,8 @@ private static TaskProvider setupRunInGradle( Consumer configureModulePath, Consumer configureLegacyClasspath, // TODO: can be removed in favor of directly passing a configuration for the moddev libraries Provider assetPropertiesFile, - Configuration devLaunchConfig + Configuration devLaunchConfig, + Provider neoFormVersion ) { var ideIntegration = IdeIntegration.of(project, branding); var configurations = project.getConfigurations(); @@ -553,7 +557,7 @@ private static TaskProvider setupRunInGradle( spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get()); spec.attributes(attributes -> { attributes.attributeProvider(MinecraftDistribution.ATTRIBUTE, type.map(t -> { - var name = t.equals("client") || t.equals("data") ? MinecraftDistribution.CLIENT : MinecraftDistribution.SERVER; + var name = t.equals("client") || t.equals("data") || t.equals("clientData") ? MinecraftDistribution.CLIENT : MinecraftDistribution.SERVER; return project.getObjects().named(MinecraftDistribution.class, name); })); setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); @@ -590,6 +594,7 @@ private static TaskProvider setupRunInGradle( task.getProgramArguments().set(run.getProgramArguments()); task.getJvmArguments().set(run.getJvmArguments()); task.getGameLogLevel().set(run.getLogLevel()); + task.getNeoFormVersion().set(neoFormVersion); }); ideIntegration.runTaskOnProjectSync(prepareRunTask); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java index 2ba0df8..997f95f 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java @@ -42,7 +42,30 @@ public static void setupRuns(Project project, runTemplatesSourceFile, configureModulePath, configureAdditionalClasspath, - assetPropertiesFile + assetPropertiesFile, + project.getObjects().property(String.class) // empty provider + ); + } + + public static void setupRuns(Project project, + Provider argFileDir, + DomainObjectCollection runs, + Object runTemplatesSourceFile, + Consumer configureModulePath, + Consumer configureAdditionalClasspath, + Provider assetPropertiesFile, + Provider neoFormVersion + ) { + ModDevPlugin.setupRuns( + project, + Branding.NEODEV, + argFileDir, + runs, + runTemplatesSourceFile, + configureModulePath, + configureAdditionalClasspath, + assetPropertiesFile, + neoFormVersion ); } diff --git a/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java b/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java index b865685..759144a 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; @@ -91,6 +93,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) { @@ -147,17 +157,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 364cff3..4327c8d 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 27ffb86..ff8a3c9 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 0000000..cd10680 --- /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 0000000..c497fe4 --- /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(); + } +}