add log messages for found incompatibilities

This commit is contained in:
moehreag 2024-05-29 16:06:22 +02:00
parent 7f22499f32
commit 6c564df5dd
2 changed files with 51 additions and 24 deletions

View file

@ -24,7 +24,7 @@ public class ModDependencyResolver {
private final Map<String, DependencyEntry> dependencies = new HashMap<>(); private final Map<String, DependencyEntry> dependencies = new HashMap<>();
private final Map<String, DependencyEntry> breakings = new HashMap<>(); private final Map<String, DependencyEntry> breakings = new HashMap<>();
private final Map<String, DependencyEntry> suggests = new HashMap<>(); private final Map<String, DependencyEntry> suggests = new HashMap<>();
private final Map<String, SemVer> provides = new HashMap<>(); private final Map<String, ProvidedMod> provides = new HashMap<>();
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 {
@ -45,7 +45,7 @@ public class ModDependencyResolver {
} }
props.dependencies().getForType(ModDependencies.Type.PROVIDE).forEach(e -> { props.dependencies().getForType(ModDependencies.Type.PROVIDE).forEach(e -> {
try { try {
provides.put(e.id(), SemVerImpl.parse(e.range())); provides.put(e.id(), new ProvidedMod(e.id(), SemVerImpl.parse(e.range()), props));
} catch (SemVerParseException ex) { } catch (SemVerParseException ex) {
LOGGER.warn("Version for {} ({}), provided by mod '{}' ({}) does not meet SemVer specifications. Mod will not be provided.", LOGGER.warn("Version for {} ({}), provided by mod '{}' ({}) does not meet SemVer specifications. Mod will not be provided.",
e.id(), e.range(), props.id(), props.name()); e.id(), e.range(), props.id(), props.name());
@ -56,29 +56,39 @@ public class ModDependencyResolver {
} }
public Collection<ModProperties> solve() throws UnfulfilledDependencyException, BreakingModException { public Collection<ModProperties> solve() throws UnfulfilledDependencyException, BreakingModException {
// Step 1: look for breakage declarations // Step 1: Combine present and provided mods, always use the latest version available
for (Map.Entry<String, DependencyEntry> e : breakings.entrySet()) {
String key = e.getKey();
DependencyEntry dependencyEntry = e.getValue();
if (presentMods.containsKey(key) && dependencyEntry.range.versionMatches(presentMods.get(key).version())) {
throw new BreakingModException(dependencyEntry.origin(), presentMods.get(key), dependencyEntry.range);
}
}
// Step 2: Combine present and provided mods, always use the latest version available
Set<ModProperties> result = new HashSet<>();
Map<String, SemVer> presentOrProvided = new HashMap<>(); Map<String, SemVer> presentOrProvided = new HashMap<>();
presentMods.forEach((s, modProperties) -> presentOrProvided.put(s, modProperties.version())); presentMods.forEach((s, modProperties) -> presentOrProvided.put(s, modProperties.version()));
provides.forEach((s, ver) -> { provides.forEach((s, ver) -> {
if (presentMods.containsKey(s)) { if (presentMods.containsKey(s)) {
if (presentMods.get(s).version().compareTo(ver) < 0) { if (presentMods.get(s).version().compareTo(ver.version()) < 0) {
presentOrProvided.replace(s, ver); presentOrProvided.replace(s, ver.version());
} }
} else { } else {
presentOrProvided.put(s, ver); presentOrProvided.put(s, ver.version());
} }
}); });
// Step 2: look for breakage declarations
Collection<BreakingModException.Entry> breaks = new HashSet<>();
for (Map.Entry<String, DependencyEntry> e : breakings.entrySet()) {
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));
}
}
if (!breaks.isEmpty()){
breaks.forEach(e ->
LOGGER.error("Mod {} ({}) breaks with mod {} ({}) for versions matching {}", e.source().id(), e.source().name(), e.broken().id(), e.broken().name(), e.range()));
throw new BreakingModException(breaks);
}
Set<ModProperties> result = new HashSet<>();
// 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.entrySet()) {
String key = e.getKey(); String key = e.getKey();
@ -96,14 +106,15 @@ public class ModDependencyResolver {
} }
}); });
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.entrySet()) {
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)))) { // The dependency also isn't fulfilled by any provided mods if (!(provides.containsKey(s) && value.range.versionMatches(provides.get(s).version()))) { // 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")) {
throw new UnfulfilledDependencyException(value.origin, s, value.range); unfulfilled.add(new UnfulfilledDependencyException.Entry(value.origin, s, value.range));
} else { } else {
continue; continue;
} }
@ -113,9 +124,22 @@ public class ModDependencyResolver {
result.add(value.origin()); result.add(value.origin());
} }
if (!unfulfilled.isEmpty()){
unfulfilled.forEach(e -> {
if (!presentOrProvided.containsKey(e.dependency)) {
LOGGER.error("Mod {} ({}) depends on mod {} with a version matching {}", e.source().id(), e.source().name(), e.dependency(), e.range());
} else {
LOGGER.error("Mod {} ({}) depends on mod {} with a version matching {}, but a different version is present or provided: {}", e.source().id(), e.source().name(), e.dependency(), e.range(), presentOrProvided.get(e.dependency));
}
});
throw new UnfulfilledDependencyException(unfulfilled);
}
return result; return result;
} }
private record ProvidedMod(String modId, SemVer version, ModProperties source){}
@AllArgsConstructor @AllArgsConstructor
private enum DependencyType { private enum DependencyType {
EQ("=", Object::equals), EQ("=", Object::equals),
@ -140,16 +164,19 @@ public class ModDependencyResolver {
@AllArgsConstructor @AllArgsConstructor
@Getter @Getter
public static class BreakingModException extends Exception { public static class BreakingModException extends Exception {
private final ModProperties source, broken; private final Collection<Entry> breaks;
private final VersionRange range;
public record Entry(ModProperties source, ModProperties broken, VersionRange range) {
}
} }
@AllArgsConstructor @AllArgsConstructor
@Getter @Getter
public static class UnfulfilledDependencyException extends Exception { public static class UnfulfilledDependencyException extends Exception {
private final ModProperties source; private final Collection<Entry> dependencies;
private final String dependency;
private final VersionRange range; public record Entry(ModProperties source, String dependency, VersionRange range){}
} }
public static class ResolverException extends Exception { public static class ResolverException extends Exception {

View file

@ -22,7 +22,7 @@ public class ModUtil {
} else { } else {
builder.append("\\- "); builder.append("\\- ");
} }
builder.append(p.name()).append(" (").append(p.id()).append(") ").append(" ").append(p.version()); builder.append(p.id()).append(" (").append(p.name()).append(") ").append(" ").append(p.version());
i++; i++;
} }
return builder.toString(); return builder.toString();