diff options
author | Owen <[email protected]> | 2023-04-22 13:44:40 -0400 |
---|---|---|
committer | GitHub <[email protected]> | 2023-04-22 12:44:40 -0500 |
commit | 8e3009e0db089f977e28d7f4c1f779c5a504da71 (patch) | |
tree | 146aa4a6992e0dfe6e343f9b03acf8a36c8e90e1 | |
parent | f7717c3712265fd480d6ff0ad808c430b9972004 (diff) | |
download | Paper-8e3009e0db089f977e28d7f4c1f779c5a504da71.tar.gz Paper-8e3009e0db089f977e28d7f4c1f779c5a504da71.zip |
Resolve Plugin Dependency Issues, Improve PluginLoading Compat, Small Loading Issues (#9129)
-rw-r--r-- | patches/server/0013-Paper-Plugins.patch | 1028 |
1 files changed, 647 insertions, 381 deletions
diff --git a/patches/server/0013-Paper-Plugins.patch b/patches/server/0013-Paper-Plugins.patch index deb38ce574..be3340453e 100644 --- a/patches/server/0013-Paper-Plugins.patch +++ b/patches/server/0013-Paper-Plugins.patch @@ -250,12 +250,13 @@ index 0000000000000000000000000000000000000000..f0fce4113fb07c64adbec029d177c236 +} diff --git a/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..615902ac9fd341d6624d8fdb1c2ae6cc9abf9554 +index 0000000000000000000000000000000000000000..8ade7eb97aa899ddd4bb8274b8f588a4d7265868 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java -@@ -0,0 +1,200 @@ +@@ -0,0 +1,203 @@ +package io.papermc.paper.command.subcommands; + ++import com.google.common.graph.GraphBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; @@ -270,8 +271,10 @@ index 0000000000000000000000000000000000000000..615902ac9fd341d6624d8fdb1c2ae6cc +import io.papermc.paper.plugin.entrypoint.classloader.group.SimpleListPluginClassLoaderGroup; +import io.papermc.paper.plugin.entrypoint.classloader.group.SpigotPluginClassLoaderGroup; +import io.papermc.paper.plugin.entrypoint.classloader.group.StaticPluginClassLoaderGroup; ++import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext; ++import io.papermc.paper.plugin.entrypoint.dependency.MetaDependencyTree; +import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; -+import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy; ++import io.papermc.paper.plugin.entrypoint.strategy.modern.ModernPluginLoadingStrategy; +import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; +import io.papermc.paper.plugin.manager.PaperPluginManagerImpl; +import io.papermc.paper.plugin.provider.PluginProvider; @@ -394,7 +397,7 @@ index 0000000000000000000000000000000000000000..615902ac9fd341d6624d8fdb1c2ae6cc + return false; + } + }); -+ modernPluginLoadingStrategy.loadProviders(pluginProviders); ++ modernPluginLoadingStrategy.loadProviders(pluginProviders, new MetaDependencyTree(GraphBuilder.directed().build())); + + rootProviders.add(entry.getKey().getDebugName(), entrypoint); + } @@ -813,7 +816,7 @@ index 0000000000000000000000000000000000000000..f9a2c55a354c877749db3f92956de802 +} diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperPluginClassLoader.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperPluginClassLoader.java new file mode 100644 -index 0000000000000000000000000000000000000000..82487d656acaf41afe3af9c05a3dbf122bdf19c1 +index 0000000000000000000000000000000000000000..3c83feac2f2f0be56e373463d47d643a5f9ae374 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperPluginClassLoader.java @@ -0,0 +1,206 @@ @@ -1671,78 +1674,25 @@ index 0000000000000000000000000000000000000000..f43295fdeaa587cf30c35a1d54516707 +} diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyUtil.java b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyUtil.java new file mode 100644 -index 0000000000000000000000000000000000000000..bfa258faf17ca6118aeddfa4e95bbd082bcd1390 +index 0000000000000000000000000000000000000000..ef8653a6c6e0653716887d47bac4ab43e3b6c788 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyUtil.java -@@ -0,0 +1,75 @@ +@@ -0,0 +1,22 @@ +package io.papermc.paper.plugin.entrypoint.dependency; + -+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 io.papermc.paper.plugin.provider.entrypoint.DependencyContext; + +import java.util.ArrayList; +import java.util.List; -+import java.util.Map; -+import java.util.function.Predicate; + +@SuppressWarnings("UnstableApiUsage") +public class DependencyUtil { + -+ @NotNull -+ public static MutableGraph<String> buildDependencyGraph(@NotNull MutableGraph<String> dependencyGraph, @NotNull PluginMeta configuration) { -+ List<String> dependencies = new ArrayList<>(); -+ dependencies.addAll(configuration.getPluginDependencies()); -+ dependencies.addAll(configuration.getPluginSoftDependencies()); -+ -+ return buildDependencyGraph(dependencyGraph, configuration.getName(), dependencies); -+ } -+ -+ @NotNull -+ public static MutableGraph<String> buildDependencyGraph(@NotNull MutableGraph<String> dependencyGraph, String identifier, @NotNull Iterable<String> depends) { -+ for (String dependency : depends) { -+ dependencyGraph.putEdge(identifier, dependency); -+ } -+ -+ dependencyGraph.addNode(identifier); // Make sure dependencies at least have a node -+ return dependencyGraph; -+ } -+ -+ @NotNull -+ public static MutableGraph<String> buildLoadGraph(@NotNull MutableGraph<String> dependencyGraph, @NotNull LoadOrderConfiguration configuration, Predicate<String> validator) { -+ String identifier = configuration.getMeta().getName(); -+ for (String dependency : configuration.getLoadAfter()) { -+ if (validator.test(dependency)) { -+ dependencyGraph.putEdge(identifier, dependency); -+ } -+ } -+ -+ for (String loadBeforeTarget : configuration.getLoadBefore()) { -+ if (validator.test(loadBeforeTarget)) { -+ dependencyGraph.putEdge(loadBeforeTarget, identifier); -+ } -+ } -+ -+ dependencyGraph.addNode(identifier); // Make sure dependencies at least have a node -+ return dependencyGraph; -+ } -+ -+ // This adds a provided plugin to another plugin, basically making it seem like a "dependency" -+ // in order to have plugins that need the provided plugin to load after the specified plugin name -+ @NotNull -+ public static MutableGraph<String> addProvidedPlugin(@NotNull MutableGraph<String> dependencyGraph, @NotNull String pluginName, @NotNull String providedName) { -+ dependencyGraph.putEdge(pluginName, providedName); -+ -+ return dependencyGraph; -+ } -+ -+ public static List<String> validateSimple(PluginMeta meta, Map<String, PluginProvider<?>> toLoad) { ++ public static List<String> validateSimple(PluginMeta meta, DependencyContext dependencyContext) { + List<String> missingDependencies = new ArrayList<>(); + for (String hardDependency : meta.getPluginDependencies()) { -+ if (!toLoad.containsKey(hardDependency)) { ++ if (!dependencyContext.hasDependency(hardDependency)) { + missingDependencies.add(hardDependency); + } + } @@ -1752,14 +1702,15 @@ index 0000000000000000000000000000000000000000..bfa258faf17ca6118aeddfa4e95bbd08 +} 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 -index 0000000000000000000000000000000000000000..6f201a8131ca9631ac4af62c75e6f2e889cb5eae +index 0000000000000000000000000000000000000000..a2fa8406bc3f0dcab6805633ae984d031d24692a --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/GraphDependencyContext.java -@@ -0,0 +1,43 @@ +@@ -0,0 +1,54 @@ +package io.papermc.paper.plugin.entrypoint.dependency; + +import com.google.common.graph.Graph; +import com.google.common.graph.Graphs; ++import com.google.common.graph.MutableGraph; +import io.papermc.paper.plugin.configuration.PluginMeta; +import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; + @@ -1768,9 +1719,9 @@ index 0000000000000000000000000000000000000000..6f201a8131ca9631ac4af62c75e6f2e8 +@SuppressWarnings("UnstableApiUsage") +public class GraphDependencyContext implements DependencyContext { + -+ private final Graph<String> dependencyGraph; ++ private final MutableGraph<String> dependencyGraph; + -+ public GraphDependencyContext(Graph<String> dependencyGraph) { ++ public GraphDependencyContext(MutableGraph<String> dependencyGraph) { + this.dependencyGraph = dependencyGraph; + } + @@ -1798,6 +1749,134 @@ index 0000000000000000000000000000000000000000..6f201a8131ca9631ac4af62c75e6f2e8 + return this.dependencyGraph.nodes().contains(pluginIdentifier); + } + ++ public MutableGraph<String> getDependencyGraph() { ++ return dependencyGraph; ++ } ++ ++ @Override ++ public String toString() { ++ return "GraphDependencyContext{" + ++ "dependencyGraph=" + this.dependencyGraph + ++ '}'; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/MetaDependencyTree.java b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/MetaDependencyTree.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1523cbb1801b937c3d40080df7d52e6cff8fc49c +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/MetaDependencyTree.java +@@ -0,0 +1,112 @@ ++package io.papermc.paper.plugin.entrypoint.dependency; ++ ++import com.google.common.graph.GraphBuilder; ++import com.google.common.graph.Graphs; ++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.entrypoint.DependencyContext; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.HashSet; ++import java.util.Set; ++ ++public class MetaDependencyTree implements DependencyContext { ++ ++ private final MutableGraph<String> graph; ++ ++ // We need to upkeep a separate collection since when populating ++ // a graph it adds nodes even if they are not present ++ private final Set<String> dependencies = new HashSet<>(); ++ ++ public MetaDependencyTree() { ++ this(GraphBuilder.directed().build()); ++ } ++ ++ public MetaDependencyTree(MutableGraph<String> graph) { ++ this.graph = graph; ++ } ++ ++ public void add(PluginProvider<?> provider) { ++ add(provider.getMeta()); ++ } ++ ++ public void remove(PluginProvider<?> provider) { ++ remove(provider.getMeta()); ++ } ++ ++ public void add(PluginMeta configuration) { ++ String identifier = configuration.getName(); ++ // Build a validated provider's dependencies into the graph ++ for (String dependency : configuration.getPluginDependencies()) { ++ this.graph.putEdge(identifier, dependency); ++ } ++ for (String dependency : configuration.getPluginSoftDependencies()) { ++ this.graph.putEdge(identifier, dependency); ++ } ++ ++ this.graph.addNode(identifier); // Make sure dependencies at least have a node ++ ++ // Add the provided plugins to the graph as well ++ for (String provides : configuration.getProvidedPlugins()) { ++ this.graph.putEdge(identifier, provides); ++ this.dependencies.add(provides); ++ } ++ this.dependencies.add(identifier); ++ } ++ ++ public void remove(PluginMeta configuration) { ++ String identifier = configuration.getName(); ++ // Remove a validated provider's dependencies into the graph ++ for (String dependency : configuration.getPluginDependencies()) { ++ this.graph.removeEdge(identifier, dependency); ++ } ++ for (String dependency : configuration.getPluginSoftDependencies()) { ++ this.graph.removeEdge(identifier, dependency); ++ } ++ ++ this.graph.removeNode(identifier); // Remove root node ++ ++ // Remove the provided plugins to the graph as well ++ for (String provides : configuration.getProvidedPlugins()) { ++ this.graph.removeEdge(identifier, provides); ++ this.dependencies.remove(provides); ++ } ++ this.dependencies.remove(identifier); ++ } ++ ++ @Override ++ public boolean isTransitiveDependency(@NotNull PluginMeta plugin, @NotNull PluginMeta depend) { ++ String pluginIdentifier = plugin.getName(); ++ ++ if (this.graph.nodes().contains(pluginIdentifier)) { ++ Set<String> reachableNodes = Graphs.reachableNodes(this.graph, pluginIdentifier); ++ if (reachableNodes.contains(depend.getName())) { ++ return true; ++ } ++ for (String provided : depend.getProvidedPlugins()) { ++ if (reachableNodes.contains(provided)) { ++ return true; ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public boolean hasDependency(@NotNull String pluginIdentifier) { ++ return this.dependencies.contains(pluginIdentifier); ++ } ++ ++ @Override ++ public String toString() { ++ return "ProviderDependencyTree{" + ++ "graph=" + this.graph + ++ '}'; ++ } ++ ++ public MutableGraph<String> getGraph() { ++ return graph; ++ } +} diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/JohnsonSimpleCycles.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/JohnsonSimpleCycles.java new file mode 100644 @@ -2161,7 +2240,7 @@ index 0000000000000000000000000000000000000000..a9bca905eba67972e4d1b07b1d243272 +} diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/LegacyPluginLoadingStrategy.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/LegacyPluginLoadingStrategy.java new file mode 100644 -index 0000000000000000000000000000000000000000..2f4d0e04676d000afacd7e5354551e77922b2dcf +index 0000000000000000000000000000000000000000..ebd193e7d9736021756223d565c8a16a7b1770d2 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/LegacyPluginLoadingStrategy.java @@ -0,0 +1,269 @@ @@ -2171,6 +2250,7 @@ index 0000000000000000000000000000000000000000..2f4d0e04676d000afacd7e5354551e77 +import com.google.common.graph.MutableGraph; +import io.papermc.paper.plugin.configuration.PluginMeta; +import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext; ++import io.papermc.paper.plugin.entrypoint.dependency.MetaDependencyTree; +import io.papermc.paper.plugin.provider.PluginProvider; +import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; +import org.bukkit.plugin.UnknownDependencyException; @@ -2198,10 +2278,9 @@ index 0000000000000000000000000000000000000000..2f4d0e04676d000afacd7e5354551e77 + } + + @Override -+ public List<ProviderPair<T>> loadProviders(List<PluginProvider<T>> providers) { ++ public List<ProviderPair<T>> loadProviders(List<PluginProvider<T>> providers, MetaDependencyTree dependencyTree) { + List<ProviderPair<T>> javapluginsLoaded = new ArrayList<>(); -+ MutableGraph<String> dependencyGraph = GraphBuilder.directed().build(); -+ GraphDependencyContext dependencyContext = new GraphDependencyContext(dependencyGraph); ++ MutableGraph<String> dependencyGraph = dependencyTree.getGraph(); + + Map<String, PluginProvider<T>> providersToLoad = new HashMap<>(); + Set<String> loadedPlugins = new HashSet<>(); @@ -2363,7 +2442,7 @@ index 0000000000000000000000000000000000000000..2f4d0e04676d000afacd7e5354551e77 + missingDependency = false; + + try { -+ this.configuration.applyContext(file, dependencyContext); ++ this.configuration.applyContext(file, dependencyTree); + T loadedPlugin = file.createInstance(); + this.warnIfPaperPlugin(file); + @@ -2395,7 +2474,7 @@ index 0000000000000000000000000000000000000000..2f4d0e04676d000afacd7e5354551e77 + providerIterator.remove(); + + try { -+ this.configuration.applyContext(file, dependencyContext); ++ this.configuration.applyContext(file, dependencyTree); + T loadedPlugin = file.createInstance(); + this.warnIfPaperPlugin(file); + @@ -2434,223 +2513,6 @@ index 0000000000000000000000000000000000000000..2f4d0e04676d000afacd7e5354551e77 + } + } +} -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 -index 0000000000000000000000000000000000000000..1d58f3a438d5be423c84b2f61e496d938fdc2995 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ModernPluginLoadingStrategy.java -@@ -0,0 +1,211 @@ -+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; -+import io.papermc.paper.plugin.configuration.PluginMeta; -+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 io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; -+import io.papermc.paper.plugin.provider.type.spigot.SpigotPluginProvider; -+import java.util.HashSet; -+import java.util.Set; -+import org.bukkit.plugin.UnknownDependencyException; -+import org.slf4j.Logger; -+ -+import java.util.ArrayList; -+import java.util.HashMap; -+import java.util.List; -+import java.util.Map; -+ -+@SuppressWarnings("UnstableApiUsage") -+public class ModernPluginLoadingStrategy<T> implements ProviderLoadingStrategy<T> { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ private final ProviderConfiguration<T> configuration; -+ -+ public ModernPluginLoadingStrategy(ProviderConfiguration<T> onLoad) { -+ this.configuration = onLoad; -+ } -+ -+ @Override -+ public List<ProviderPair<T>> loadProviders(List<PluginProvider<T>> pluginProviders) { -+ Map<String, PluginProviderEntry<T>> providerMap = new HashMap<>(); -+ Map<String, PluginProvider<?>> providerMapMirror = Maps.transformValues(providerMap, (entry) -> entry.provider); -+ List<PluginProvider<T>> validatedProviders = new ArrayList<>(); -+ -+ // Populate provider map -+ for (PluginProvider<T> provider : pluginProviders) { -+ PluginMeta providerConfig = provider.getMeta(); -+ PluginProviderEntry<T> entry = new PluginProviderEntry<>(provider); -+ -+ PluginProviderEntry<T> replacedProvider = providerMap.put(providerConfig.getName(), entry); -+ if (replacedProvider != null) { -+ LOGGER.error(String.format( -+ "Ambiguous plugin name '%s' for files '%s' and '%s' in '%s'", -+ providerConfig.getName(), -+ provider.getSource(), -+ replacedProvider.provider.getSource(), -+ replacedProvider.provider.getParentSource() -+ )); -+ } -+ -+ for (String extra : providerConfig.getProvidedPlugins()) { -+ PluginProviderEntry<T> replacedExtraProvider = providerMap.putIfAbsent(extra, entry); -+ if (replacedExtraProvider != null) { -+ LOGGER.warn(String.format( -+ "`%s' is provided by both `%s' and `%s'", -+ extra, -+ providerConfig.getName(), -+ replacedExtraProvider.provider.getMeta().getName() -+ )); -+ } -+ } -+ } -+ -+ // Validate providers, ensuring all of them have valid dependencies. Removing those who are invalid -+ for (PluginProvider<T> provider : pluginProviders) { -+ PluginMeta configuration = provider.getMeta(); -+ -+ // Populate missing dependencies to capture if there are multiple missing ones. -+ List<String> missingDependencies = provider.validateDependencies(providerMapMirror); -+ -+ if (missingDependencies.isEmpty()) { -+ validatedProviders.add(provider); -+ } else { -+ LOGGER.error("Could not load '%s' in '%s'".formatted(provider.getSource(), provider.getParentSource()), new UnknownDependencyException(missingDependencies, configuration.getName())); // Paper -+ // Because the validator is invalid, remove it from the provider map -+ providerMap.remove(configuration.getName()); -+ } -+ } -+ -+ MutableGraph<String> loadOrderGraph = GraphBuilder.directed().build(); -+ MutableGraph<String> 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, providerMap::containsKey); -+ -+ // 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()) { -+ 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<String> reversedTopographicSort; -+ try { -+ reversedTopographicSort = Lists.reverse(TopographicGraphSorter.sortGraph(loadOrderGraph)); -+ } catch (TopographicGraphSorter.GraphCycleException exception) { -+ List<List<String>> cycles = new JohnsonSimpleCycles<>(loadOrderGraph).findAndRemoveSimpleCycles(); -+ -+ // Only log an error if at least non-Spigot plugin is present in the cycle -+ // Due to Spigot plugin metadata making no distinction between load order and dependencies (= class loader access), cycles are an unfortunate reality we have to deal with -+ Set<String> cyclingPlugins = new HashSet<>(); -+ cycles.forEach(cyclingPlugins::addAll); -+ if (cyclingPlugins.stream().anyMatch(plugin -> { -+ PluginProvider<?> pluginProvider = providerMapMirror.get(plugin); -+ return pluginProvider != null && !(pluginProvider instanceof SpigotPluginProvider); -+ })) { -+ logCycleError(cycles, providerMapMirror); -+ } -+ -+ // Try again after hopefully having removed all cycles -+ try { -+ reversedTopographicSort = Lists.reverse(TopographicGraphSorter.sortGraph(loadOrderGraph)); -+ } catch (TopographicGraphSorter.GraphCycleException e) { -+ throw new PluginGraphCycleException(cycles); -+ } -+ } -+ -+ GraphDependencyContext graphDependencyContext = new GraphDependencyContext(dependencyGraph); -+ List<ProviderPair<T>> loadedPlugins = new ArrayList<>(); -+ for (String providerIdentifier : reversedTopographicSort) { -+ // It's possible that this will be null because the above dependencies for soft/load before aren't validated if they exist. -+ // The graph could be MutableGraph<PluginProvider<T>>, but we would have to check if each dependency exists there... just -+ // nicer to do it here TBH. -+ PluginProviderEntry<T> retrievedProviderEntry = providerMap.get(providerIdentifier); -+ if (retrievedProviderEntry == null || retrievedProviderEntry.provided) { -+ // OR if this was already provided (most likely from a plugin that already "provides" that dependency) -+ // This won't matter since the provided plugin is loaded as a dependency, meaning it should have been loaded correctly anyways -+ continue; // Skip provider that doesn't exist.... -+ } -+ retrievedProviderEntry.provided = true; -+ PluginProvider<T> retrievedProvider = retrievedProviderEntry.provider; -+ try { -+ this.configuration.applyContext(retrievedProvider, graphDependencyContext); -+ -+ if (this.configuration.preloadProvider(retrievedProvider)) { -+ T instance = retrievedProvider.createInstance(); -+ if (this.configuration.load(retrievedProvider, instance)) { -+ loadedPlugins.add(new ProviderPair<>(retrievedProvider, instance)); -+ } -+ } -+ } catch (Throwable ex) { -+ LOGGER.error("Could not load plugin '%s' in folder '%s'".formatted(retrievedProvider.getFileName(), retrievedProvider.getParentSource()), ex); // Paper -+ } -+ } -+ -+ return loadedPlugins; -+ } -+ -+ private void logCycleError(List<List<String>> cycles, Map<String, PluginProvider<?>> providerMapMirror) { -+ LOGGER.error("================================="); -+ LOGGER.error("Circular plugin loading detected:"); -+ for (int i = 0; i < cycles.size(); i++) { -+ List<String> cycle = cycles.get(i); -+ LOGGER.error("{}) {} -> {}", i + 1, String.join(" -> ", cycle), cycle.get(0)); -+ for (String pluginName : cycle) { -+ PluginProvider<?> pluginProvider = providerMapMirror.get(pluginName); -+ if (pluginProvider == null) { -+ return; -+ } -+ -+ logPluginInfo(pluginProvider.getMeta()); -+ } -+ } -+ -+ 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("================================="); -+ } -+ -+ private void logPluginInfo(PluginMeta meta) { -+ if (!meta.getLoadBeforePlugins().isEmpty()) { -+ LOGGER.error(" {} loadbefore: {}", meta.getName(), meta.getLoadBeforePlugins()); -+ } -+ -+ if (meta instanceof PaperPluginMeta paperPluginMeta) { -+ if (!paperPluginMeta.getLoadAfterPlugins().isEmpty()) { -+ LOGGER.error(" {} loadafter: {}", meta.getName(), paperPluginMeta.getLoadAfterPlugins()); -+ } -+ } else { -+ List<String> dependencies = new ArrayList<>(); -+ dependencies.addAll(meta.getPluginDependencies()); -+ dependencies.addAll(meta.getPluginSoftDependencies()); -+ if (!dependencies.isEmpty()) { -+ LOGGER.error(" {} depend/softdepend: {}", meta.getName(), dependencies); -+ } -+ } -+ } -+ -+ private static class PluginProviderEntry<T> { -+ -+ private final PluginProvider<T> provider; -+ private boolean provided; -+ -+ private PluginProviderEntry(PluginProvider<T> provider) { -+ this.provider = provider; -+ } -+ } -+} diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/PluginGraphCycleException.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/PluginGraphCycleException.java new file mode 100644 index 0000000000000000000000000000000000000000..2ea978ac957849260e7ca69c9ff56588d0ccc41b @@ -2678,10 +2540,10 @@ index 0000000000000000000000000000000000000000..2ea978ac957849260e7ca69c9ff56588 +} diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderConfiguration.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderConfiguration.java new file mode 100644 -index 0000000000000000000000000000000000000000..2c7a0751e5c8d0d1e42f7e245ba09815d4f6f8ff +index 0000000000000000000000000000000000000000..67c4ef672ee509deba2b4bcaac42d9db24d4c89a --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderConfiguration.java -@@ -0,0 +1,21 @@ +@@ -0,0 +1,25 @@ +package io.papermc.paper.plugin.entrypoint.strategy; + +import io.papermc.paper.plugin.provider.PluginProvider; @@ -2702,15 +2564,20 @@ index 0000000000000000000000000000000000000000..2c7a0751e5c8d0d1e42f7e245ba09815 + return true; + } + ++ default void onGenericError(PluginProvider<T> 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 -index 0000000000000000000000000000000000000000..dee83e821dcc9baf3a3e5ca8325b03ed2d5eb81c +index 0000000000000000000000000000000000000000..a79eb9e2c8c42ecf823aecbd859576415e9981dc --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderLoadingStrategy.java -@@ -0,0 +1,20 @@ +@@ -0,0 +1,22 @@ +package io.papermc.paper.plugin.entrypoint.strategy; + ++import io.papermc.paper.plugin.entrypoint.dependency.MetaDependencyTree; +import io.papermc.paper.plugin.provider.PluginProvider; + +import java.util.List; @@ -2719,11 +2586,12 @@ index 0000000000000000000000000000000000000000..dee83e821dcc9baf3a3e5ca8325b03ed + * Used by a {@link io.papermc.paper.plugin.storage.SimpleProviderStorage} to load plugin providers in a certain order. + * <p> + * Returns providers loaded. ++ * + * @param <P> provider type + */ +public interface ProviderLoadingStrategy<P> { + -+ List<ProviderPair<P>> loadProviders(List<PluginProvider<P>> providers); ++ List<ProviderPair<P>> loadProviders(List<PluginProvider<P>> providers, MetaDependencyTree dependencyTree); + + record ProviderPair<P>(PluginProvider<P> provider, P provided) { + @@ -2793,6 +2661,278 @@ index 0000000000000000000000000000000000000000..52a110044611c8a0ace6d49549e8acc1 + + } +} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/modern/LoadOrderTree.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/modern/LoadOrderTree.java +new file mode 100644 +index 0000000000000000000000000000000000000000..862c2d9f195fe325d5e5d4aacbdf4051fa1feacd +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/modern/LoadOrderTree.java +@@ -0,0 +1,122 @@ ++package io.papermc.paper.plugin.entrypoint.strategy.modern; ++ ++import com.google.common.collect.Lists; ++import com.google.common.graph.MutableGraph; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.entrypoint.dependency.DependencyUtil; ++import io.papermc.paper.plugin.entrypoint.strategy.JohnsonSimpleCycles; ++import io.papermc.paper.plugin.entrypoint.strategy.PluginGraphCycleException; ++import io.papermc.paper.plugin.entrypoint.strategy.TopographicGraphSorter; ++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.slf4j.Logger; ++ ++import java.util.ArrayList; ++import java.util.HashSet; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++ ++class LoadOrderTree { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ private final Map<String, PluginProvider<?>> providerMap; ++ private final MutableGraph<String> graph; ++ ++ public LoadOrderTree(Map<String, PluginProvider<?>> providerMapMirror, MutableGraph<String> graph) { ++ this.providerMap = providerMapMirror; ++ this.graph = graph; ++ } ++ ++ public void add(PluginProvider<?> provider) { ++ LoadOrderConfiguration configuration = provider.createConfiguration(this.providerMap); ++ ++ // Build a validated provider's load order changes ++ String identifier = configuration.getMeta().getName(); ++ for (String dependency : configuration.getLoadAfter()) { ++ if (this.providerMap.containsKey(dependency)) { ++ this.graph.putEdge(identifier, dependency); ++ } ++ } ++ ++ for (String loadBeforeTarget : configuration.getLoadBefore()) { ++ if (this.providerMap.containsKey(loadBeforeTarget)) { ++ this.graph.putEdge(loadBeforeTarget, identifier); ++ } ++ } ++ ++ this.graph.addNode(identifier); // Make sure load order has at least one node ++ } ++ ++ public List<String> getLoadOrder() throws PluginGraphCycleException { ++ List<String> reversedTopographicSort; ++ try { ++ reversedTopographicSort = Lists.reverse(TopographicGraphSorter.sortGraph(this.graph)); ++ } catch (TopographicGraphSorter.GraphCycleException exception) { ++ List<List<String>> cycles = new JohnsonSimpleCycles<>(this.graph).findAndRemoveSimpleCycles(); ++ ++ // Only log an error if at least non-Spigot plugin is present in the cycle ++ // Due to Spigot plugin metadata making no distinction between load order and dependencies (= class loader access), cycles are an unfortunate reality we have to deal with ++ Set<String> cyclingPlugins = new HashSet<>(); ++ cycles.forEach(cyclingPlugins::addAll); ++ if (cyclingPlugins.stream().anyMatch(plugin -> { ++ PluginProvider<?> pluginProvider = this.providerMap.get(plugin); ++ return pluginProvider != null && !(pluginProvider instanceof SpigotPluginProvider); ++ })) { ++ logCycleError(cycles, this.providerMap); ++ } ++ ++ // Try again after hopefully having removed all cycles ++ try { ++ reversedTopographicSort = Lists.reverse(TopographicGraphSorter.sortGraph(this.graph)); ++ } catch (TopographicGraphSorter.GraphCycleException e) { ++ throw new PluginGraphCycleException(cycles); ++ } ++ } ++ ++ return reversedTopographicSort; ++ } ++ ++ private void logCycleError(List<List<String>> cycles, Map<String, PluginProvider<?>> providerMapMirror) { ++ LOGGER.error("================================="); ++ LOGGER.error("Circular plugin loading detected:"); ++ for (int i = 0; i < cycles.size(); i++) { ++ List<String> cycle = cycles.get(i); ++ LOGGER.error("{}) {} -> {}", i + 1, String.join(" -> ", cycle), cycle.get(0)); ++ for (String pluginName : cycle) { ++ PluginProvider<?> pluginProvider = providerMapMirror.get(pluginName); ++ if (pluginProvider == null) { ++ return; ++ } ++ ++ logPluginInfo(pluginProvider.getMeta()); ++ } ++ } ++ ++ 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("================================="); ++ } ++ ++ private void logPluginInfo(PluginMeta meta) { ++ if (!meta.getLoadBeforePlugins().isEmpty()) { ++ LOGGER.error(" {} loadbefore: {}", meta.getName(), meta.getLoadBeforePlugins()); ++ } ++ ++ if (meta instanceof PaperPluginMeta paperPluginMeta) { ++ if (!paperPluginMeta.getLoadAfterPlugins().isEmpty()) { ++ LOGGER.error(" {} loadafter: {}", meta.getName(), paperPluginMeta.getLoadAfterPlugins()); ++ } ++ } else { ++ List<String> dependencies = new ArrayList<>(); ++ dependencies.addAll(meta.getPluginDependencies()); ++ dependencies.addAll(meta.getPluginSoftDependencies()); ++ if (!dependencies.isEmpty()) { ++ LOGGER.error(" {} depend/softdepend: {}", meta.getName(), dependencies); ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/modern/ModernPluginLoadingStrategy.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/modern/ModernPluginLoadingStrategy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9af388a8e56806ab44f8c3ef4f97086ce38ef3b4 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/modern/ModernPluginLoadingStrategy.java +@@ -0,0 +1,138 @@ ++package io.papermc.paper.plugin.entrypoint.strategy.modern; ++ ++import com.google.common.collect.Maps; ++import com.google.common.graph.GraphBuilder; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext; ++import io.papermc.paper.plugin.entrypoint.dependency.MetaDependencyTree; ++import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; ++import io.papermc.paper.plugin.entrypoint.strategy.ProviderLoadingStrategy; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import org.bukkit.plugin.UnknownDependencyException; ++import org.slf4j.Logger; ++ ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.List; ++import java.util.Map; ++ ++@SuppressWarnings("UnstableApiUsage") ++public class ModernPluginLoadingStrategy<T> implements ProviderLoadingStrategy<T> { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ private final ProviderConfiguration<T> configuration; ++ ++ public ModernPluginLoadingStrategy(ProviderConfiguration<T> onLoad) { ++ this.configuration = onLoad; ++ } ++ ++ @Override ++ public List<ProviderPair<T>> loadProviders(List<PluginProvider<T>> pluginProviders, MetaDependencyTree dependencyTree) { ++ Map<String, PluginProviderEntry<T>> providerMap = new HashMap<>(); ++ Map<String, PluginProvider<?>> providerMapMirror = Maps.transformValues(providerMap, (entry) -> entry.provider); ++ List<PluginProvider<T>> validatedProviders = new ArrayList<>(); ++ ++ // Populate provider map ++ for (PluginProvider<T> provider : pluginProviders) { ++ PluginMeta providerConfig = provider.getMeta(); ++ PluginProviderEntry<T> entry = new PluginProviderEntry<>(provider); ++ ++ PluginProviderEntry<T> replacedProvider = providerMap.put(providerConfig.getName(), entry); ++ if (replacedProvider != null) { ++ LOGGER.error(String.format( ++ "Ambiguous plugin name '%s' for files '%s' and '%s' in '%s'", ++ providerConfig.getName(), ++ provider.getSource(), ++ replacedProvider.provider.getSource(), ++ replacedProvider.provider.getParentSource() ++ )); ++ this.configuration.onGenericError(replacedProvider.provider); ++ } ++ ++ for (String extra : providerConfig.getProvidedPlugins()) { ++ PluginProviderEntry<T> replacedExtraProvider = providerMap.putIfAbsent(extra, entry); ++ if (replacedExtraProvider != null) { ++ LOGGER.warn(String.format( ++ "`%s' is provided by both `%s' and `%s'", ++ extra, ++ providerConfig.getName(), ++ replacedExtraProvider.provider.getMeta().getName() ++ )); ++ } ++ } ++ } ++ ++ // Populate dependency tree ++ for (PluginProvider<?> validated : pluginProviders) { ++ dependencyTree.add(validated); ++ } ++ ++ // Validate providers, ensuring all of them have valid dependencies. Removing those who are invalid ++ for (PluginProvider<T> provider : pluginProviders) { ++ PluginMeta configuration = provider.getMeta(); ++ ++ // Populate missing dependencies to capture if there are multiple missing ones. ++ List<String> missingDependencies = provider.validateDependencies(dependencyTree); ++ ++ if (missingDependencies.isEmpty()) { ++ validatedProviders.add(provider); ++ } else { ++ LOGGER.error("Could not load '%s' in '%s'".formatted(provider.getSource(), provider.getParentSource()), new UnknownDependencyException(missingDependencies, configuration.getName())); // Paper ++ // Because the validator is invalid, remove it from the provider map ++ providerMap.remove(configuration.getName()); ++ // Cleanup plugins that failed to load ++ dependencyTree.remove(provider); ++ this.configuration.onGenericError(provider); ++ } ++ } ++ ++ LoadOrderTree loadOrderTree = new LoadOrderTree(providerMapMirror, GraphBuilder.directed().build()); ++ // Populate load order tree ++ for (PluginProvider<?> validated : validatedProviders) { ++ loadOrderTree.add(validated); ++ } ++ ++ // Reverse the topographic search to let us see which providers we can load first. ++ List<String> reversedTopographicSort = loadOrderTree.getLoadOrder(); ++ List<ProviderPair<T>> loadedPlugins = new ArrayList<>(); ++ for (String providerIdentifier : reversedTopographicSort) { ++ // It's possible that this will be null because the above dependencies for soft/load before aren't validated if they exist. ++ // The graph could be MutableGraph<PluginProvider<T>>, but we would have to check if each dependency exists there... just ++ // nicer to do it here TBH. ++ PluginProviderEntry<T> retrievedProviderEntry = providerMap.get(providerIdentifier); ++ if (retrievedProviderEntry == null || retrievedProviderEntry.provided) { ++ // OR if this was already provided (most likely from a plugin that already "provides" that dependency) ++ // This won't matter since the provided plugin is loaded as a dependency, meaning it should have been loaded correctly anyways ++ continue; // Skip provider that doesn't exist.... ++ } ++ retrievedProviderEntry.provided = true; ++ PluginProvider<T> retrievedProvider = retrievedProviderEntry.provider; ++ try { ++ this.configuration.applyContext(retrievedProvider, dependencyTree); ++ ++ if (this.configuration.preloadProvider(retrievedProvider)) { ++ T instance = retrievedProvider.createInstance(); ++ if (this.configuration.load(retrievedProvider, instance)) { ++ loadedPlugins.add(new ProviderPair<>(retrievedProvider, instance)); ++ } ++ } ++ } catch (Throwable ex) { ++ LOGGER.error("Could not load plugin '%s' in folder '%s'".formatted(retrievedProvider.getFileName(), retrievedProvider.getParentSource()), ex); // Paper ++ } ++ } ++ ++ return loadedPlugins; ++ } ++ ++ private static class PluginProviderEntry<T> { ++ ++ private final PluginProvider<T> provider; ++ private boolean provided; ++ ++ private PluginProviderEntry(PluginProvider<T> provider) { ++ this.provider = provider; ++ } ++ } ++} diff --git a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..f38ecd7f65dc24e4a3f0bc675e3730287ac353f1 @@ -2892,12 +3032,14 @@ index 0000000000000000000000000000000000000000..5fcce65009f715d46dd3013f1f92ec83 +} diff --git a/src/main/java/io/papermc/paper/plugin/manager/DummyBukkitPluginLoader.java b/src/main/java/io/papermc/paper/plugin/manager/DummyBukkitPluginLoader.java new file mode 100644 -index 0000000000000000000000000000000000000000..ea37ace14849ef4589a4f97287e6dcd64351370f +index 0000000000000000000000000000000000000000..aef19b44075a3b2e8696315baa89117dd8ebb513 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/manager/DummyBukkitPluginLoader.java -@@ -0,0 +1,57 @@ +@@ -0,0 +1,82 @@ +package io.papermc.paper.plugin.manager; + ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.provider.type.PluginFileType; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.Listener; @@ -2912,8 +3054,11 @@ index 0000000000000000000000000000000000000000..ea37ace14849ef4589a4f97287e6dcd6 +import org.jetbrains.annotations.NotNull; + +import java.io.File; ++import java.io.FileNotFoundException; ++import java.io.IOException; +import java.util.Map; +import java.util.Set; ++import java.util.jar.JarFile; +import java.util.regex.Pattern; + +/** @@ -2923,19 +3068,39 @@ index 0000000000000000000000000000000000000000..ea37ace14849ef4589a4f97287e6dcd6 +public class DummyBukkitPluginLoader implements PluginLoader { + ++ private static final Pattern[] PATTERNS = new Pattern[0]; ++ + @Override + public @NotNull Plugin loadPlugin(@NotNull File file) throws InvalidPluginException, UnknownDependencyException { -+ throw new UnsupportedOperationException(); ++ try { ++ return PaperPluginManagerImpl.getInstance().loadPlugin(file); ++ } catch (InvalidDescriptionException e) { ++ throw new InvalidPluginException(e); ++ } + } + + @Override + public @NotNull PluginDescriptionFile getPluginDescription(@NotNull File file) throws InvalidDescriptionException { -+ throw new UnsupportedOperationException(); ++ try (JarFile jar = new JarFile(file)) { ++ PluginFileType<?, ?> type = PluginFileType.guessType(jar); ++ if (type == null) { ++ throw new InvalidDescriptionException(new FileNotFoundException("Jar does not contain plugin.yml")); ++ } ++ ++ PluginMeta meta = type.getConfig(jar); ++ if (meta instanceof PluginDescriptionFile pluginDescriptionFile) { ++ return pluginDescriptionFile; ++ } else { ++ throw new InvalidDescriptionException("Plugin type does not use plugin.yml. Cannot read file description."); ++ } ++ } catch (Exception e) { ++ throw new InvalidDescriptionException(e); ++ } + } + + @Override + public @NotNull Pattern[] getPluginFileFilters() { -+ throw new UnsupportedOperationException(); ++ return PATTERNS; + } + + @Override @@ -2955,15 +3120,17 @@ index 0000000000000000000000000000000000000000..ea37ace14849ef4589a4f97287e6dcd6 +} diff --git a/src/main/java/io/papermc/paper/plugin/manager/MultiRuntimePluginProviderStorage.java b/src/main/java/io/papermc/paper/plugin/manager/MultiRuntimePluginProviderStorage.java new file mode 100644 -index 0000000000000000000000000000000000000000..48696ee62cfaef27ea512464d33ac502f5ec3ccd +index 0000000000000000000000000000000000000000..49bcce79139797649c775af65c7310c149611a56 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/manager/MultiRuntimePluginProviderStorage.java -@@ -0,0 +1,49 @@ +@@ -0,0 +1,61 @@ +package io.papermc.paper.plugin.manager; + +import com.mojang.logging.LogUtils; +import io.papermc.paper.plugin.entrypoint.Entrypoint; +import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler; ++import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext; ++import io.papermc.paper.plugin.entrypoint.dependency.MetaDependencyTree; +import io.papermc.paper.plugin.provider.PluginProvider; +import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; +import io.papermc.paper.plugin.storage.ServerPluginProviderStorage; @@ -2978,6 +3145,12 @@ index 0000000000000000000000000000000000000000..48696ee62cfaef27ea512464d33ac502 + private static final Logger LOGGER = LogUtils.getClassLogger(); + private final List<JavaPlugin> provided = new ArrayList<>(); + ++ private final MetaDependencyTree dependencyTree; ++ ++ MultiRuntimePluginProviderStorage(MetaDependencyTree dependencyTree) { ++ this.dependencyTree = dependencyTree; ++ } ++ + @Override + public void register(PluginProvider<JavaPlugin> provider) { + if (provider instanceof PaperPluginParent.PaperServerPluginProvider) { @@ -3007,6 +3180,10 @@ index 0000000000000000000000000000000000000000..48696ee62cfaef27ea512464d33ac502 + return this.provided; + } + ++ @Override ++ public MetaDependencyTree getDependencyTree() { ++ return this.dependencyTree; ++ } +} diff --git a/src/main/java/io/papermc/paper/plugin/manager/NormalPaperPermissionManager.java b/src/main/java/io/papermc/paper/plugin/manager/NormalPaperPermissionManager.java new file mode 100644 @@ -3466,10 +3643,10 @@ index 0000000000000000000000000000000000000000..92a69677f21b2c1c035119d8e5a6af63 +} diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java new file mode 100644 -index 0000000000000000000000000000000000000000..c0e896343c22badd97c774c4ed1daa4e274f5d44 +index 0000000000000000000000000000000000000000..9c7552968b8c017c71a7a77557a66a03ed89f125 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -@@ -0,0 +1,304 @@ +@@ -0,0 +1,301 @@ +package io.papermc.paper.plugin.manager; + +import com.google.common.base.Preconditions; @@ -3477,12 +3654,10 @@ index 0000000000000000000000000000000000000000..c0e896343c22badd97c774c4ed1daa4e +import com.google.common.graph.MutableGraph; +import io.papermc.paper.plugin.configuration.PluginMeta; +import io.papermc.paper.plugin.entrypoint.Entrypoint; -+import io.papermc.paper.plugin.entrypoint.dependency.DependencyUtil; -+import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext; ++import io.papermc.paper.plugin.entrypoint.dependency.MetaDependencyTree; +import io.papermc.paper.plugin.entrypoint.strategy.PluginGraphCycleException; +import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; +import io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage; -+import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; +import io.papermc.paper.plugin.provider.source.DirectoryProviderSource; +import io.papermc.paper.plugin.provider.source.FileProviderSource; +import org.bukkit.Bukkit; @@ -3529,8 +3704,7 @@ index 0000000000000000000000000000000000000000..c0e896343c22badd97c774c4ed1daa4e + private final CommandMap commandMap; + private final Server server; + -+ private final MutableGraph<String> dependencyGraph = GraphBuilder.directed().build(); -+ private final DependencyContext context = new GraphDependencyContext(this.dependencyGraph); ++ private final MetaDependencyTree dependencyTree = new MetaDependencyTree(GraphBuilder.directed().build()); + + public PaperPluginInstanceManager(PluginManager pluginManager, CommandMap commandMap, Server server) { + this.commandMap = commandMap; @@ -3569,12 +3743,12 @@ index 0000000000000000000000000000000000000000..c0e896343c22badd97c774c4ed1daa4e + this.lookupNames.putIfAbsent(providedPlugin.toLowerCase(java.util.Locale.ENGLISH), provided); + } + -+ DependencyUtil.buildDependencyGraph(this.dependencyGraph, configuration); ++ this.dependencyTree.add(configuration); + } + + // InvalidDescriptionException is never used, because the old JavaPluginLoader would wrap the exception. + public @Nullable Plugin loadPlugin(@NotNull Path path) throws InvalidPluginException, UnknownDependencyException { -+ RuntimePluginEntrypointHandler<SingularRuntimePluginProviderStorage> runtimePluginEntrypointHandler = new RuntimePluginEntrypointHandler<>(new SingularRuntimePluginProviderStorage()); ++ RuntimePluginEntrypointHandler<SingularRuntimePluginProviderStorage> runtimePluginEntrypointHandler = new RuntimePluginEntrypointHandler<>(new SingularRuntimePluginProviderStorage(this.dependencyTree)); + + try { + FILE_PROVIDER_SOURCE.registerProviders(runtimePluginEntrypointHandler, path); @@ -3603,7 +3777,7 @@ index 0000000000000000000000000000000000000000..c0e896343c22badd97c774c4ed1daa4e + public @NotNull Plugin[] loadPlugins(@NotNull Path directory) { + Preconditions.checkArgument(Files.isDirectory(directory), "Directory must be a directory"); // Avoid creating a directory if it doesn't exist + -+ RuntimePluginEntrypointHandler<MultiRuntimePluginProviderStorage> runtimePluginEntrypointHandler = new RuntimePluginEntrypointHandler<>(new MultiRuntimePluginProviderStorage()); ++ RuntimePluginEntrypointHandler<MultiRuntimePluginProviderStorage> runtimePluginEntrypointHandler = new RuntimePluginEntrypointHandler<>(new MultiRuntimePluginProviderStorage(this.dependencyTree)); + try { + DIRECTORY_PROVIDER_SOURCE.registerProviders(runtimePluginEntrypointHandler, directory); + runtimePluginEntrypointHandler.enter(Entrypoint.PLUGIN); @@ -3761,7 +3935,7 @@ index 0000000000000000000000000000000000000000..c0e896343c22badd97c774c4ed1daa4e + } + + public boolean isTransitiveDepend(@NotNull PluginMeta plugin, @NotNull PluginMeta depend) { -+ return this.context.isTransitiveDependency(plugin, depend); ++ return this.dependencyTree.isTransitiveDependency(plugin, depend); + } + + public boolean hasDependency(String pluginIdentifier) { @@ -3771,7 +3945,7 @@ index 0000000000000000000000000000000000000000..c0e896343c22badd97c774c4ed1daa4e + // Debug only + @ApiStatus.Internal + public MutableGraph<String> getDependencyGraph() { -+ return this.dependencyGraph; ++ return this.dependencyTree.getGraph(); + } +} diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java @@ -4076,15 +4250,17 @@ index 0000000000000000000000000000000000000000..5d50d1d312388e979c0e1cd53a6bf597 +} diff --git a/src/main/java/io/papermc/paper/plugin/manager/SingularRuntimePluginProviderStorage.java b/src/main/java/io/papermc/paper/plugin/manager/SingularRuntimePluginProviderStorage.java new file mode 100644 -index 0000000000000000000000000000000000000000..3d1b60e0427b1da1965fe81fe02176a70a8d56a2 +index 0000000000000000000000000000000000000000..3872f5a9287fc348d57812847ab18eb2d3e4b362 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/manager/SingularRuntimePluginProviderStorage.java -@@ -0,0 +1,80 @@ +@@ -0,0 +1,79 @@ +package io.papermc.paper.plugin.manager; + +import com.destroystokyo.paper.util.SneakyThrow; +import io.papermc.paper.plugin.entrypoint.Entrypoint; +import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler; ++import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext; ++import io.papermc.paper.plugin.entrypoint.dependency.MetaDependencyTree; +import io.papermc.paper.plugin.provider.PluginProvider; +import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; +import io.papermc.paper.plugin.storage.ServerPluginProviderStorage; @@ -4103,9 +4279,14 @@ index 0000000000000000000000000000000000000000..3d1b60e0427b1da1965fe81fe02176a7 + */ +class SingularRuntimePluginProviderStorage extends ServerPluginProviderStorage { + ++ private final MetaDependencyTree dependencyTree; + private PluginProvider<JavaPlugin> lastProvider; + private JavaPlugin singleLoaded; + ++ SingularRuntimePluginProviderStorage(MetaDependencyTree dependencyTree) { ++ this.dependencyTree = dependencyTree; ++ } ++ + @Override + public void register(PluginProvider<JavaPlugin> provider) { + super.register(provider); @@ -4128,19 +4309,6 @@ index 0000000000000000000000000000000000000000..3d1b60e0427b1da1965fe81fe02176a7 + return; + } + -+ // Manually validate dependencies, LEGACY BEHAVIOR. -+ // Normally it is logged, but manually adding one plugin will cause it to actually throw exceptions. -+ PluginDescriptionFile descriptionFile = (PluginDescriptionFile) provider.getMeta(); -+ List<String> missingDependencies = new ArrayList<>(); -+ for (String dependency : descriptionFile.getDepend()) { -+ if (!PaperPluginManagerImpl.getInstance().isPluginEnabled(dependency)) { -+ missingDependencies.add(dependency); -+ } -+ } -+ if (!missingDependencies.isEmpty()) { -+ throw new UnknownDependencyException(missingDependencies, provider.getFileName().toString()); -+ } -+ + // Go through normal plugin loading logic + super.enter(); + } @@ -4159,6 +4327,11 @@ index 0000000000000000000000000000000000000000..3d1b60e0427b1da1965fe81fe02176a7 + public Optional<JavaPlugin> getSingleLoaded() { + return Optional.ofNullable(this.singleLoaded); + } ++ ++ @Override ++ public MetaDependencyTree getDependencyTree() { ++ return this.dependencyTree; ++ } +} diff --git a/src/main/java/io/papermc/paper/plugin/manager/StupidSPMPermissionManagerWrapper.java b/src/main/java/io/papermc/paper/plugin/manager/StupidSPMPermissionManagerWrapper.java new file mode 100644 @@ -4210,14 +4383,15 @@ index 0000000000000000000000000000000000000000..ea8cf22c35242eb9f3914b95df00e205 +} diff --git a/src/main/java/io/papermc/paper/plugin/provider/PluginProvider.java b/src/main/java/io/papermc/paper/plugin/provider/PluginProvider.java new file mode 100644 -index 0000000000000000000000000000000000000000..3fc9246c111f862438f2cebda7d429c1c0b9582e +index 0000000000000000000000000000000000000000..fd9748c693a029c0d75f6ad813ea46a2b528d140 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/provider/PluginProvider.java -@@ -0,0 +1,55 @@ +@@ -0,0 +1,56 @@ +package io.papermc.paper.plugin.provider; + +import io.papermc.paper.plugin.configuration.PluginMeta; +import io.papermc.paper.plugin.provider.configuration.LoadOrderConfiguration; ++import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; @@ -4266,7 +4440,7 @@ index 0000000000000000000000000000000000000000..3fc9246c111f862438f2cebda7d429c1 + LoadOrderConfiguration createConfiguration(@NotNull Map<String, PluginProvider<?>> toLoad); + + // Returns a list of missing dependencies -+ List<String> validateDependencies(@NotNull Map<String, PluginProvider<?>> toLoad); ++ List<String> validateDependencies(@NotNull DependencyContext context); + +} diff --git a/src/main/java/io/papermc/paper/plugin/provider/ProviderStatus.java b/src/main/java/io/papermc/paper/plugin/provider/ProviderStatus.java @@ -4978,19 +5152,19 @@ index 0000000000000000000000000000000000000000..a180612a1ec395202dbae1ca5b97ec01 +} diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java new file mode 100644 -index 0000000000000000000000000000000000000000..1572c3942c850eb36df38cf06395bcafa24e18e0 +index 0000000000000000000000000000000000000000..2f2e183cdee865448ca90d2da9e3db7135b741f5 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java -@@ -0,0 +1,40 @@ +@@ -0,0 +1,47 @@ +package io.papermc.paper.plugin.provider.source; + +import com.mojang.logging.LogUtils; +import io.papermc.paper.plugin.entrypoint.EntrypointHandler; +import org.slf4j.Logger; + ++import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.Path; -+import java.util.logging.Level; + +/** + * Loads all plugin providers in the given directory. @@ -5001,7 +5175,7 @@ index 0000000000000000000000000000000000000000..1572c3942c850eb36df38cf06395bcaf + private static final Logger LOGGER = LogUtils.getClassLogger(); + + public DirectoryProviderSource() { -+ super("File '%s'"::formatted); ++ super("Directory '%s'"::formatted); + } + + @Override @@ -5011,20 +5185,27 @@ index 0000000000000000000000000000000000000000..1572c3942c850eb36df38cf06395bcaf + Files.createDirectories(context); + } + -+ Files.walk(context, 1).filter(Files::isRegularFile).forEach((path) -> { -+ try { -+ super.registerProviders(entrypointHandler, path); -+ } catch (IllegalArgumentException ignored) { -+ // Ignore initial argument exceptions -+ } catch (Exception e) { -+ LOGGER.error("Error loading plugin: " + e.getMessage(), e); -+ } -+ }); ++ Files.walk(context, 1, FileVisitOption.FOLLOW_LINKS) ++ .filter(this::isValidFile) ++ .forEach((path) -> { ++ try { ++ super.registerProviders(entrypointHandler, path); ++ } catch (IllegalArgumentException ignored) { ++ // Ignore initial argument exceptions ++ } catch (Exception e) { ++ LOGGER.error("Error loading plugin: " + e.getMessage(), e); ++ } ++ }); ++ } ++ ++ public boolean isValidFile(Path path) { ++ // Avoid loading plugins that start with a dot ++ return Files.isRegularFile(path) && !path.startsWith("."); + } +} diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/FileProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/FileProviderSource.java new file mode 100644 -index 0000000000000000000000000000000000000000..3b1215fcfa1b9de42d8f946c4e2b4572e8834f8d +index 0000000000000000000000000000000000000000..c828eeb8b0b87ee7f3e76a4b0ee146c86348061c --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/provider/source/FileProviderSource.java @@ -0,0 +1,157 @@ @@ -5081,7 +5262,7 @@ index 0000000000000000000000000000000000000000..3b1215fcfa1b9de42d8f946c4e2b4572 + JarFile file = new JarFile(context.toFile(), true, JarFile.OPEN_READ, JarFile.runtimeVersion()); + PluginFileType<?, ?> type = PluginFileType.guessType(file); + if (type == null) { -+ throw new IllegalArgumentException(source + " is not a valid plugin file, cannot load a plugin from it!"); ++ throw new IllegalArgumentException(source + " does not contain a " + String.join(" or ", PluginFileType.getConfigTypes()) + "! Could not determine plugin type, cannot load a plugin from it!"); + } + + type.register(entrypointHandler, file, context); @@ -5131,7 +5312,7 @@ index 0000000000000000000000000000000000000000..3b1215fcfa1b9de42d8f946c4e2b4572 + try (JarFile file = new JarFile(path.toFile())) { + PluginFileType<?, ?> type = PluginFileType.guessType(file); + if (type == null) { -+ throw new IllegalArgumentException(path + " is not a valid plugin file, cannot load a plugin from it!"); ++ throw new IllegalArgumentException(path + " does not contain a " + String.join(" or ", PluginFileType.getConfigTypes()) + "! Could not determine plugin type, cannot load a plugin from it!"); + } + + return type.getConfig(file).getName(); @@ -5242,10 +5423,10 @@ index 0000000000000000000000000000000000000000..6d247819ee842eb054a74711a0e5805a +} diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/PluginFileType.java b/src/main/java/io/papermc/paper/plugin/provider/type/PluginFileType.java new file mode 100644 -index 0000000000000000000000000000000000000000..22c25dc6fdfd336f5074fa52c3a4e8128d433ccc +index 0000000000000000000000000000000000000000..87128685015d550440a798028f50be24bc755f6c --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/provider/type/PluginFileType.java -@@ -0,0 +1,77 @@ +@@ -0,0 +1,85 @@ +package io.papermc.paper.plugin.provider.type; + +import io.papermc.paper.plugin.configuration.PluginMeta; @@ -5258,6 +5439,7 @@ index 0000000000000000000000000000000000000000..22c25dc6fdfd336f5074fa52c3a4e812 +import org.jetbrains.annotations.Nullable; + +import java.nio.file.Path; ++import java.util.ArrayList; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; @@ -5269,6 +5451,8 @@ index 0000000000000000000000000000000000000000..22c25dc6fdfd336f5074fa52c3a4e812 + */ +public abstract class PluginFileType<T, C extends PluginMeta> { + ++ private static final List<String> CONFIG_TYPES = new ArrayList<>(); ++ + public static final PluginFileType<PaperPluginParent, PaperPluginMeta> PAPER = new PluginFileType<>("paper-plugin.yml", PaperPluginParent.FACTORY) { + @Override + protected void register(EntrypointHandler entrypointHandler, PaperPluginParent parent) { @@ -5296,6 +5480,7 @@ index 0000000000000000000000000000000000000000..22c25dc6fdfd336f5074fa52c3a4e812 + PluginFileType(String config, PluginTypeFactory<T, C> factory) { + this.config = config; + this.factory = factory; ++ CONFIG_TYPES.add(config); + } + + @Nullable @@ -5322,6 +5507,10 @@ index 0000000000000000000000000000000000000000..22c25dc6fdfd336f5074fa52c3a4e812 + } + + protected abstract void register(EntrypointHandler entrypointHandler, T provider); ++ ++ public static List<String> getConfigTypes() { ++ return CONFIG_TYPES; ++ } +} diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/PluginTypeFactory.java b/src/main/java/io/papermc/paper/plugin/provider/type/PluginTypeFactory.java new file mode 100644 @@ -5455,7 +5644,7 @@ index 0000000000000000000000000000000000000000..b7e8a5ba375a558e0442aa9facf96954 +} 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..016c5af192948a3908f77aa6e3e6db1ce31c78ad +index 0000000000000000000000000000000000000000..009a055c36378353b1b156e04b230519a577bd50 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginParent.java @@ -0,0 +1,257 @@ @@ -5557,11 +5746,11 @@ index 0000000000000000000000000000000000000000..016c5af192948a3908f77aa6e3e6db1c + } + + @Override -+ public List<String> validateDependencies(@NotNull Map<String, PluginProvider<?>> toLoad) { ++ public List<String> validateDependencies(@NotNull DependencyContext context) { + List<String> missingDependencies = new ArrayList<>(); + for (DependencyConfiguration configuration : this.getMeta().getDependencies()) { + String dependency = configuration.name(); -+ if (configuration.required() && configuration.bootstrap() && !toLoad.containsKey(dependency)) { ++ if (configuration.required() && configuration.bootstrap() && !context.hasDependency(dependency)) { + missingDependencies.add(dependency); + } + } @@ -5663,8 +5852,8 @@ index 0000000000000000000000000000000000000000..016c5af192948a3908f77aa6e3e6db1c + } + + @Override -+ public List<String> validateDependencies(@NotNull Map<String, PluginProvider<?>> toLoad) { -+ return DependencyUtil.validateSimple(this.getMeta(), toLoad); ++ public List<String> validateDependencies(@NotNull DependencyContext context) { ++ return DependencyUtil.validateSimple(this.getMeta(), context); + } + + @Override @@ -5858,7 +6047,7 @@ index 0000000000000000000000000000000000000000..b2a6544e321fa61c58bdf5684231de10 +} 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..d5db789074ca5a9e005c26a221ee3879252b3d6c +index 0000000000000000000000000000000000000000..96b252b33047705f6c48917cc1e4e1edec3cf03a --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProvider.java @@ -0,0 +1,190 @@ @@ -6022,8 +6211,8 @@ index 0000000000000000000000000000000000000000..d5db789074ca5a9e005c26a221ee3879 + } + + @Override -+ public List<String> validateDependencies(@NotNull Map<String, PluginProvider<?>> toLoad) { -+ return DependencyUtil.validateSimple(this.getMeta(), toLoad); ++ public List<String> validateDependencies(@NotNull DependencyContext context) { ++ return DependencyUtil.validateSimple(this.getMeta(), context); + } + + @Override @@ -6105,10 +6294,10 @@ index 0000000000000000000000000000000000000000..14ed05945ba5bfeb2b539d4786278b0e + diff --git a/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java new file mode 100644 -index 0000000000000000000000000000000000000000..2851c22ed74792bf7b60139c46776407e5163463 +index 0000000000000000000000000000000000000000..94207f1489dc024dc660cfacaa107cd0c094b99f --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java -@@ -0,0 +1,57 @@ +@@ -0,0 +1,58 @@ +package io.papermc.paper.plugin.storage; + +import com.mojang.logging.LogUtils; @@ -6118,19 +6307,13 @@ index 0000000000000000000000000000000000000000..2851c22ed74792bf7b60139c46776407 +import io.papermc.paper.plugin.bootstrap.PluginProviderContextImpl; +import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; +import io.papermc.paper.plugin.entrypoint.dependency.DependencyContextHolder; -+import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy; -+import io.papermc.paper.plugin.entrypoint.strategy.PluginGraphCycleException; ++import io.papermc.paper.plugin.entrypoint.strategy.modern.ModernPluginLoadingStrategy; +import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; +import io.papermc.paper.plugin.provider.PluginProvider; +import io.papermc.paper.plugin.provider.ProviderStatus; +import io.papermc.paper.plugin.provider.ProviderStatusHolder; -+import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; -+import io.papermc.paper.plugin.provider.configuration.type.DependencyConfiguration; +import org.slf4j.Logger; + -+import java.util.ArrayList; -+import java.util.List; -+ +public class BootstrapProviderStorage extends SimpleProviderStorage<PluginBootstrap> { + + private static final Logger LOGGER = LogUtils.getClassLogger(); @@ -6150,7 +6333,7 @@ index 0000000000000000000000000000000000000000..2851c22ed74792bf7b60139c46776407 + PluginProviderContext context = PluginProviderContextImpl.of(provider, PluginInitializerManager.instance().pluginDirectoryPath()); + provided.bootstrap(context); + return true; -+ } catch (Exception e) { ++ } catch (Throwable e) { + LOGGER.error("Failed to run bootstrapper for %s. This plugin will not be loaded.".formatted(provider.getSource()), e); + if (provider instanceof ProviderStatusHolder statusHolder) { + statusHolder.setStatus(ProviderStatus.ERRORED); @@ -6158,6 +6341,13 @@ index 0000000000000000000000000000000000000000..2851c22ed74792bf7b60139c46776407 + return false; + } + } ++ ++ @Override ++ public void onGenericError(PluginProvider<PluginBootstrap> provider) { ++ if (provider instanceof ProviderStatusHolder statusHolder) { ++ statusHolder.setStatus(ProviderStatus.ERRORED); ++ } ++ } + })); + } + @@ -6168,14 +6358,14 @@ index 0000000000000000000000000000000000000000..2851c22ed74792bf7b60139c46776407 +} diff --git a/src/main/java/io/papermc/paper/plugin/storage/ConfiguredProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/ConfiguredProviderStorage.java new file mode 100644 -index 0000000000000000000000000000000000000000..c49fd0d21f5c591fb2076ac87f158bca1a8e12b1 +index 0000000000000000000000000000000000000000..8ef4806cadabe56264dd861f1a1854b2354b3b5c --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/storage/ConfiguredProviderStorage.java @@ -0,0 +1,17 @@ +package io.papermc.paper.plugin.storage; + +import io.papermc.paper.plugin.entrypoint.strategy.LegacyPluginLoadingStrategy; -+import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy; ++import io.papermc.paper.plugin.entrypoint.strategy.modern.ModernPluginLoadingStrategy; +import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; + +public abstract class ConfiguredProviderStorage<T> extends SimpleProviderStorage<T> { @@ -6291,13 +6481,17 @@ index 0000000000000000000000000000000000000000..cb9b13522a976b82bcb71cef486f11f4 +} diff --git a/src/main/java/io/papermc/paper/plugin/storage/SimpleProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/SimpleProviderStorage.java new file mode 100644 -index 0000000000000000000000000000000000000000..9abfc8778614f7bc4da32a27c7f4964caff05c63 +index 0000000000000000000000000000000000000000..861c245290696eef0fca846c3026b407593fdce1 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/storage/SimpleProviderStorage.java -@@ -0,0 +1,85 @@ +@@ -0,0 +1,93 @@ +package io.papermc.paper.plugin.storage; + ++import com.google.common.graph.GraphBuilder; ++import com.google.common.graph.MutableGraph; +import com.mojang.logging.LogUtils; ++import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext; ++import io.papermc.paper.plugin.entrypoint.dependency.MetaDependencyTree; +import io.papermc.paper.plugin.entrypoint.strategy.PluginGraphCycleException; +import io.papermc.paper.plugin.entrypoint.strategy.ProviderLoadingStrategy; +import io.papermc.paper.plugin.provider.PluginProvider; @@ -6329,7 +6523,7 @@ index 0000000000000000000000000000000000000000..9abfc8778614f7bc4da32a27c7f4964c + this.filterLoadingProviders(providerList); + + try { -+ for (ProviderLoadingStrategy.ProviderPair<T> providerPair : this.strategy.loadProviders(providerList)) { ++ for (ProviderLoadingStrategy.ProviderPair<T> providerPair : this.strategy.loadProviders(providerList, this.getDependencyTree())) { + this.processProvided(providerPair.provider(), providerPair.provided()); + } + } catch (PluginGraphCycleException exception) { @@ -6337,6 +6531,10 @@ index 0000000000000000000000000000000000000000..9abfc8778614f7bc4da32a27c7f4964c + } + } + ++ public MetaDependencyTree getDependencyTree() { ++ return new MetaDependencyTree(GraphBuilder.directed().build()); ++ } ++ + @Override + public Iterable<PluginProvider<T>> getRegisteredProviders() { + return this.providers; @@ -6823,16 +7021,83 @@ index 0000000000000000000000000000000000000000..1d14f530ef888102e47eeeaf0d1a6076 + throw new UnsupportedOperationException("Not supported."); + } +} +diff --git a/src/test/java/io/papermc/paper/plugin/PluginDependencyValidationTest.java b/src/test/java/io/papermc/paper/plugin/PluginDependencyValidationTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e08a22a753aba0c83894db7216a50724a9393dfa +--- /dev/null ++++ b/src/test/java/io/papermc/paper/plugin/PluginDependencyValidationTest.java +@@ -0,0 +1,59 @@ ++package io.papermc.paper.plugin; ++ ++import io.papermc.paper.plugin.entrypoint.dependency.MetaDependencyTree; ++import org.junit.Test; ++ ++import java.util.List; ++ ++import static org.hamcrest.MatcherAssert.assertThat; ++ ++public class PluginDependencyValidationTest { ++ ++ private static final TestPluginMeta MAIN; ++ private static final TestPluginMeta HARD_DEPENDENCY_1; ++ private static final TestPluginMeta SOFT_DEPENDENCY_1; ++ ++ public static final String ROOT_NAME = "main"; ++ ++ public static final String REGISTERED_HARD_DEPEND = "hard1"; ++ public static final String REGISTERED_SOFT_DEPEND = "soft1"; ++ public static final String UNREGISTERED_HARD_DEPEND = "hard2"; ++ public static final String UNREGISTERED_SOFT_DEPEND = "soft2"; ++ ++ static { ++ MAIN = new TestPluginMeta(ROOT_NAME); ++ MAIN.setSoftDependencies(List.of(REGISTERED_SOFT_DEPEND, UNREGISTERED_SOFT_DEPEND)); ++ MAIN.setHardDependencies(List.of(REGISTERED_HARD_DEPEND, UNREGISTERED_HARD_DEPEND)); ++ ++ HARD_DEPENDENCY_1 = new TestPluginMeta(REGISTERED_HARD_DEPEND); ++ SOFT_DEPENDENCY_1 = new TestPluginMeta(REGISTERED_SOFT_DEPEND); ++ } ++ ++ @Test ++ public void testDependencyTree() { ++ MetaDependencyTree tree = new MetaDependencyTree(); ++ tree.add(MAIN); ++ tree.add(HARD_DEPENDENCY_1); ++ tree.add(SOFT_DEPENDENCY_1); ++ ++ // Test simple transitive dependencies ++ assertThat("%s was not a transitive dependency of %s".formatted(ROOT_NAME, REGISTERED_SOFT_DEPEND), tree.isTransitiveDependency(MAIN, SOFT_DEPENDENCY_1)); ++ assertThat("%s was not a transitive dependency of %s".formatted(ROOT_NAME, REGISTERED_HARD_DEPEND), tree.isTransitiveDependency(MAIN, HARD_DEPENDENCY_1)); ++ ++ assertThat("%s was a transitive dependency of %s".formatted(REGISTERED_SOFT_DEPEND, ROOT_NAME), !tree.isTransitiveDependency(SOFT_DEPENDENCY_1, MAIN)); ++ assertThat("%s was a transitive dependency of %s".formatted(REGISTERED_HARD_DEPEND, ROOT_NAME), !tree.isTransitiveDependency(HARD_DEPENDENCY_1, MAIN)); ++ ++ // Test to ensure that registered dependencies exist ++ assertThat("tree did not contain dependency %s".formatted(ROOT_NAME), tree.hasDependency(ROOT_NAME)); ++ assertThat("tree did not contain dependency %s".formatted(REGISTERED_HARD_DEPEND), tree.hasDependency(REGISTERED_HARD_DEPEND)); ++ assertThat("tree did not contain dependency %s".formatted(REGISTERED_SOFT_DEPEND), tree.hasDependency(REGISTERED_SOFT_DEPEND)); ++ ++ // Test to ensure unregistered dependencies don't exist ++ assertThat("tree contained dependency %s".formatted(UNREGISTERED_HARD_DEPEND), !tree.hasDependency(UNREGISTERED_HARD_DEPEND)); ++ assertThat("tree contained dependency %s".formatted(UNREGISTERED_SOFT_DEPEND), !tree.hasDependency(UNREGISTERED_SOFT_DEPEND)); ++ ++ // Test removal ++ tree.remove(HARD_DEPENDENCY_1); ++ assertThat("tree contained dependency %s".formatted(REGISTERED_HARD_DEPEND), !tree.hasDependency(REGISTERED_HARD_DEPEND)); ++ } ++} 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..d137461a3a1896a367c5245a99a9b30afd9f6ad5 +index 0000000000000000000000000000000000000000..9d393aca5e2de4d046dc1a1232d57318432e4742 --- /dev/null +++ b/src/test/java/io/papermc/paper/plugin/PluginLoadOrderTest.java -@@ -0,0 +1,146 @@ +@@ -0,0 +1,148 @@ +package io.papermc.paper.plugin; + ++import com.google.common.graph.GraphBuilder; ++import io.papermc.paper.plugin.entrypoint.dependency.MetaDependencyTree; +import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; -+import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy; ++import io.papermc.paper.plugin.entrypoint.strategy.modern.ModernPluginLoadingStrategy; +import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; +import io.papermc.paper.plugin.provider.PluginProvider; +import org.junit.Assert; @@ -6934,7 +7199,7 @@ index 0000000000000000000000000000000000000000..d137461a3a1896a367c5245a99a9b30a + + }); + -+ modernPluginLoadingStrategy.loadProviders(REGISTERED_PROVIDERS); ++ modernPluginLoadingStrategy.loadProviders(REGISTERED_PROVIDERS, new MetaDependencyTree(GraphBuilder.directed().build())); + } + + @Test @@ -7168,16 +7433,17 @@ index 0000000000000000000000000000000000000000..04903794a8ee4dd73162ae240862ff6d +} diff --git a/src/test/java/io/papermc/paper/plugin/TestJavaPluginProvider.java b/src/test/java/io/papermc/paper/plugin/TestJavaPluginProvider.java new file mode 100644 -index 0000000000000000000000000000000000000000..e3871de8a5e1c04b915927d852157c48b0f6612f +index 0000000000000000000000000000000000000000..a4ef50027e4411f13ed840919136ca9ee4c58c41 --- /dev/null +++ b/src/test/java/io/papermc/paper/plugin/TestJavaPluginProvider.java -@@ -0,0 +1,76 @@ +@@ -0,0 +1,77 @@ +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 io.papermc.paper.plugin.provider.entrypoint.DependencyContext; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; +import org.jetbrains.annotations.NotNull; + @@ -7244,8 +7510,8 @@ index 0000000000000000000000000000000000000000..e3871de8a5e1c04b915927d852157c48 + } + + @Override -+ public List<String> validateDependencies(@NotNull Map<String, PluginProvider<?>> toLoad) { -+ return DependencyUtil.validateSimple(this.getMeta(), toLoad); ++ public List<String> validateDependencies(@NotNull DependencyContext context) { ++ return DependencyUtil.validateSimple(this.getMeta(), context); + } +} diff --git a/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java b/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java |