diff --git a/loader/src/main/java/net/neoforged/fml/loading/ModSorter.java b/loader/src/main/java/net/neoforged/fml/loading/ModSorter.java index 833b40e90..823d72556 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/ModSorter.java +++ b/loader/src/main/java/net/neoforged/fml/loading/ModSorter.java @@ -32,7 +32,6 @@ import net.neoforged.fml.ModLoadingException; import net.neoforged.fml.ModLoadingIssue; import net.neoforged.fml.loading.moddiscovery.ModFile; -import net.neoforged.fml.loading.moddiscovery.ModFileInfo; import net.neoforged.fml.loading.moddiscovery.ModInfo; import net.neoforged.fml.loading.toposort.CyclePresentException; import net.neoforged.fml.loading.toposort.TopologicalSort; @@ -99,7 +98,7 @@ public static LoadingModList sort(List plugins, List gameLibra if (modLoadingException == null) { list = LoadingModList.of(plugins, gameLibraries, ms.modFiles, ms.sortedList, issues, ms.modDependencies); } else { - list = LoadingModList.of(plugins, gameLibraries, ms.modFiles, ms.sortedList, concat(issues, modLoadingException.getIssues()), Map.of()); + list = LoadingModList.of(plugins, gameLibraries, ms.systemMods, ms.systemMods.stream().map(mf -> (ModInfo) mf.getModInfos().getFirst()).collect(toList()), concat(issues, modLoadingException.getIssues()), Map.of()); } } @@ -171,14 +170,15 @@ private void sort(List issues) { try { sorted = TopologicalSort.topologicalSort(graph, Comparator.comparing(infos::get)); } catch (CyclePresentException e) { - Set> cycles = e.getCycles(); + Set> cycles = e.getCycles(); if (LOGGER.isErrorEnabled(LogMarkers.LOADING)) { LOGGER.error(LogMarkers.LOADING, "Mod Sorting failed.\nDetected Cycles: {}\n", cycles); } var dataList = cycles.stream() - .mapMulti(Iterable::forEach) - .mapMulti((mf, c) -> mf.getMods().forEach(c)) - .map(IModInfo::getModId) + .map(cycle -> cycle.stream() + .map(IModInfo::getModId) + .sorted() + .collect(Collectors.joining(", "))) .map(list -> ModLoadingIssue.error("fml.modloadingissue.cycle", list).withCause(e)) .toList(); throw new ModLoadingException(dataList); diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModInfo.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModInfo.java index 4447bdbf1..f5d463720 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModInfo.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModInfo.java @@ -201,6 +201,11 @@ private ForgeFeature.Bound makeBound(Map.Entry e) { } } + @Override + public String toString() { + return modId + " (" + displayName + ") @ " + version; + } + class ModVersion implements net.neoforged.neoforgespi.language.IModInfo.ModVersion { private IModInfo owner; private final String modId; diff --git a/loader/src/test/java/net/neoforged/fml/loading/FMLLoaderTest.java b/loader/src/test/java/net/neoforged/fml/loading/FMLLoaderTest.java index 46136a1eb..f2d349976 100644 --- a/loader/src/test/java/net/neoforged/fml/loading/FMLLoaderTest.java +++ b/loader/src/test/java/net/neoforged/fml/loading/FMLLoaderTest.java @@ -794,6 +794,35 @@ void testMissingRequiredDependency() throws Exception { assertThat(getTranslatedIssues(e.getIssues())).containsOnly("ERROR: Mod testmod requires requiredmod any\nCurrently, requiredmod is not installed\n"); } + @Test + void testDependencyCycle() throws Exception { + installation.setupProductionClient(); + installation.buildModJar("mod_one.jar") + .withModsToml(builder -> { + builder.unlicensedJavaMod(); + builder.addMod("mod_one", "1.0"); + builder.addDependency("mod_one", "mod_two", "[1.0,)", dep -> dep.set("ordering", "BEFORE")); + }) + .build(); + installation.buildModJar("mod_two.jar") + .withModsToml(builder -> { + builder.unlicensedJavaMod(); + builder.addMod("mod_two", "1.0"); + builder.addDependency("mod_two", "mod_three", "[1.0,)", dep -> dep.set("ordering", "BEFORE")); + }) + .build(); + installation.buildModJar("mod_three.jar") + .withModsToml(builder -> { + builder.unlicensedJavaMod(); + builder.addMod("mod_three", "1.0"); + builder.addDependency("mod_three", "mod_one", "[1.0,)", dep -> dep.set("ordering", "BEFORE")); + }) + .build(); + + var e = assertThrows(ModLoadingException.class, () -> launchInstalledDist()); + assertThat(getTranslatedIssues(e.getIssues())).containsOnly("ERROR: Detected a mod dependency cycle: mod_one, mod_three, mod_two"); + } + @Test void testDependencyOverride() throws Exception { installation.setupProductionClient();