Search in sources :

Example 1 with PluginCandidate

use of org.spongepowered.plugin.PluginCandidate in project SpongeCommon by SpongePowered.

the class DependencyResolver method resolveAndSortCandidates.

public static <T extends PluginResource> ResolutionResult<T> resolveAndSortCandidates(final Collection<PluginCandidate<T>> candidates, final Logger logger) {
    final Map<String, Node<T>> nodes = new HashMap<>();
    final ResolutionResult<T> resolutionResult = new ResolutionResult<>();
    for (final PluginCandidate<T> candidate : candidates) {
        final String id = candidate.metadata().id();
        // If we already have an entry, this is now a duplicate ID situation.
        if (nodes.containsKey(id)) {
            resolutionResult.duplicateIds().add(id);
        } else {
            nodes.put(id, new Node<>(candidate));
        }
    }
    for (final Map.Entry<String, Node<T>> entry : nodes.entrySet()) {
        // Attach deps, invalid deps will appear at this point.
        final Node<T> node = entry.getValue();
        for (final PluginDependency pd : node.candidate.metadata().dependencies()) {
            final boolean isOptional = pd.optional();
            final Node<T> dep = nodes.get(pd.id());
            if (dep == null) {
                if (isOptional) {
                    // just move on to the next dep
                    continue;
                }
                node.invalid = true;
                final String failure;
                if (pd.version() != null) {
                    failure = String.format("%s version %s", pd.id(), pd.version());
                } else {
                    failure = pd.id();
                }
                resolutionResult.missingDependencies().computeIfAbsent(entry.getValue().candidate, k -> new ArrayList<>()).add(failure);
                // no need to process this further
                node.checked = true;
                continue;
            }
            if (!DependencyResolver.checkVersion(pd.version(), dep.candidate.metadata().version())) {
                if (isOptional) {
                    // just move on to the next dep
                    continue;
                }
                resolutionResult.versionMismatch().computeIfAbsent(entry.getValue().candidate, k -> new ArrayList<>()).add(Tuple.of(pd.version().toString(), dep.candidate));
                node.invalid = true;
                // no need to process this further.
                node.checked = true;
            }
            if (pd.loadOrder() == PluginDependency.LoadOrder.BEFORE) {
                if (!pd.optional()) {
                    // Because of what we're about to do, we need to just make sure that
                    // if the "before" dep fails within here, then we still throw it out.
                    // Note, we can only do this for sorting, once sorted, if this loads
                    // but the before dep doesn't, well, it's on the plugin to solve, not
                    // us.
                    node.beforeRequiredDependency.add(node);
                }
                // don't bomb out the dep if this doesn't load - so set it to be optional.
                // however, we otherwise treat it as an AFTER dep on the target dep
                DependencyResolver.setDependency(dep, node, true);
            } else {
                DependencyResolver.setDependency(node, dep, pd.optional());
            }
        }
    }
    // Check for invalid deps
    DependencyResolver.checkCyclic(nodes.values(), resolutionResult);
    for (final Node<T> node : nodes.values()) {
        DependencyResolver.calculateSecondaryFailures(node, resolutionResult);
    }
    // Now to sort them.
    final List<Node<T>> original = nodes.values().stream().filter(x -> !x.invalid).collect(Collectors.toCollection(ArrayList::new));
    final List<Node<T>> toLoad = new ArrayList<>(original);
    final LinkedHashSet<Node<T>> sorted = new LinkedHashSet<>();
    toLoad.stream().filter(x -> x.dependencies.isEmpty() && x.optionalDependencies.isEmpty()).forEach(sorted::add);
    toLoad.removeIf(sorted::contains);
    int size = toLoad.size();
    boolean excludeOptionals = false;
    while (!toLoad.isEmpty()) {
        boolean containsOptionalDeps = false;
        for (final Node<T> node : toLoad) {
            if (sorted.containsAll(node.dependencies) && DependencyResolver.checkOptionalDependencies(excludeOptionals, sorted, node)) {
                final boolean hasOptionalDeps = !node.optionalDependencies.isEmpty();
                containsOptionalDeps |= hasOptionalDeps;
                sorted.add(node);
                if (excludeOptionals && hasOptionalDeps) {
                    logger.warn("Plugin {} will be loaded before its optional dependencies: [ {} ]", node.candidate.metadata().id(), node.optionalDependencies.stream().map(x -> x.candidate.metadata().id()).collect(Collectors.joining(", ")));
                }
            }
        }
        toLoad.removeIf(sorted::contains);
        if (toLoad.size() == size) {
            // without them.
            if (excludeOptionals || !containsOptionalDeps) {
                // We have a problem
                throw new IllegalStateException(String.format("Dependency resolver could not resolve order of all plugins.\n\n" + "Attempted to sort %d plugins: [ %s ]\n" + "Could not sort %d plugins: [ %s ]", original.size(), original.stream().map(x -> x.candidate.metadata().id()).collect(Collectors.joining(", ")), toLoad.size(), toLoad.stream().map(x -> x.candidate.metadata().id()).collect(Collectors.joining(", "))));
            }
            logger.warn("Failed to resolve plugin load order due to failed dependency resolution, attempting to resolve order ignoring optional" + " dependencies.");
            excludeOptionals = true;
        } else {
            size = toLoad.size();
            excludeOptionals = false;
        }
    }
    final Collection<PluginCandidate<T>> sortedSuccesses = resolutionResult.sortedSuccesses();
    for (final Node<T> x : sorted) {
        sortedSuccesses.add(x.candidate);
    }
    return resolutionResult;
}
Also used : PluginCandidate(org.spongepowered.plugin.PluginCandidate) Collection(java.util.Collection) ArtifactVersion(org.apache.maven.artifact.versioning.ArtifactVersion) Set(java.util.Set) HashMap(java.util.HashMap) Tuple(org.spongepowered.api.util.Tuple) Collectors(java.util.stream.Collectors) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) Objects(java.util.Objects) List(java.util.List) Logger(org.apache.logging.log4j.Logger) PluginDependency(org.spongepowered.plugin.metadata.model.PluginDependency) Map(java.util.Map) VersionRange(org.apache.maven.artifact.versioning.VersionRange) LinkedList(java.util.LinkedList) PluginResource(org.spongepowered.plugin.PluginResource) Nullable(org.checkerframework.checker.nullness.qual.Nullable) LinkedHashSet(java.util.LinkedHashSet) LinkedHashSet(java.util.LinkedHashSet) PluginDependency(org.spongepowered.plugin.metadata.model.PluginDependency) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) PluginCandidate(org.spongepowered.plugin.PluginCandidate) HashMap(java.util.HashMap) Map(java.util.Map)

