From 2764604d0ab681ca70a1c7b19ee6fed462a600f5 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Wed, 1 Oct 2025 10:03:22 +0200 Subject: [PATCH 1/3] Remove additionalRuntimeClasspath when targeting Minecraft 1.21.9+ --- README.md | 6 + .../internal/ModDevRunWorkflow.java | 154 +++++++++++------- .../moddevgradle/internal/NeoDevFacade.java | 29 +++- .../internal/PrepareRunOrTest.java | 1 + .../utils/VersionCapabilitiesInternal.java | 13 +- 5 files changed, 139 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index c58ebc4c..85d6d468 100644 --- a/README.md +++ b/README.md @@ -323,6 +323,11 @@ To decrease the likelihood of conflicts if no explicit module name is set, we prefix the filename of embedded subprojects with the group id. ### External Dependencies: Runs +As of Minecraft 1.21.9, external dependencies do not need special handling anymore to be loaded in runs. + +
+Show information for 1.21.8 and older Minecraft versions + External dependencies will only be loaded in your runs if they are mods (with a `META-INF/neoforge.mods.toml` file), or if they have the `FMLModType` entry set in their `META-INF/MANIFEST.MF` file. Usually, Java libraries do not fit either of these requirements, @@ -340,6 +345,7 @@ dependencies { _Advanced_: The additional runtime classpath can be configured per-run. For example, to add a dependency to the `client` run only, it can be added to `clientAdditionalRuntimeClasspath`. +
### Isolated Source Sets diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java index 6c0fdbbb..dfd39aff 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java @@ -59,7 +59,6 @@ public class ModDevRunWorkflow { @Nullable private final ModuleDependency testFixturesDependency; private final ModuleDependency gameLibrariesDependency; - private final Configuration additionalClasspath; private final Configuration userDevConfigOnly; /** @@ -97,19 +96,27 @@ private ModDevRunWorkflow(Project project, } }); - additionalClasspath = configurations.create("additionalRuntimeClasspath", spec -> { - spec.setDescription("Contains dependencies of every run, that should not be considered boot classpath modules."); - spec.setCanBeResolved(true); - spec.setCanBeConsumed(false); - - spec.getDependencies().add(gameLibrariesDependency); - addClientResources(project, spec, artifactsWorkflow.createArtifacts()); - if (!versionCapabilities.modLocatorRework()) { - // Forge expects to find the Forge and client-extra jar on the legacy classpath - // Newer FML versions also search for it on the java.class.path. - spec.getDependencies().addLater(artifactsWorkflow.minecraftClassesDependency()); - } - }); + Consumer configureLegacyClasspath; + if (versionCapabilities.legacyClasspath()) { + var additionalClasspath = configurations.create("additionalRuntimeClasspath", spec -> { + spec.setDescription("Contains dependencies of every run, that should not be considered boot classpath modules."); + spec.setCanBeResolved(true); + spec.setCanBeConsumed(false); + + spec.getDependencies().add(gameLibrariesDependency); + addClientResources(project, spec, artifactsWorkflow.createArtifacts()); + if (!versionCapabilities.modLocatorRework()) { + // Forge expects to find the Forge and client-extra jar on the legacy classpath + // Newer FML versions also search for it on the java.class.path. + spec.getDependencies().addLater(artifactsWorkflow.minecraftClassesDependency()); + } + }); + configureLegacyClasspath = legacyClassPath -> legacyClassPath.extendsFrom(additionalClasspath); + } else { + configureLegacyClasspath = legacyClassPath -> { + throw new IllegalStateException("There is no legacy classpath for Minecraft " + versionCapabilities.minecraftVersion()); + }; + } setupRuns( project, @@ -122,7 +129,7 @@ private ModDevRunWorkflow(Project project, modulePath.getDependencies().add(modulePathDependency); } }, - legacyClassPath -> legacyClassPath.extendsFrom(additionalClasspath), + configureLegacyClasspath, artifactsWorkflow.downloadAssets().flatMap(DownloadAssets::getAssetPropertiesFile), versionCapabilities); } @@ -198,7 +205,8 @@ public void configureTesting(Provider testedMod, Provider setupRunInGradle( configureModulePath.accept(spec); }); - var legacyClasspathConfiguration = configurations.create(InternalModelHelper.nameOfRun(run, "", "legacyClasspath"), spec -> { - spec.setDescription("Contains all dependencies of the " + run.getName() + " run that should not be considered boot classpath modules."); - spec.setCanBeResolved(true); - spec.setCanBeConsumed(false); - spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get()); - spec.attributes(attributes -> { - attributes.attributeProvider(MinecraftDistribution.ATTRIBUTE, type.map(t -> { - 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); + Provider legacyClasspathFile; + if (versionCapabilities.legacyClasspath()) { + var legacyClasspathConfiguration = configurations.create(InternalModelHelper.nameOfRun(run, "", "legacyClasspath"), spec -> { + spec.setDescription("Contains all dependencies of the " + run.getName() + " run that should not be considered boot classpath modules."); + spec.setCanBeResolved(true); + spec.setCanBeConsumed(false); + spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get()); + spec.attributes(attributes -> { + attributes.attributeProvider(MinecraftDistribution.ATTRIBUTE, type.map(t -> { + 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); + }); + configureLegacyClasspath.accept(spec); + spec.extendsFrom(run.getAdditionalRuntimeClasspathConfiguration()); }); - configureLegacyClasspath.accept(spec); - spec.extendsFrom(run.getAdditionalRuntimeClasspathConfiguration()); - }); - var writeLcpTask = tasks.register(InternalModelHelper.nameOfRun(run, "write", "legacyClasspath"), WriteLegacyClasspath.class, writeLcp -> { - writeLcp.setGroup(branding.internalTaskGroup()); - writeLcp.setDescription("Writes the legacyClasspath file for the " + run.getName() + " Minecraft run, containing all dependencies that shouldn't be considered boot modules."); - writeLcp.getLegacyClasspathFile().set(argFileDir.map(dir -> dir.file(InternalModelHelper.nameOfRun(run, "", "legacyClasspath") + ".txt"))); - writeLcp.addEntries(legacyClasspathConfiguration); - }); + var writeLcpTask = tasks.register(InternalModelHelper.nameOfRun(run, "write", "legacyClasspath"), WriteLegacyClasspath.class, writeLcp -> { + writeLcp.setGroup(branding.internalTaskGroup()); + writeLcp.setDescription("Writes the legacyClasspath file for the " + run.getName() + " Minecraft run, containing all dependencies that shouldn't be considered boot modules."); + writeLcp.getLegacyClasspathFile().set(argFileDir.map(dir -> dir.file(InternalModelHelper.nameOfRun(run, "", "legacyClasspath") + ".txt"))); + writeLcp.addEntries(legacyClasspathConfiguration); + }); + legacyClasspathFile = writeLcpTask.get().getLegacyClasspathFile(); + } else { + legacyClasspathFile = null; + + // Disallow adding dependencies to the run's additional classpath configuration since it would have no effect. + // We cannot use withDependencies() since the configuration should never get resolved, + // but we want to inform the user anyway. + run.getAdditionalRuntimeClasspathConfiguration().getDependencies().all(dependency -> { + throw new IllegalStateException(String.format( + "Tried to add a dependency to configuration %s, but there is no additional classpath anymore for Minecraft %s. " + + "Add the dependency to a standard configuration such as implementation or runtimeOnly. Dependency: %s", + run.getAdditionalRuntimeClasspathConfiguration(), + versionCapabilities.minecraftVersion(), + dependency)); + }); + } var prepareRunTask = tasks.register(InternalModelHelper.nameOfRun(run, "prepare", "run"), PrepareRun.class, task -> { task.setGroup(branding.internalTaskGroup()); @@ -337,7 +363,9 @@ private static TaskProvider setupRunInGradle( task.getRunType().set(run.getType()); task.getRunTypeTemplatesSource().from(runTemplatesFile); task.getModules().from(modulePathConfiguration); - task.getLegacyClasspathFile().set(writeLcpTask.get().getLegacyClasspathFile()); + if (legacyClasspathFile != null) { + task.getLegacyClasspathFile().set(legacyClasspathFile); + } task.getAssetProperties().set(assetPropertiesFile); task.getSystemProperties().set(run.getSystemProperties().map(props -> { props = new HashMap<>(props); @@ -405,7 +433,8 @@ static void setupTestTask(Project project, Provider argFileDir, Consumer configureModulePath, Consumer configureLegacyClasspath, - Provider assetPropertiesFile) { + Provider assetPropertiesFile, + VersionCapabilitiesInternal versionCapabilities) { var gameDirectory = new File(project.getProjectDir(), JUNIT_GAME_DIR); var ideIntegration = IdeIntegration.of(project, branding); @@ -423,27 +452,34 @@ static void setupTestTask(Project project, configureModulePath.accept(spec); }); - var legacyClasspathConfiguration = configurations.create("neoForgeTestLibraries", spec -> { - spec.setDescription("Contains the legacy classpath of unit tests."); - spec.setCanBeResolved(true); - spec.setCanBeConsumed(false); - spec.shouldResolveConsistentlyWith(testRuntimeClasspath); - spec.attributes(attributes -> { - setNamedAttribute(project, attributes, MinecraftDistribution.ATTRIBUTE, MinecraftDistribution.CLIENT); - setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); - }); - configureLegacyClasspath.accept(spec); - }); // Place files for junit runtime in a subdirectory to avoid conflicting with other runs var runArgsDir = argFileDir.map(dir -> dir.dir("junit")); - var writeLcpTask = tasks.register("writeNeoForgeTestClasspath", WriteLegacyClasspath.class, writeLcp -> { - writeLcp.setGroup(branding.internalTaskGroup()); - writeLcp.setDescription("Writes the legacyClasspath file for the test run, containing all dependencies that shouldn't be considered boot modules."); - writeLcp.getLegacyClasspathFile().convention(runArgsDir.map(dir -> dir.file("legacyClasspath.txt"))); - writeLcp.addEntries(legacyClasspathConfiguration); - }); + Provider legacyClasspathFile; + if (versionCapabilities.legacyClasspath()) { + var legacyClasspathConfiguration = configurations.create("neoForgeTestLibraries", spec -> { + spec.setDescription("Contains the legacy classpath of unit tests."); + spec.setCanBeResolved(true); + spec.setCanBeConsumed(false); + spec.shouldResolveConsistentlyWith(testRuntimeClasspath); + spec.attributes(attributes -> { + setNamedAttribute(project, attributes, MinecraftDistribution.ATTRIBUTE, MinecraftDistribution.CLIENT); + setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); + }); + configureLegacyClasspath.accept(spec); + }); + + var writeLcpTask = tasks.register("writeNeoForgeTestClasspath", WriteLegacyClasspath.class, writeLcp -> { + writeLcp.setGroup(branding.internalTaskGroup()); + writeLcp.setDescription("Writes the legacyClasspath file for the test run, containing all dependencies that shouldn't be considered boot modules."); + writeLcp.getLegacyClasspathFile().convention(runArgsDir.map(dir -> dir.file("legacyClasspath.txt"))); + writeLcp.addEntries(legacyClasspathConfiguration); + }); + legacyClasspathFile = writeLcpTask.get().getLegacyClasspathFile(); + } else { + legacyClasspathFile = null; + } var vmArgsFile = runArgsDir.map(dir -> dir.file("vmArgs.txt")); var programArgsFile = runArgsDir.map(dir -> dir.file("programArgs.txt")); @@ -457,7 +493,9 @@ static void setupTestTask(Project project, task.getLog4jConfigFile().set(log4j2ConfigFile); task.getRunTypeTemplatesSource().from(runTemplatesSourceFile); task.getModules().from(neoForgeModDevModules); - task.getLegacyClasspathFile().set(writeLcpTask.get().getLegacyClasspathFile()); + if (legacyClasspathFile != null) { + task.getLegacyClasspathFile().set(legacyClasspathFile); + } task.getAssetProperties().set(assetPropertiesFile); task.getGameLogLevel().set(Level.INFO); }); @@ -485,10 +523,6 @@ static void setupTestTask(Project project, ideIntegration.configureTesting(loadedMods, testedMod, runArgsDir, gameDirectory, programArgsFile, vmArgsFile); } - public Configuration getAdditionalClasspath() { - return additionalClasspath; - } - private static void setNamedAttribute(Project project, AttributeContainer attributes, Attribute attribute, String value) { attributes.attribute(attribute, project.getObjects().named(attribute.getType(), value)); } diff --git a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java index 272dfe3f..a7d1e128 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java @@ -83,7 +83,34 @@ public static void setupTestTask(Project project, argFileDir, configureModulePath, configureAdditionalClasspath, - assetPropertiesFile); + assetPropertiesFile, + // This overload of the method was only used by NeoForge before 1.21.9 + VersionCapabilitiesInternal.ofMinecraftVersion("1.21.8")); + } + + + public static void setupTestTask(Project project, + Provider argFileDir, + TaskProvider testTask, + Object runTemplatesSourceFile, + Provider> loadedMods, + Provider testedMod, + Consumer configureModulePath, + Consumer configureAdditionalClasspath, + Provider assetPropertiesFile, + Provider neoFormVersion) { + ModDevRunWorkflow.setupTestTask( + project, + Branding.NEODEV, + runTemplatesSourceFile, + testTask, + loadedMods, + testedMod, + argFileDir, + configureModulePath, + configureAdditionalClasspath, + assetPropertiesFile, + neoFormVersion.map(VersionCapabilitiesInternal::ofNeoFormVersion).getOrElse(VersionCapabilitiesInternal.latest())); } public static void runTaskOnProjectSync(Project project, Object task) { diff --git a/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java b/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java index 0543e684..116bb72f 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java @@ -89,6 +89,7 @@ abstract class PrepareRunOrTest extends DefaultTask { @InputFile @PathSensitive(PathSensitivity.RELATIVE) + @Optional abstract RegularFileProperty getLegacyClasspathFile(); @Classpath diff --git a/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionCapabilitiesInternal.java b/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionCapabilitiesInternal.java index 4921a31f..3cfcb31c 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionCapabilitiesInternal.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionCapabilitiesInternal.java @@ -16,7 +16,7 @@ * @param testFixtures If the NeoForge version for this Minecraft version supports test fixtures. */ public record VersionCapabilitiesInternal(String minecraftVersion, int javaVersion, boolean splitDataRuns, - boolean testFixtures, boolean modLocatorRework) implements VersionCapabilities, Serializable { + boolean testFixtures, boolean modLocatorRework, boolean legacyClasspath) implements VersionCapabilities, Serializable { private static final Logger LOG = Logging.getLogger(VersionCapabilitiesInternal.class); @@ -26,6 +26,7 @@ public record VersionCapabilitiesInternal(String minecraftVersion, int javaVersi // Strips NeoForm timestamp suffixes OR dynamic version markers private static final Pattern NEOFORM_PATTERN = Pattern.compile("^(.*)-(?:\\+|\\d{8}\\.\\d{6})$"); + private static final int MC_1_21_9_INDEX = getReferenceVersionIndex("1.21.9"); private static final int MC_24W45A_INDEX = getReferenceVersionIndex("24w45a"); private static final int MC_1_20_5_INDEX = getReferenceVersionIndex("1.20.5"); private static final int MC_24W14A_INDEX = getReferenceVersionIndex("24w14a"); @@ -56,8 +57,9 @@ public static VersionCapabilitiesInternal ofVersionIndex(int versionIndex, Strin var splitData = hasSplitDataEntrypoints(versionIndex); var testFixtures = hasTestFixtures(versionIndex); var modLocatorRework = hasModLocatorRework(versionIndex); + var legacyClasspath = hasLegacyClasspath(versionIndex); - return new VersionCapabilitiesInternal(minecraftVersion, javaVersion, splitData, testFixtures, modLocatorRework); + return new VersionCapabilitiesInternal(minecraftVersion, javaVersion, splitData, testFixtures, modLocatorRework, legacyClasspath); } static int getJavaVersion(int versionIndex) { @@ -84,6 +86,10 @@ static boolean hasModLocatorRework(int versionIndex) { return versionIndex <= MC_1_20_5_INDEX; } + static boolean hasLegacyClasspath(int versionIndex) { + return versionIndex > MC_1_21_9_INDEX; + } + static int indexOfNeoForgeVersion(String version) { // NeoForge omits the "1." at the start of the Minecraft version and just adds an incrementing last digit var matcher = NEOFORGE_PATTERN.matcher(version); @@ -168,6 +174,7 @@ public VersionCapabilitiesInternal withMinecraftVersion(String minecraftVersion) javaVersion, splitDataRuns, testFixtures, - modLocatorRework); + modLocatorRework, + legacyClasspath); } } From 521f8834b598442ca38b7fd19ce6aa12cfdee729 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Wed, 1 Oct 2025 10:28:47 +0200 Subject: [PATCH 2/3] Remove spots --- .../net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java | 1 - .../java/net/neoforged/moddevgradle/internal/NeoDevFacade.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java index dfd39aff..af275f39 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java @@ -452,7 +452,6 @@ static void setupTestTask(Project project, configureModulePath.accept(spec); }); - // Place files for junit runtime in a subdirectory to avoid conflicting with other runs var runArgsDir = argFileDir.map(dir -> dir.dir("junit")); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java index a7d1e128..24692694 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java @@ -88,7 +88,6 @@ public static void setupTestTask(Project project, VersionCapabilitiesInternal.ofMinecraftVersion("1.21.8")); } - public static void setupTestTask(Project project, Provider argFileDir, TaskProvider testTask, From b1db3ae12572e63404be62b0c5480501faa5d1b2 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Wed, 1 Oct 2025 21:03:36 +0200 Subject: [PATCH 3/3] Keep additionalRuntimeClasspath but don't allow adding any dep to it --- .../internal/ModDevRunWorkflow.java | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java index af275f39..4ccaf0f3 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java @@ -113,6 +113,11 @@ private ModDevRunWorkflow(Project project, }); configureLegacyClasspath = legacyClassPath -> legacyClassPath.extendsFrom(additionalClasspath); } else { + // Create the configuration but disallow adding anything to it, to notify users about potential mistakes. + // We might decide to remove it entirely in the future + var additionalClasspath = configurations.create("additionalRuntimeClasspath"); + forbidAdditionalRuntimeDependencies(additionalClasspath, versionCapabilities); + configureLegacyClasspath = legacyClassPath -> { throw new IllegalStateException("There is no legacy classpath for Minecraft " + versionCapabilities.minecraftVersion()); }; @@ -134,6 +139,19 @@ private ModDevRunWorkflow(Project project, versionCapabilities); } + private static void forbidAdditionalRuntimeDependencies(Configuration configuration, VersionCapabilitiesInternal versionCapabilities) { + // We cannot use withDependencies() since the configuration should never get resolved, + // but we want to inform the user anyway. + configuration.getDependencies().all(dependency -> { + throw new IllegalStateException(String.format( + "Tried to add a dependency to configuration %s, but there is no additional classpath anymore for Minecraft %s. " + + "Add the dependency to a standard configuration such as implementation or runtimeOnly. Dependency: %s", + configuration, + versionCapabilities.minecraftVersion(), + dependency)); + }); + } + public static ModDevRunWorkflow get(Project project) { var workflow = ExtensionUtils.findExtension(project, EXTENSION_NAME, ModDevRunWorkflow.class); if (workflow == null) { @@ -336,19 +354,9 @@ private static TaskProvider setupRunInGradle( }); legacyClasspathFile = writeLcpTask.get().getLegacyClasspathFile(); } else { + // Disallow adding dependencies to the additional classpath configuration since it would have no effect. + forbidAdditionalRuntimeDependencies(run.getAdditionalRuntimeClasspathConfiguration(), versionCapabilities); legacyClasspathFile = null; - - // Disallow adding dependencies to the run's additional classpath configuration since it would have no effect. - // We cannot use withDependencies() since the configuration should never get resolved, - // but we want to inform the user anyway. - run.getAdditionalRuntimeClasspathConfiguration().getDependencies().all(dependency -> { - throw new IllegalStateException(String.format( - "Tried to add a dependency to configuration %s, but there is no additional classpath anymore for Minecraft %s. " - + "Add the dependency to a standard configuration such as implementation or runtimeOnly. Dependency: %s", - run.getAdditionalRuntimeClasspathConfiguration(), - versionCapabilities.minecraftVersion(), - dependency)); - }); } var prepareRunTask = tasks.register(InternalModelHelper.nameOfRun(run, "prepare", "run"), PrepareRun.class, task -> {