Skip to content

Commit

Permalink
Implement validation in lang loaders (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
Matyrobbrt authored May 4, 2024
1 parent 9cf72d0 commit 88c198b
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 16 deletions.
15 changes: 13 additions & 2 deletions loader/src/main/java/net/neoforged/fml/ModLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
Expand All @@ -37,6 +39,7 @@
import net.neoforged.fml.loading.moddiscovery.ModInfo;
import net.neoforged.fml.loading.progress.StartupNotificationManager;
import net.neoforged.neoforgespi.language.IModInfo;
import net.neoforged.neoforgespi.language.IModLanguageLoader;
import net.neoforged.neoforgespi.language.ModFileScanData;
import net.neoforged.neoforgespi.locating.ForgeFeature;
import net.neoforged.neoforgespi.locating.IModFile;
Expand Down Expand Up @@ -275,12 +278,20 @@ private static void waitForFuture(String name, Runnable periodicTask, Completabl
}

private static List<ModContainer> buildMods(final IModFile modFile) {
return modFile.getModFileInfo()
final Map<IModLanguageLoader, Set<ModContainer>> byLoader = new IdentityHashMap<>();
var containers = modFile.getModFileInfo()
.getMods()
.stream()
.map(info -> buildModContainerFromTOML(info, modFile.getScanResult()))
.map(info -> {
var container = buildModContainerFromTOML(info, modFile.getScanResult());
var cont = byLoader.computeIfAbsent(info.getLoader(), k -> new HashSet<>());
if (container != null) cont.add(container);
return container;
})
.filter(Objects::nonNull)
.toList();
byLoader.forEach((loader, loaded) -> loader.validate(modFile, loaded, ModLoader::addLoadingIssue));
return containers;
}

private static ModContainer buildModContainerFromTOML(final IModInfo modInfo, final ModFileScanData scanData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@
package net.neoforged.fml.javafmlmod;

import java.lang.annotation.ElementType;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.ModLoadingException;
import net.neoforged.fml.ModLoadingIssue;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.loading.FMLLoader;
import net.neoforged.neoforgespi.IIssueReporting;
import net.neoforged.neoforgespi.language.IModInfo;
import net.neoforged.neoforgespi.language.IModLanguageLoader;
import net.neoforged.neoforgespi.language.ModFileScanData;
import net.neoforged.neoforgespi.locating.IModFile;

public class FMLJavaModLanguageProvider implements IModLanguageLoader {
@Override
public String name() {
return "javafml";
}
Expand All @@ -24,13 +29,28 @@ public String name() {
public ModContainer loadMod(IModInfo info, ModFileScanData modFileScanResults, ModuleLayer layer) {
final var modClasses = modFileScanResults.getAnnotatedBy(Mod.class, ElementType.TYPE)
.filter(data -> data.annotationData().get("value").equals(info.getModId()))
.filter(ad -> AutomaticEventSubscriber.getSides(ad.annotationData().get("dist")).contains(FMLLoader.getDist()))
.map(ad -> ad.clazz().getClassName())
.toList();
if (modClasses.isEmpty()) {
throw new ModLoadingException(ModLoadingIssue.error("fml.modloading.javafml.missing_entrypoint").withAffectedMod(info));
return new FMLModContainer(info, modClasses, modFileScanResults, layer);
}

@Override
public void validate(IModFile file, Collection<ModContainer> loadedContainers, IIssueReporting reporter) {
final Set<String> modIds = new HashSet<>();
for (IModInfo modInfo : file.getModInfos()) {
if (modInfo.getLoader() == this) {
modIds.add(modInfo.getModId());
}
}
return new FMLModContainer(info, modClasses
.stream().filter(ad -> AutomaticEventSubscriber.getSides(ad.annotationData().get("dist")).contains(FMLLoader.getDist()))
.map(ad -> ad.clazz().getClassName())
.toList(), modFileScanResults, layer);

file.getScanResult().getAnnotatedBy(Mod.class, ElementType.TYPE)
.filter(data -> !modIds.contains((String) data.annotationData().get("value")))
.forEach(data -> {
var modId = data.annotationData().get("value");
var entrypointClass = data.clazz().getClassName();
var issue = ModLoadingIssue.error("fml.modloading.javafml.dangling_entrypoint", modId, entrypointClass, file.getFilePath()).withAffectedModFile(file);
reporter.addIssue(issue);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforgespi;

import net.neoforged.fml.ModLoadingIssue;

/**
* Interface used to report issues to the game.
*/
public interface IIssueReporting {
/**
* Report an issue to the loader.
*/
void addIssue(ModLoadingIssue issue);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

package net.neoforged.neoforgespi.language;

import java.util.Collection;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.ModLoadingException;
import net.neoforged.neoforgespi.IIssueReporting;
import net.neoforged.neoforgespi.locating.IModFile;

/**
Expand All @@ -43,4 +45,13 @@ public interface IModLanguageLoader {
* @throws ModLoadingException if loading encountered an exception
*/
ModContainer loadMod(IModInfo info, ModFileScanData modFileScanResults, ModuleLayer layer) throws ModLoadingException;

/**
* Validate mod files using this loader, and report any issues (such as entrpoints without medatata).
*
* @param file the file to validate
* @param loadedContainers the containers of mods in the file, that have been created using this loader. This list does not contain errored containers
* @param reporter the interface used to report issues to the game
*/
default void validate(IModFile file, Collection<ModContainer> loadedContainers, IIssueReporting reporter) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import net.neoforged.fml.ModLoadingIssue;
import net.neoforged.neoforgespi.IIssueReporting;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;

Expand All @@ -18,7 +18,7 @@
* discovery pipeline.
*/
@ApiStatus.NonExtendable
public interface IDiscoveryPipeline {
public interface IDiscoveryPipeline extends IIssueReporting {
/**
* Adds a single file or folder to the discovery pipeline,
* to be further processed by registered {@linkplain IModFileReader readers} into a {@linkplain IModFile mod file}.
Expand Down Expand Up @@ -62,11 +62,6 @@ default Optional<IModFile> addPath(Path path, ModFileDiscoveryAttributes attribu
*/
boolean addModFile(IModFile modFile);

/**
* Add an issue to the pipeline that arose during the discovery of mod files (i.e. broken files).
*/
void addIssue(ModLoadingIssue issue);

/**
* Use the registered {@linkplain IModFileReader readers} to attempt to create a mod-file from the given jar
* contents.
Expand Down

0 comments on commit 88c198b

Please sign in to comment.