diff --git a/patches/server/Paper-Plugins.patch b/patches/server/Paper-Plugins.patch index bf4230d19e..f460040272 100644 --- a/patches/server/Paper-Plugins.patch +++ b/patches/server/Paper-Plugins.patch @@ -1708,34 +1708,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + void setContext(DependencyContext context); + +} -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..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyUtil.java -@@ -0,0 +0,0 @@ -+package io.papermc.paper.plugin.entrypoint.dependency; -+ -+import io.papermc.paper.plugin.configuration.PluginMeta; -+import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; -+ -+import java.util.ArrayList; -+import java.util.List; -+ -+@SuppressWarnings("UnstableApiUsage") -+public class DependencyUtil { -+ -+ public static List validateSimple(PluginMeta meta, DependencyContext dependencyContext) { -+ List missingDependencies = new ArrayList<>(); -+ for (String hardDependency : meta.getPluginDependencies()) { -+ if (!dependencyContext.hasDependency(hardDependency)) { -+ missingDependencies.add(hardDependency); -+ } -+ } -+ -+ return missingDependencies; -+ } -+} diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/GraphDependencyContext.java b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/GraphDependencyContext.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -2716,7 +2688,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +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; @@ -4557,6 +4528,159 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + +} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/LegacyPaperMeta.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/LegacyPaperMeta.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/LegacyPaperMeta.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.configuration; ++ ++import com.google.gson.reflect.TypeToken; ++import io.papermc.paper.plugin.provider.configuration.type.DependencyConfiguration; ++import io.papermc.paper.plugin.provider.configuration.type.PluginDependencyLifeCycle; ++import org.spongepowered.configurate.CommentedConfigurationNode; ++import org.spongepowered.configurate.ConfigurateException; ++import org.spongepowered.configurate.NodePath; ++import org.spongepowered.configurate.objectmapping.ConfigSerializable; ++import org.spongepowered.configurate.objectmapping.meta.Required; ++import org.spongepowered.configurate.serialize.SerializationException; ++import org.spongepowered.configurate.transformation.ConfigurationTransformation; ++ ++import java.util.EnumMap; ++import java.util.EnumSet; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++ ++class LegacyPaperMeta { ++ ++ ++ private static final TypeToken>> TYPE_TOKEN = new TypeToken<>() { ++ }; ++ ++ public static void migrate(CommentedConfigurationNode node) throws ConfigurateException { ++ ConfigurationTransformation.chain(notVersioned()).apply(node); ++ } ++ ++ private static ConfigurationTransformation notVersioned() { ++ return ConfigurationTransformation.builder() ++ .addAction(NodePath.path(), (path, value) -> { ++ boolean bootstrapSubSection = value.hasChild("bootstrap"); ++ boolean serverSubSection = value.hasChild("server"); ++ ++ // Ignore if using newer format ++ if (bootstrapSubSection || serverSubSection) { ++ return null; ++ } ++ ++ // First collect all load before elements ++ LegacyConfiguration legacyConfiguration; ++ try { ++ legacyConfiguration = value.require(LegacyConfiguration.class); ++ } catch (SerializationException exception) { ++ // Ignore if not present ++ return null; ++ } ++ ++ Map> dependencies = new EnumMap<>(PluginDependencyLifeCycle.class); ++ dependencies.put(PluginDependencyLifeCycle.BOOTSTRAP, new HashMap<>()); ++ dependencies.put(PluginDependencyLifeCycle.SERVER, new HashMap<>()); ++ ++ Map>> dependencyConfigurationMap = new HashMap<>(); ++ dependencyConfigurationMap.put(PluginDependencyLifeCycle.BOOTSTRAP, new HashMap<>()); ++ dependencyConfigurationMap.put(PluginDependencyLifeCycle.SERVER, new HashMap<>()); ++ ++ // Migrate loadafter ++ for (LegacyLoadConfiguration legacyConfig : legacyConfiguration.loadAfter) { ++ Set dependencyFlags = dependencyConfigurationMap ++ .get(legacyConfig.bootstrap ? PluginDependencyLifeCycle.BOOTSTRAP : PluginDependencyLifeCycle.SERVER) ++ .computeIfAbsent(legacyConfig.name, s -> EnumSet.noneOf(DependencyFlag.class)); ++ ++ dependencyFlags.add(DependencyFlag.LOAD_AFTER); ++ } ++ ++ // Migrate loadbefore ++ for (LegacyLoadConfiguration legacyConfig : legacyConfiguration.loadBefore) { ++ Set dependencyFlags = dependencyConfigurationMap ++ .get(legacyConfig.bootstrap ? PluginDependencyLifeCycle.BOOTSTRAP : PluginDependencyLifeCycle.SERVER) ++ .computeIfAbsent(legacyConfig.name, s -> EnumSet.noneOf(DependencyFlag.class)); ++ ++ dependencyFlags.add(DependencyFlag.LOAD_BEFORE); ++ } ++ ++ // Migrate dependencies ++ for (LegacyDependencyConfiguration legacyConfig : legacyConfiguration.dependencies) { ++ Set dependencyFlags = dependencyConfigurationMap ++ .get(legacyConfig.bootstrap ? PluginDependencyLifeCycle.BOOTSTRAP : PluginDependencyLifeCycle.SERVER) ++ .computeIfAbsent(legacyConfig.name, s -> EnumSet.noneOf(DependencyFlag.class)); ++ ++ dependencyFlags.add(DependencyFlag.DEPENDENCY); ++ if (legacyConfig.required) { ++ dependencyFlags.add(DependencyFlag.REQUIRED); ++ } ++ } ++ for (Map.Entry>> legacyTypes : dependencyConfigurationMap.entrySet()) { ++ Map flagMap = dependencies.get(legacyTypes.getKey()); ++ for (Map.Entry> entry : legacyTypes.getValue().entrySet()) { ++ Set flags = entry.getValue(); ++ ++ ++ DependencyConfiguration.LoadOrder loadOrder = DependencyConfiguration.LoadOrder.OMIT; ++ // These meanings are now swapped ++ if (flags.contains(DependencyFlag.LOAD_BEFORE)) { ++ loadOrder = DependencyConfiguration.LoadOrder.AFTER; ++ } else if (flags.contains(DependencyFlag.LOAD_AFTER)) { ++ loadOrder = DependencyConfiguration.LoadOrder.BEFORE; ++ } ++ ++ flagMap.put(entry.getKey(), new DependencyConfiguration( ++ loadOrder, ++ flags.contains(DependencyFlag.REQUIRED), ++ flags.contains(DependencyFlag.DEPENDENCY) ++ )); ++ } ++ } ++ ++ value.node("dependencies").set(TYPE_TOKEN.getType(), dependencies); ++ return null; ++ }) ++ .build(); ++ } ++ ++ @ConfigSerializable ++ record LegacyLoadConfiguration( ++ @Required String name, ++ boolean bootstrap ++ ) { ++ } ++ ++ @ConfigSerializable ++ private static class LegacyConfiguration { ++ ++ private List loadAfter = List.of(); ++ private List loadBefore = List.of(); ++ private List dependencies = List.of(); ++ } ++ ++ ++ @ConfigSerializable ++ public record LegacyDependencyConfiguration( ++ @Required String name, ++ boolean required, ++ boolean bootstrap ++ ) { ++ } ++ ++ enum DependencyFlag { ++ LOAD_AFTER, ++ LOAD_BEFORE, ++ REQUIRED, ++ DEPENDENCY ++ } ++ ++} diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/LoadOrderConfiguration.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/LoadOrderConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -4611,18 +4735,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; -+import io.leangen.geantyref.TypeToken; +import io.papermc.paper.configuration.constraint.Constraint; +import io.papermc.paper.configuration.serializer.ComponentSerializer; +import io.papermc.paper.configuration.serializer.EnumValueSerializer; -+import io.papermc.paper.configuration.serializer.collections.MapSerializer; +import io.papermc.paper.plugin.configuration.PluginMeta; -+import io.papermc.paper.plugin.provider.configuration.serializer.ImmutableListSerializer; +import io.papermc.paper.plugin.provider.configuration.serializer.PermissionConfigurationSerializer; +import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints; +import io.papermc.paper.plugin.provider.configuration.type.DependencyConfiguration; -+import io.papermc.paper.plugin.provider.configuration.type.LoadConfiguration; +import io.papermc.paper.plugin.provider.configuration.type.PermissionConfiguration; ++import io.papermc.paper.plugin.provider.configuration.type.PluginDependencyLifeCycle; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.bukkit.plugin.PluginLoadOrder; @@ -4639,7 +4760,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import org.spongepowered.configurate.yaml.YamlConfigurationLoader; + +import java.io.BufferedReader; ++import java.util.EnumMap; +import java.util.List; ++import java.util.Map; + +@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"}) +@ConfigSerializable @@ -4655,9 +4778,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private String bootstrapper; + @PluginConfigConstraints.PluginNameSpace + private String loader; -+ private List dependencies = List.of(); -+ private List loadBefore = List.of(); -+ private List loadAfter = List.of(); + private List provides = List.of(); + private boolean hasOpenClassloader = false; + @Required @@ -4674,6 +4794,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + @PluginConfigConstraints.PluginVersion + private String apiVersion; + ++ private Map> dependencies = new EnumMap<>(PluginDependencyLifeCycle.class); ++ + public PaperPluginMeta() { + } + @@ -4688,9 +4810,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return options.serializers((serializers) -> { + serializers + .register(new EnumValueSerializer()) -+ .register(MapSerializer.TYPE, new MapSerializer(false)) -+ .register(new TypeToken<>() { -+ }, new ImmutableListSerializer()) + .register(PermissionConfiguration.class, PermissionConfigurationSerializer.SERIALIZER) + .register(new ComponentSerializer()) + .registerAnnotatedObjects( @@ -4707,6 +4826,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + }) + .build(); + CommentedConfigurationNode node = loader.load(); ++ LegacyPaperMeta.migrate(node); + PaperPluginMeta pluginConfiguration = node.require(PaperPluginMeta.class); + + if (!node.node("author").virtual()) { @@ -4753,29 +4873,52 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + @Override + public @NotNull List getPluginDependencies() { -+ return this.dependencies.stream().filter((dependency) -> dependency.required() && !dependency.bootstrap()).map(DependencyConfiguration::name).toList(); ++ return this.dependencies.getOrDefault(PluginDependencyLifeCycle.SERVER, Map.of()) ++ .entrySet() ++ .stream() ++ .filter((entry) -> entry.getValue().required() && entry.getValue().joinClasspath()) ++ .map(Map.Entry::getKey) ++ .toList(); + } + + @Override + public @NotNull List getPluginSoftDependencies() { -+ return this.dependencies.stream().filter((dependency) -> !dependency.required() && !dependency.bootstrap()).map(DependencyConfiguration::name).toList(); ++ return this.dependencies.getOrDefault(PluginDependencyLifeCycle.SERVER, Map.of()) ++ .entrySet() ++ .stream() ++ .filter((entry) -> !entry.getValue().required() && entry.getValue().joinClasspath()) ++ .map(Map.Entry::getKey) ++ .toList(); + } + + @Override + public @NotNull List getLoadBeforePlugins() { -+ return this.loadBefore.stream().filter((dependency) -> !dependency.bootstrap()).map(LoadConfiguration::name).toList(); ++ return this.dependencies.getOrDefault(PluginDependencyLifeCycle.SERVER, Map.of()) ++ .entrySet() ++ .stream() ++ // This plugin will load BEFORE all dependencies (so dependencies will load AFTER plugin) ++ .filter((entry) -> entry.getValue().load() == DependencyConfiguration.LoadOrder.AFTER) ++ .map(Map.Entry::getKey) ++ .toList(); + } + + public @NotNull List getLoadAfterPlugins() { -+ return this.loadAfter.stream().filter((dependency) -> !dependency.bootstrap()).map(LoadConfiguration::name).toList(); ++ return this.dependencies.getOrDefault(PluginDependencyLifeCycle.SERVER, Map.of()) ++ .entrySet() ++ .stream() ++ // This plugin will load AFTER all dependencies (so dependencies will load BEFORE plugin) ++ .filter((entry) -> entry.getValue().load() == DependencyConfiguration.LoadOrder.BEFORE) ++ .map(Map.Entry::getKey) ++ .toList(); + } + -+ public List getLoadAfter() { -+ return this.loadAfter; ++ ++ public Map getServerDependencies() { ++ return this.dependencies.getOrDefault(PluginDependencyLifeCycle.SERVER, Map.of()); + } + -+ public List getLoadBefore() { -+ return this.loadBefore; ++ public Map getBoostrapDependencies() { ++ return this.dependencies.getOrDefault(PluginDependencyLifeCycle.BOOTSTRAP, Map.of()); + } + + @Override @@ -4835,9 +4978,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.hasOpenClassloader; + } + -+ public List getDependencies() { -+ return dependencies; -+ } +} diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/ImmutableCollectionSerializer.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/ImmutableCollectionSerializer.java new file mode 100644 @@ -5147,14 +5287,34 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +package io.papermc.paper.plugin.provider.configuration.type; + +import org.spongepowered.configurate.objectmapping.ConfigSerializable; -+import org.spongepowered.configurate.objectmapping.meta.Required; + +@ConfigSerializable +public record DependencyConfiguration( -+ @Required String name, ++ LoadOrder load, + boolean required, -+ boolean bootstrap ++ boolean joinClasspath +) { ++ ++ public DependencyConfiguration(boolean required, boolean joinClasspath) { ++ this(LoadOrder.OMIT, required, joinClasspath); ++ } ++ ++ public DependencyConfiguration(boolean required) { ++ this(required, true); ++ } ++ ++ public DependencyConfiguration() { ++ this(true); ++ } ++ ++ @ConfigSerializable ++ public enum LoadOrder { ++ // dependency will now load BEFORE your plugin ++ BEFORE, ++ // the dependency will now load AFTER your plugin ++ AFTER, ++ OMIT ++ } +} diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/type/LoadConfiguration.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/type/LoadConfiguration.java new file mode 100644 @@ -5193,6 +5353,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + PermissionDefault defaultPerm, + List permissions) { +} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/type/PluginDependencyLifeCycle.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/type/PluginDependencyLifeCycle.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/type/PluginDependencyLifeCycle.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.configuration.type; ++ ++public enum PluginDependencyLifeCycle { ++ BOOTSTRAP, ++ SERVER ++} 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..0000000000000000000000000000000000000000 @@ -5593,11 +5765,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import io.papermc.paper.plugin.configuration.PluginMeta; +import io.papermc.paper.plugin.provider.configuration.LoadOrderConfiguration; +import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; -+import io.papermc.paper.plugin.provider.configuration.type.LoadConfiguration; ++import io.papermc.paper.plugin.provider.configuration.type.DependencyConfiguration; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; ++import java.util.Map; + +public class PaperBootstrapOrderConfiguration implements LoadOrderConfiguration { + @@ -5608,14 +5781,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public PaperBootstrapOrderConfiguration(PaperPluginMeta paperPluginMeta) { + this.paperPluginMeta = paperPluginMeta; + -+ for (LoadConfiguration configuration : paperPluginMeta.getLoadAfter()) { -+ if (configuration.bootstrap()) { -+ this.loadAfter.add(configuration.name()); -+ } -+ } -+ for (LoadConfiguration configuration : paperPluginMeta.getLoadBefore()) { -+ if (configuration.bootstrap()) { -+ this.loadBefore.add(configuration.name()); ++ for (Map.Entry configuration : paperPluginMeta.getBoostrapDependencies().entrySet()) { ++ String name = configuration.getKey(); ++ DependencyConfiguration dependencyConfiguration = configuration.getValue(); ++ ++ if (dependencyConfiguration.load() == DependencyConfiguration.LoadOrder.AFTER) { ++ // This plugin will load BEFORE all dependencies (so dependencies will load AFTER plugin) ++ this.loadBefore.add(name); ++ } else if (dependencyConfiguration.load() == DependencyConfiguration.LoadOrder.BEFORE) { ++ // This plugin will load AFTER all dependencies (so dependencies will load BEFORE plugin) ++ this.loadAfter.add(name); + } + } + } @@ -5695,7 +5870,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + +import com.destroystokyo.paper.util.SneakyThrow; +import io.papermc.paper.plugin.bootstrap.PluginProviderContext; -+import io.papermc.paper.plugin.entrypoint.dependency.DependencyUtil; +import io.papermc.paper.plugin.provider.configuration.LoadOrderConfiguration; +import io.papermc.paper.plugin.provider.configuration.type.DependencyConfiguration; +import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; @@ -5791,9 +5965,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + @Override + public List validateDependencies(@NotNull DependencyContext context) { + List missingDependencies = new ArrayList<>(); -+ for (DependencyConfiguration configuration : this.getMeta().getDependencies()) { -+ String dependency = configuration.name(); -+ if (configuration.required() && configuration.bootstrap() && !context.hasDependency(dependency)) { ++ for (Map.Entry configuration : this.getMeta().getBoostrapDependencies().entrySet()) { ++ String dependency = configuration.getKey(); ++ if (configuration.getValue().required() && !context.hasDependency(dependency)) { + missingDependencies.add(dependency); + } + } @@ -5896,7 +6070,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + @Override + public List validateDependencies(@NotNull DependencyContext context) { -+ return DependencyUtil.validateSimple(this.getMeta(), context); ++ List missingDependencies = new ArrayList<>(); ++ for (Map.Entry dependency : this.getMeta().getServerDependencies().entrySet()) { ++ String name = dependency.getKey(); ++ if (dependency.getValue().required() && !context.hasDependency(name)) { ++ missingDependencies.add(name); ++ } ++ } ++ ++ return missingDependencies; + } + + @Override @@ -6098,7 +6280,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + +import com.destroystokyo.paper.util.SneakyThrow; +import com.destroystokyo.paper.utils.PaperPluginLogger; -+import io.papermc.paper.plugin.entrypoint.dependency.DependencyUtil; +import io.papermc.paper.plugin.manager.PaperPluginManagerImpl; +import io.papermc.paper.plugin.provider.configuration.LoadOrderConfiguration; +import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; @@ -6120,6 +6301,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + +import java.io.File; +import java.nio.file.Path; ++import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; @@ -6255,7 +6437,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + @Override + public List validateDependencies(@NotNull DependencyContext context) { -+ return DependencyUtil.validateSimple(this.getMeta(), context); ++ List missingDependencies = new ArrayList<>(); ++ for (String hardDependency : this.getMeta().getPluginDependencies()) { ++ if (!context.hasDependency(hardDependency)) { ++ missingDependencies.add(hardDependency); ++ } ++ } ++ ++ return missingDependencies; + } + + @Override @@ -7483,7 +7672,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +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; @@ -7554,7 +7742,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + @Override + public List validateDependencies(@NotNull DependencyContext context) { -+ return DependencyUtil.validateSimple(this.getMeta(), context); ++ List missingDependencies = new ArrayList<>(); ++ for (String hardDependency : this.getMeta().getPluginDependencies()) { ++ if (!context.hasDependency(hardDependency)) { ++ missingDependencies.add(hardDependency); ++ } ++ } ++ ++ return missingDependencies; + } +} diff --git a/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java b/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java