Example 2 with PluginCandidate

use of org.spongepowered.plugin.PluginCandidate in project SpongeCommon by SpongePowered.

the class VanillaPluginManager method loadPlugins.

@SuppressWarnings("unchecked")
public void loadPlugins(final VanillaPluginPlatform platform) {
    this.locatedResources.putAll(platform.getResources());
    final Map<PluginCandidate<PluginResource>, PluginLanguageService<PluginResource>> pluginLanguageLookup = new HashMap<>();
    final Map<PluginLanguageService<PluginResource>, PluginLoader<PluginResource, PluginContainer>> pluginLoaders = new HashMap<>();
    // Initialise the plugin language loaders.
    for (final Map.Entry<PluginLanguageService<PluginResource>, List<PluginCandidate<PluginResource>>> candidate : platform.getCandidates().entrySet()) {
        final PluginLanguageService<PluginResource> languageService = candidate.getKey();
        final String loaderClass = languageService.pluginLoader();
        try {
            pluginLoaders.put(languageService, (PluginLoader<PluginResource, PluginContainer>) Class.forName(loaderClass).getConstructor().newInstance());
        } catch (final InstantiationException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
        candidate.getValue().forEach(x -> pluginLanguageLookup.put(x, languageService));
    }
    // Priority to platform plugins that will already exist here -- meaning the resolver will act upon them first
    // and if someone decides to give a plugin an ID that is the same as a platform plugin, the resolver will effectively
    // reject it.
    final Set<PluginCandidate<PluginResource>> resources = new LinkedHashSet<>();
    pluginLanguageLookup.keySet().stream().filter(x -> this.plugins.containsKey(x.metadata().id())).forEach(resources::add);
    resources.addAll(pluginLanguageLookup.keySet());
    final ResolutionResult<PluginResource> resolutionResult = DependencyResolver.resolveAndSortCandidates(resources, platform.logger());
    final Map<PluginCandidate<PluginResource>, String> failedInstances = new HashMap<>();
    final Map<PluginCandidate<PluginResource>, String> consequentialFailedInstances = new HashMap<>();
    final ClassLoader launchClassloader = VanillaLaunch.instance().getClass().getClassLoader();
    for (final PluginCandidate<PluginResource> candidate : resolutionResult.sortedSuccesses()) {
        final PluginContainer plugin = this.plugins.get(candidate.metadata().id());
        if (plugin != null) {
            if (plugin instanceof VanillaDummyPluginContainer) {
                continue;
            }
            // If we get here, we screwed up - duplicate IDs should have been detected earlier.
            // Place it in the resolution result... it'll then get picked up in the big error message
            resolutionResult.duplicateIds().add(candidate.metadata().id());
            // but this is our screw up, let's also make a big point of it
            final PrettyPrinter prettyPrinter = new PrettyPrinter(120).add("ATTEMPTED TO CREATE PLUGIN WITH DUPLICATE PLUGIN ID").centre().hr().addWrapped("Sponge attempted to create a second plugin with ID '%s'. This is not allowed - all plugins must have a unique " + "ID. Usually, Sponge will catch this earlier -- but in this case Sponge has validated two plugins with " + "the same ID. Please report this error to Sponge.", candidate.metadata().id()).add().add("Technical Details:").add("Plugins to load:", 4);
            resolutionResult.sortedSuccesses().forEach(x -> prettyPrinter.add("*" + x.metadata().id(), 4));
            prettyPrinter.add().add("Detected Duplicate IDs:", 4);
            resolutionResult.duplicateIds().forEach(x -> prettyPrinter.add("*" + x, 4));
            prettyPrinter.log(platform.logger(), Level.ERROR);
            continue;
        }
        // This should work fine, we're sorted so all deps should be in place at this stage.
        if (this.stillValid(candidate, consequentialFailedInstances)) {
            final PluginLanguageService<PluginResource> languageService = pluginLanguageLookup.get(candidate);
            final PluginLoader<PluginResource, PluginContainer> pluginLoader = pluginLoaders.get(languageService);
            try {
                final PluginContainer container = pluginLoader.loadPlugin(platform.getStandardEnvironment(), candidate, launchClassloader);
                this.addPlugin(container);
                this.containerToResource.put(container, candidate.resource());
            } catch (final InvalidPluginException e) {
                failedInstances.put(candidate, "Failed to construct: see stacktrace(s) above this message for details.");
                e.printStackTrace();
            }
        }
    }
    resolutionResult.printErrorsIfAny(failedInstances, consequentialFailedInstances, platform.logger());
    platform.logger().info("Loaded plugin(s): {}", this.sortedPlugins.stream().map(p -> p.metadata().id()).collect(Collectors.toList()));
}
Also used : LinkedHashSet(java.util.LinkedHashSet) SpongePluginManager(org.spongepowered.common.launch.plugin.SpongePluginManager) PluginLanguageService(org.spongepowered.plugin.PluginLanguageService) PluginCandidate(org.spongepowered.plugin.PluginCandidate) Level(org.apache.logging.log4j.Level) PrettyPrinter(org.spongepowered.common.util.PrettyPrinter) HashMap(java.util.HashMap) DependencyResolver(org.spongepowered.vanilla.launch.plugin.resolver.DependencyResolver) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) Object2ObjectOpenHashMap(it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap) PluginLoader(org.spongepowered.plugin.PluginLoader) Map(java.util.Map) LinkedHashSet(java.util.LinkedHashSet) IdentityHashMap(java.util.IdentityHashMap) ResolutionResult(org.spongepowered.vanilla.launch.plugin.resolver.ResolutionResult) Collection(java.util.Collection) Set(java.util.Set) InvalidPluginException(org.spongepowered.plugin.InvalidPluginException) Collectors(java.util.stream.Collectors) InvocationTargetException(java.lang.reflect.InvocationTargetException) Objects(java.util.Objects) Nullable(org.jetbrains.annotations.Nullable) List(java.util.List) PluginContainer(org.spongepowered.plugin.PluginContainer) PluginDependency(org.spongepowered.plugin.metadata.model.PluginDependency) VanillaPluginPlatform(org.spongepowered.vanilla.applaunch.plugin.VanillaPluginPlatform) Optional(java.util.Optional) PluginResource(org.spongepowered.plugin.PluginResource) Collections(java.util.Collections) Singleton(com.google.inject.Singleton) VanillaLaunch(org.spongepowered.vanilla.launch.VanillaLaunch) HashMap(java.util.HashMap) Object2ObjectOpenHashMap(it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap) IdentityHashMap(java.util.IdentityHashMap) PluginLanguageService(org.spongepowered.plugin.PluginLanguageService) PrettyPrinter(org.spongepowered.common.util.PrettyPrinter) InvalidPluginException(org.spongepowered.plugin.InvalidPluginException) PluginResource(org.spongepowered.plugin.PluginResource) ArrayList(java.util.ArrayList) List(java.util.List) PluginLoader(org.spongepowered.plugin.PluginLoader) PluginContainer(org.spongepowered.plugin.PluginContainer) PluginCandidate(org.spongepowered.plugin.PluginCandidate) InvocationTargetException(java.lang.reflect.InvocationTargetException) HashMap(java.util.HashMap) Object2ObjectOpenHashMap(it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap) Map(java.util.Map) IdentityHashMap(java.util.IdentityHashMap)

