diff --git a/build.gradle b/build.gradle index e7147e1ba..6b50fc0d6 100644 --- a/build.gradle +++ b/build.gradle @@ -58,12 +58,6 @@ allprojects { subprojects { subProject -> subProject.version = gradleutils.getTagOffsetVersion() - ext { - MANIFESTS = [ - '' : [ - ] as LinkedHashMap - ] - } jar.doFirst { manifest.attributes( diff --git a/gradle.properties b/gradle.properties index bcc4b069f..f2e784977 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,3 @@ -spi_version=9.0.2 mergetool_version=2.0.0 accesstransformers_version=10.0.1 coremods_version=6.0.0 diff --git a/loader/build.gradle b/loader/build.gradle index 2cf4cec1d..ace9f37d8 100644 --- a/loader/build.gradle +++ b/loader/build.gradle @@ -4,7 +4,7 @@ dependencies { api("org.ow2.asm:asm:${asm_version}") api("org.ow2.asm:asm-tree:${asm_version}") api("org.ow2.asm:asm-commons:${asm_version}") - api("net.neoforged:neoforgespi:${spi_version}") + api(project(':spi')) api("net.neoforged:mergetool:${mergetool_version}:api") api("org.apache.logging.log4j:log4j-api:${log4j_version}") api("org.slf4j:slf4j-api:${slf4j_api_version}") diff --git a/settings.gradle b/settings.gradle index 0d4486c4c..478b10720 100644 --- a/settings.gradle +++ b/settings.gradle @@ -31,6 +31,7 @@ include 'loader' include 'core' include 'events' include 'earlydisplay' +include 'spi' includeLanguage 'minecraft' includeLanguage 'java' diff --git a/spi/build.gradle b/spi/build.gradle new file mode 100644 index 000000000..6bd8a0905 --- /dev/null +++ b/spi/build.gradle @@ -0,0 +1,29 @@ +plugins { + id 'org.javamodularity.moduleplugin' + id 'maven-publish' + id 'java-library' +} + +dependencies { + implementation("cpw.mods:modlauncher:$modlauncher_version") + implementation("org.ow2.asm:asm:$asm_version") + implementation("org.ow2.asm:asm-commons:$asm_version") + implementation("org.ow2.asm:asm-tree:$asm_version") + implementation("org.apache.logging.log4j:log4j-api:$log4j_version") + implementation("org.apache.maven:maven-artifact:$apache_maven_artifact_version") + implementation("cpw.mods:securejarhandler:$securejarhandler_version") + api("net.neoforged:mergetool:$mergetool_version:api") { + transitive false + } + + + testRuntimeOnly("org.apache.logging.log4j:log4j-core:$log4j_version") + testImplementation("org.junit.jupiter:junit-jupiter-api:$jupiter_version") + testImplementation("org.powermock:powermock-core:$powermock_version") + testImplementation("org.hamcrest:hamcrest-core:2.2+") + testImplementation("org.junit.jupiter:junit-jupiter-engine:$jupiter_version") +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/spi/src/main/java/net/neoforged/neoforgespi/Environment.java b/spi/src/main/java/net/neoforged/neoforgespi/Environment.java new file mode 100644 index 000000000..81c455467 --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/Environment.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi; + +import cpw.mods.modlauncher.api.IEnvironment; +import cpw.mods.modlauncher.api.ITransformationService; +import cpw.mods.modlauncher.api.TypesafeMap; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.neoforgespi.locating.IModDirectoryLocatorFactory; +import net.neoforged.neoforgespi.locating.IModLocator; +import net.neoforged.neoforgespi.locating.IModFile; +import net.neoforged.neoforgespi.locating.ModFileFactory; + +import java.nio.file.Path; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Global environment variables - allows discoverability with other systems without full forge + * dependency + */ +public class Environment { + public static final class Keys { + + /** + * The @{@link Dist} which is running. + * Populated by forge during {@link ITransformationService#initialize(IEnvironment)} + */ + public static final Supplier> DIST = IEnvironment.buildKey("FORGEDIST", Dist.class); + + /** + * Use {@link #MODDIRECTORYFACTORY} instead. + */ + @Deprecated + public static final Supplier>> MODFOLDERFACTORY = IEnvironment.buildKey("MODFOLDERFACTORY", Function.class); + /** + * Build a custom modlocator based on a supplied directory, with custom name + */ + public static final Supplier> MODDIRECTORYFACTORY = IEnvironment.buildKey("MODDIRFACTORY", IModDirectoryLocatorFactory.class); + + /** + * Factory for building {@link IModFile} instances + */ + public static final Supplier> MODFILEFACTORY = IEnvironment.buildKey("MODFILEFACTORY", ModFileFactory.class); + /** + * Provides a string consumer which can be used to push notification messages to the early startup GUI. + */ + public static final Supplier>> PROGRESSMESSAGE = IEnvironment.buildKey("PROGRESSMESSAGE", Consumer.class); + } + private static Environment INSTANCE; + + public static void build(IEnvironment environment) { + if (INSTANCE != null) throw new RuntimeException("Environment is a singleton"); + INSTANCE = new Environment(environment); + } + + public static Environment get() { + return INSTANCE; + } + + + private final IEnvironment environment; + + private final Dist dist; + private final ModFileFactory modFileFactory; + + private Environment(IEnvironment setup) { + this.environment = setup; + this.dist = setup.getProperty(Keys.DIST.get()).orElseThrow(()->new RuntimeException("Missing DIST in environment!")); + this.modFileFactory = setup.getProperty(Keys.MODFILEFACTORY.get()).orElseThrow(()->new RuntimeException("Missing MODFILEFACTORY in environment!")); + } + + public Dist getDist() { + return this.dist; + } + public ModFileFactory getModFileFactory() { + return this.modFileFactory; + } +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/coremod/ICoreModFile.java b/spi/src/main/java/net/neoforged/neoforgespi/coremod/ICoreModFile.java new file mode 100644 index 000000000..9bd685b68 --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/coremod/ICoreModFile.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.coremod; + +import java.io.*; +import java.nio.file.*; + +/** + * Interface for core mods to discover content and properties + * of their location and context to the coremod implementation. + */ +public interface ICoreModFile { + String getOwnerId(); + Reader readCoreMod() throws IOException; + Path getPath(); + Reader getAdditionalFile(final String fileName) throws IOException; +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/coremod/ICoreModProvider.java b/spi/src/main/java/net/neoforged/neoforgespi/coremod/ICoreModProvider.java new file mode 100644 index 000000000..cab5d27cc --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/coremod/ICoreModProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.coremod; + +import cpw.mods.modlauncher.api.*; + +import java.util.*; + +/** + * Core Mod Provider - core mod logic is implemented + * in a separate library. Connection is provided here + * + */ +public interface ICoreModProvider { + /** + * Add a coremod file to the list of coremods + * @param file the file to add + */ + void addCoreMod(ICoreModFile file); + + /** + * Return the completed list of transformers for all coremods + * @return all coremod transformers + */ + List> getCoreModTransformers(); +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/language/IConfigurable.java b/spi/src/main/java/net/neoforged/neoforgespi/language/IConfigurable.java new file mode 100644 index 000000000..7589eb7f4 --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/language/IConfigurable.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.language; + +import java.util.List; +import java.util.Optional; + + +/** + * This is an interface for querying configuration elements + */ +public interface IConfigurable { + Optional getConfigElement(final String... key); + public List getConfigList(final String... key); +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/language/ILifecycleEvent.java b/spi/src/main/java/net/neoforged/neoforgespi/language/ILifecycleEvent.java new file mode 100644 index 000000000..9aa854ea6 --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/language/ILifecycleEvent.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.language; + +public interface ILifecycleEvent> { + @SuppressWarnings("unchecked") + default R concrete() { + return (R) this; + } +} + diff --git a/spi/src/main/java/net/neoforged/neoforgespi/language/IModFileInfo.java b/spi/src/main/java/net/neoforged/neoforgespi/language/IModFileInfo.java new file mode 100644 index 000000000..99649d83e --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/language/IModFileInfo.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.language; + +import net.neoforged.neoforgespi.locating.IModFile; +import org.apache.maven.artifact.versioning.VersionRange; + +import java.util.List; +import java.util.Map; + +public interface IModFileInfo +{ + List getMods(); + + record LanguageSpec(String languageName, VersionRange acceptedVersions) {} + + List requiredLanguageLoaders(); + + boolean showAsResourcePack(); + + boolean showAsDataPack(); + + Map getFileProperties(); + + String getLicense(); + + String moduleName(); + + String versionString(); + + List usesServices(); + + IModFile getFile(); + + IConfigurable getConfig(); +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/language/IModInfo.java b/spi/src/main/java/net/neoforged/neoforgespi/language/IModInfo.java new file mode 100644 index 000000000..2a36e762f --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/language/IModInfo.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.language; + +import net.neoforged.api.distmarker.Dist; +import net.neoforged.neoforgespi.Environment; +import net.neoforged.neoforgespi.locating.ForgeFeature; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.VersionRange; + +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public interface IModInfo +{ + // " " will just be the preferred version for Maven, but it will accept anything. + // The space is very important, else we get a range that doesn't accept anything. + VersionRange UNBOUNDED = MavenVersionAdapter.createFromVersionSpec(" "); + + IModFileInfo getOwningFile(); + + String getModId(); + + String getDisplayName(); + + String getDescription(); + + ArtifactVersion getVersion(); + + List getDependencies(); + + List getForgeFeatures(); + + String getNamespace(); + + Map getModProperties(); + + Optional getUpdateURL(); + + Optional getModURL(); + + Optional getLogoFile(); + + boolean getLogoBlur(); + + IConfigurable getConfig(); + + enum Ordering { + BEFORE, AFTER, NONE + } + + enum DependencySide { + CLIENT(Dist.CLIENT), SERVER(Dist.DEDICATED_SERVER), BOTH(Dist.values()); + + private final Dist[] dist; + + DependencySide(final Dist... dist) { + this.dist = dist; + } + + public boolean isContained(Dist side) { + return this == BOTH || dist[0] == side; + } + public boolean isCorrectSide() + { + return this == BOTH || Environment.get().getDist().equals(this.dist[0]); + } + } + + enum DependencyType { + REQUIRED, OPTIONAL, + /** + * Prevents the game from loading if the dependency is loaded. + */ + INCOMPATIBLE, + /** + * Shows a warning if the dependency is loaded. + */ + DISCOURAGED + } + + interface ModVersion { + String getModId(); + + VersionRange getVersionRange(); + + DependencyType getType(); + + /** + * {@return the reason of this dependency} + * Only displayed if the type is either {@link DependencyType#DISCOURAGED} or {@link DependencyType#INCOMPATIBLE} + */ + Optional getReason(); + + Ordering getOrdering(); + + DependencySide getSide(); + + void setOwner(IModInfo owner); + + IModInfo getOwner(); + + Optional getReferralURL(); + } +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/language/IModLanguageProvider.java b/spi/src/main/java/net/neoforged/neoforgespi/language/IModLanguageProvider.java new file mode 100644 index 000000000..c5ee9e5e7 --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/language/IModLanguageProvider.java @@ -0,0 +1,42 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2018. + * + * 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.neoforged.neoforgespi.language; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Loaded as a ServiceLoader, from the classpath. ExtensionPoint are loaded from + * the mods directory, with the FMLType META-INF of LANGPROVIDER. + * + * Version data is read from the manifest's implementation version. + */ +public interface IModLanguageProvider +{ + String name(); + + Consumer getFileVisitor(); + + > void consumeLifecycleEvent(Supplier consumeEvent); + + interface IModLanguageLoader { + T loadMod(IModInfo info, ModFileScanData modFileScanResults, ModuleLayer layer); + } +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/language/MavenVersionAdapter.java b/spi/src/main/java/net/neoforged/neoforgespi/language/MavenVersionAdapter.java new file mode 100644 index 000000000..54d2582bc --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/language/MavenVersionAdapter.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.language; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; + +public final class MavenVersionAdapter { + private static final Logger LOGGER = LogManager.getLogger(); + private MavenVersionAdapter() {} + + public static VersionRange createFromVersionSpec(final String spec) { + try { + return VersionRange.createFromVersionSpec(spec); + } catch (InvalidVersionSpecificationException e) { + LOGGER.fatal("Failed to parse version spec {}", spec, e); + throw new RuntimeException("Failed to parse spec", e); + } + } + +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/language/ModFileScanData.java b/spi/src/main/java/net/neoforged/neoforgespi/language/ModFileScanData.java new file mode 100644 index 000000000..2e8cd0a68 --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/language/ModFileScanData.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.language; + + +import java.lang.annotation.ElementType; +import java.util.*; +import java.util.function.Predicate; + +import org.objectweb.asm.Type; + +public class ModFileScanData +{ + private final Set annotations = new LinkedHashSet<>(); + private final Set classes = new LinkedHashSet<>(); + private final Map modTargets = new HashMap<>(); + private final List modFiles = new ArrayList<>(); + + public static Predicate interestingAnnotations() { + return t->true; + } + + public Set getClasses() { + return classes; + } + + public Set getAnnotations() { + return annotations; + } + + public void addLanguageLoader(final Map modTargetMap) + { + modTargets.putAll(modTargetMap); + } + + public void addModFileInfo(IModFileInfo info) { + this.modFiles.add(info); + } + + public Map getTargets() + { + return modTargets; + } + + public List getIModInfoData() { + return this.modFiles; + } + + public record ClassData(Type clazz, Type parent, Set interfaces) {} + + public record AnnotationData(Type annotationType, ElementType targetType, Type clazz, String memberName, Map annotationData) {} +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/locating/ForgeFeature.java b/spi/src/main/java/net/neoforged/neoforgespi/locating/ForgeFeature.java new file mode 100644 index 000000000..fe2e05f83 --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/locating/ForgeFeature.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.locating; + +import net.neoforged.api.distmarker.Dist; +import net.neoforged.neoforgespi.language.IModInfo; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; + +/** + * ForgeFeature is a simple test for mods for the presence of specific features + * such as OpenGL of a specific version or better or whatever. + * + * {@snippet : + * ForgeFeature.registerFeature("openGLVersion", VersionFeatureTest.forVersionString(IModInfo.DependencySide.CLIENT, "3.2")); + *} + * + * This will be tested during early mod loading against lists of features in the mods.toml file for mods. Those + * that are absent or out of range will be rejected. + */ +public class ForgeFeature { + private ForgeFeature() {} + private static final Map> features = new HashMap<>(); + + public static void registerFeature(final String featureName, final IFeatureTest featureTest) { + if (features.putIfAbsent(featureName, featureTest) != null) { + throw new IllegalArgumentException("ForgeFeature with name "+featureName +" exists"); + } + } + + private static final MissingFeatureTest MISSING = new MissingFeatureTest(); + + public static boolean testFeature(final Dist side, final Bound bound) { + return features.getOrDefault(bound.featureName(), MISSING).testSideWithString(side, bound.featureBound()); + } + + public static Object featureValue(final Bound bound) { + return features.getOrDefault(bound.featureName(), MISSING).featureValue(); + } + public sealed interface IFeatureTest extends Predicate { + IModInfo.DependencySide applicableSides(); + F convertFromString(final String value); + + String featureValue(); + + default boolean testSideWithString(final Dist side, final String value) { + return !applicableSides().isContained(side) || test(convertFromString(value)); + } + } + + /** + * A Bound, from a mods.toml file + * + * @param featureName the name of the feature + * @param featureBound the requested bound + */ + public record Bound(String featureName, String featureBound, IModInfo modInfo) { + @SuppressWarnings("unchecked") + public T bound() { + return (T) features.getOrDefault(featureName, MISSING).convertFromString(featureBound); + } + } + /** + * Version based feature test. Uses standard MavenVersion system. Will test the constructed version against + * ranges requested by mods. + * @param version The version we wish to test against + */ + public record VersionFeatureTest(IModInfo.DependencySide applicableSides, ArtifactVersion version) implements IFeatureTest { + /** + * Convenience method for constructing the feature test for a version string + * @param version the string + * @return the feature test for the supplied string + */ + public static VersionFeatureTest forVersionString(final IModInfo.DependencySide side, final String version) { + return new VersionFeatureTest(side, new DefaultArtifactVersion(version)); + } + + @Override + public String featureValue() { + return version.toString(); + } + + @Override + public boolean test(final VersionRange versionRange) { + return versionRange.containsVersion(version); + } + + @Override + public VersionRange convertFromString(final String value) { + try { + return VersionRange.createFromVersionSpec(value); + } catch (InvalidVersionSpecificationException e) { + throw new IllegalArgumentException(e); + } + } + } + + public record BooleanFeatureTest(IModInfo.DependencySide applicableSides, boolean value) implements IFeatureTest { + @Override + public boolean test(final Boolean aBoolean) { + return aBoolean.equals(value); + } + + @Override + public String featureValue() { + return Boolean.toString(value); + } + + @Override + public Boolean convertFromString(final String value) { + return Boolean.parseBoolean(value); + } + } + + private record MissingFeatureTest() implements IFeatureTest { + @Override + public IModInfo.DependencySide applicableSides() { + return IModInfo.DependencySide.BOTH; + } + + @Override + public String featureValue() { + return "NONE"; + } + + @Override + public boolean test(final Object o) { + return false; + } + + @Override + public Object convertFromString(final String value) { + return null; + } + } +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/locating/IDependencyLocator.java b/spi/src/main/java/net/neoforged/neoforgespi/locating/IDependencyLocator.java new file mode 100644 index 000000000..8cc7dcbdf --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/locating/IDependencyLocator.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.locating; + +import java.util.List; + +/** + * Loaded as a ServiceLoader. Takes mechanisms for locating candidate "mod-dependencies". + * and transforms them into {@link IModFile} objects. + */ +public interface IDependencyLocator extends IModProvider +{ + /** + * Invoked to find all mod dependencies that this dependency locator can find. + * It is not guaranteed that all these are loaded into the runtime, + * as such the result of this method should be seen as a list of candidates to load. + * + * @return All found, or discovered, mod files which function as dependencies. + */ + List scanMods(final Iterable loadedMods); +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/locating/IModDirectoryLocatorFactory.java b/spi/src/main/java/net/neoforged/neoforgespi/locating/IModDirectoryLocatorFactory.java new file mode 100644 index 000000000..e6c3db439 --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/locating/IModDirectoryLocatorFactory.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.locating; + +import net.neoforged.neoforgespi.Environment; + +import java.nio.file.Path; + +/** + * Functional interface for generating a custom {@link IModLocator} from a directory, with a specific name. + * + * FML provides this factory at {@link Environment.Keys#MODDIRECTORYFACTORY} during + * locator construction. + */ +public interface IModDirectoryLocatorFactory { + IModLocator build(Path directory, String name); +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/locating/IModFile.java b/spi/src/main/java/net/neoforged/neoforgespi/locating/IModFile.java new file mode 100644 index 000000000..0b37b4af3 --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/locating/IModFile.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.locating; + +import cpw.mods.jarhandling.SecureJar; +import net.neoforged.neoforgespi.language.*; +import net.neoforged.neoforgespi.language.IModFileInfo; +import net.neoforged.neoforgespi.language.IModInfo; +import net.neoforged.neoforgespi.language.IModLanguageProvider; +import net.neoforged.neoforgespi.language.ModFileScanData; + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +/** + * Represents a single "mod" file in the runtime. + * + * Although these are known as "Mod"-Files, they do not always represent mods. + * However, they should be more treated as an extension or modification of minecraft. + * And as such can contain any number of things, like language loaders, dependencies of other mods + * or code which does not interact with minecraft at all and is just a utility for any of the other mod + * files. + */ +public interface IModFile { + /** + * The language loaders which are included in this mod file. + * + * If this method returns any entries then {@link #getType()} has to return {@link Type#LIBRARY}, + * else this mod file will not be loaded in the proper module layer in 1.17 and above. + * + * As such, returning entries from this method is mutually exclusive with returning entries from {@link #getModInfos()}. + * + * @return The mod language providers provided by this mod file. (Also known as the loaders). + */ + List getLoaders(); + + /** + * Invoked to find a particular resource in this mod file, with the given path. + * + * @param pathName The string representation of the path to find the mod resource on. + * @return The {@link Path} that represents the requested resource. + */ + Path findResource(String... pathName); + + /** + * The mod files specific string data substitution map. + * The map returned here is used to interpolate values in the metadata of the included mods. + * Examples of where this is used in FML: While parsing the mods.toml file, keys like: + * ${file.xxxxx} are replaced with the values of this map, with keys xxxxx. + * + * @return The string substitution map used during metadata load. + */ + Supplier> getSubstitutionMap(); + + /** + * The type of the mod jar. + * This primarily defines how and where this mod file is loaded into the module system. + * + * @return The type of the mod file. + */ + Type getType(); + + /** + * The path to the underlying mod file. + * @return The path to the mod file. + */ + Path getFilePath(); + + /** + * The secure jar that represents this mod file. + * + * @return The secure jar. + */ + SecureJar getSecureJar(); + + /** + * Sets the security status after verification of the mod file has been concluded. + * The security status is only determined if the jar is to be loaded into the runtime. + * + * @param status The new status. + */ + void setSecurityStatus(SecureJar.Status status); + + /** + * Returns a list of all mods located inside this jar. + * + * If this method returns any entries then {@link #getType()} has to return {@link Type#MOD}, + * else this mod file will not be loaded in the proper module layer in 1.17 and above. + * + * As such returning entries from this method is mutually exclusive with {@link #getLoaders()}. + * + * @return The mods in this mod file. + */ + List getModInfos(); + + /** + * The metadata scan result of all the classes located inside this file. + * + * @return The metadata scan result. + */ + ModFileScanData getScanResult(); + + /** + * The raw file name of this file. + * @return The raw file name. + */ + String getFileName(); + + /** + * The provider who provided the runtime with this jar. + * Implicitly indicates what caused the load of the PR. (Mod in mods directory, mod in dev environment, etc) + * + * @return The provider of this file. + */ + IModProvider getProvider(); + + /** + * The metadata info related to this particular file. + * @return The info for this file. + */ + IModFileInfo getModFileInfo(); + + /** + * The type of file. + * Determines into which module layer the data is loaded and how metadata is loaded and processed. + */ + enum Type { + /** + * A mod file holds mod code and loads in the game module layer + */ + MOD, + /** + * A library can reference lang providers in the plugin module layer + */ + LIBRARY, + /** + * A language provider provides a custom way to load mods in the plugin module layer + */ + LANGPROVIDER, + /** + * A game library can reference MC code and loads in the game module layer + */ + GAMELIBRARY + } +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/locating/IModLocator.java b/spi/src/main/java/net/neoforged/neoforgespi/locating/IModLocator.java new file mode 100644 index 000000000..f7bb416ac --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/locating/IModLocator.java @@ -0,0 +1,44 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * 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.neoforged.neoforgespi.locating; + +import java.util.List; + +/** + * Loaded as a ServiceLoader. Takes mechanisms for locating candidate "mods" + * and transforms them into {@link IModFile} objects. + */ +public interface IModLocator extends IModProvider +{ + /** + * A simple record which contains either a valid modfile or a reason one failed to be constructed by {@link #scanMods()} + * @param file the file + * @param ex an exception that occurred during the attempt to load the mod + */ + record ModFileOrException(IModFile file, ModFileLoadingException ex) {} + /** + * Invoked to find all mods that this mod locator can find. + * It is not guaranteed that all these are loaded into the runtime, + * as such the result of this method should be seen as a list of candidates to load. + * + * @return All found, or discovered, mod files. + */ + List scanMods(); +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/locating/IModProvider.java b/spi/src/main/java/net/neoforged/neoforgespi/locating/IModProvider.java new file mode 100644 index 000000000..170b38353 --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/locating/IModProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.locating; + +import java.nio.file.Path; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Describes objects which can provide mods (or related jars) to the loading runtime. + */ +public interface IModProvider +{ + /** + * The name of the provider. + * Has to be unique between all providers loaded into the runtime. + * + * @return The name. + */ + String name(); + + /** + * Invoked to scan a particular {@link IModFile} for metadata. + * + * @param modFile The mod file to scan. + * @param pathConsumer A consumer which extracts metadata from the path given. + */ + void scanFile(IModFile modFile, Consumer pathConsumer); + + /** + * Invoked with the game startup arguments to allow for configuration of the provider. + * + * @param arguments The arguments. + */ + void initArguments(Map arguments); + + /** + * Indicates if the given mod file is valid. + * + * @param modFile The mod file in question. + * @return True to mark as valid, false otherwise. + */ + boolean isValid(IModFile modFile); +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/locating/ModFileFactory.java b/spi/src/main/java/net/neoforged/neoforgespi/locating/ModFileFactory.java new file mode 100644 index 000000000..396a848be --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/locating/ModFileFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.locating; + +import cpw.mods.jarhandling.SecureJar; +import net.neoforged.neoforgespi.Environment; +import net.neoforged.neoforgespi.language.IModFileInfo; + +/** + * A factory to build new mod file instances. + */ +public interface ModFileFactory { + /** + * The current instance. Equals to {@link Environment#getModFileFactory()}, of the current environment instance. + */ + ModFileFactory FACTORY = Environment.get().getModFileFactory(); + + /** + * Builds a new mod file instance depending on the current runtime. + * @param jar The secure jar to load the mod file from. + * @param provider The provider which is offering the mod file for loading- + * @param parser The parser which is responsible for parsing the metadata of the file itself. + * @return The mod file. + */ + IModFile build(final SecureJar jar, final IModProvider provider, ModFileInfoParser parser); + + /** + * A parser specification for building a particular mod files metadata. + */ + interface ModFileInfoParser { + /** + * Invoked to get the freshly build mod files metadata. + * + * @param file The file to parse the metadata for. + * @return The mod file metadata info. + */ + IModFileInfo build(IModFile file); + } +} diff --git a/spi/src/main/java/net/neoforged/neoforgespi/locating/ModFileLoadingException.java b/spi/src/main/java/net/neoforged/neoforgespi/locating/ModFileLoadingException.java new file mode 100644 index 000000000..03b95c4a9 --- /dev/null +++ b/spi/src/main/java/net/neoforged/neoforgespi/locating/ModFileLoadingException.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.locating; + +/** + * An exception that can be thrown/caught by {@link IModLocator} code, indicating a bad mod file or something similar. + */ +public class ModFileLoadingException extends RuntimeException { + public ModFileLoadingException(String message) { + super(message); + } +} diff --git a/spi/src/test/java/net/neoforged/neoforgespi/language/ModInfoTest.java b/spi/src/test/java/net/neoforged/neoforgespi/language/ModInfoTest.java new file mode 100644 index 000000000..7e8cd13d4 --- /dev/null +++ b/spi/src/test/java/net/neoforged/neoforgespi/language/ModInfoTest.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforgespi.language; + +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ModInfoTest { + /** + * Regression test to ensure that unbounded ranges are actually unbounded. + * See issue. + */ + @Test + public void testUnboundedRange() { + assertTrue(IModInfo.UNBOUNDED.containsVersion(new DefaultArtifactVersion("0.0.1"))); + assertTrue(IModInfo.UNBOUNDED.containsVersion(new DefaultArtifactVersion("1.0.0"))); + assertTrue(IModInfo.UNBOUNDED.containsVersion(new DefaultArtifactVersion("10000.0.0"))); + } +}