Paper Plugins Dependency Format Update (#9160)

* Rework dependency management (WIP)

* Revert "Rework dependency management (WIP)"

This reverts commit e046cd59c68743dc00303b1ab42317bf474abd6a.

* Correctly add soft dependencies to the dependency tree for classloading resolution

* Add support for new dependency config format

* Rebase

* swap load order meaning

* Dependencies should be required by default
This commit is contained in:
Owen1212055 2023-06-07 11:41:25 -04:00
parent c66a18ad54
commit 1004620742

View File

@ -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<String> validateSimple(PluginMeta meta, DependencyContext dependencyContext) {
+ List<String> 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<Map<PluginDependencyLifeCycle, Map<String, DependencyConfiguration>>> 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<PluginDependencyLifeCycle, Map<String, DependencyConfiguration>> dependencies = new EnumMap<>(PluginDependencyLifeCycle.class);
+ dependencies.put(PluginDependencyLifeCycle.BOOTSTRAP, new HashMap<>());
+ dependencies.put(PluginDependencyLifeCycle.SERVER, new HashMap<>());
+
+ Map<PluginDependencyLifeCycle, Map<String, Set<DependencyFlag>>> dependencyConfigurationMap = new HashMap<>();
+ dependencyConfigurationMap.put(PluginDependencyLifeCycle.BOOTSTRAP, new HashMap<>());
+ dependencyConfigurationMap.put(PluginDependencyLifeCycle.SERVER, new HashMap<>());
+
+ // Migrate loadafter
+ for (LegacyLoadConfiguration legacyConfig : legacyConfiguration.loadAfter) {
+ Set<DependencyFlag> 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<DependencyFlag> 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<DependencyFlag> 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<PluginDependencyLifeCycle, Map<String, Set<DependencyFlag>>> legacyTypes : dependencyConfigurationMap.entrySet()) {
+ Map<String, DependencyConfiguration> flagMap = dependencies.get(legacyTypes.getKey());
+ for (Map.Entry<String, Set<DependencyFlag>> entry : legacyTypes.getValue().entrySet()) {
+ Set<DependencyFlag> 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<LegacyLoadConfiguration> loadAfter = List.of();
+ private List<LegacyLoadConfiguration> loadBefore = List.of();
+ private List<LegacyDependencyConfiguration> 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<DependencyConfiguration> dependencies = List.of();
+ private List<LoadConfiguration> loadBefore = List.of();
+ private List<LoadConfiguration> loadAfter = List.of();
+ private List<String> provides = List.of();
+ private boolean hasOpenClassloader = false;
+ @Required
@ -4674,6 +4794,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ @PluginConfigConstraints.PluginVersion
+ private String apiVersion;
+
+ private Map<PluginDependencyLifeCycle, Map<String, DependencyConfiguration>> 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<String> 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<String> 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<String> 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<String> 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<LoadConfiguration> getLoadAfter() {
+ return this.loadAfter;
+
+ public Map<String, DependencyConfiguration> getServerDependencies() {
+ return this.dependencies.getOrDefault(PluginDependencyLifeCycle.SERVER, Map.of());
+ }
+
+ public List<LoadConfiguration> getLoadBefore() {
+ return this.loadBefore;
+ public Map<String, DependencyConfiguration> getBoostrapDependencies() {
+ return this.dependencies.getOrDefault(PluginDependencyLifeCycle.BOOTSTRAP, Map.of());
+ }
+
+ @Override
@ -4835,9 +4978,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return this.hasOpenClassloader;
+ }
+
+ public List<DependencyConfiguration> 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<Permission> 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<String, DependencyConfiguration> 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<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() && !context.hasDependency(dependency)) {
+ for (Map.Entry<String, DependencyConfiguration> 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<String> validateDependencies(@NotNull DependencyContext context) {
+ return DependencyUtil.validateSimple(this.getMeta(), context);
+ List<String> missingDependencies = new ArrayList<>();
+ for (Map.Entry<String, DependencyConfiguration> 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<String> validateDependencies(@NotNull DependencyContext context) {
+ return DependencyUtil.validateSimple(this.getMeta(), context);
+ List<String> 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<String> validateDependencies(@NotNull DependencyContext context) {
+ return DependencyUtil.validateSimple(this.getMeta(), context);
+ List<String> 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