Example 3 with PluginCandidate

use of org.spongepowered.plugin.PluginCandidate in project SpongeCommon by SpongePowered.

the class VanillaPluginPlatform method createPluginCandidates.

public void createPluginCandidates() {
    for (final Map.Entry<String, PluginLanguageService<PluginResource>> languageEntry : this.languageServices.entrySet()) {
        final PluginLanguageService<PluginResource> languageService = languageEntry.getValue();
        for (final Map.Entry<String, Set<PluginResource>> resourcesEntry : this.locatorResources.entrySet()) {
            for (final PluginResource pluginResource : resourcesEntry.getValue()) {
                try {
                    final List<PluginCandidate<PluginResource>> candidates = languageService.createPluginCandidates(this.standardEnvironment, pluginResource);
                    if (candidates.isEmpty()) {
                        continue;
                    }
                    this.pluginCandidates.computeIfAbsent(languageService, k -> new LinkedList<>()).addAll(candidates);
                } catch (final Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}
Also used : JVMKeys(org.spongepowered.plugin.builtin.jvm.JVMKeys) PluginLanguageService(org.spongepowered.plugin.PluginLanguageService) IdentityHashMap(java.util.IdentityHashMap) Iterator(java.util.Iterator) PluginCandidate(org.spongepowered.plugin.PluginCandidate) PluginPlatform(org.spongepowered.common.applaunch.plugin.PluginPlatform) Set(java.util.Set) HashMap(java.util.HashMap) ServiceLoader(java.util.ServiceLoader) Key(org.spongepowered.plugin.blackboard.Key) HashSet(java.util.HashSet) List(java.util.List) Logger(org.apache.logging.log4j.Logger) PluginResourceLocatorService(org.spongepowered.plugin.PluginResourceLocatorService) StandardEnvironment(org.spongepowered.plugin.builtin.StandardEnvironment) Keys(org.spongepowered.plugin.blackboard.Keys) Map(java.util.Map) ServiceConfigurationError(java.util.ServiceConfigurationError) LinkedList(java.util.LinkedList) PluginResource(org.spongepowered.plugin.PluginResource) Path(java.nio.file.Path) Collections(java.util.Collections) Set(java.util.Set) HashSet(java.util.HashSet) PluginCandidate(org.spongepowered.plugin.PluginCandidate) LinkedList(java.util.LinkedList) PluginLanguageService(org.spongepowered.plugin.PluginLanguageService) PluginResource(org.spongepowered.plugin.PluginResource) IdentityHashMap(java.util.IdentityHashMap) HashMap(java.util.HashMap) Map(java.util.Map)

Example 4 with PluginCandidate

use of org.spongepowered.plugin.PluginCandidate in project SpongeCommon by SpongePowered.

the class ResolutionResult method printErrorsIfAny.

public void printErrorsIfAny(final Map<PluginCandidate<T>, String> failedInstance, final Map<PluginCandidate<T>, String> consequentialFailedInstance, final Logger logger) {
    final int noOfFailures = this.numberOfFailures() + failedInstance.size() + consequentialFailedInstance.size();
    if (noOfFailures == 0) {
        return;
    }
    final PrettyPrinter errorPrinter = new PrettyPrinter(120);
    errorPrinter.add("SPONGE PLUGINS FAILED TO LOAD").centre().hr().addWrapped("%d plugin(s) have unfulfilled or cyclic dependencies or failed to load. Your game will continue to load without" + " these plugins.", noOfFailures);
    if (!this.duplicateIds.isEmpty()) {
        errorPrinter.add();
        errorPrinter.add("The following plugins IDs were duplicated - some plugins will not have been loaded:");
        for (final String id : this.duplicateIds) {
            errorPrinter.add(" * %s", id);
        }
    }
    if (!this.missingDependencies.isEmpty()) {
        errorPrinter.add();
        errorPrinter.add("The following plugins are missing dependencies:");
        for (final Map.Entry<PluginCandidate<T>, Collection<String>> entry : this.missingDependencies.entrySet()) {
            errorPrinter.add(" * %s requires [ %s ]", entry.getKey().metadata().id(), String.join(", ", entry.getValue()));
        }
    }
    if (!this.versionMismatch.isEmpty()) {
        errorPrinter.add();
        errorPrinter.add("The following plugins require different version(s) of dependencies you have installed:");
        for (final Map.Entry<PluginCandidate<T>, Collection<Tuple<String, PluginCandidate<T>>>> entry : this.versionMismatch.entrySet()) {
            final PluginCandidate<T> candidate = entry.getKey();
            final Collection<Tuple<String, PluginCandidate<T>>> mismatchedDeps = entry.getValue();
            final String errorString = mismatchedDeps.stream().map(x -> String.format("%s version %s (currently version %s)", x.second().metadata().id(), x.first(), x.second().metadata().version())).collect(Collectors.joining(", "));
            errorPrinter.add(" * %s requires [ %s ]", candidate.metadata().id(), errorString);
        }
    }
    if (!this.cyclicDependency.isEmpty()) {
        errorPrinter.add();
        errorPrinter.add("The following plugins were found to have cyclic dependencies:");
        for (final Map.Entry<PluginCandidate<T>, Collection<PluginCandidate<T>>> node : this.cyclicDependency.entrySet()) {
            errorPrinter.add(" * %s has dependency cycle [ ... -> %s -> ... ]", node.getKey().metadata().id(), node.getValue().stream().map(x -> x.metadata().id()).collect(Collectors.joining(" -> ")));
        }
    }
    if (!failedInstance.isEmpty()) {
        errorPrinter.add();
        errorPrinter.add("The following plugins threw exceptions when being created (report these to the plugin authors):");
        for (final Map.Entry<PluginCandidate<T>, String> node : failedInstance.entrySet()) {
            errorPrinter.add(" * %s with the error message \"%s\"", node.getKey().metadata().id(), node.getValue());
        }
    }
    if (!this.cascadedFailure.isEmpty() || !consequentialFailedInstance.isEmpty()) {
        final Map<PluginCandidate<T>, String> mergedFailures = new HashMap<>(consequentialFailedInstance);
        for (final Map.Entry<PluginCandidate<T>, Collection<PluginCandidate<T>>> entry : this.cascadedFailure.entrySet()) {
            final String error = entry.getValue().stream().map(x -> x.metadata().id()).collect(Collectors.joining(", "));
            mergedFailures.merge(entry.getKey(), error, (old, incoming) -> old + ", " + incoming);
        }
        errorPrinter.add();
        errorPrinter.add("The following plugins are not loading because they depend on plugins that will not load:");
        for (final Map.Entry<PluginCandidate<T>, String> node : mergedFailures.entrySet()) {
            errorPrinter.add(" * %s depends on [ %s ]", node.getKey().metadata().id(), // so we just list all the plugins that failed
            node.getValue());
        }
    }
    errorPrinter.add().hr().addWrapped("DO NOT REPORT THIS TO SPONGE. These errors are not Sponge errors, they are plugin loading errors. Seek " + "support from the authors of the plugins listed above if you need help getting these plugins to load.").add();
    errorPrinter.addWrapped("Your game will continue to start without the %d plugins listed above. Other plugins will continue to load, " + "however you may wish to stop your game and fix these issues. For any missing dependencies, you " + "may be able to find them at https://ore.spongepowered.org/. For any plugins that have cyclic dependencies or threw " + "exceptions, it is likely a bug in the plugin.", noOfFailures);
    errorPrinter.log(logger, Level.ERROR);
}
Also used : HashSet(java.util.HashSet) Logger(org.apache.logging.log4j.Logger) PluginCandidate(org.spongepowered.plugin.PluginCandidate) Collection(java.util.Collection) Map(java.util.Map) Level(org.apache.logging.log4j.Level) PrettyPrinter(org.spongepowered.common.util.PrettyPrinter) HashMap(java.util.HashMap) Tuple(org.spongepowered.api.util.Tuple) PluginResource(org.spongepowered.plugin.PluginResource) Collectors(java.util.stream.Collectors) LinkedHashSet(java.util.LinkedHashSet) HashMap(java.util.HashMap) PluginCandidate(org.spongepowered.plugin.PluginCandidate) PrettyPrinter(org.spongepowered.common.util.PrettyPrinter) Collection(java.util.Collection) Map(java.util.Map) HashMap(java.util.HashMap) Tuple(org.spongepowered.api.util.Tuple)

Aggregations

HashMap (java.util.HashMap)4 HashSet (java.util.HashSet)4 Map (java.util.Map)4 PluginCandidate (org.spongepowered.plugin.PluginCandidate)4 PluginResource (org.spongepowered.plugin.PluginResource)4 Collection (java.util.Collection)3 LinkedHashSet (java.util.LinkedHashSet)3 List (java.util.List)3 Set (java.util.Set)3 Collectors (java.util.stream.Collectors)3 Logger (org.apache.logging.log4j.Logger)3 ArrayList (java.util.ArrayList)2 Collections (java.util.Collections)2 IdentityHashMap (java.util.IdentityHashMap)2 LinkedList (java.util.LinkedList)2 Objects (java.util.Objects)2 Level (org.apache.logging.log4j.Level)2 Tuple (org.spongepowered.api.util.Tuple)2 PrettyPrinter (org.spongepowered.common.util.PrettyPrinter)2 PluginLanguageService (org.spongepowered.plugin.PluginLanguageService)2