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;
}
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()));
}
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();
}
}
}
}
}
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);
}
Aggregations