diff --git a/patches/server/Paper-Plugins.patch b/patches/server/Paper-Plugins.patch index 6af874a419..92f54574cb 100644 --- a/patches/server/Paper-Plugins.patch +++ b/patches/server/Paper-Plugins.patch @@ -256,7 +256,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +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; @@ -271,10 +270,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +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.modern.ModernPluginLoadingStrategy; ++import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy; +import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; +import io.papermc.paper.plugin.manager.PaperPluginManagerImpl; +import io.papermc.paper.plugin.provider.PluginProvider; @@ -397,7 +394,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return false; + } + }); -+ modernPluginLoadingStrategy.loadProviders(pluginProviders, new MetaDependencyTree(GraphBuilder.directed().build())); ++ modernPluginLoadingStrategy.loadProviders(pluginProviders); + + rootProviders.add(entry.getKey().getDebugName(), entrypoint); + } @@ -1681,19 +1678,72 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +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.entrypoint.DependencyContext; ++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; ++import java.util.function.Predicate; + +@SuppressWarnings("UnstableApiUsage") +public class DependencyUtil { + -+ public static List validateSimple(PluginMeta meta, DependencyContext dependencyContext) { ++ @NotNull ++ public static MutableGraph buildDependencyGraph(@NotNull MutableGraph dependencyGraph, @NotNull PluginMeta configuration) { ++ List dependencies = new ArrayList<>(); ++ dependencies.addAll(configuration.getPluginDependencies()); ++ dependencies.addAll(configuration.getPluginSoftDependencies()); ++ ++ return buildDependencyGraph(dependencyGraph, configuration.getName(), dependencies); ++ } ++ ++ @NotNull ++ public static MutableGraph buildDependencyGraph(@NotNull MutableGraph dependencyGraph, String identifier, @NotNull Iterable 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 buildLoadGraph(@NotNull MutableGraph dependencyGraph, @NotNull LoadOrderConfiguration configuration, Predicate 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 addProvidedPlugin(@NotNull MutableGraph dependencyGraph, @NotNull String pluginName, @NotNull String providedName) { ++ dependencyGraph.putEdge(pluginName, providedName); ++ ++ return dependencyGraph; ++ } ++ ++ public static List validateSimple(PluginMeta meta, Map> toLoad) { + List missingDependencies = new ArrayList<>(); + for (String hardDependency : meta.getPluginDependencies()) { -+ if (!dependencyContext.hasDependency(hardDependency)) { ++ if (!toLoad.containsKey(hardDependency)) { + missingDependencies.add(hardDependency); + } + } @@ -1711,7 +1761,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + +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; + @@ -1720,9 +1769,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +@SuppressWarnings("UnstableApiUsage") +public class GraphDependencyContext implements DependencyContext { + -+ private final MutableGraph dependencyGraph; ++ private final Graph dependencyGraph; + -+ public GraphDependencyContext(MutableGraph dependencyGraph) { ++ public GraphDependencyContext(Graph dependencyGraph) { + this.dependencyGraph = dependencyGraph; + } + @@ -1750,123 +1799,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.dependencyGraph.nodes().contains(pluginIdentifier); + } + -+ public MutableGraph 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..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/MetaDependencyTree.java -@@ -0,0 +0,0 @@ -+package io.papermc.paper.plugin.entrypoint.dependency; -+ -+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 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 dependencies = new HashSet<>(); -+ -+ public MetaDependencyTree(MutableGraph 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); -+ } -+ -+ 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); -+ } -+ -+ 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 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 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 @@ -2240,7 +2172,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +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; @@ -2268,9 +2199,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override -+ public List> loadProviders(List> providers, MetaDependencyTree dependencyTree) { ++ public List> loadProviders(List> providers) { + List> javapluginsLoaded = new ArrayList<>(); -+ MutableGraph dependencyGraph = dependencyTree.getGraph(); ++ MutableGraph dependencyGraph = GraphBuilder.directed().build(); ++ GraphDependencyContext dependencyContext = new GraphDependencyContext(dependencyGraph); + + Map> providersToLoad = new HashMap<>(); + Set loadedPlugins = new HashSet<>(); @@ -2432,7 +2364,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + missingDependency = false; + + try { -+ this.configuration.applyContext(file, dependencyTree); ++ this.configuration.applyContext(file, dependencyContext); + T loadedPlugin = file.createInstance(); + this.warnIfPaperPlugin(file); + @@ -2464,7 +2396,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + providerIterator.remove(); + + try { -+ this.configuration.applyContext(file, dependencyTree); ++ this.configuration.applyContext(file, dependencyContext); + T loadedPlugin = file.createInstance(); + this.warnIfPaperPlugin(file); + @@ -2503,6 +2435,223 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } +} +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..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ModernPluginLoadingStrategy.java +@@ -0,0 +0,0 @@ ++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 implements ProviderLoadingStrategy { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ private final ProviderConfiguration configuration; ++ ++ public ModernPluginLoadingStrategy(ProviderConfiguration onLoad) { ++ this.configuration = onLoad; ++ } ++ ++ @Override ++ public List> loadProviders(List> pluginProviders) { ++ Map> providerMap = new HashMap<>(); ++ Map> providerMapMirror = Maps.transformValues(providerMap, (entry) -> entry.provider); ++ List> validatedProviders = new ArrayList<>(); ++ ++ // Populate provider map ++ for (PluginProvider provider : pluginProviders) { ++ PluginMeta providerConfig = provider.getMeta(); ++ PluginProviderEntry entry = new PluginProviderEntry<>(provider); ++ ++ PluginProviderEntry 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 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 provider : pluginProviders) { ++ PluginMeta configuration = provider.getMeta(); ++ ++ // Populate missing dependencies to capture if there are multiple missing ones. ++ List 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 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, 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 reversedTopographicSort; ++ try { ++ reversedTopographicSort = Lists.reverse(TopographicGraphSorter.sortGraph(loadOrderGraph)); ++ } catch (TopographicGraphSorter.GraphCycleException exception) { ++ List> 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 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> 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>, but we would have to check if each dependency exists there... just ++ // nicer to do it here TBH. ++ PluginProviderEntry 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 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> cycles, Map> providerMapMirror) { ++ LOGGER.error("================================="); ++ LOGGER.error("Circular plugin loading detected:"); ++ for (int i = 0; i < cycles.size(); i++) { ++ List 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 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 { ++ ++ private final PluginProvider provider; ++ private boolean provided; ++ ++ private PluginProviderEntry(PluginProvider 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..0000000000000000000000000000000000000000 @@ -2563,7 +2712,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +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; @@ -2572,12 +2720,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * Used by a {@link io.papermc.paper.plugin.storage.SimpleProviderStorage} to load plugin providers in a certain order. + *

+ * Returns providers loaded. -+ * + * @param

provider type + */ +public interface ProviderLoadingStrategy

{ + -+ List> loadProviders(List> providers, MetaDependencyTree dependencyTree); ++ List> loadProviders(List> providers); + + record ProviderPair

(PluginProvider

provider, P provided) { + @@ -2647,276 +2794,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + } +} -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..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/modern/LoadOrderTree.java -@@ -0,0 +0,0 @@ -+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> providerMap; -+ private final MutableGraph graph; -+ -+ public LoadOrderTree(Map> providerMapMirror, MutableGraph 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 getLoadOrder() throws PluginGraphCycleException { -+ List reversedTopographicSort; -+ try { -+ reversedTopographicSort = Lists.reverse(TopographicGraphSorter.sortGraph(this.graph)); -+ } catch (TopographicGraphSorter.GraphCycleException exception) { -+ List> 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 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> cycles, Map> providerMapMirror) { -+ LOGGER.error("================================="); -+ LOGGER.error("Circular plugin loading detected:"); -+ for (int i = 0; i < cycles.size(); i++) { -+ List 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 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..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/modern/ModernPluginLoadingStrategy.java -@@ -0,0 +0,0 @@ -+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 implements ProviderLoadingStrategy { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ private final ProviderConfiguration configuration; -+ -+ public ModernPluginLoadingStrategy(ProviderConfiguration onLoad) { -+ this.configuration = onLoad; -+ } -+ -+ @Override -+ public List> loadProviders(List> pluginProviders, MetaDependencyTree dependencyTree) { -+ Map> providerMap = new HashMap<>(); -+ Map> providerMapMirror = Maps.transformValues(providerMap, (entry) -> entry.provider); -+ List> validatedProviders = new ArrayList<>(); -+ -+ // Populate provider map -+ for (PluginProvider provider : pluginProviders) { -+ PluginMeta providerConfig = provider.getMeta(); -+ PluginProviderEntry entry = new PluginProviderEntry<>(provider); -+ -+ PluginProviderEntry 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 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 provider : pluginProviders) { -+ PluginMeta configuration = provider.getMeta(); -+ -+ // Populate missing dependencies to capture if there are multiple missing ones. -+ List 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); -+ } -+ } -+ -+ 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 reversedTopographicSort = loadOrderTree.getLoadOrder(); -+ List> 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>, but we would have to check if each dependency exists there... just -+ // nicer to do it here TBH. -+ PluginProviderEntry 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 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 { -+ -+ private final PluginProvider provider; -+ private boolean provided; -+ -+ private PluginProviderEntry(PluginProvider 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..0000000000000000000000000000000000000000 @@ -3022,8 +2899,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +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; @@ -3038,11 +2913,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +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; + +/** @@ -3052,39 +2924,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +@ApiStatus.Internal +public class DummyBukkitPluginLoader implements PluginLoader { + -+ private static final Pattern[] PATTERNS = new Pattern[0]; -+ + @Override + public @NotNull Plugin loadPlugin(@NotNull File file) throws InvalidPluginException, UnknownDependencyException { -+ try { -+ return PaperPluginManagerImpl.getInstance().loadPlugin(file); -+ } catch (InvalidDescriptionException e) { -+ throw new InvalidPluginException(e); -+ } ++ throw new UnsupportedOperationException(); + } + + @Override + public @NotNull PluginDescriptionFile getPluginDescription(@NotNull File file) throws InvalidDescriptionException { -+ 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); -+ } ++ throw new UnsupportedOperationException(); + } + + @Override + public @NotNull Pattern[] getPluginFileFilters() { -+ return PATTERNS; ++ throw new UnsupportedOperationException(); + } + + @Override @@ -3113,8 +2965,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +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; @@ -3129,12 +2979,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private static final Logger LOGGER = LogUtils.getClassLogger(); + private final List provided = new ArrayList<>(); + -+ private final MetaDependencyTree dependencyTree; -+ -+ MultiRuntimePluginProviderStorage(MetaDependencyTree dependencyTree) { -+ this.dependencyTree = dependencyTree; -+ } -+ + @Override + public void register(PluginProvider provider) { + if (provider instanceof PaperPluginParent.PaperServerPluginProvider) { @@ -3164,10 +3008,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + 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 @@ -3638,10 +3478,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +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.MetaDependencyTree; ++import io.papermc.paper.plugin.entrypoint.dependency.DependencyUtil; ++import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext; +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; @@ -3688,7 +3530,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private final CommandMap commandMap; + private final Server server; + -+ private final MetaDependencyTree dependencyTree = new MetaDependencyTree(GraphBuilder.directed().build()); ++ private final MutableGraph dependencyGraph = GraphBuilder.directed().build(); ++ private final DependencyContext context = new GraphDependencyContext(this.dependencyGraph); + + public PaperPluginInstanceManager(PluginManager pluginManager, CommandMap commandMap, Server server) { + this.commandMap = commandMap; @@ -3727,12 +3570,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.lookupNames.putIfAbsent(providedPlugin.toLowerCase(java.util.Locale.ENGLISH), provided); + } + -+ this.dependencyTree.add(configuration); ++ DependencyUtil.buildDependencyGraph(this.dependencyGraph, configuration); + } + + // InvalidDescriptionException is never used, because the old JavaPluginLoader would wrap the exception. + public @Nullable Plugin loadPlugin(@NotNull Path path) throws InvalidPluginException, UnknownDependencyException { -+ RuntimePluginEntrypointHandler runtimePluginEntrypointHandler = new RuntimePluginEntrypointHandler<>(new SingularRuntimePluginProviderStorage(this.dependencyTree)); ++ RuntimePluginEntrypointHandler runtimePluginEntrypointHandler = new RuntimePluginEntrypointHandler<>(new SingularRuntimePluginProviderStorage()); + + try { + FILE_PROVIDER_SOURCE.registerProviders(runtimePluginEntrypointHandler, path); @@ -3761,7 +3604,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + 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 runtimePluginEntrypointHandler = new RuntimePluginEntrypointHandler<>(new MultiRuntimePluginProviderStorage(this.dependencyTree)); ++ RuntimePluginEntrypointHandler runtimePluginEntrypointHandler = new RuntimePluginEntrypointHandler<>(new MultiRuntimePluginProviderStorage()); + try { + DIRECTORY_PROVIDER_SOURCE.registerProviders(runtimePluginEntrypointHandler, directory); + runtimePluginEntrypointHandler.enter(Entrypoint.PLUGIN); @@ -3919,7 +3762,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + public boolean isTransitiveDepend(@NotNull PluginMeta plugin, @NotNull PluginMeta depend) { -+ return this.dependencyTree.isTransitiveDependency(plugin, depend); ++ return this.context.isTransitiveDependency(plugin, depend); + } + + public boolean hasDependency(String pluginIdentifier) { @@ -3929,7 +3772,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Debug only + @ApiStatus.Internal + public MutableGraph getDependencyGraph() { -+ return this.dependencyTree.getGraph(); ++ return this.dependencyGraph; + } +} diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java @@ -4243,8 +4086,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +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; @@ -4263,14 +4104,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + */ +class SingularRuntimePluginProviderStorage extends ServerPluginProviderStorage { + -+ private final MetaDependencyTree dependencyTree; + private PluginProvider lastProvider; + private JavaPlugin singleLoaded; + -+ SingularRuntimePluginProviderStorage(MetaDependencyTree dependencyTree) { -+ this.dependencyTree = dependencyTree; -+ } -+ + @Override + public void register(PluginProvider provider) { + super.register(provider); @@ -4293,6 +4129,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + 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 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(); + } @@ -4311,11 +4160,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public Optional 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 @@ -4375,7 +4219,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + +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; @@ -4424,7 +4267,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + LoadOrderConfiguration createConfiguration(@NotNull Map> toLoad); + + // Returns a list of missing dependencies -+ List validateDependencies(@NotNull DependencyContext context); ++ 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 @@ -5141,9 +4984,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +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. @@ -5154,7 +4997,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private static final Logger LOGGER = LogUtils.getClassLogger(); + + public DirectoryProviderSource() { -+ super("Directory '%s'"::formatted); ++ super("File '%s'"::formatted); + } + + @Override @@ -5164,22 +5007,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + Files.createDirectories(context); + } + -+ 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("."); ++ 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); ++ } ++ }); + } +} 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 @@ -5717,11 +5553,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override -+ public List validateDependencies(@NotNull DependencyContext context) { ++ 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() && !context.hasDependency(dependency)) { ++ if (configuration.required() && configuration.bootstrap() && !toLoad.containsKey(dependency)) { + missingDependencies.add(dependency); + } + } @@ -5823,8 +5659,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override -+ public List validateDependencies(@NotNull DependencyContext context) { -+ return DependencyUtil.validateSimple(this.getMeta(), context); ++ public List validateDependencies(@NotNull Map> toLoad) { ++ return DependencyUtil.validateSimple(this.getMeta(), toLoad); + } + + @Override @@ -6182,8 +6018,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override -+ public List validateDependencies(@NotNull DependencyContext context) { -+ return DependencyUtil.validateSimple(this.getMeta(), context); ++ public List validateDependencies(@NotNull Map> toLoad) { ++ return DependencyUtil.validateSimple(this.getMeta(), toLoad); + } + + @Override @@ -6278,13 +6114,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +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.modern.ModernPluginLoadingStrategy; ++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 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 { + + private static final Logger LOGGER = LogUtils.getClassLogger(); @@ -6329,7 +6171,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +package io.papermc.paper.plugin.storage; + +import io.papermc.paper.plugin.entrypoint.strategy.LegacyPluginLoadingStrategy; -+import io.papermc.paper.plugin.entrypoint.strategy.modern.ModernPluginLoadingStrategy; ++import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy; +import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; + +public abstract class ConfiguredProviderStorage extends SimpleProviderStorage { @@ -6451,11 +6293,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +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; @@ -6487,7 +6325,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.filterLoadingProviders(providerList); + + try { -+ for (ProviderLoadingStrategy.ProviderPair providerPair : this.strategy.loadProviders(providerList, this.getDependencyTree())) { ++ for (ProviderLoadingStrategy.ProviderPair providerPair : this.strategy.loadProviders(providerList)) { + this.processProvided(providerPair.provider(), providerPair.provided()); + } + } catch (PluginGraphCycleException exception) { @@ -6495,10 +6333,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + -+ public MetaDependencyTree getDependencyTree() { -+ return new MetaDependencyTree(GraphBuilder.directed().build()); -+ } -+ + @Override + public Iterable> getRegisteredProviders() { + return this.providers; @@ -6993,10 +6827,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +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.modern.ModernPluginLoadingStrategy; ++import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy; +import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; +import io.papermc.paper.plugin.provider.PluginProvider; +import org.junit.Assert; @@ -7098,7 +6930,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + }); + -+ modernPluginLoadingStrategy.loadProviders(REGISTERED_PROVIDERS, new MetaDependencyTree(GraphBuilder.directed().build())); ++ modernPluginLoadingStrategy.loadProviders(REGISTERED_PROVIDERS); + } + + @Test @@ -7308,7 +7140,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +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; + @@ -7375,8 +7206,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override -+ public List validateDependencies(@NotNull DependencyContext context) { -+ return DependencyUtil.validateSimple(this.getMeta(), context); ++ 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