[1.12] Add support for client & server dependencies for mods (#4403)
This commit is contained in:
parent
cf39ff18e1
commit
f494117453
7 changed files with 577 additions and 108 deletions
|
@ -67,7 +67,9 @@ extractForgeSources { exclude "**/SideOnly.java", "**/Side.java" }
|
|||
extractForgeResources { exclude "**/log4j2.xml" }
|
||||
|
||||
genGradleProjects {
|
||||
addTestCompileDep "junit:junit:4.12"
|
||||
addTestCompileDep "junit:junit:4.12" // TODO update unit tests to junit 5 and remove this
|
||||
addTestCompileDep "org.junit.jupiter:junit-jupiter-api:5.0.0"
|
||||
addTestCompileDep "org.opentest4j:opentest4j:1.0.0" // needed for junit 5
|
||||
addTestCompileDep "org.hamcrest:hamcrest-core:1.3"
|
||||
filter { dep -> !dep.contains("scala") }
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ import net.minecraftforge.fml.common.event.FMLFingerprintViolationEvent;
|
|||
import net.minecraftforge.fml.common.network.NetworkRegistry;
|
||||
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
|
||||
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
|
||||
import net.minecraftforge.fml.common.versioning.DependencyParser;
|
||||
import net.minecraftforge.fml.common.versioning.VersionParser;
|
||||
import net.minecraftforge.fml.common.versioning.VersionRange;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
|
@ -67,14 +68,11 @@ import java.util.function.Function;
|
|||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
|
@ -140,19 +138,18 @@ public class FMLModContainer implements ModContainer
|
|||
String modid = (String)this.descriptor.get("modid");
|
||||
if (Strings.isNullOrEmpty(modid))
|
||||
{
|
||||
throw new IllegalArgumentException("Modid cannot be null or empty");
|
||||
throw new IllegalArgumentException("The modId is null or empty");
|
||||
}
|
||||
if (modid.length() > 64)
|
||||
{
|
||||
FMLLog.bigWarning("The modid {} is longer than the recommended maximum of 64 characters. Truncation is enforced in 1.11", modid);
|
||||
throw new IllegalArgumentException(String.format("The modid %s is longer than the recommended maximum of 64 characters. Truncation is enforced in 1.11", modid));
|
||||
throw new IllegalArgumentException(String.format("The modId %s is longer than the maximum of 64 characters.", modid));
|
||||
}
|
||||
if (!modid.equals(modid.toLowerCase(Locale.ENGLISH)))
|
||||
{
|
||||
FMLLog.bigWarning("The modid {} is not the same as it's lowercase version. Lowercasing is enforced in 1.11", modid);
|
||||
throw new IllegalArgumentException(String.format("The modid %s is not the same as it's lowercase version. Lowercasing will be enforced in 1.11", modid));
|
||||
throw new IllegalArgumentException(String.format("The modId %s must be all lowercase.", modid));
|
||||
}
|
||||
}
|
||||
|
||||
private ILanguageAdapter getLanguageAdapter()
|
||||
{
|
||||
if (languageAdapter == null)
|
||||
|
@ -212,17 +209,15 @@ public class FMLModContainer implements ModContainer
|
|||
|
||||
if (overridesMetadata || !modMetadata.useDependencyInformation)
|
||||
{
|
||||
Set<ArtifactVersion> requirements = Sets.newHashSet();
|
||||
List<ArtifactVersion> dependencies = Lists.newArrayList();
|
||||
List<ArtifactVersion> dependants = Lists.newArrayList();
|
||||
annotationDependencies = (String)descriptor.get("dependencies");
|
||||
Loader.instance().computeDependencies(annotationDependencies, requirements, dependencies, dependants);
|
||||
dependants.addAll(Loader.instance().getInjectedBefore(getModId()));
|
||||
dependencies.addAll(Loader.instance().getInjectedAfter(getModId()));
|
||||
modMetadata.requiredMods = requirements;
|
||||
modMetadata.dependencies = dependencies;
|
||||
modMetadata.dependants = dependants;
|
||||
modLog.trace("Parsed dependency info : {} {} {}", requirements, dependencies, dependants);
|
||||
DependencyParser dependencyParser = new DependencyParser(getModId(), FMLCommonHandler.instance().getSide());
|
||||
DependencyParser.DependencyInfo info = dependencyParser.parseDependencies(annotationDependencies);
|
||||
info.dependants.addAll(Loader.instance().getInjectedBefore(getModId()));
|
||||
info.dependencies.addAll(Loader.instance().getInjectedAfter(getModId()));
|
||||
modMetadata.requiredMods = info.requirements;
|
||||
modMetadata.dependencies = info.dependencies;
|
||||
modMetadata.dependants = info.dependants;
|
||||
modLog.trace("Parsed dependency info : Requirements: {} After:{} Before:{}", info.requirements, info.dependencies, info.dependants);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -26,7 +26,6 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -35,6 +34,7 @@ import java.util.Properties;
|
|||
import java.util.Set;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.common.ForgeVersion;
|
||||
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||
import net.minecraftforge.common.config.ConfigManager;
|
||||
import net.minecraftforge.common.crafting.CraftingHelper;
|
||||
|
@ -52,6 +52,7 @@ import net.minecraftforge.fml.common.toposort.ModSortingException;
|
|||
import net.minecraftforge.fml.common.toposort.TopologicalSort;
|
||||
import net.minecraftforge.fml.common.toposort.ModSortingException.SortingExceptionData;
|
||||
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
|
||||
import net.minecraftforge.fml.common.versioning.DependencyParser;
|
||||
import net.minecraftforge.fml.common.versioning.VersionParser;
|
||||
import net.minecraftforge.fml.relauncher.ModListHelper;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
|
@ -62,7 +63,6 @@ import org.apache.commons.io.IOUtils;
|
|||
import org.apache.logging.log4j.Level;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import java.util.function.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
|
@ -124,9 +124,7 @@ import javax.annotation.Nullable;
|
|||
@SuppressWarnings("unused")
|
||||
public class Loader
|
||||
{
|
||||
public static final String MC_VERSION = net.minecraftforge.common.ForgeVersion.mcVersion;
|
||||
private static final Splitter DEPENDENCYPARTSPLITTER = Splitter.on(":").omitEmptyStrings().trimResults();
|
||||
private static final Splitter DEPENDENCYSPLITTER = Splitter.on(";").omitEmptyStrings().trimResults();
|
||||
public static final String MC_VERSION = ForgeVersion.mcVersion;
|
||||
/**
|
||||
* The singleton instance
|
||||
*/
|
||||
|
@ -695,77 +693,17 @@ public class Loader
|
|||
return modClassLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link DependencyParser#parseDependencies(String)}
|
||||
*/
|
||||
@Deprecated // TODO: remove in 1.13
|
||||
public void computeDependencies(String dependencyString, Set<ArtifactVersion> requirements, List<ArtifactVersion> dependencies, List<ArtifactVersion> dependants)
|
||||
{
|
||||
if (dependencyString == null || dependencyString.length() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
boolean parseFailure = false;
|
||||
|
||||
for (String dep : DEPENDENCYSPLITTER.split(dependencyString))
|
||||
{
|
||||
List<String> depparts = Lists.newArrayList(DEPENDENCYPARTSPLITTER.split(dep));
|
||||
// Need two parts to the string
|
||||
if (depparts.size() != 2)
|
||||
{
|
||||
parseFailure = true;
|
||||
continue;
|
||||
}
|
||||
String instruction = depparts.get(0);
|
||||
String target = depparts.get(1);
|
||||
boolean targetIsAll = target.startsWith("*");
|
||||
|
||||
// Cannot have an "all" relationship with anything except pure *
|
||||
if (targetIsAll && target.length() > 1)
|
||||
{
|
||||
parseFailure = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is a required element, add it to the required list
|
||||
if ("required-before".equals(instruction) || "required-after".equals(instruction))
|
||||
{
|
||||
// You can't require everything
|
||||
if (!targetIsAll)
|
||||
{
|
||||
requirements.add(VersionParser.parseVersionReference(target));
|
||||
}
|
||||
else
|
||||
{
|
||||
parseFailure = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// You cannot have a versioned dependency on everything
|
||||
if (targetIsAll && target.indexOf('@') > -1)
|
||||
{
|
||||
parseFailure = true;
|
||||
continue;
|
||||
}
|
||||
// before elements are things we are loaded before (so they are our dependants)
|
||||
if ("required-before".equals(instruction) || "before".equals(instruction))
|
||||
{
|
||||
dependants.add(VersionParser.parseVersionReference(target));
|
||||
}
|
||||
// after elements are things that load before we do (so they are out dependencies)
|
||||
else if ("required-after".equals(instruction) || "after".equals(instruction))
|
||||
{
|
||||
dependencies.add(VersionParser.parseVersionReference(target));
|
||||
}
|
||||
else
|
||||
{
|
||||
parseFailure = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseFailure)
|
||||
{
|
||||
FMLLog.log.warn("Unable to parse dependency string {}", dependencyString);
|
||||
throw new LoaderException(String.format("Unable to parse dependency string %s", dependencyString));
|
||||
}
|
||||
DependencyParser dependencyParser = new DependencyParser("unknown", FMLCommonHandler.instance().getSide());
|
||||
DependencyParser.DependencyInfo info = dependencyParser.parseDependencies(dependencyString);
|
||||
requirements.addAll(info.requirements);
|
||||
dependencies.addAll(info.dependencies);
|
||||
dependants.addAll(info.dependants);
|
||||
}
|
||||
|
||||
public Map<String,ModContainer> getIndexedModList()
|
||||
|
|
|
@ -101,24 +101,37 @@ public @interface Mod
|
|||
/**
|
||||
* A dependency string for this mod, which specifies which mod(s) it depends on in order to run.
|
||||
*
|
||||
* A dependency string can start with the following four prefixes:
|
||||
* before, after, required-before, required-after
|
||||
* A dependency string must start with a combination of these prefixes, separated by "-":
|
||||
* [before, after], [required], [client, server]
|
||||
* At least one "before", "after", or "required" must be specified.
|
||||
* Then ":" and the mod id.
|
||||
*
|
||||
* Optionally, a version range can be specified for the mod by adding "@" and then the version range.
|
||||
* The version range format is described in the javadoc here: {@link VersionRange#createFromVersionSpec(java.lang.String)}
|
||||
* Then a version range should be specified for the mod by adding "@" and the version range.
|
||||
* The version range format is described in the javadoc here:
|
||||
* {@link VersionRange#createFromVersionSpec(java.lang.String)}
|
||||
* Then a ";".
|
||||
*
|
||||
* If a "required" mod is missing, or a mod exists with a version outside the specified range,
|
||||
* the game will not start and an error screen will tell the player which versions are required.
|
||||
*
|
||||
* Example:
|
||||
* Our example mod is a dedicated addon to mod1 and has optional integration with mod2.
|
||||
* It uses new features that were introduced in forge version 14.21.1.2395 and mod2 version 4.7.0
|
||||
* Our example mod:
|
||||
* * depends on Forge and uses new features that were introduced in Forge version 14.21.1.2395
|
||||
* "required:forge@[14.21.1.2395,);"
|
||||
* * is a dedicated addon to mod1 and has to have its event handlers run after mod1's are run,
|
||||
* "required-after:mod1;"
|
||||
* * has optional integration with mod2 which depends on features introduced in mod2 version 4.7.0,
|
||||
* "after:mod2@[4.7.0,);"
|
||||
* * depends on a client-side-only rendering library called rendermod
|
||||
* "required-client:rendermod;"
|
||||
*
|
||||
* the dependencies string = "after:forge@[14.21.1.2395,);required-after:mod1;after:mod2@[4.7.0,);"
|
||||
* The full dependencies string is all of those combined:
|
||||
* "required:forge@[14.21.1.2395,);required-after:mod1;after:mod2@[4.7.0,);required-client:rendermod;"
|
||||
*
|
||||
* This will stop the game and display an error message if any of these is true:
|
||||
* The installed forge is too old, mod1 is missing, or an old version of mod2 is present.
|
||||
* The installed forge is too old,
|
||||
* mod1 is missing,
|
||||
* an old version of mod2 is present,
|
||||
* rendermod is missing on the client.
|
||||
*/
|
||||
String dependencies() default "";
|
||||
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.common.versioning;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.minecraftforge.fml.common.LoaderException;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public final class DependencyParser
|
||||
{
|
||||
public static class DependencyInfo
|
||||
{
|
||||
public final Set<ArtifactVersion> requirements = new HashSet<>();
|
||||
public final List<ArtifactVersion> dependencies = new ArrayList<>();
|
||||
public final List<ArtifactVersion> dependants = new ArrayList<>();
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("FML");
|
||||
private static final ImmutableList<String> DEPENDENCY_INSTRUCTIONS = ImmutableList.of("client", "server", "required", "before", "after");
|
||||
private static final Splitter DEPENDENCY_INSTRUCTIONS_SPLITTER = Splitter.on("-").omitEmptyStrings().trimResults();
|
||||
private static final Splitter DEPENDENCY_PART_SPLITTER = Splitter.on(":").omitEmptyStrings().trimResults();
|
||||
private static final Splitter DEPENDENCY_SPLITTER = Splitter.on(";").omitEmptyStrings().trimResults();
|
||||
|
||||
private final String modId;
|
||||
private final Side side;
|
||||
|
||||
public DependencyParser(String modId, Side side)
|
||||
{
|
||||
this.modId = modId;
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
public DependencyInfo parseDependencies(String dependencyString)
|
||||
{
|
||||
DependencyInfo info = new DependencyInfo();
|
||||
if (dependencyString == null || dependencyString.length() == 0)
|
||||
{
|
||||
return info;
|
||||
}
|
||||
|
||||
for (String dep : DEPENDENCY_SPLITTER.split(dependencyString))
|
||||
{
|
||||
final List<String> depParts = DEPENDENCY_PART_SPLITTER.splitToList(dep);
|
||||
if (depParts.size() != 2)
|
||||
{
|
||||
throw new DependencyParserException(modId, dep, "Dependency string needs 2 parts.");
|
||||
}
|
||||
|
||||
final List<String> instructions = DEPENDENCY_INSTRUCTIONS_SPLITTER.splitToList(depParts.get(0));
|
||||
final String target = depParts.get(1);
|
||||
parseDependency(dep, instructions, target, info);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private void parseDependency(String dep, List<String> instructions, String target, DependencyInfo info)
|
||||
{
|
||||
final boolean targetIsAll = target.startsWith("*");
|
||||
final boolean targetIsBounded = target.contains("@");
|
||||
if (targetIsAll)
|
||||
{
|
||||
if (target.length() > 1)
|
||||
{
|
||||
throw new DependencyParserException(modId, dep, "Cannot have an \"all\" (*) relationship with anything except pure *");
|
||||
}
|
||||
else if (targetIsBounded)
|
||||
{
|
||||
throw new DependencyParserException(modId, dep, "You cannot have a versioned dependency on everything (*)");
|
||||
}
|
||||
}
|
||||
|
||||
Side depSide = null;
|
||||
String depOrder = null;
|
||||
boolean depRequired = false;
|
||||
|
||||
for (String instruction : instructions)
|
||||
{
|
||||
if ("client".equals(instruction))
|
||||
{
|
||||
if (depSide != null)
|
||||
{
|
||||
throw new DependencyParserException(modId, dep, "Up to one side (client or server) can be specified.");
|
||||
}
|
||||
depSide = Side.CLIENT;
|
||||
}
|
||||
else if ("server".equals(instruction))
|
||||
{
|
||||
if (depSide != null)
|
||||
{
|
||||
throw new DependencyParserException(modId, dep, "Up to one side (client or server) can be specified.");
|
||||
}
|
||||
depSide = Side.SERVER;
|
||||
}
|
||||
else if ("required".equals(instruction))
|
||||
{
|
||||
if (depRequired)
|
||||
{
|
||||
throw new DependencyParserException(modId, dep, "'required' can only be specified once.");
|
||||
}
|
||||
if (targetIsAll)
|
||||
{
|
||||
throw new DependencyParserException(modId, dep, "You can't 'require' everything (*)");
|
||||
}
|
||||
depRequired = true;
|
||||
}
|
||||
else if ("before".equals(instruction) || "after".equals(instruction))
|
||||
{
|
||||
if (depOrder != null)
|
||||
{
|
||||
throw new DependencyParserException(modId, dep, "'before' or 'after' can only be specified once.");
|
||||
}
|
||||
depOrder = instruction;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DependencyParserException(modId, dep, String.format("Found invalid instruction '%s'. Only %s are allowed.", instruction, DEPENDENCY_INSTRUCTIONS));
|
||||
}
|
||||
}
|
||||
|
||||
ArtifactVersion artifactVersion;
|
||||
try
|
||||
{
|
||||
artifactVersion = VersionParser.parseVersionReference(target);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
throw new DependencyParserException(modId, dep, "Could not parse version string.", e);
|
||||
}
|
||||
|
||||
if (!targetIsAll)
|
||||
{
|
||||
String depModId = artifactVersion.getLabel();
|
||||
sanityCheckModId(modId, dep, depModId);
|
||||
}
|
||||
|
||||
if (!depRequired && depOrder == null)
|
||||
{
|
||||
throw new DependencyParserException(modId, dep, "'required', 'client', or 'server' must be specified.");
|
||||
}
|
||||
|
||||
if (depSide == null || depSide == this.side)
|
||||
{
|
||||
if (depRequired)
|
||||
{
|
||||
info.requirements.add(artifactVersion);
|
||||
}
|
||||
|
||||
if ("before".equals(depOrder))
|
||||
{
|
||||
info.dependants.add(artifactVersion);
|
||||
}
|
||||
else if ("after".equals(depOrder))
|
||||
{
|
||||
info.dependencies.add(artifactVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO 1.13: throw these exceptions instead of logging them
|
||||
/** Based on {@link net.minecraftforge.fml.common.FMLModContainer#sanityCheckModId()} */
|
||||
private static void sanityCheckModId(String modId, String dep, String depModId)
|
||||
{
|
||||
if (Strings.isNullOrEmpty(depModId))
|
||||
{
|
||||
LOGGER.error(new DependencyParserException(modId, dep, "The modId is null or empty").getMessage());
|
||||
}
|
||||
else if (depModId.length() > 64)
|
||||
{
|
||||
LOGGER.error(new DependencyParserException(modId, dep, String.format("The modId '%s' is longer than the maximum of 64 characters.", depModId)).getMessage());
|
||||
}
|
||||
else if (!depModId.equals(depModId.toLowerCase(Locale.ENGLISH)))
|
||||
{
|
||||
LOGGER.error(new DependencyParserException(modId, dep, String.format("The modId '%s' must be all lowercase.", depModId)).getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static class DependencyParserException extends LoaderException
|
||||
{
|
||||
public DependencyParserException(String modId, String dependencyString, String explanation)
|
||||
{
|
||||
super(formatMessage(modId, dependencyString, explanation));
|
||||
}
|
||||
|
||||
public DependencyParserException(String modId, String dependencyString, String explanation, Throwable cause)
|
||||
{
|
||||
super(formatMessage(modId, dependencyString, explanation), cause);
|
||||
}
|
||||
|
||||
public static String formatMessage(String modId, String dependencyString, String explanation)
|
||||
{
|
||||
return String.format("Unable to parse dependency for mod '%s' with dependency string '%s'. %s", modId, dependencyString, explanation);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,11 +21,8 @@ package net.minecraftforge.fml.common.versioning;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
import net.minecraftforge.fml.common.LoaderException;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
|
@ -76,8 +73,7 @@ public class VersionParser
|
|||
}
|
||||
catch (InvalidVersionSpecificationException e)
|
||||
{
|
||||
FMLLog.log.error("Unable to parse a version range specification successfully {}", range, e);
|
||||
throw new LoaderException(e);
|
||||
throw new LoaderException("Unable to parse a version range specification successfully " + range, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,304 @@
|
|||
package net.minecraftforge.fml.test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.minecraftforge.fml.common.versioning.DependencyParser;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
|
||||
import com.google.common.base.Functions;
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import net.minecraftforge.fml.common.LoaderException;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class DependencyParserTest
|
||||
{
|
||||
private static DependencyParser clientDependencyParser;
|
||||
private static DependencyParser serverDependencyParser;
|
||||
private static List<DependencyParser> parsers;
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeAll()
|
||||
{
|
||||
clientDependencyParser = new DependencyParser("test", Side.CLIENT);
|
||||
serverDependencyParser = new DependencyParser("test", Side.SERVER);
|
||||
parsers = ImmutableList.of(clientDependencyParser, serverDependencyParser);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingNothing()
|
||||
{
|
||||
List<String> strings = new ArrayList<>();
|
||||
strings.add(null);
|
||||
strings.add("");
|
||||
strings.add(";;;;;;;");
|
||||
strings.add("; ; ; ; ; ; ; ");
|
||||
for (String string : strings)
|
||||
{
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
DependencyParser.DependencyInfo info = parser.parseDependencies(string);
|
||||
assertTrue(info.requirements.isEmpty());
|
||||
assertTrue(info.dependants.isEmpty());
|
||||
assertTrue(info.dependencies.isEmpty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingRequired()
|
||||
{
|
||||
String mod = "supermod3000@[1.2,)";
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
DependencyParser.DependencyInfo info = parser.parseDependencies("required:" + mod);
|
||||
assertContainsSameToString(info.requirements, Sets.newHashSet(mod));
|
||||
assertTrue(info.dependants.isEmpty());
|
||||
assertTrue(info.dependencies.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingBefore()
|
||||
{
|
||||
String mod = "supermod3000@[1.2,)";
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
DependencyParser.DependencyInfo info = parser.parseDependencies("before:" + mod);
|
||||
assertTrue(info.requirements.isEmpty());
|
||||
assertContainsSameToString(info.dependants, Sets.newHashSet(mod));
|
||||
assertTrue(info.dependencies.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingAfter()
|
||||
{
|
||||
String mod = "supermod3000@[1.2,)";
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
DependencyParser.DependencyInfo info = parser.parseDependencies("after:" + mod);
|
||||
assertTrue(info.requirements.isEmpty());
|
||||
assertTrue(info.dependants.isEmpty());
|
||||
assertContainsSameToString(info.dependencies, Sets.newHashSet(mod));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingNoPrefix()
|
||||
{
|
||||
String mod = "supermod3000@[1.2,)";
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
assertThrows(LoaderException.class, () -> {
|
||||
parser.parseDependencies(mod);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingCombined()
|
||||
{
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
String dependencyString = "after:supermod2000@[1.3,);required-before:yetanothermod;required:modw";
|
||||
DependencyParser.DependencyInfo info = parser.parseDependencies(dependencyString);
|
||||
assertContainsSameToString(info.requirements, Sets.newHashSet("yetanothermod", "modw"));
|
||||
assertContainsSameToString(info.dependencies, Sets.newHashSet("supermod2000@[1.3,)"));
|
||||
assertContainsSameToString(info.dependants, Sets.newHashSet("yetanothermod"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingSided()
|
||||
{
|
||||
String mod = "testmod@[1.0,2.0)";
|
||||
{
|
||||
DependencyParser.DependencyInfo info = clientDependencyParser.parseDependencies("client-after:" + mod);
|
||||
assertTrue(info.requirements.isEmpty());
|
||||
assertTrue(info.dependants.isEmpty());
|
||||
assertContainsSameToString(info.dependencies, Sets.newHashSet(mod));
|
||||
}
|
||||
|
||||
{
|
||||
DependencyParser.DependencyInfo info = clientDependencyParser.parseDependencies("server-after:testmod@[1.0,2.0);server-required:testmod2;server-after:testmod3;server-before:testmod4");
|
||||
assertTrue(info.requirements.isEmpty());
|
||||
assertTrue(info.dependants.isEmpty());
|
||||
assertTrue(info.dependencies.isEmpty());
|
||||
}
|
||||
|
||||
{
|
||||
DependencyParser.DependencyInfo info = clientDependencyParser.parseDependencies("client-before:testmod@[1.0,2.0);server-required:testmod2;server-after:testmod3;server-before:testmod4");
|
||||
assertTrue(info.requirements.isEmpty());
|
||||
assertContainsSameToString(info.dependants, Sets.newHashSet(mod));
|
||||
assertTrue(info.dependencies.isEmpty());
|
||||
}
|
||||
|
||||
{
|
||||
DependencyParser.DependencyInfo info = serverDependencyParser.parseDependencies("server-before:" + mod);
|
||||
assertTrue(info.requirements.isEmpty());
|
||||
assertContainsSameToString(info.dependants, Sets.newHashSet(mod));
|
||||
assertTrue(info.dependencies.isEmpty());
|
||||
}
|
||||
|
||||
{
|
||||
DependencyParser.DependencyInfo info = serverDependencyParser.parseDependencies("client-before:testmod@[1.0,2.0);client-required:testmod2;client-after:testmod3;client-before:testmod4");
|
||||
assertTrue(info.requirements.isEmpty());
|
||||
assertTrue(info.dependants.isEmpty());
|
||||
assertTrue(info.dependencies.isEmpty());
|
||||
}
|
||||
|
||||
{
|
||||
DependencyParser.DependencyInfo info = serverDependencyParser.parseDependencies("server-before:testmod@[1.0,2.0);client-required:testmod2;client-after:testmod3;client-before:testmod4");
|
||||
assertTrue(info.requirements.isEmpty());
|
||||
assertContainsSameToString(info.dependants, Sets.newHashSet(mod));
|
||||
assertTrue(info.dependencies.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingInvalidDependencyInstructions()
|
||||
{
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
assertThrows(LoaderException.class, () -> {
|
||||
parser.parseDependencies("gibberishtext:amod");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingInvalidDependencyVersionClient()
|
||||
{
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
assertThrows(LoaderException.class, () -> {
|
||||
parser.parseDependencies("amod@[10");
|
||||
});
|
||||
assertThrows(LoaderException.class, () -> {
|
||||
parser.parseDependencies("client:amod@[10");
|
||||
});
|
||||
assertThrows(LoaderException.class, () -> {
|
||||
parser.parseDependencies("server:amod@[10");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: enable this in 1.13
|
||||
// @Test(expected = LoaderException.class)
|
||||
// public void testParsingUppercaseModId()
|
||||
// {
|
||||
// for (DependencyParser parser : parsers)
|
||||
// {
|
||||
// assertThrows(LoaderException.class, () -> {
|
||||
// parser.parseDependencies("Forge@[1.0]");
|
||||
// });
|
||||
// assertThrows(LoaderException.class, () -> {
|
||||
// parser.parseDependencies("client:Forge@[1.0]");
|
||||
// });
|
||||
// assertThrows(LoaderException.class, () -> {
|
||||
// parser.parseDependencies("server:Forge@[1.0]");
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void testParsingSoftDepWithNoVersion()
|
||||
{
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
assertThrows(LoaderException.class, () -> {
|
||||
parser.parseDependencies("amod");
|
||||
});
|
||||
assertThrows(LoaderException.class, () -> {
|
||||
parser.parseDependencies("client:amod");
|
||||
});
|
||||
assertThrows(LoaderException.class, () -> {
|
||||
parser.parseDependencies("server:amod");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingDepAfterAll()
|
||||
{
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
parser.parseDependencies("after:*");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingDepBeforeAll()
|
||||
{
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
parser.parseDependencies("before:*");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingDepOnAll()
|
||||
{
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
assertThrows(LoaderException.class, () -> {
|
||||
parser.parseDependencies("*");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingSidedDepOnAll()
|
||||
{
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
assertThrows(LoaderException.class, () -> {
|
||||
parser.parseDependencies("client:*");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingRequireAll()
|
||||
{
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
assertThrows(LoaderException.class, () -> {
|
||||
parser.parseDependencies("required:*");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingVersionedAll()
|
||||
{
|
||||
for (DependencyParser parser : parsers)
|
||||
{
|
||||
assertThrows(LoaderException.class, () -> {
|
||||
parser.parseDependencies("*@[1.0]");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertContainsSameToString(Collection<?> c1, Collection<String> expected)
|
||||
{
|
||||
Collection<String> transformedToString = Collections2.transform(c1, Functions.toStringFunction());
|
||||
assertContainsSame(transformedToString, expected);
|
||||
}
|
||||
|
||||
public static <T> void assertContainsSame(Collection<T> c1, Collection<T> c2)
|
||||
{
|
||||
if (!c1.containsAll(c2) || !c2.containsAll(c1))
|
||||
{
|
||||
fail(c1 + " does not contain the same as " + c2);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue