From 08d77ce67abb5e8e32e9091e68336623fa9b357e Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Mon, 20 Feb 2023 20:09:38 -0500 Subject: [PATCH] Redefine Cyclic Dependencies (#8873) --- ...rty-to-print-stacktrace-on-bad-plugi.patch | 23 - ...or-plugins-modifying-the-parent-of-t.patch | 2 +- patches/api/Paper-Plugins.patch | 39 +- ...s-to-contain-the-source-jars-in-stac.patch | 2 +- patches/server/Paper-Plugins.patch | 713 ++++++++++++------ 5 files changed, 514 insertions(+), 265 deletions(-) delete mode 100644 patches/api/Add-system-property-to-print-stacktrace-on-bad-plugi.patch diff --git a/patches/api/Add-system-property-to-print-stacktrace-on-bad-plugi.patch b/patches/api/Add-system-property-to-print-stacktrace-on-bad-plugi.patch deleted file mode 100644 index faf7c8f1e7..0000000000 --- a/patches/api/Add-system-property-to-print-stacktrace-on-bad-plugi.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Sat, 1 Oct 2022 09:44:26 +0200 -Subject: [PATCH] Add system property to print stacktrace on bad plugin class - access - - -diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -+++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm - // In case the bad access occurs on construction - org.bukkit.Bukkit.getLogger().log(Level.WARNING, "[{0}] Loaded class {1} from {2} which is not a depend or softdepend of this plugin.", new Object[]{description.getName(), name, provider.getFullName()}); // Paper - } -+ // Paper start -+ if (Boolean.getBoolean("Paper.printStacktraceOnBadPluginClassAccess")) { -+ (plugin != null ? plugin.getLogger() : org.bukkit.Bukkit.getLogger()).log(Level.WARNING, "Stacktrace", new Exception()); -+ } -+ // Paper end - } - } - diff --git a/patches/api/Add-workaround-for-plugins-modifying-the-parent-of-t.patch b/patches/api/Add-workaround-for-plugins-modifying-the-parent-of-t.patch index feb529d95d..63e160a94b 100644 --- a/patches/api/Add-workaround-for-plugins-modifying-the-parent-of-t.patch +++ b/patches/api/Add-workaround-for-plugins-modifying-the-parent-of-t.patch @@ -104,7 +104,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.logger = com.destroystokyo.paper.utils.PaperPluginLogger.getLogger(description); // Paper - Register logger early // Paper start this.classLoaderGroup = io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage.instance().registerSpigotGroup(this); // Paper - this.dependencyContext = dependencyContext; + // Paper end @@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm pluginState = new IllegalStateException("Initial initialization"); this.pluginInit = javaPlugin; diff --git a/patches/api/Paper-Plugins.patch b/patches/api/Paper-Plugins.patch index f87857e7f4..168210027b 100644 --- a/patches/api/Paper-Plugins.patch +++ b/patches/api/Paper-Plugins.patch @@ -1966,7 +1966,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 final PluginClassLoader loader; try { - loader = new PluginClassLoader(this, getClass().getClassLoader(), description, dataFolder, file, (libraryLoader != null) ? libraryLoader.createLoader(description) : null); -+ loader = new PluginClassLoader(getClass().getClassLoader(), description, dataFolder, file, (libraryLoader != null) ? libraryLoader.createLoader(description) : null, null); // Paper ++ loader = new PluginClassLoader(getClass().getClassLoader(), description, dataFolder, file, (libraryLoader != null) ? libraryLoader.createLoader(description) : null); // Paper } catch (InvalidPluginException ex) { throw ex; } catch (Throwable ex) { @@ -2011,7 +2011,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private final Set seenIllegalAccess = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private java.util.logging.Logger logger; // Paper - add field + private io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup classLoaderGroup; // Paper -+ public io.papermc.paper.plugin.provider.entrypoint.DependencyContext dependencyContext; // Paper static { ClassLoader.registerAsParallelCapable(); @@ -2019,7 +2018,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - PluginClassLoader(@NotNull final JavaPluginLoader loader, @Nullable final ClassLoader parent, @NotNull final PluginDescriptionFile description, @NotNull final File dataFolder, @NotNull final File file, @Nullable ClassLoader libraryLoader) throws IOException, InvalidPluginException, MalformedURLException { + @org.jetbrains.annotations.ApiStatus.Internal // Paper -+ public PluginClassLoader(@Nullable final ClassLoader parent, @NotNull final PluginDescriptionFile description, @NotNull final File dataFolder, @NotNull final File file, @Nullable ClassLoader libraryLoader, io.papermc.paper.plugin.provider.entrypoint.DependencyContext dependencyContext) throws IOException, InvalidPluginException, MalformedURLException { // Paper ++ public PluginClassLoader(@Nullable final ClassLoader parent, @NotNull final PluginDescriptionFile description, @NotNull final File dataFolder, @NotNull final File file, @Nullable ClassLoader libraryLoader) throws IOException, InvalidPluginException, MalformedURLException { // Paper super(new URL[] {file.toURI().toURL()}, parent); - Preconditions.checkArgument(loader != null, "Loader cannot be null"); + this.loader = null; // Paper - pass null into loader field @@ -2035,7 +2034,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + // Paper start + this.classLoaderGroup = io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage.instance().registerSpigotGroup(this); // Paper -+ this.dependencyContext = dependencyContext; + // Paper end try { Class jarClass; @@ -2072,23 +2070,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 if (result != null) { // If the class was loaded from a library instead of a PluginClassLoader, we can assume that its associated plugin is a transitive dependency and can therefore skip this check. -@@ -0,0 +0,0 @@ final class PluginClassLoader extends URLClassLoader { - - if (provider != description - && !seenIllegalAccess.contains(provider.getName()) +- if (result.getClassLoader() instanceof PluginClassLoader) { +- PluginDescriptionFile provider = ((PluginClassLoader) result.getClassLoader()).description; +- +- if (provider != description +- && !seenIllegalAccess.contains(provider.getName()) - && !((SimplePluginManager) loader.server.getPluginManager()).isTransitiveDepend(description, provider)) { -+ && !this.dependencyContext.isTransitiveDependency(description, provider)) { // Paper - - seenIllegalAccess.add(provider.getName()); - if (plugin != null) { - plugin.getLogger().log(Level.WARNING, "Loaded class {0} from {1} which is not a depend or softdepend of this plugin.", new Object[]{name, provider.getFullName()}); - } else { - // In case the bad access occurs on construction +- +- seenIllegalAccess.add(provider.getName()); +- if (plugin != null) { +- plugin.getLogger().log(Level.WARNING, "Loaded class {0} from {1} which is not a depend or softdepend of this plugin.", new Object[]{name, provider.getFullName()}); +- } else { +- // In case the bad access occurs on construction - loader.server.getLogger().log(Level.WARNING, "[{0}] Loaded class {1} from {2} which is not a depend or softdepend of this plugin.", new Object[]{description.getName(), name, provider.getFullName()}); -+ org.bukkit.Bukkit.getLogger().log(Level.WARNING, "[{0}] Loaded class {1} from {2} which is not a depend or softdepend of this plugin.", new Object[]{description.getName(), name, provider.getFullName()}); // Paper - } - } - } +- } +- } +- } ++ // Paper - Totally delete the illegal access logic, we are never going to enforce it anyways here. + + return result; + } @@ -0,0 +0,0 @@ final class PluginClassLoader extends URLClassLoader { throw new ClassNotFoundException(name, ex); } diff --git a/patches/api/Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch b/patches/api/Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch index f790f54550..044b6229b6 100644 --- a/patches/api/Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch +++ b/patches/api/Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch @@ -11,7 +11,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm @org.jetbrains.annotations.ApiStatus.Internal // Paper - public PluginClassLoader(@Nullable final ClassLoader parent, @NotNull final PluginDescriptionFile description, @NotNull final File dataFolder, @NotNull final File file, @Nullable ClassLoader libraryLoader, io.papermc.paper.plugin.provider.entrypoint.DependencyContext dependencyContext) throws IOException, InvalidPluginException, MalformedURLException { // Paper + public PluginClassLoader(@Nullable final ClassLoader parent, @NotNull final PluginDescriptionFile description, @NotNull final File dataFolder, @NotNull final File file, @Nullable ClassLoader libraryLoader) throws IOException, InvalidPluginException, MalformedURLException { // Paper - super(new URL[] {file.toURI().toURL()}, parent); + super(file.getName(), new URL[] {file.toURI().toURL()}, parent); this.loader = null; // Paper - pass null into loader field diff --git a/patches/server/Paper-Plugins.patch b/patches/server/Paper-Plugins.patch index 7af730b17e..6adedf4899 100644 --- a/patches/server/Paper-Plugins.patch +++ b/patches/server/Paper-Plugins.patch @@ -386,20 +386,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return false; + } + -+ @Override -+ public List requiredDependencies(PluginProvider provider) { -+ return provider.getMeta().getPluginDependencies(); -+ } -+ -+ @Override -+ public List optionalDependencies(PluginProvider provider) { -+ return provider.getMeta().getPluginSoftDependencies(); -+ } -+ -+ @Override -+ public List loadBeforeDependencies(PluginProvider provider) { -+ return provider.getMeta().getLoadBeforePlugins(); -+ } + }); + modernPluginLoadingStrategy.loadProviders(pluginProviders); + @@ -1595,10 +1581,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + +import com.google.common.graph.MutableGraph; +import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.configuration.LoadOrderConfiguration; ++import org.bukkit.plugin.PluginDescriptionFile; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; ++import java.util.Map; + +@SuppressWarnings("UnstableApiUsage") +public class DependencyUtil { @@ -1609,16 +1599,27 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + dependencies.addAll(configuration.getPluginDependencies()); + dependencies.addAll(configuration.getPluginSoftDependencies()); + -+ return buildDependencyGraph(dependencyGraph, configuration.getName(), dependencies, configuration.getLoadBeforePlugins()); ++ return buildDependencyGraph(dependencyGraph, configuration.getName(), dependencies); + } + + @NotNull -+ public static MutableGraph buildDependencyGraph(@NotNull MutableGraph dependencyGraph, String identifier, @NotNull Iterable depends, @NotNull Iterable loadBefore) { ++ public static MutableGraph buildDependencyGraph(@NotNull MutableGraph dependencyGraph, String identifier, @NotNull Iterable depends) { + for (String dependency : depends) { + dependencyGraph.putEdge(identifier, dependency); + } + -+ for (String loadBeforeTarget : loadBefore) { ++ dependencyGraph.addNode(identifier); // Make sure dependencies at least have a node ++ return dependencyGraph; ++ } ++ ++ @NotNull ++ public static MutableGraph buildLoadGraph(@NotNull MutableGraph dependencyGraph, @NotNull LoadOrderConfiguration configuration) { ++ String identifier = configuration.getMeta().getName(); ++ for (String dependency : configuration.getLoadAfter()) { ++ dependencyGraph.putEdge(identifier, dependency); ++ } ++ ++ for (String loadBeforeTarget : configuration.getLoadBefore()) { + dependencyGraph.putEdge(loadBeforeTarget, identifier); + } + @@ -1635,6 +1636,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return dependencyGraph; + } + ++ public static List validateSimple(PluginMeta meta, Map> toLoad) { ++ List missingDependencies = new ArrayList<>(); ++ for (String hardDependency : meta.getPluginDependencies()) { ++ if (!toLoad.containsKey(hardDependency)) { ++ missingDependencies.add(hardDependency); ++ } ++ } ++ ++ return missingDependencies; ++ } +} diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/GraphDependencyContext.java b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/GraphDependencyContext.java new file mode 100644 @@ -2054,6 +2065,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import io.papermc.paper.plugin.configuration.PluginMeta; +import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext; +import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; +import org.bukkit.plugin.UnknownDependencyException; + +import java.util.ArrayList; @@ -2137,7 +2149,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + -+ Collection softDependencySet = this.configuration.optionalDependencies(provider); ++ Collection softDependencySet = provider.getMeta().getPluginSoftDependencies(); + if (softDependencySet != null && !softDependencySet.isEmpty()) { + if (softDependencies.containsKey(configuration.getName())) { + // Duplicates do not matter, they will be removed together if applicable @@ -2151,7 +2163,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + -+ Collection dependencySet = this.configuration.requiredDependencies(provider); ++ Collection dependencySet = provider.getMeta().getPluginDependencies(); + if (dependencySet != null && !dependencySet.isEmpty()) { + dependencies.put(configuration.getName(), new LinkedList(dependencySet)); + @@ -2160,7 +2172,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + -+ Collection loadBeforeSet = this.configuration.loadBeforeDependencies(provider); ++ Collection loadBeforeSet = provider.getMeta().getPluginSoftDependencies(); + if (loadBeforeSet != null && !loadBeforeSet.isEmpty()) { + for (String loadBeforeTarget : loadBeforeSet) { + if (softDependencies.containsKey(loadBeforeTarget)) { @@ -2246,6 +2258,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + try { + this.configuration.applyContext(file, dependencyContext); + T loadedPlugin = file.createInstance(); ++ this.warnIfPaperPlugin(file); + + if (this.configuration.load(file, loadedPlugin)) { + loadedPlugins.add(file.getMeta().getName()); @@ -2277,6 +2290,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + try { + this.configuration.applyContext(file, dependencyContext); + T loadedPlugin = file.createInstance(); ++ this.warnIfPaperPlugin(file); + + if (this.configuration.load(file, loadedPlugin)) { + loadedPlugins.add(file.getMeta().getName()); @@ -2306,6 +2320,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + return javapluginsLoaded; + } ++ ++ private void warnIfPaperPlugin(PluginProvider provider) { ++ if (provider instanceof PaperPluginParent.PaperServerPluginProvider) { ++ provider.getLogger().warning("Loading Paper plugin in the legacy plugin loading logic. This is not recommended and may introduce some differences into load order. It's highly recommended you move away from this if you are wanting to use Paper plugins."); ++ } ++ } +} diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ModernPluginLoadingStrategy.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ModernPluginLoadingStrategy.java new file mode 100644 @@ -2316,6 +2336,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +package io.papermc.paper.plugin.entrypoint.strategy; + +import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; +import com.google.common.graph.GraphBuilder; +import com.google.common.graph.MutableGraph; +import com.mojang.logging.LogUtils; @@ -2323,6 +2344,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import io.papermc.paper.plugin.entrypoint.dependency.DependencyUtil; +import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext; +import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.configuration.LoadOrderConfiguration; +import org.bukkit.plugin.UnknownDependencyException; +import org.slf4j.Logger; + @@ -2343,8 +2365,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + @Override + public List> loadProviders(List> pluginProviders) { -+ MutableGraph dependencyGraph = GraphBuilder.directed().build(); + Map> providerMap = new HashMap<>(); ++ Map> providerMapMirror = Maps.transformValues(providerMap, (entry) -> entry.provider); + List> validatedProviders = new ArrayList<>(); + + // Populate provider map @@ -2381,12 +2403,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + PluginMeta configuration = provider.getMeta(); + + // Populate missing dependencies to capture if there are multiple missing ones. -+ List missingDependencies = new ArrayList<>(); -+ for (String hardDependency : this.configuration.requiredDependencies(provider)) { -+ if (!providerMap.containsKey(hardDependency)) { -+ missingDependencies.add(hardDependency); -+ } -+ } ++ List missingDependencies = provider.validateDependencies(providerMapMirror); + + if (missingDependencies.isEmpty()) { + validatedProviders.add(provider); @@ -2397,24 +2414,32 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + ++ MutableGraph loadOrderGraph = GraphBuilder.directed().build(); ++ MutableGraph dependencyGraph = GraphBuilder.directed().build(); + for (PluginProvider validated : validatedProviders) { + PluginMeta configuration = validated.getMeta(); ++ LoadOrderConfiguration loadOrderConfiguration = validated.createConfiguration(providerMapMirror); ++ ++ // Build a validated provider's load order changes ++ DependencyUtil.buildLoadGraph(loadOrderGraph, loadOrderConfiguration); + + // Build a validated provider's dependencies into the graph + DependencyUtil.buildDependencyGraph(dependencyGraph, configuration); + + // Add the provided plugins to the graph as well + for (String provides : configuration.getProvidedPlugins()) { -+ DependencyUtil.addProvidedPlugin(dependencyGraph, configuration.getName(), provides); ++ String name = configuration.getName(); ++ DependencyUtil.addProvidedPlugin(loadOrderGraph, name, provides); ++ DependencyUtil.addProvidedPlugin(dependencyGraph, name, provides); + } + } + + // Reverse the topographic search to let us see which providers we can load first. + List reversedTopographicSort; + try { -+ reversedTopographicSort = Lists.reverse(TopographicGraphSorter.sortGraph(dependencyGraph)); ++ reversedTopographicSort = Lists.reverse(TopographicGraphSorter.sortGraph(loadOrderGraph)); + } catch (TopographicGraphSorter.GraphCycleException exception) { -+ throw new PluginGraphCycleException(new JohnsonSimpleCycles<>(dependencyGraph).findSimpleCycles()); ++ throw new PluginGraphCycleException(new JohnsonSimpleCycles<>(loadOrderGraph).findSimpleCycles()); + } + + GraphDependencyContext graphDependencyContext = new GraphDependencyContext(dependencyGraph); @@ -2505,13 +2530,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + boolean load(PluginProvider provider, T provided); + -+ List requiredDependencies(PluginProvider provider); -+ -+ List optionalDependencies(PluginProvider provider); -+ -+ List loadBeforeDependencies(PluginProvider provider); -+ -+ +} diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderLoadingStrategy.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderLoadingStrategy.java new file mode 100644 @@ -2812,7 +2830,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override -+ public boolean exitOnCycleDependencies() { ++ public boolean throwOnCycle() { + return false; + } + @@ -3965,7 +3983,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override -+ public boolean exitOnCycleDependencies() { ++ public boolean throwOnCycle() { + return false; + } + @@ -4030,10 +4048,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +package io.papermc.paper.plugin.provider; + +import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.provider.configuration.LoadOrderConfiguration; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.nio.file.Path; ++import java.util.List; ++import java.util.Map; +import java.util.jar.JarFile; +import java.util.logging.Logger; + @@ -4073,6 +4094,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + Logger getLogger(); + ++ LoadOrderConfiguration createConfiguration(@NotNull Map> toLoad); ++ ++ // Returns a list of missing dependencies ++ List validateDependencies(@NotNull Map> toLoad); ++ +} diff --git a/src/main/java/io/papermc/paper/plugin/provider/ProviderStatus.java b/src/main/java/io/papermc/paper/plugin/provider/ProviderStatus.java new file mode 100644 @@ -4145,6 +4171,50 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + +} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/LoadOrderConfiguration.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/LoadOrderConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/LoadOrderConfiguration.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.configuration; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.List; ++ ++/** ++ * This is used for plugins to configure the load order of strategies. ++ */ ++public interface LoadOrderConfiguration { ++ ++ /** ++ * Provides a list of plugins that THIS configuration should load ++ * before. ++ * ++ * @return list of plugins ++ */ ++ @NotNull ++ List getLoadBefore(); ++ ++ /** ++ * Provides a list of plugins that THIS configuration should load ++ * before. ++ * ++ * @return list of plugins ++ */ ++ @NotNull ++ List getLoadAfter(); ++ ++ /** ++ * Gets the responsible plugin provider's meta. ++ * ++ * @return meta ++ */ ++ @NotNull ++ PluginMeta getMeta(); ++} diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -4164,6 +4234,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import io.papermc.paper.plugin.provider.configuration.serializer.PermissionConfigurationSerializer; +import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints; +import io.papermc.paper.plugin.provider.configuration.type.DependencyConfiguration; ++import io.papermc.paper.plugin.provider.configuration.type.LoadConfiguration; +import io.papermc.paper.plugin.provider.configuration.type.PermissionConfiguration; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; @@ -4197,7 +4268,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + @PluginConfigConstraints.PluginNameSpace + private String loader; + private List dependencies = List.of(); -+ private List loadBefore = List.of(); ++ private List loadBefore = List.of(); ++ private List loadAfter = List.of(); + private List provides = List.of(); + private boolean hasOpenClassloader = false; + @Required @@ -4300,6 +4372,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + @Override + public @NotNull List getLoadBeforePlugins() { ++ return this.loadBefore.stream().filter((dependency) -> !dependency.bootstrap()).map(LoadConfiguration::name).toList(); ++ } ++ ++ public @NotNull List getLoadAfterPlugins() { ++ return this.loadAfter.stream().filter((dependency) -> !dependency.bootstrap()).map(LoadConfiguration::name).toList(); ++ } ++ ++ public List getLoadAfter() { ++ return this.loadAfter; ++ } ++ ++ public List getLoadBefore() { + return this.loadBefore; + } + @@ -4681,6 +4765,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + boolean bootstrap +) { +} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/type/LoadConfiguration.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/type/LoadConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/type/LoadConfiguration.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.configuration.type; ++ ++import org.spongepowered.configurate.objectmapping.ConfigSerializable; ++import org.spongepowered.configurate.objectmapping.meta.Required; ++ ++@ConfigSerializable ++public record LoadConfiguration( ++ @Required String name, ++ boolean bootstrap ++) { ++} diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/type/PermissionConfiguration.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/type/PermissionConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -5074,6 +5175,109 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + C create(JarFile file, JarEntry config) throws Exception; +} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperBootstrapOrderConfiguration.java b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperBootstrapOrderConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperBootstrapOrderConfiguration.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.type.paper; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.provider.configuration.LoadOrderConfiguration; ++import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; ++import io.papermc.paper.plugin.provider.configuration.type.LoadConfiguration; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++public class PaperBootstrapOrderConfiguration implements LoadOrderConfiguration { ++ ++ private final PaperPluginMeta paperPluginMeta; ++ private final List loadBefore = new ArrayList<>(); ++ private final List loadAfter = new ArrayList<>(); ++ ++ public PaperBootstrapOrderConfiguration(PaperPluginMeta paperPluginMeta) { ++ this.paperPluginMeta = paperPluginMeta; ++ ++ for (LoadConfiguration configuration : paperPluginMeta.getLoadAfter()) { ++ if (configuration.bootstrap()) { ++ this.loadAfter.add(configuration.name()); ++ } ++ } ++ for (LoadConfiguration configuration : paperPluginMeta.getLoadBefore()) { ++ if (configuration.bootstrap()) { ++ this.loadBefore.add(configuration.name()); ++ } ++ } ++ } ++ ++ @Override ++ public @NotNull List getLoadBefore() { ++ return this.loadBefore; ++ } ++ ++ @Override ++ public @NotNull List getLoadAfter() { ++ return this.loadAfter; ++ } ++ ++ @Override ++ public @NotNull PluginMeta getMeta() { ++ return this.paperPluginMeta; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperLoadOrderConfiguration.java b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperLoadOrderConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperLoadOrderConfiguration.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.type.paper; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.configuration.LoadOrderConfiguration; ++import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; ++import io.papermc.paper.plugin.provider.type.spigot.SpigotPluginProvider; ++import org.bukkit.plugin.PluginDescriptionFile; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++ ++public class PaperLoadOrderConfiguration implements LoadOrderConfiguration { ++ ++ private final PaperPluginMeta meta; ++ private final List loadBefore; ++ private final List loadAfter; ++ ++ public PaperLoadOrderConfiguration(PaperPluginMeta meta) { ++ this.meta = meta; ++ ++ this.loadBefore = this.meta.getLoadBeforePlugins(); ++ this.loadAfter = this.meta.getLoadAfterPlugins(); ++ } ++ ++ @Override ++ public @NotNull List getLoadBefore() { ++ return this.loadBefore; ++ } ++ ++ @Override ++ public @NotNull List getLoadAfter() { ++ return this.loadAfter; ++ } ++ ++ @Override ++ public @NotNull PluginMeta getMeta() { ++ return this.meta; ++ } ++} diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginParent.java b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginParent.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -5084,10 +5288,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + +import com.destroystokyo.paper.util.SneakyThrow; +import io.papermc.paper.plugin.bootstrap.PluginProviderContext; ++import io.papermc.paper.plugin.entrypoint.dependency.DependencyUtil; ++import io.papermc.paper.plugin.provider.configuration.LoadOrderConfiguration; ++import io.papermc.paper.plugin.provider.configuration.type.DependencyConfiguration; +import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; +import io.papermc.paper.plugin.entrypoint.dependency.DependencyContextHolder; +import io.papermc.paper.plugin.bootstrap.PluginBootstrap; -+import io.papermc.paper.plugin.bootstrap.PluginProviderContextImpl; +import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader; +import io.papermc.paper.plugin.provider.PluginProvider; +import io.papermc.paper.plugin.provider.ProviderStatus; @@ -5095,11 +5301,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; +import io.papermc.paper.plugin.provider.type.PluginTypeFactory; +import io.papermc.paper.plugin.provider.util.ProviderUtil; -+import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; + +import java.nio.file.Path; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Map; +import java.util.jar.JarFile; +import java.util.logging.Logger; + @@ -5169,6 +5377,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override ++ public LoadOrderConfiguration createConfiguration(@NotNull Map> toLoad) { ++ return new PaperBootstrapOrderConfiguration(PaperPluginParent.this.description); ++ } ++ ++ @Override ++ public List validateDependencies(@NotNull Map> toLoad) { ++ List missingDependencies = new ArrayList<>(); ++ for (DependencyConfiguration configuration : this.getMeta().getDependencies()) { ++ String dependency = configuration.name(); ++ if (configuration.required() && configuration.bootstrap() && !toLoad.containsKey(dependency)) { ++ missingDependencies.add(dependency); ++ } ++ } ++ ++ return missingDependencies; ++ } ++ ++ @Override + public ProviderStatus getLastProvidedStatus() { + return this.status; + } @@ -5258,6 +5484,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override ++ public LoadOrderConfiguration createConfiguration(@NotNull Map> toLoad) { ++ return new PaperLoadOrderConfiguration(PaperPluginParent.this.description); ++ } ++ ++ @Override ++ public List validateDependencies(@NotNull Map> toLoad) { ++ return DependencyUtil.validateSimple(this.getMeta(), toLoad); ++ } ++ ++ @Override + public ProviderStatus getLastProvidedStatus() { + return this.status; + } @@ -5366,6 +5602,84 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return configuration; + } +} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotLoadOrderConfiguration.java b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotLoadOrderConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotLoadOrderConfiguration.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.type.spigot; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.configuration.LoadOrderConfiguration; ++import org.bukkit.plugin.PluginDescriptionFile; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.ArrayList; ++import java.util.HashSet; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++ ++public class SpigotLoadOrderConfiguration implements LoadOrderConfiguration { ++ ++ private final PluginDescriptionFile meta; ++ private final List loadBefore; ++ private final List loadAfter; ++ ++ public SpigotLoadOrderConfiguration(SpigotPluginProvider spigotPluginProvider, Map> toLoad) { ++ this.meta = spigotPluginProvider.getMeta(); ++ ++ this.loadBefore = meta.getLoadBeforePlugins(); ++ this.loadAfter = new ArrayList<>(); ++ this.loadAfter.addAll(meta.getDepend()); ++ this.loadAfter.addAll(meta.getSoftDepend()); ++ ++ // First: Remove as load after IF already in loadbefore ++ // Some plugins would put a plugin both in depends and in loadbefore, ++ // so in this case, we just ignore the effects of depend. ++ for (String loadBefore : this.loadBefore) { ++ this.loadAfter.remove(loadBefore); ++ } ++ ++ // Second: Do a basic check to see if any other dependencies refer back to this plugin. ++ Iterator iterators = this.loadAfter.iterator(); ++ while (iterators.hasNext()) { ++ String loadAfter = iterators.next(); ++ PluginProvider provider = toLoad.get(loadAfter); ++ if (provider != null) { ++ PluginMeta configuration = provider.getMeta(); ++ // Does a configuration refer back to this plugin? ++ Set dependencies = new HashSet<>(); ++ dependencies.addAll(configuration.getPluginDependencies()); ++ dependencies.addAll(configuration.getPluginSoftDependencies()); ++ ++ if (configuration.getName().equals(this.meta.getName()) || dependencies.contains(this.meta.getName())) { ++ iterators.remove(); // Let the other config deal with it ++ } ++ } ++ } ++ ++ } ++ ++ @Override ++ public @NotNull List getLoadBefore() { ++ return this.loadBefore; ++ } ++ ++ @Override ++ public @NotNull List getLoadAfter() { ++ return this.loadAfter; ++ } ++ ++ @Override ++ public @NotNull PluginMeta getMeta() { ++ return this.meta; ++ } ++} diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProvider.java b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -5376,7 +5690,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + +import com.destroystokyo.paper.util.SneakyThrow; +import com.destroystokyo.paper.utils.PaperPluginLogger; ++import io.papermc.paper.plugin.entrypoint.dependency.DependencyUtil; +import io.papermc.paper.plugin.manager.PaperPluginManagerImpl; ++import io.papermc.paper.plugin.provider.configuration.LoadOrderConfiguration; +import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; +import io.papermc.paper.plugin.entrypoint.dependency.DependencyContextHolder; +import io.papermc.paper.plugin.provider.PluginProvider; @@ -5396,6 +5712,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import java.io.File; +import java.nio.file.Path; +import java.util.HashSet; ++import java.util.List; ++import java.util.Map; +import java.util.Set; +import java.util.jar.JarFile; +import java.util.logging.Level; @@ -5487,7 +5805,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + final PluginClassLoader loader; + try { -+ loader = new PluginClassLoader(this.getClass().getClassLoader(), this.description, dataFolder, this.path.toFile(), LIBRARY_LOADER.createLoader(this.description), this.dependencyContext); // Paper ++ loader = new PluginClassLoader(this.getClass().getClassLoader(), this.description, dataFolder, this.path.toFile(), LIBRARY_LOADER.createLoader(this.description)); // Paper + } catch (InvalidPluginException ex) { + throw ex; + } catch (Throwable ex) { @@ -5496,7 +5814,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + // Override dependency context. + // We must provide a temporary context in order to properly handle dependencies on the plugin classloader constructor. -+ loader.dependencyContext = PaperPluginManagerImpl.getInstance(); ++ // EDIT - Only re add if dependency checking is needed for spigot plugins, but not anymore. ++ // loader.dependencyContext = PaperPluginManagerImpl.getInstance(); ++ + + this.status = ProviderStatus.INITIALIZED; + return loader.plugin; @@ -5519,6 +5839,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override ++ public LoadOrderConfiguration createConfiguration(@NotNull Map> toLoad) { ++ return new SpigotLoadOrderConfiguration(this, toLoad); ++ } ++ ++ @Override ++ public List validateDependencies(@NotNull Map> toLoad) { ++ return DependencyUtil.validateSimple(this.getMeta(), toLoad); ++ } ++ ++ @Override + public ProviderStatus getLastProvidedStatus() { + return this.status; + } @@ -5650,66 +5980,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return false; + } + } -+ -+ @Override -+ public List requiredDependencies(PluginProvider provider) { -+ List dependencies = new ArrayList<>(); -+ if (provider.getMeta() instanceof PaperPluginMeta paperPluginMeta) { -+ for (DependencyConfiguration configuration : paperPluginMeta.getDependencies()) { -+ if (configuration.required() && configuration.bootstrap()) { -+ dependencies.add(configuration.name()); -+ } -+ } -+ -+ return dependencies; -+ } -+ -+ throw new IllegalStateException(); -+ } -+ -+ @Override -+ public List optionalDependencies(PluginProvider provider) { -+ List dependencies = new ArrayList<>(); -+ if (provider.getMeta() instanceof PaperPluginMeta paperPluginMeta) { -+ for (DependencyConfiguration configuration : paperPluginMeta.getDependencies()) { -+ if (!configuration.required() && configuration.bootstrap()) { -+ dependencies.add(configuration.name()); -+ } -+ } -+ -+ return dependencies; -+ } -+ -+ throw new IllegalStateException(); -+ } -+ -+ @Override -+ public List loadBeforeDependencies(PluginProvider provider) { -+ return provider.getMeta().getLoadBeforePlugins(); -+ } + })); + } + + @Override -+ protected void handleCycle(PluginGraphCycleException exception) { -+ List logMessages = new ArrayList<>(); -+ for (List list : exception.getCycles()) { -+ // CoolPlugin depends on Dependency depends on CoolPlugin... -+ logMessages.add(String.join(" depends on ", list) + " depends on " + list.get(0) + "..."); -+ } -+ -+ LOGGER.error("Circular dependencies detected!"); -+ LOGGER.error("You have a plugin that is depending on a plugin which refers back to that plugin. Your server will shut down until these are resolved, or the strategy is changed."); -+ LOGGER.error("Circular dependencies:"); -+ for (String message : logMessages) { -+ LOGGER.error(message); -+ } -+ LOGGER.error("If you would like to still load these plugins, acknowledging that there may be unexpected plugin loading issues, run the server with -Dpaper.useLegacyPluginLoading=true"); -+ -+ System.exit(-1); -+ } -+ -+ @Override + public String toString() { + return "BOOTSTRAP:" + super.toString(); + } @@ -5724,18 +5998,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + +import io.papermc.paper.plugin.entrypoint.strategy.LegacyPluginLoadingStrategy; +import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy; -+import io.papermc.paper.plugin.entrypoint.strategy.PluginGraphCycleException; +import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; + -+import java.util.ArrayList; -+import java.util.List; -+import java.util.logging.Level; -+import java.util.logging.Logger; -+import java.util.stream.Collectors; -+ +public abstract class ConfiguredProviderStorage extends SimpleProviderStorage { + -+ private static final Logger LOGGER = Logger.getLogger("ConfiguredOrderedProviderStorage"); + public static final boolean LEGACY_PLUGIN_LOADING = Boolean.getBoolean("paper.useLegacyPluginLoading"); + + protected ConfiguredProviderStorage(ProviderConfiguration onLoad) { @@ -5744,32 +6010,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + super(LEGACY_PLUGIN_LOADING ? new LegacyPluginLoadingStrategy<>(onLoad) : new ModernPluginLoadingStrategy<>(onLoad)); + } + -+ @Override -+ protected void handleCycle(PluginGraphCycleException exception) { -+ List logMessages = new ArrayList<>(); -+ for (List list : exception.getCycles()) { -+ logMessages.add(String.join(" -> ", list) + " -> " + list.get(0)); -+ } -+ -+ LOGGER.log(Level.SEVERE, "Circular dependencies detected! This happens when"); -+ LOGGER.log(Level.SEVERE, " i) plugin A has a plugin B in its (soft)depend list, and plugin B has plugin A in its (soft)depend list, or"); -+ LOGGER.log(Level.SEVERE, " ii) plugin A has plugin B both in its (soft)depend list and its loadbefore list."); -+ LOGGER.log(Level.SEVERE, "Circular dependencies:"); -+ for (String logMessage : logMessages) { -+ LOGGER.log(Level.SEVERE, " " + logMessage); -+ } -+ LOGGER.log(Level.SEVERE, "Please report this to the plugin authors of the first plugin of each loop or join the PaperMC Discord server for further help."); -+ LOGGER.log(Level.SEVERE, "If you would like to still load these plugins, acknowledging that there may be unexpected plugin loading issues, run the server with -Dpaper.useLegacyPluginLoading=true"); -+ -+ if (this.exitOnCycleDependencies()) { -+ throw new IllegalStateException("Circular plugin dependencies from plugins " + exception.getCycles().stream().map(cycle -> cycle.get(0)).collect(Collectors.joining(", "))); -+ } -+ } -+ -+ public boolean exitOnCycleDependencies() { -+ return true; -+ } -+ +} diff --git a/src/main/java/io/papermc/paper/plugin/storage/ProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/ProviderStorage.java new file mode 100644 @@ -5842,21 +6082,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + PaperPluginManagerImpl.getInstance().loadPlugin(provided); + return true; + } -+ -+ @Override -+ public List requiredDependencies(PluginProvider provider) { -+ return provider.getMeta().getPluginDependencies(); -+ } -+ -+ @Override -+ public List optionalDependencies(PluginProvider provider) { -+ return provider.getMeta().getPluginSoftDependencies(); -+ } -+ -+ @Override -+ public List loadBeforeDependencies(PluginProvider provider) { -+ return provider.getMeta().getLoadBeforePlugins(); -+ } + }); + } + @@ -5894,15 +6119,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +package io.papermc.paper.plugin.storage; + ++import com.mojang.logging.LogUtils; +import io.papermc.paper.plugin.entrypoint.strategy.PluginGraphCycleException; +import io.papermc.paper.plugin.entrypoint.strategy.ProviderLoadingStrategy; +import io.papermc.paper.plugin.provider.PluginProvider; ++import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.List; ++import java.util.stream.Collectors; + +public abstract class SimpleProviderStorage implements ProviderStorage { + ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ + protected final List> providers = new ArrayList<>(); + protected ProviderLoadingStrategy strategy; + @@ -5934,12 +6164,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.providers; + } + -+ public void processProvided(PluginProvider provider, T provided) {} ++ public void processProvided(PluginProvider provider, T provided) { ++ } + + // Mutable enter -+ protected void filterLoadingProviders(List> providers) {} ++ protected void filterLoadingProviders(List> providers) { ++ } + -+ protected abstract void handleCycle(PluginGraphCycleException exception); ++ protected void handleCycle(PluginGraphCycleException exception) { ++ List logMessages = new ArrayList<>(); ++ for (List list : exception.getCycles()) { ++ logMessages.add(String.join(" -> ", list) + " -> " + list.get(0)); ++ } ++ ++ LOGGER.error("Circular plugin loading detected!"); ++ LOGGER.error("Circular load order:"); ++ for (String logMessage : logMessages) { ++ LOGGER.error(" " + logMessage); ++ } ++ LOGGER.error("Please report this to the plugin authors of the first plugin of each loop or join the PaperMC Discord server for further help."); ++ LOGGER.error("If you would like to still load these plugins, acknowledging that there may be unexpected plugin loading issues, run the server with -Dpaper.useLegacyPluginLoading=true"); ++ ++ if (this.throwOnCycle()) { ++ throw new IllegalStateException("Circular plugin loading from plugins " + exception.getCycles().stream().map(cycle -> cycle.get(0)).collect(Collectors.joining(", "))); ++ } ++ } ++ ++ public boolean throwOnCycle() { ++ return true; ++ } + + @Override + public String toString() { @@ -6396,11 +6649,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + throw new UnsupportedOperationException("Not supported."); + } +} -diff --git a/src/test/java/io/papermc/paper/plugin/PluginDependencyLoadingTest.java b/src/test/java/io/papermc/paper/plugin/PluginDependencyLoadingTest.java +diff --git a/src/test/java/io/papermc/paper/plugin/PluginLoadOrderTest.java b/src/test/java/io/papermc/paper/plugin/PluginLoadOrderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null -+++ b/src/test/java/io/papermc/paper/plugin/PluginDependencyLoadingTest.java ++++ b/src/test/java/io/papermc/paper/plugin/PluginLoadOrderTest.java @@ -0,0 +0,0 @@ +package io.papermc.paper.plugin; + @@ -6418,19 +6671,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + -+public class PluginDependencyLoadingTest { ++public class PluginLoadOrderTest { + + private static List> REGISTERED_PROVIDERS = new ArrayList<>(); + private static Map LOAD_ORDER = new HashMap<>(); ++ private static final String[] EMPTY = {}; + + static { + setup(); + } + -+ private static TestJavaPluginProvider setup(String identifier, String[] hard, String[] soft, String[] before) { ++ private static TestJavaPluginProvider setup(String identifier, String[] loadAfter, String[] loadAfterSoft, String[] before) { + TestPluginMeta configuration = new TestPluginMeta(identifier); -+ configuration.setHardDependencies(List.of(hard)); -+ configuration.setSoftDependencies(List.of(soft)); ++ configuration.setHardDependencies(List.of(loadAfter)); ++ configuration.setSoftDependencies(List.of(loadAfterSoft)); + configuration.setLoadBefore(List.of(before)); + + TestJavaPluginProvider provider = new TestJavaPluginProvider(configuration); @@ -6442,52 +6696,52 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * Obfuscated plugin names, this uses a real dependency tree... + */ + private static void setup() { -+ setup("RedAir", new String[]{}, new String[]{"NightShovel", "EmeraldFire"}, new String[]{"GreenShovel", "IronSpork", "BrightBlueShovel", "WireDoor"}); -+ setup("BigGrass", new String[]{}, new String[]{"IronEarth", "RedAir"}, new String[]{"BlueFire"}); -+ setup("BlueFire", new String[]{}, new String[]{}, new String[]{}); -+ setup("BigPaper", new String[]{}, new String[]{"BlueFire"}, new String[]{}); -+ setup("EmeraldSpork", new String[]{}, new String[]{}, new String[]{"GoldPaper", "YellowSnow"}); -+ setup("GreenShovel", new String[]{}, new String[]{}, new String[]{}); -+ setup("BrightBlueGrass", new String[]{"BigPaper"}, new String[]{"DarkSpork"}, new String[]{}); -+ setup("GoldPaper", new String[]{}, new String[]{"BlueFire"}, new String[]{}); -+ setup("GreenGlass", new String[]{}, new String[]{}, new String[]{}); -+ setup("GoldNeptune", new String[]{}, new String[]{"GreenShovel", "GoldNeptuneVersioning"}, new String[]{}); -+ setup("RedPaper", new String[]{}, new String[]{"GoldPaper", "GoldFire", "EmeraldGrass", "BlueFire", "CopperSpork", "YellowDoor", "OrangeClam", "BlueSponge", "GoldNeptune", "BrightBlueGrass", "DarkSpoon", "BigShovel", "GreenGlass", "IronGlass"}, new String[]{"IronPaper", "YellowFire"}); -+ setup("YellowGrass", new String[]{}, new String[]{"RedAir"}, new String[]{}); -+ setup("WireFire", new String[]{}, new String[]{"RedPaper", "WireGrass", "YellowSpork", "NightAir"}, new String[]{}); -+ setup("OrangeNeptune", new String[]{}, new String[]{}, new String[]{}); -+ setup("BigSpoon", new String[]{"YellowGrass", "GreenShovel"}, new String[]{"RedAir", "GoldNeptune", "BrightBlueGrass", "LightDoor", "LightSpork", "LightEarth", "NightDoor", "OrangeSpoon", "GoldSponge", "GoldDoor", "DarkPaper", "RedPaper", "GreenGlass", "IronGlass", "NightGlass", "BigGrass", "BlueFire", "YellowSpoon", "DiamondGrass", "DiamondShovel", "DarkSnow", "EmeraldGlass", "EmeraldSpoon", "LightFire", "WireGrass", "RedEarth", "WireFire"}, new String[]{}); -+ setup("CopperSnow", new String[]{}, new String[]{"RedSnow", "OrangeFire", "WireAir", "GreenGlass", "NightSpork", "EmeraldPaper"}, new String[]{"BlueGrass"}); -+ setup("BrightBluePaper", new String[]{}, new String[]{"GoldEarth", "BrightBlueSpoon", "CopperGlass", "LightSporkChat", "DarkAir", "LightEarth", "DiamondDoor", "YellowShovel", "BlueAir", "DarkShovel", "GoldPaper", "BlueFire", "GreenGlass", "YellowSpork", "BigGrass", "OrangePaper", "DarkPaper"}, new String[]{"WireShovel"}); -+ setup("LightSponge", new String[]{}, new String[]{}, new String[]{}); -+ setup("OrangeShovel", new String[]{}, new String[]{}, new String[]{}); -+ setup("GoldGrass", new String[]{}, new String[]{"GreenGlass", "BlueFire"}, new String[]{}); -+ setup("IronSponge", new String[]{}, new String[]{"DiamondEarth"}, new String[]{}); -+ setup("EmeraldSnow", new String[]{}, new String[]{}, new String[]{}); -+ setup("BlueSpoon", new String[]{"BigGrass"}, new String[]{"GreenGlass", "GoldPaper", "GreenShovel", "YellowClam"}, new String[]{}); -+ setup("BigSpork", new String[]{}, new String[]{"BigPaper"}, new String[]{}); -+ setup("BluePaper", new String[]{}, new String[]{"BigClam", "RedSpoon", "GreenFire", "WireSnow", "OrangeSnow", "BlueFire", "BrightBlueGrass", "YellowSpork", "GreenGlass"}, new String[]{}); -+ setup("OrangeSpork", new String[]{}, new String[]{}, new String[]{}); -+ setup("DiamondNeptune", new String[]{}, new String[]{"GreenGlass", "GreenShovel", "YellowNeptune"}, new String[]{}); -+ setup("BigFire", new String[]{}, new String[]{"BlueFire", "BrightBlueDoor", "GreenGlass"}, new String[]{}); -+ setup("NightNeptune", new String[]{}, new String[]{"BlueFire", "DarkGlass", "GoldPaper", "YellowNeptune", "BlueShovel"}, new String[]{}); -+ setup("YellowEarth", new String[]{"RedAir"}, new String[]{}, new String[]{}); -+ setup("DiamondClam", new String[]{}, new String[]{}, new String[]{}); -+ setup("CopperAir", new String[]{}, new String[]{"BigPaper"}, new String[]{}); -+ setup("NightSpoon", new String[]{"OrangeNeptune"}, new String[]{"BlueFire", "GreenGlass", "RedSpork", "GoldPaper", "BigShovel", "YellowSponge", "EmeraldSpork"}, new String[]{}); -+ setup("GreenClam", new String[]{}, new String[]{"GreenShovel", "BrightBlueEarth", "BigSpoon", "RedPaper", "BlueFire", "GreenGlass", "WireFire", "GreenSnow"}, new String[]{}); -+ setup("YellowPaper", new String[]{}, new String[]{}, new String[]{}); -+ setup("WireGlass", new String[]{"YellowGrass"}, new String[]{"YellowGlass", "BigSpoon", "CopperSnow", "GreenGlass", "BlueEarth"}, new String[]{}); -+ setup("BlueSpork", new String[]{}, new String[]{"BrightBlueGrass"}, new String[]{}); -+ setup("CopperShovel", new String[]{}, new String[]{"GreenGlass"}, new String[]{}); -+ setup("RedClam", new String[]{}, new String[]{}, new String[]{}); -+ setup("EmeraldClam", new String[]{}, new String[]{"BlueFire"}, new String[]{}); -+ setup("DarkClam", new String[]{}, new String[]{"GoldAir", "LightGlass"}, new String[]{}); -+ setup("WireSpoon", new String[]{}, new String[]{"GoldPaper", "LightSnow"}, new String[]{}); -+ setup("CopperNeptune", new String[]{}, new String[]{"GreenGlass", "BigGrass"}, new String[]{}); -+ setup("RedNeptune", new String[]{}, new String[]{}, new String[]{}); -+ setup("GreenAir", new String[]{}, new String[]{}, new String[]{}); -+ setup("RedFire", new String[]{"BrightBlueGrass", "BigPaper"}, new String[]{"BlueFire", "GreenGlass", "BigGrass"}, new String[]{}); ++ setup("RedAir", EMPTY, new String[]{"NightShovel", "EmeraldFire"}, new String[]{"GreenShovel", "IronSpork", "BrightBlueShovel", "WireDoor"}); ++ setup("BigGrass", EMPTY, new String[]{"IronEarth", "RedAir"}, new String[]{"BlueFire"}); ++ setup("BlueFire", EMPTY, EMPTY, EMPTY); ++ setup("BigPaper", EMPTY, new String[]{"BlueFire"}, EMPTY); ++ setup("EmeraldSpork", EMPTY, EMPTY, new String[]{"GoldPaper", "YellowSnow"}); ++ setup("GreenShovel", EMPTY, EMPTY, EMPTY); ++ setup("BrightBlueGrass", new String[]{"BigPaper"}, new String[]{"DarkSpork"}, EMPTY); ++ setup("GoldPaper", EMPTY, new String[]{"BlueFire"}, EMPTY); ++ setup("GreenGlass", EMPTY, EMPTY, EMPTY); ++ setup("GoldNeptune", EMPTY, new String[]{"GreenShovel", "GoldNeptuneVersioning"}, EMPTY); ++ setup("RedPaper", EMPTY, new String[]{"GoldPaper", "GoldFire", "EmeraldGrass", "BlueFire", "CopperSpork", "YellowDoor", "OrangeClam", "BlueSponge", "GoldNeptune", "BrightBlueGrass", "DarkSpoon", "BigShovel", "GreenGlass", "IronGlass"}, new String[]{"IronPaper", "YellowFire"}); ++ setup("YellowGrass", EMPTY, new String[]{"RedAir"}, EMPTY); ++ setup("WireFire", EMPTY, new String[]{"RedPaper", "WireGrass", "YellowSpork", "NightAir"}, EMPTY); ++ setup("OrangeNeptune", EMPTY, EMPTY, EMPTY); ++ setup("BigSpoon", new String[]{"YellowGrass", "GreenShovel"}, new String[]{"RedAir", "GoldNeptune", "BrightBlueGrass", "LightDoor", "LightSpork", "LightEarth", "NightDoor", "OrangeSpoon", "GoldSponge", "GoldDoor", "DarkPaper", "RedPaper", "GreenGlass", "IronGlass", "NightGlass", "BigGrass", "BlueFire", "YellowSpoon", "DiamondGrass", "DiamondShovel", "DarkSnow", "EmeraldGlass", "EmeraldSpoon", "LightFire", "WireGrass", "RedEarth", "WireFire"}, EMPTY); ++ setup("CopperSnow", EMPTY, new String[]{"RedSnow", "OrangeFire", "WireAir", "GreenGlass", "NightSpork", "EmeraldPaper"}, new String[]{"BlueGrass"}); ++ setup("BrightBluePaper", EMPTY, new String[]{"GoldEarth", "BrightBlueSpoon", "CopperGlass", "LightSporkChat", "DarkAir", "LightEarth", "DiamondDoor", "YellowShovel", "BlueAir", "DarkShovel", "GoldPaper", "BlueFire", "GreenGlass", "YellowSpork", "BigGrass", "OrangePaper", "DarkPaper"}, new String[]{"WireShovel"}); ++ setup("LightSponge", EMPTY, EMPTY, EMPTY); ++ setup("OrangeShovel", EMPTY, EMPTY, EMPTY); ++ setup("GoldGrass", EMPTY, new String[]{"GreenGlass", "BlueFire"}, EMPTY); ++ setup("IronSponge", EMPTY, new String[]{"DiamondEarth"}, EMPTY); ++ setup("EmeraldSnow", EMPTY, EMPTY, EMPTY); ++ setup("BlueSpoon", new String[]{"BigGrass"}, new String[]{"GreenGlass", "GoldPaper", "GreenShovel", "YellowClam"}, EMPTY); ++ setup("BigSpork", EMPTY, new String[]{"BigPaper"}, EMPTY); ++ setup("BluePaper", EMPTY, new String[]{"BigClam", "RedSpoon", "GreenFire", "WireSnow", "OrangeSnow", "BlueFire", "BrightBlueGrass", "YellowSpork", "GreenGlass"}, EMPTY); ++ setup("OrangeSpork", EMPTY, EMPTY, EMPTY); ++ setup("DiamondNeptune", EMPTY, new String[]{"GreenGlass", "GreenShovel", "YellowNeptune"}, EMPTY); ++ setup("BigFire", EMPTY, new String[]{"BlueFire", "BrightBlueDoor", "GreenGlass"}, EMPTY); ++ setup("NightNeptune", EMPTY, new String[]{"BlueFire", "DarkGlass", "GoldPaper", "YellowNeptune", "BlueShovel"}, EMPTY); ++ setup("YellowEarth", new String[]{"RedAir"}, EMPTY, EMPTY); ++ setup("DiamondClam", EMPTY, EMPTY, EMPTY); ++ setup("CopperAir", EMPTY, new String[]{"BigPaper"}, EMPTY); ++ setup("NightSpoon", new String[]{"OrangeNeptune"}, new String[]{"BlueFire", "GreenGlass", "RedSpork", "GoldPaper", "BigShovel", "YellowSponge", "EmeraldSpork"}, EMPTY); ++ setup("GreenClam", EMPTY, new String[]{"GreenShovel", "BrightBlueEarth", "BigSpoon", "RedPaper", "BlueFire", "GreenGlass", "WireFire", "GreenSnow"}, EMPTY); ++ setup("YellowPaper", EMPTY, EMPTY, EMPTY); ++ setup("WireGlass", new String[]{"YellowGrass"}, new String[]{"YellowGlass", "BigSpoon", "CopperSnow", "GreenGlass", "BlueEarth"}, EMPTY); ++ setup("BlueSpork", EMPTY, new String[]{"BrightBlueGrass"}, EMPTY); ++ setup("CopperShovel", EMPTY, new String[]{"GreenGlass"}, EMPTY); ++ setup("RedClam", EMPTY, EMPTY, EMPTY); ++ setup("EmeraldClam", EMPTY, new String[]{"BlueFire"}, EMPTY); ++ setup("DarkClam", EMPTY, new String[]{"GoldAir", "LightGlass"}, EMPTY); ++ setup("WireSpoon", EMPTY, new String[]{"GoldPaper", "LightSnow"}, EMPTY); ++ setup("CopperNeptune", EMPTY, new String[]{"GreenGlass", "BigGrass"}, EMPTY); ++ setup("RedNeptune", EMPTY, EMPTY, EMPTY); ++ setup("GreenAir", EMPTY, EMPTY, EMPTY); ++ setup("RedFire", new String[]{"BrightBlueGrass", "BigPaper"}, new String[]{"BlueFire", "GreenGlass", "BigGrass"}, EMPTY); + } + + @Before @@ -6504,20 +6758,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return false; + } + -+ @Override -+ public List requiredDependencies(PluginProvider provider) { -+ return provider.getMeta().getPluginDependencies(); -+ } -+ -+ @Override -+ public List optionalDependencies(PluginProvider provider) { -+ return provider.getMeta().getPluginSoftDependencies(); -+ } -+ -+ @Override -+ public List loadBeforeDependencies(PluginProvider provider) { -+ return provider.getMeta().getLoadBeforePlugins(); -+ } + }); + + modernPluginLoadingStrategy.loadProviders(REGISTERED_PROVIDERS); @@ -6561,9 +6801,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } +} -diff --git a/src/test/java/io/papermc/paper/plugin/PluginLoadingTest.java b/src/test/java/io/papermc/paper/plugin/PluginLoadingTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 diff --git a/src/test/java/io/papermc/paper/plugin/PluginManagerTest.java b/src/test/java/io/papermc/paper/plugin/PluginManagerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -6729,10 +6966,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +package io.papermc.paper.plugin; + ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.entrypoint.dependency.DependencyUtil; +import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.configuration.LoadOrderConfiguration; +import org.jetbrains.annotations.NotNull; + +import java.nio.file.Path; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Map; +import java.util.jar.JarFile; +import java.util.logging.Logger; + @@ -6768,6 +7011,34 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public Logger getLogger() { + return Logger.getLogger("TestPlugin"); + } ++ ++ @Override ++ public LoadOrderConfiguration createConfiguration(@NotNull Map> toLoad) { ++ return new LoadOrderConfiguration() { ++ @Override ++ public @NotNull List getLoadBefore() { ++ return TestJavaPluginProvider.this.testPluginConfiguration.getLoadBeforePlugins(); ++ } ++ ++ @Override ++ public @NotNull List getLoadAfter() { ++ List loadAfter = new ArrayList<>(); ++ loadAfter.addAll(TestJavaPluginProvider.this.testPluginConfiguration.getPluginDependencies()); ++ loadAfter.addAll(TestJavaPluginProvider.this.testPluginConfiguration.getPluginSoftDependencies()); ++ return loadAfter; ++ } ++ ++ @Override ++ public @NotNull PluginMeta getMeta() { ++ return TestJavaPluginProvider.this.testPluginConfiguration; ++ } ++ }; ++ } ++ ++ @Override ++ public List validateDependencies(@NotNull Map> toLoad) { ++ return DependencyUtil.validateSimple(this.getMeta(), toLoad); ++ } +} diff --git a/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java b/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java new file mode 100644