Skip to content

Commit 09fded7

Browse files
authored
Disallow usage of additionalRuntimeClasspath when targeting Minecraft 1.21.9+ (#292)
1 parent f7d04d9 commit 09fded7

File tree

5 files changed

+146
-65
lines changed

5 files changed

+146
-65
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,11 @@ To decrease the likelihood of conflicts if no explicit module name is set,
323323
we prefix the filename of embedded subprojects with the group id.
324324

325325
### External Dependencies: Runs
326+
As of Minecraft 1.21.9, external dependencies do not need special handling anymore to be loaded in runs.
327+
328+
<details>
329+
<summary>Show information for 1.21.8 and older Minecraft versions</summary>
330+
326331
External dependencies will only be loaded in your runs if they are mods (with a `META-INF/neoforge.mods.toml` file),
327332
or if they have the `FMLModType` entry set in their `META-INF/MANIFEST.MF` file.
328333
Usually, Java libraries do not fit either of these requirements,
@@ -340,6 +345,7 @@ dependencies {
340345

341346
_Advanced_: The additional runtime classpath can be configured per-run.
342347
For example, to add a dependency to the `client` run only, it can be added to `clientAdditionalRuntimeClasspath`.
348+
</details>
343349

344350
### Isolated Source Sets
345351

src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java

Lines changed: 102 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ public class ModDevRunWorkflow {
5959
@Nullable
6060
private final ModuleDependency testFixturesDependency;
6161
private final ModuleDependency gameLibrariesDependency;
62-
private final Configuration additionalClasspath;
6362
private final Configuration userDevConfigOnly;
6463

6564
/**
@@ -97,19 +96,32 @@ private ModDevRunWorkflow(Project project,
9796
}
9897
});
9998

100-
additionalClasspath = configurations.create("additionalRuntimeClasspath", spec -> {
101-
spec.setDescription("Contains dependencies of every run, that should not be considered boot classpath modules.");
102-
spec.setCanBeResolved(true);
103-
spec.setCanBeConsumed(false);
104-
105-
spec.getDependencies().add(gameLibrariesDependency);
106-
addClientResources(project, spec, artifactsWorkflow.createArtifacts());
107-
if (!versionCapabilities.modLocatorRework()) {
108-
// Forge expects to find the Forge and client-extra jar on the legacy classpath
109-
// Newer FML versions also search for it on the java.class.path.
110-
spec.getDependencies().addLater(artifactsWorkflow.minecraftClassesDependency());
111-
}
112-
});
99+
Consumer<Configuration> configureLegacyClasspath;
100+
if (versionCapabilities.legacyClasspath()) {
101+
var additionalClasspath = configurations.create("additionalRuntimeClasspath", spec -> {
102+
spec.setDescription("Contains dependencies of every run, that should not be considered boot classpath modules.");
103+
spec.setCanBeResolved(true);
104+
spec.setCanBeConsumed(false);
105+
106+
spec.getDependencies().add(gameLibrariesDependency);
107+
addClientResources(project, spec, artifactsWorkflow.createArtifacts());
108+
if (!versionCapabilities.modLocatorRework()) {
109+
// Forge expects to find the Forge and client-extra jar on the legacy classpath
110+
// Newer FML versions also search for it on the java.class.path.
111+
spec.getDependencies().addLater(artifactsWorkflow.minecraftClassesDependency());
112+
}
113+
});
114+
configureLegacyClasspath = legacyClassPath -> legacyClassPath.extendsFrom(additionalClasspath);
115+
} else {
116+
// Create the configuration but disallow adding anything to it, to notify users about potential mistakes.
117+
// We might decide to remove it entirely in the future
118+
var additionalClasspath = configurations.create("additionalRuntimeClasspath");
119+
forbidAdditionalRuntimeDependencies(additionalClasspath, versionCapabilities);
120+
121+
configureLegacyClasspath = legacyClassPath -> {
122+
throw new IllegalStateException("There is no legacy classpath for Minecraft " + versionCapabilities.minecraftVersion());
123+
};
124+
}
113125

114126
setupRuns(
115127
project,
@@ -122,11 +134,24 @@ private ModDevRunWorkflow(Project project,
122134
modulePath.getDependencies().add(modulePathDependency);
123135
}
124136
},
125-
legacyClassPath -> legacyClassPath.extendsFrom(additionalClasspath),
137+
configureLegacyClasspath,
126138
artifactsWorkflow.downloadAssets().flatMap(DownloadAssets::getAssetPropertiesFile),
127139
versionCapabilities);
128140
}
129141

142+
private static void forbidAdditionalRuntimeDependencies(Configuration configuration, VersionCapabilitiesInternal versionCapabilities) {
143+
// We cannot use withDependencies() since the configuration should never get resolved,
144+
// but we want to inform the user anyway.
145+
configuration.getDependencies().all(dependency -> {
146+
throw new IllegalStateException(String.format(
147+
"Tried to add a dependency to configuration %s, but there is no additional classpath anymore for Minecraft %s. "
148+
+ "Add the dependency to a standard configuration such as implementation or runtimeOnly. Dependency: %s",
149+
configuration,
150+
versionCapabilities.minecraftVersion(),
151+
dependency));
152+
});
153+
}
154+
130155
public static ModDevRunWorkflow get(Project project) {
131156
var workflow = ExtensionUtils.findExtension(project, EXTENSION_NAME, ModDevRunWorkflow.class);
132157
if (workflow == null) {
@@ -198,7 +223,8 @@ public void configureTesting(Provider<ModModel> testedMod, Provider<Set<ModModel
198223
legacyClassPath.getDependencies().add(gameLibrariesDependency);
199224
addClientResources(project, legacyClassPath, artifactsWorkflow.createArtifacts());
200225
},
201-
artifactsWorkflow.downloadAssets().flatMap(DownloadAssets::getAssetPropertiesFile));
226+
artifactsWorkflow.downloadAssets().flatMap(DownloadAssets::getAssetPropertiesFile),
227+
artifactsWorkflow.versionCapabilities());
202228
}
203229
}
204230

@@ -302,28 +328,36 @@ private static TaskProvider<PrepareRun> setupRunInGradle(
302328
configureModulePath.accept(spec);
303329
});
304330

305-
var legacyClasspathConfiguration = configurations.create(InternalModelHelper.nameOfRun(run, "", "legacyClasspath"), spec -> {
306-
spec.setDescription("Contains all dependencies of the " + run.getName() + " run that should not be considered boot classpath modules.");
307-
spec.setCanBeResolved(true);
308-
spec.setCanBeConsumed(false);
309-
spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get());
310-
spec.attributes(attributes -> {
311-
attributes.attributeProvider(MinecraftDistribution.ATTRIBUTE, type.map(t -> {
312-
var name = t.equals("client") || t.equals("data") || t.equals("clientData") ? MinecraftDistribution.CLIENT : MinecraftDistribution.SERVER;
313-
return project.getObjects().named(MinecraftDistribution.class, name);
314-
}));
315-
setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME);
331+
Provider<RegularFile> legacyClasspathFile;
332+
if (versionCapabilities.legacyClasspath()) {
333+
var legacyClasspathConfiguration = configurations.create(InternalModelHelper.nameOfRun(run, "", "legacyClasspath"), spec -> {
334+
spec.setDescription("Contains all dependencies of the " + run.getName() + " run that should not be considered boot classpath modules.");
335+
spec.setCanBeResolved(true);
336+
spec.setCanBeConsumed(false);
337+
spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get());
338+
spec.attributes(attributes -> {
339+
attributes.attributeProvider(MinecraftDistribution.ATTRIBUTE, type.map(t -> {
340+
var name = t.equals("client") || t.equals("data") || t.equals("clientData") ? MinecraftDistribution.CLIENT : MinecraftDistribution.SERVER;
341+
return project.getObjects().named(MinecraftDistribution.class, name);
342+
}));
343+
setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME);
344+
});
345+
configureLegacyClasspath.accept(spec);
346+
spec.extendsFrom(run.getAdditionalRuntimeClasspathConfiguration());
316347
});
317-
configureLegacyClasspath.accept(spec);
318-
spec.extendsFrom(run.getAdditionalRuntimeClasspathConfiguration());
319-
});
320348

321-
var writeLcpTask = tasks.register(InternalModelHelper.nameOfRun(run, "write", "legacyClasspath"), WriteLegacyClasspath.class, writeLcp -> {
322-
writeLcp.setGroup(branding.internalTaskGroup());
323-
writeLcp.setDescription("Writes the legacyClasspath file for the " + run.getName() + " Minecraft run, containing all dependencies that shouldn't be considered boot modules.");
324-
writeLcp.getLegacyClasspathFile().set(argFileDir.map(dir -> dir.file(InternalModelHelper.nameOfRun(run, "", "legacyClasspath") + ".txt")));
325-
writeLcp.addEntries(legacyClasspathConfiguration);
326-
});
349+
var writeLcpTask = tasks.register(InternalModelHelper.nameOfRun(run, "write", "legacyClasspath"), WriteLegacyClasspath.class, writeLcp -> {
350+
writeLcp.setGroup(branding.internalTaskGroup());
351+
writeLcp.setDescription("Writes the legacyClasspath file for the " + run.getName() + " Minecraft run, containing all dependencies that shouldn't be considered boot modules.");
352+
writeLcp.getLegacyClasspathFile().set(argFileDir.map(dir -> dir.file(InternalModelHelper.nameOfRun(run, "", "legacyClasspath") + ".txt")));
353+
writeLcp.addEntries(legacyClasspathConfiguration);
354+
});
355+
legacyClasspathFile = writeLcpTask.get().getLegacyClasspathFile();
356+
} else {
357+
// Disallow adding dependencies to the additional classpath configuration since it would have no effect.
358+
forbidAdditionalRuntimeDependencies(run.getAdditionalRuntimeClasspathConfiguration(), versionCapabilities);
359+
legacyClasspathFile = null;
360+
}
327361

328362
var prepareRunTask = tasks.register(InternalModelHelper.nameOfRun(run, "prepare", "run"), PrepareRun.class, task -> {
329363
task.setGroup(branding.internalTaskGroup());
@@ -337,7 +371,9 @@ private static TaskProvider<PrepareRun> setupRunInGradle(
337371
task.getRunType().set(run.getType());
338372
task.getRunTypeTemplatesSource().from(runTemplatesFile);
339373
task.getModules().from(modulePathConfiguration);
340-
task.getLegacyClasspathFile().set(writeLcpTask.get().getLegacyClasspathFile());
374+
if (legacyClasspathFile != null) {
375+
task.getLegacyClasspathFile().set(legacyClasspathFile);
376+
}
341377
task.getAssetProperties().set(assetPropertiesFile);
342378
task.getSystemProperties().set(run.getSystemProperties().map(props -> {
343379
props = new HashMap<>(props);
@@ -405,7 +441,8 @@ static void setupTestTask(Project project,
405441
Provider<Directory> argFileDir,
406442
Consumer<Configuration> configureModulePath,
407443
Consumer<Configuration> configureLegacyClasspath,
408-
Provider<RegularFile> assetPropertiesFile) {
444+
Provider<RegularFile> assetPropertiesFile,
445+
VersionCapabilitiesInternal versionCapabilities) {
409446
var gameDirectory = new File(project.getProjectDir(), JUNIT_GAME_DIR);
410447

411448
var ideIntegration = IdeIntegration.of(project, branding);
@@ -423,27 +460,33 @@ static void setupTestTask(Project project,
423460
configureModulePath.accept(spec);
424461
});
425462

426-
var legacyClasspathConfiguration = configurations.create("neoForgeTestLibraries", spec -> {
427-
spec.setDescription("Contains the legacy classpath of unit tests.");
428-
spec.setCanBeResolved(true);
429-
spec.setCanBeConsumed(false);
430-
spec.shouldResolveConsistentlyWith(testRuntimeClasspath);
431-
spec.attributes(attributes -> {
432-
setNamedAttribute(project, attributes, MinecraftDistribution.ATTRIBUTE, MinecraftDistribution.CLIENT);
433-
setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME);
434-
});
435-
configureLegacyClasspath.accept(spec);
436-
});
437-
438463
// Place files for junit runtime in a subdirectory to avoid conflicting with other runs
439464
var runArgsDir = argFileDir.map(dir -> dir.dir("junit"));
440465

441-
var writeLcpTask = tasks.register("writeNeoForgeTestClasspath", WriteLegacyClasspath.class, writeLcp -> {
442-
writeLcp.setGroup(branding.internalTaskGroup());
443-
writeLcp.setDescription("Writes the legacyClasspath file for the test run, containing all dependencies that shouldn't be considered boot modules.");
444-
writeLcp.getLegacyClasspathFile().convention(runArgsDir.map(dir -> dir.file("legacyClasspath.txt")));
445-
writeLcp.addEntries(legacyClasspathConfiguration);
446-
});
466+
Provider<RegularFile> legacyClasspathFile;
467+
if (versionCapabilities.legacyClasspath()) {
468+
var legacyClasspathConfiguration = configurations.create("neoForgeTestLibraries", spec -> {
469+
spec.setDescription("Contains the legacy classpath of unit tests.");
470+
spec.setCanBeResolved(true);
471+
spec.setCanBeConsumed(false);
472+
spec.shouldResolveConsistentlyWith(testRuntimeClasspath);
473+
spec.attributes(attributes -> {
474+
setNamedAttribute(project, attributes, MinecraftDistribution.ATTRIBUTE, MinecraftDistribution.CLIENT);
475+
setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME);
476+
});
477+
configureLegacyClasspath.accept(spec);
478+
});
479+
480+
var writeLcpTask = tasks.register("writeNeoForgeTestClasspath", WriteLegacyClasspath.class, writeLcp -> {
481+
writeLcp.setGroup(branding.internalTaskGroup());
482+
writeLcp.setDescription("Writes the legacyClasspath file for the test run, containing all dependencies that shouldn't be considered boot modules.");
483+
writeLcp.getLegacyClasspathFile().convention(runArgsDir.map(dir -> dir.file("legacyClasspath.txt")));
484+
writeLcp.addEntries(legacyClasspathConfiguration);
485+
});
486+
legacyClasspathFile = writeLcpTask.get().getLegacyClasspathFile();
487+
} else {
488+
legacyClasspathFile = null;
489+
}
447490

448491
var vmArgsFile = runArgsDir.map(dir -> dir.file("vmArgs.txt"));
449492
var programArgsFile = runArgsDir.map(dir -> dir.file("programArgs.txt"));
@@ -457,7 +500,9 @@ static void setupTestTask(Project project,
457500
task.getLog4jConfigFile().set(log4j2ConfigFile);
458501
task.getRunTypeTemplatesSource().from(runTemplatesSourceFile);
459502
task.getModules().from(neoForgeModDevModules);
460-
task.getLegacyClasspathFile().set(writeLcpTask.get().getLegacyClasspathFile());
503+
if (legacyClasspathFile != null) {
504+
task.getLegacyClasspathFile().set(legacyClasspathFile);
505+
}
461506
task.getAssetProperties().set(assetPropertiesFile);
462507
task.getGameLogLevel().set(Level.INFO);
463508
});
@@ -485,10 +530,6 @@ static void setupTestTask(Project project,
485530
ideIntegration.configureTesting(loadedMods, testedMod, runArgsDir, gameDirectory, programArgsFile, vmArgsFile);
486531
}
487532

488-
public Configuration getAdditionalClasspath() {
489-
return additionalClasspath;
490-
}
491-
492533
private static <T extends Named> void setNamedAttribute(Project project, AttributeContainer attributes, Attribute<T> attribute, String value) {
493534
attributes.attribute(attribute, project.getObjects().named(attribute.getType(), value));
494535
}

src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,33 @@ public static void setupTestTask(Project project,
8383
argFileDir,
8484
configureModulePath,
8585
configureAdditionalClasspath,
86-
assetPropertiesFile);
86+
assetPropertiesFile,
87+
// This overload of the method was only used by NeoForge before 1.21.9
88+
VersionCapabilitiesInternal.ofMinecraftVersion("1.21.8"));
89+
}
90+
91+
public static void setupTestTask(Project project,
92+
Provider<Directory> argFileDir,
93+
TaskProvider<Test> testTask,
94+
Object runTemplatesSourceFile,
95+
Provider<Set<ModModel>> loadedMods,
96+
Provider<ModModel> testedMod,
97+
Consumer<Configuration> configureModulePath,
98+
Consumer<Configuration> configureAdditionalClasspath,
99+
Provider<RegularFile> assetPropertiesFile,
100+
Provider<String> neoFormVersion) {
101+
ModDevRunWorkflow.setupTestTask(
102+
project,
103+
Branding.NEODEV,
104+
runTemplatesSourceFile,
105+
testTask,
106+
loadedMods,
107+
testedMod,
108+
argFileDir,
109+
configureModulePath,
110+
configureAdditionalClasspath,
111+
assetPropertiesFile,
112+
neoFormVersion.map(VersionCapabilitiesInternal::ofNeoFormVersion).getOrElse(VersionCapabilitiesInternal.latest()));
87113
}
88114

89115
public static void runTaskOnProjectSync(Project project, Object task) {

src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ abstract class PrepareRunOrTest extends DefaultTask {
8989

9090
@InputFile
9191
@PathSensitive(PathSensitivity.RELATIVE)
92+
@Optional
9293
abstract RegularFileProperty getLegacyClasspathFile();
9394

9495
@Classpath

src/main/java/net/neoforged/moddevgradle/internal/utils/VersionCapabilitiesInternal.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* @param testFixtures If the NeoForge version for this Minecraft version supports test fixtures.
1717
*/
1818
public record VersionCapabilitiesInternal(String minecraftVersion, int javaVersion, boolean splitDataRuns,
19-
boolean testFixtures, boolean modLocatorRework) implements VersionCapabilities, Serializable {
19+
boolean testFixtures, boolean modLocatorRework, boolean legacyClasspath) implements VersionCapabilities, Serializable {
2020

2121
private static final Logger LOG = Logging.getLogger(VersionCapabilitiesInternal.class);
2222

@@ -26,6 +26,7 @@ public record VersionCapabilitiesInternal(String minecraftVersion, int javaVersi
2626
// Strips NeoForm timestamp suffixes OR dynamic version markers
2727
private static final Pattern NEOFORM_PATTERN = Pattern.compile("^(.*)-(?:\\+|\\d{8}\\.\\d{6})$");
2828

29+
private static final int MC_1_21_9_INDEX = getReferenceVersionIndex("1.21.9");
2930
private static final int MC_24W45A_INDEX = getReferenceVersionIndex("24w45a");
3031
private static final int MC_1_20_5_INDEX = getReferenceVersionIndex("1.20.5");
3132
private static final int MC_24W14A_INDEX = getReferenceVersionIndex("24w14a");
@@ -56,8 +57,9 @@ public static VersionCapabilitiesInternal ofVersionIndex(int versionIndex, Strin
5657
var splitData = hasSplitDataEntrypoints(versionIndex);
5758
var testFixtures = hasTestFixtures(versionIndex);
5859
var modLocatorRework = hasModLocatorRework(versionIndex);
60+
var legacyClasspath = hasLegacyClasspath(versionIndex);
5961

60-
return new VersionCapabilitiesInternal(minecraftVersion, javaVersion, splitData, testFixtures, modLocatorRework);
62+
return new VersionCapabilitiesInternal(minecraftVersion, javaVersion, splitData, testFixtures, modLocatorRework, legacyClasspath);
6163
}
6264

6365
static int getJavaVersion(int versionIndex) {
@@ -84,6 +86,10 @@ static boolean hasModLocatorRework(int versionIndex) {
8486
return versionIndex <= MC_1_20_5_INDEX;
8587
}
8688

89+
static boolean hasLegacyClasspath(int versionIndex) {
90+
return versionIndex > MC_1_21_9_INDEX;
91+
}
92+
8793
static int indexOfNeoForgeVersion(String version) {
8894
// NeoForge omits the "1." at the start of the Minecraft version and just adds an incrementing last digit
8995
var matcher = NEOFORGE_PATTERN.matcher(version);
@@ -168,6 +174,7 @@ public VersionCapabilitiesInternal withMinecraftVersion(String minecraftVersion)
168174
javaVersion,
169175
splitDataRuns,
170176
testFixtures,
171-
modLocatorRework);
177+
modLocatorRework,
178+
legacyClasspath);
172179
}
173180
}

0 commit comments

Comments
 (0)