[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
|
@ -66,8 +66,10 @@ version = getVersionFromJava(file("src/main/java/net/minecraftforge/common/Forge
|
||||||
extractForgeSources { exclude "**/SideOnly.java", "**/Side.java" }
|
extractForgeSources { exclude "**/SideOnly.java", "**/Side.java" }
|
||||||
extractForgeResources { exclude "**/log4j2.xml" }
|
extractForgeResources { exclude "**/log4j2.xml" }
|
||||||
|
|
||||||
genGradleProjects {
|
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"
|
addTestCompileDep "org.hamcrest:hamcrest-core:1.3"
|
||||||
filter { dep -> !dep.contains("scala") }
|
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.network.NetworkRegistry;
|
||||||
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
|
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
|
||||||
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
|
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.VersionParser;
|
||||||
import net.minecraftforge.fml.common.versioning.VersionRange;
|
import net.minecraftforge.fml.common.versioning.VersionRange;
|
||||||
import net.minecraftforge.fml.relauncher.Side;
|
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.base.Strings;
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableList.Builder;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.ListMultimap;
|
import com.google.common.collect.ListMultimap;
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.SetMultimap;
|
import com.google.common.collect.SetMultimap;
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import com.google.common.eventbus.EventBus;
|
import com.google.common.eventbus.EventBus;
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
|
@ -140,19 +138,18 @@ public class FMLModContainer implements ModContainer
|
||||||
String modid = (String)this.descriptor.get("modid");
|
String modid = (String)this.descriptor.get("modid");
|
||||||
if (Strings.isNullOrEmpty(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)
|
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 maximum of 64 characters.", 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));
|
|
||||||
}
|
}
|
||||||
if (!modid.equals(modid.toLowerCase(Locale.ENGLISH)))
|
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 must be all lowercase.", 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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ILanguageAdapter getLanguageAdapter()
|
private ILanguageAdapter getLanguageAdapter()
|
||||||
{
|
{
|
||||||
if (languageAdapter == null)
|
if (languageAdapter == null)
|
||||||
|
@ -212,17 +209,15 @@ public class FMLModContainer implements ModContainer
|
||||||
|
|
||||||
if (overridesMetadata || !modMetadata.useDependencyInformation)
|
if (overridesMetadata || !modMetadata.useDependencyInformation)
|
||||||
{
|
{
|
||||||
Set<ArtifactVersion> requirements = Sets.newHashSet();
|
|
||||||
List<ArtifactVersion> dependencies = Lists.newArrayList();
|
|
||||||
List<ArtifactVersion> dependants = Lists.newArrayList();
|
|
||||||
annotationDependencies = (String)descriptor.get("dependencies");
|
annotationDependencies = (String)descriptor.get("dependencies");
|
||||||
Loader.instance().computeDependencies(annotationDependencies, requirements, dependencies, dependants);
|
DependencyParser dependencyParser = new DependencyParser(getModId(), FMLCommonHandler.instance().getSide());
|
||||||
dependants.addAll(Loader.instance().getInjectedBefore(getModId()));
|
DependencyParser.DependencyInfo info = dependencyParser.parseDependencies(annotationDependencies);
|
||||||
dependencies.addAll(Loader.instance().getInjectedAfter(getModId()));
|
info.dependants.addAll(Loader.instance().getInjectedBefore(getModId()));
|
||||||
modMetadata.requiredMods = requirements;
|
info.dependencies.addAll(Loader.instance().getInjectedAfter(getModId()));
|
||||||
modMetadata.dependencies = dependencies;
|
modMetadata.requiredMods = info.requirements;
|
||||||
modMetadata.dependants = dependants;
|
modMetadata.dependencies = info.dependencies;
|
||||||
modLog.trace("Parsed dependency info : {} {} {}", requirements, dependencies, dependants);
|
modMetadata.dependants = info.dependants;
|
||||||
|
modLog.trace("Parsed dependency info : Requirements: {} After:{} Before:{}", info.requirements, info.dependencies, info.dependants);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,7 +26,6 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -35,6 +34,7 @@ import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import net.minecraft.util.ResourceLocation;
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
import net.minecraftforge.common.ForgeVersion;
|
||||||
import net.minecraftforge.common.capabilities.CapabilityManager;
|
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||||
import net.minecraftforge.common.config.ConfigManager;
|
import net.minecraftforge.common.config.ConfigManager;
|
||||||
import net.minecraftforge.common.crafting.CraftingHelper;
|
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.TopologicalSort;
|
||||||
import net.minecraftforge.fml.common.toposort.ModSortingException.SortingExceptionData;
|
import net.minecraftforge.fml.common.toposort.ModSortingException.SortingExceptionData;
|
||||||
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
|
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.common.versioning.VersionParser;
|
||||||
import net.minecraftforge.fml.relauncher.ModListHelper;
|
import net.minecraftforge.fml.relauncher.ModListHelper;
|
||||||
import net.minecraftforge.fml.relauncher.Side;
|
import net.minecraftforge.fml.relauncher.Side;
|
||||||
|
@ -62,7 +63,6 @@ import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.logging.log4j.Level;
|
import org.apache.logging.log4j.Level;
|
||||||
|
|
||||||
import com.google.common.base.CharMatcher;
|
import com.google.common.base.CharMatcher;
|
||||||
import java.util.function.Function;
|
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
|
@ -124,9 +124,7 @@ import javax.annotation.Nullable;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class Loader
|
public class Loader
|
||||||
{
|
{
|
||||||
public static final String MC_VERSION = net.minecraftforge.common.ForgeVersion.mcVersion;
|
public static final String MC_VERSION = ForgeVersion.mcVersion;
|
||||||
private static final Splitter DEPENDENCYPARTSPLITTER = Splitter.on(":").omitEmptyStrings().trimResults();
|
|
||||||
private static final Splitter DEPENDENCYSPLITTER = Splitter.on(";").omitEmptyStrings().trimResults();
|
|
||||||
/**
|
/**
|
||||||
* The singleton instance
|
* The singleton instance
|
||||||
*/
|
*/
|
||||||
|
@ -695,77 +693,17 @@ public class Loader
|
||||||
return modClassLoader;
|
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)
|
public void computeDependencies(String dependencyString, Set<ArtifactVersion> requirements, List<ArtifactVersion> dependencies, List<ArtifactVersion> dependants)
|
||||||
{
|
{
|
||||||
if (dependencyString == null || dependencyString.length() == 0)
|
DependencyParser dependencyParser = new DependencyParser("unknown", FMLCommonHandler.instance().getSide());
|
||||||
{
|
DependencyParser.DependencyInfo info = dependencyParser.parseDependencies(dependencyString);
|
||||||
return;
|
requirements.addAll(info.requirements);
|
||||||
}
|
dependencies.addAll(info.dependencies);
|
||||||
|
dependants.addAll(info.dependants);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String,ModContainer> getIndexedModList()
|
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 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:
|
* A dependency string must start with a combination of these prefixes, separated by "-":
|
||||||
* before, after, required-before, required-after
|
* [before, after], [required], [client, server]
|
||||||
|
* At least one "before", "after", or "required" must be specified.
|
||||||
* Then ":" and the mod id.
|
* Then ":" and the mod id.
|
||||||
*
|
* Then a version range should be specified for the mod by adding "@" and the version range.
|
||||||
* 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:
|
||||||
* The version range format is described in the javadoc here: {@link VersionRange#createFromVersionSpec(java.lang.String)}
|
* {@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,
|
* 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.
|
* the game will not start and an error screen will tell the player which versions are required.
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* Our example mod is a dedicated addon to mod1 and has optional integration with mod2.
|
* Our example mod:
|
||||||
* It uses new features that were introduced in forge version 14.21.1.2395 and mod2 version 4.7.0
|
* * 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:
|
* 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 "";
|
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 java.util.List;
|
||||||
|
|
||||||
import net.minecraftforge.fml.common.FMLLog;
|
|
||||||
import net.minecraftforge.fml.common.LoaderException;
|
import net.minecraftforge.fml.common.LoaderException;
|
||||||
|
|
||||||
import org.apache.logging.log4j.Level;
|
|
||||||
|
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
@ -76,8 +73,7 @@ public class VersionParser
|
||||||
}
|
}
|
||||||
catch (InvalidVersionSpecificationException e)
|
catch (InvalidVersionSpecificationException e)
|
||||||
{
|
{
|
||||||
FMLLog.log.error("Unable to parse a version range specification successfully {}", range, e);
|
throw new LoaderException("Unable to parse a version range specification successfully " + range, e);
|
||||||
throw new LoaderException(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