fix resolution with multiple mods depending on the same one
All checks were successful
Publish to snapshot maven / build (push) Successful in 15s

- sort mods list before printing it out
- add more debug logging
This commit is contained in:
moehreag 2024-06-09 23:14:54 +02:00
parent a52d97708c
commit d8289deea7
7 changed files with 60 additions and 21 deletions

View file

@ -27,3 +27,7 @@ java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
tasks.runClient {
classpath(sourceSets.test.get().runtimeClasspath)
}

View file

@ -0,0 +1,10 @@
package dev.frogmc.example;
import dev.frogmc.frogloader.api.extensions.PreLaunchExtension;
public class ExampleTestMod implements PreLaunchExtension {
@Override
public void onPreLaunch() {
System.out.println("Hello World!");
}
}

View file

@ -0,0 +1,15 @@
[frog]
format_version = "1.0.0"
[frog.mod]
id = "example_mod_test"
name = "Example Mod Test Mod"
version = "0.0.1"
license = "CC0-1.0"
credits = [
{ name = "You", roles = ["author", "other_role"] }
]
[frog.extensions]
pre_launch="dev.frogmc.example.ExampleTestMod"

View file

@ -6,6 +6,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.google.common.collect.*;
import dev.frogmc.frogloader.api.mod.ModDependencies;
import dev.frogmc.frogloader.api.mod.ModProperties;
import dev.frogmc.frogloader.api.mod.SemVer;
@ -17,15 +18,16 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("UnstableApiUsage")
public class ModDependencyResolver {
private final static Logger LOGGER = LoggerFactory.getLogger(ModDependencyResolver.class);
private static final Pattern COMPARATOR = Pattern.compile("(=|>=|<=|>|<)?(\\d.*)");
private final Collection<ModProperties> original;
private final Map<String, DependencyEntry> dependencies = new HashMap<>();
private final Map<String, DependencyEntry> breakings = new HashMap<>();
private final Map<String, DependencyEntry> suggests = new HashMap<>();
private final Map<String, ProvidedMod> provides = new HashMap<>();
private final Multimap<String, DependencyEntry> dependencies = MultimapBuilder.hashKeys().arrayListValues().build();
private final Multimap<String, DependencyEntry> breakings = MultimapBuilder.hashKeys().arrayListValues().build();
private final Multimap<String, DependencyEntry> suggests = MultimapBuilder.hashKeys().arrayListValues().build();
private final Multimap<String, ProvidedMod> provides = MultimapBuilder.hashKeys().arrayListValues().build();
private final Map<String, ModProperties> presentMods = new HashMap<>();
public ModDependencyResolver(Collection<ModProperties> mods) throws ResolverException {
@ -52,6 +54,7 @@ public class ModDependencyResolver {
e.id(), e.range(), props.id(), props.name());
}
});
LOGGER.debug("Considering mod for dependency resolution: {} ({})", props.id(), props.name());
presentMods.put(props.id(), props);
}
}
@ -72,11 +75,13 @@ public class ModDependencyResolver {
// Step 2: look for breakage declarations
Collection<BreakingModException.Entry> breaks = new HashSet<>();
for (Map.Entry<String, DependencyEntry> e : breakings.entrySet()) {
for (Map.Entry<String, DependencyEntry> e : breakings.entries()) {
String key = e.getKey();
DependencyEntry dependencyEntry = e.getValue();
if (presentOrProvided.containsKey(key) && dependencyEntry.range.versionMatches(presentOrProvided.get(key))) {
breaks.add(new BreakingModException.Entry(dependencyEntry.origin(), Optional.ofNullable(presentMods.get(key)).orElseGet(() -> provides.get(key).source()), dependencyEntry.range));
breaks.add(new BreakingModException.Entry(dependencyEntry.origin(),
Optional.ofNullable(presentMods.get(key)).orElseGet(() -> provides.get(key).stream().findFirst().map(ProvidedMod::source).orElseThrow()),
dependencyEntry.range));
}
}
@ -91,7 +96,7 @@ public class ModDependencyResolver {
// Step 3: print out information about suggested mods
for (Map.Entry<String, DependencyEntry> e : suggests.entrySet()) {
for (Map.Entry<String, DependencyEntry> e : suggests.entries()) {
String key = e.getKey();
DependencyEntry v = e.getValue();
if (!presentOrProvided.containsKey(key) || !v.range.versionMatches(presentOrProvided.get(key))) {
@ -103,25 +108,28 @@ public class ModDependencyResolver {
// Step 4.1: Add all mods to the result that do not depend on any other mods
presentMods.forEach((s, modProperties) -> {
if (modProperties.dependencies().getForType(ModDependencies.Type.DEPEND).isEmpty()) {
LOGGER.debug("Adding mod to result: {} ({})", modProperties.id(), modProperties.name());
result.add(modProperties);
}
});
Collection<UnfulfilledDependencyException.Entry> unfulfilled = new HashSet<>();
// Step 4.2: Check that all dependencies are satisfied by present or provided mods.
for (Map.Entry<String, DependencyEntry> entry : dependencies.entrySet()) {
for (Map.Entry<String, DependencyEntry> entry : dependencies.entries()) {
String s = entry.getKey();
DependencyEntry value = entry.getValue();
if (!(presentMods.containsKey(s) && value.range.versionMatches(presentOrProvided.get(s)))) { // The dependency isn't fulfilled by any present mods
if (!(provides.containsKey(s) && value.range.versionMatches(provides.get(s).version()))) { // The dependency also isn't fulfilled by any provided mods
if (!(provides.containsKey(s) && provides.get(s).stream().map(ProvidedMod::version).allMatch(value.range::versionMatches))) { // The dependency also isn't fulfilled by any provided mods
if (value.origin.extensions().getOrDefault(BuiltinExtensions.LOADING_TYPE, "required").equals("required")) {
unfulfilled.add(new UnfulfilledDependencyException.Entry(value.origin, s, value.range, presentOrProvided.get(s), value.link(), value.name()));
} else {
LOGGER.debug("Skipping optional mod: {} ({})", value.origin().id(), value.origin().name());
continue;
}
}
}
// as there hasn't been thrown an exception the dependency must have been fulfilled, add the mod that set the dependency
LOGGER.debug("Adding mod to result: {} ({})", value.origin().id(), value.origin().name());
result.add(value.origin());
}

View file

@ -27,6 +27,7 @@ public class ModPropertiesReader {
public static Optional<ModProperties> read(Path mod) {
LOGGER.debug("Loading mod from: {}", mod);
try (FileSystem fs = FileSystems.newFileSystem(mod)) {
CommentedConfig props = PARSER.parse(fs.getPath(PROPERTIES_FILE_NAME), FileNotFoundAction.THROW_ERROR);
@ -39,9 +40,10 @@ public class ModPropertiesReader {
}
public static Optional<ModProperties> readFile(URL in) {
try {
LOGGER.debug("Loading mod from: {}", in);
CommentedConfig props = PARSER.parse(in);
try {
String url = in.toString();
Path source = Path.of(url.substring(url.lastIndexOf(":")+1).split("!")[0]).toAbsolutePath();
if (!source.getFileName().toString().endsWith(".jar")){
@ -53,7 +55,7 @@ public class ModPropertiesReader {
}
return Optional.of(readProperties(props, Collections.singleton(source)));
} catch (Exception e) {
LOGGER.warn("Failed to read mod properties from "+in+": ", e);
LOGGER.warn("Failed to read mod properties from {}: ", in, e);
}
return Optional.empty();
}

View file

@ -4,6 +4,8 @@ import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.util.Collection;
import java.util.Comparator;
import java.util.concurrent.atomic.AtomicInteger;
import dev.frogmc.frogloader.api.FrogLoader;
import dev.frogmc.frogloader.api.mod.ModProperties;
@ -26,17 +28,17 @@ public class ModUtil {
builder.append("s");
}
builder.append(":");
int i = 0;
for (ModProperties p : mods) {
AtomicInteger i = new AtomicInteger();
mods.stream().sorted(Comparator.comparing(ModProperties::id)).forEach(p -> {
builder.append("\n\t");
if (i < size - 1) {
if (i.get() < size - 1) {
builder.append("|- ");
} else {
builder.append("\\- ");
}
builder.append(p.id()).append(" (").append(p.name()).append(") ").append(" ").append(p.version());
i++;
}
i.getAndIncrement();
});
return builder.toString();
}

View file

@ -6,7 +6,6 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.*;
import java.util.*;
@ -74,9 +73,8 @@ public class Minecraft implements FrogPlugin {
Collection<Path> mods = Discovery.find(loader.getModsDir(), path ->
version.equals(path.getFileName().toString()), path ->
path.getFileName().toString().endsWith(FrogLoaderImpl.MOD_FILE_EXTENSION));
Collection<URL> classpathMods = this.getClass().getClassLoader().resources(ModPropertiesReader.PROPERTIES_FILE_NAME).distinct().toList();
classpathMods.stream().map(ModPropertiesReader::readFile).map(o -> o.orElse(null)).filter(Objects::nonNull).forEach(modProperties::add);
this.getClass().getClassLoader().resources(ModPropertiesReader.PROPERTIES_FILE_NAME).map(ModPropertiesReader::readFile)
.map(o -> o.orElse(null)).filter(Objects::nonNull).forEach(modProperties::add);
Map<Path, ModProperties> modPaths = new HashMap<>();
for (Path mod : new HashSet<>(mods)) {
findJiJMods(mod, mods, modPaths);