fix resolution with multiple mods depending on the same one
All checks were successful
Publish to snapshot maven / build (push) Successful in 15s
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:
parent
a52d97708c
commit
d8289deea7
|
@ -27,3 +27,7 @@ java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_21
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
targetCompatibility = JavaVersion.VERSION_21
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.runClient {
|
||||||
|
classpath(sourceSets.test.get().runtimeClasspath)
|
||||||
|
}
|
|
@ -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!");
|
||||||
|
}
|
||||||
|
}
|
15
minecraft/src/test/resources/frog.mod.toml
Normal file
15
minecraft/src/test/resources/frog.mod.toml
Normal 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"
|
||||||
|
|
|
@ -6,6 +6,7 @@ import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import com.google.common.collect.*;
|
||||||
import dev.frogmc.frogloader.api.mod.ModDependencies;
|
import dev.frogmc.frogloader.api.mod.ModDependencies;
|
||||||
import dev.frogmc.frogloader.api.mod.ModProperties;
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
import dev.frogmc.frogloader.api.mod.SemVer;
|
import dev.frogmc.frogloader.api.mod.SemVer;
|
||||||
|
@ -17,15 +18,16 @@ import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
public class ModDependencyResolver {
|
public class ModDependencyResolver {
|
||||||
|
|
||||||
private final static Logger LOGGER = LoggerFactory.getLogger(ModDependencyResolver.class);
|
private final static Logger LOGGER = LoggerFactory.getLogger(ModDependencyResolver.class);
|
||||||
private static final Pattern COMPARATOR = Pattern.compile("(=|>=|<=|>|<)?(\\d.*)");
|
private static final Pattern COMPARATOR = Pattern.compile("(=|>=|<=|>|<)?(\\d.*)");
|
||||||
private final Collection<ModProperties> original;
|
private final Collection<ModProperties> original;
|
||||||
private final Map<String, DependencyEntry> dependencies = new HashMap<>();
|
private final Multimap<String, DependencyEntry> dependencies = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||||
private final Map<String, DependencyEntry> breakings = new HashMap<>();
|
private final Multimap<String, DependencyEntry> breakings = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||||
private final Map<String, DependencyEntry> suggests = new HashMap<>();
|
private final Multimap<String, DependencyEntry> suggests = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||||
private final Map<String, ProvidedMod> provides = new HashMap<>();
|
private final Multimap<String, ProvidedMod> provides = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||||
private final Map<String, ModProperties> presentMods = new HashMap<>();
|
private final Map<String, ModProperties> presentMods = new HashMap<>();
|
||||||
|
|
||||||
public ModDependencyResolver(Collection<ModProperties> mods) throws ResolverException {
|
public ModDependencyResolver(Collection<ModProperties> mods) throws ResolverException {
|
||||||
|
@ -52,6 +54,7 @@ public class ModDependencyResolver {
|
||||||
e.id(), e.range(), props.id(), props.name());
|
e.id(), e.range(), props.id(), props.name());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
LOGGER.debug("Considering mod for dependency resolution: {} ({})", props.id(), props.name());
|
||||||
presentMods.put(props.id(), props);
|
presentMods.put(props.id(), props);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,11 +75,13 @@ public class ModDependencyResolver {
|
||||||
|
|
||||||
// Step 2: look for breakage declarations
|
// Step 2: look for breakage declarations
|
||||||
Collection<BreakingModException.Entry> breaks = new HashSet<>();
|
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();
|
String key = e.getKey();
|
||||||
DependencyEntry dependencyEntry = e.getValue();
|
DependencyEntry dependencyEntry = e.getValue();
|
||||||
if (presentOrProvided.containsKey(key) && dependencyEntry.range.versionMatches(presentOrProvided.get(key))) {
|
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
|
// 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();
|
String key = e.getKey();
|
||||||
DependencyEntry v = e.getValue();
|
DependencyEntry v = e.getValue();
|
||||||
if (!presentOrProvided.containsKey(key) || !v.range.versionMatches(presentOrProvided.get(key))) {
|
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
|
// Step 4.1: Add all mods to the result that do not depend on any other mods
|
||||||
presentMods.forEach((s, modProperties) -> {
|
presentMods.forEach((s, modProperties) -> {
|
||||||
if (modProperties.dependencies().getForType(ModDependencies.Type.DEPEND).isEmpty()) {
|
if (modProperties.dependencies().getForType(ModDependencies.Type.DEPEND).isEmpty()) {
|
||||||
|
LOGGER.debug("Adding mod to result: {} ({})", modProperties.id(), modProperties.name());
|
||||||
result.add(modProperties);
|
result.add(modProperties);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Collection<UnfulfilledDependencyException.Entry> unfulfilled = new HashSet<>();
|
Collection<UnfulfilledDependencyException.Entry> unfulfilled = new HashSet<>();
|
||||||
// Step 4.2: Check that all dependencies are satisfied by present or provided mods.
|
// 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();
|
String s = entry.getKey();
|
||||||
DependencyEntry value = entry.getValue();
|
DependencyEntry value = entry.getValue();
|
||||||
if (!(presentMods.containsKey(s) && value.range.versionMatches(presentOrProvided.get(s)))) { // The dependency isn't fulfilled by any present mods
|
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")) {
|
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()));
|
unfulfilled.add(new UnfulfilledDependencyException.Entry(value.origin, s, value.range, presentOrProvided.get(s), value.link(), value.name()));
|
||||||
} else {
|
} else {
|
||||||
|
LOGGER.debug("Skipping optional mod: {} ({})", value.origin().id(), value.origin().name());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// as there hasn't been thrown an exception the dependency must have been fulfilled, add the mod that set the dependency
|
// 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());
|
result.add(value.origin());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ public class ModPropertiesReader {
|
||||||
|
|
||||||
public static Optional<ModProperties> read(Path mod) {
|
public static Optional<ModProperties> read(Path mod) {
|
||||||
|
|
||||||
|
LOGGER.debug("Loading mod from: {}", mod);
|
||||||
try (FileSystem fs = FileSystems.newFileSystem(mod)) {
|
try (FileSystem fs = FileSystems.newFileSystem(mod)) {
|
||||||
CommentedConfig props = PARSER.parse(fs.getPath(PROPERTIES_FILE_NAME), FileNotFoundAction.THROW_ERROR);
|
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) {
|
public static Optional<ModProperties> readFile(URL in) {
|
||||||
|
try {
|
||||||
|
LOGGER.debug("Loading mod from: {}", in);
|
||||||
CommentedConfig props = PARSER.parse(in);
|
CommentedConfig props = PARSER.parse(in);
|
||||||
|
|
||||||
try {
|
|
||||||
String url = in.toString();
|
String url = in.toString();
|
||||||
Path source = Path.of(url.substring(url.lastIndexOf(":")+1).split("!")[0]).toAbsolutePath();
|
Path source = Path.of(url.substring(url.lastIndexOf(":")+1).split("!")[0]).toAbsolutePath();
|
||||||
if (!source.getFileName().toString().endsWith(".jar")){
|
if (!source.getFileName().toString().endsWith(".jar")){
|
||||||
|
@ -53,7 +55,7 @@ public class ModPropertiesReader {
|
||||||
}
|
}
|
||||||
return Optional.of(readProperties(props, Collections.singleton(source)));
|
return Optional.of(readProperties(props, Collections.singleton(source)));
|
||||||
} catch (Exception e) {
|
} 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();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.Collection;
|
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.FrogLoader;
|
||||||
import dev.frogmc.frogloader.api.mod.ModProperties;
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
|
@ -26,17 +28,17 @@ public class ModUtil {
|
||||||
builder.append("s");
|
builder.append("s");
|
||||||
}
|
}
|
||||||
builder.append(":");
|
builder.append(":");
|
||||||
int i = 0;
|
AtomicInteger i = new AtomicInteger();
|
||||||
for (ModProperties p : mods) {
|
mods.stream().sorted(Comparator.comparing(ModProperties::id)).forEach(p -> {
|
||||||
builder.append("\n\t");
|
builder.append("\n\t");
|
||||||
if (i < size - 1) {
|
if (i.get() < size - 1) {
|
||||||
builder.append("|- ");
|
builder.append("|- ");
|
||||||
} else {
|
} else {
|
||||||
builder.append("\\- ");
|
builder.append("\\- ");
|
||||||
}
|
}
|
||||||
builder.append(p.id()).append(" (").append(p.name()).append(") ").append(" ").append(p.version());
|
builder.append(p.id()).append(" (").append(p.name()).append(") ").append(" ").append(p.version());
|
||||||
i++;
|
i.getAndIncrement();
|
||||||
}
|
});
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import java.lang.invoke.MethodHandle;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.invoke.MethodType;
|
import java.lang.invoke.MethodType;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
@ -74,9 +73,8 @@ public class Minecraft implements FrogPlugin {
|
||||||
Collection<Path> mods = Discovery.find(loader.getModsDir(), path ->
|
Collection<Path> mods = Discovery.find(loader.getModsDir(), path ->
|
||||||
version.equals(path.getFileName().toString()), path ->
|
version.equals(path.getFileName().toString()), path ->
|
||||||
path.getFileName().toString().endsWith(FrogLoaderImpl.MOD_FILE_EXTENSION));
|
path.getFileName().toString().endsWith(FrogLoaderImpl.MOD_FILE_EXTENSION));
|
||||||
Collection<URL> classpathMods = this.getClass().getClassLoader().resources(ModPropertiesReader.PROPERTIES_FILE_NAME).distinct().toList();
|
this.getClass().getClassLoader().resources(ModPropertiesReader.PROPERTIES_FILE_NAME).map(ModPropertiesReader::readFile)
|
||||||
|
.map(o -> o.orElse(null)).filter(Objects::nonNull).forEach(modProperties::add);
|
||||||
classpathMods.stream().map(ModPropertiesReader::readFile).map(o -> o.orElse(null)).filter(Objects::nonNull).forEach(modProperties::add);
|
|
||||||
Map<Path, ModProperties> modPaths = new HashMap<>();
|
Map<Path, ModProperties> modPaths = new HashMap<>();
|
||||||
for (Path mod : new HashSet<>(mods)) {
|
for (Path mod : new HashSet<>(mods)) {
|
||||||
findJiJMods(mod, mods, modPaths);
|
findJiJMods(mod, mods, modPaths);
|
||||||
|
|
Loading…
Reference in a new issue