use of org.spongepowered.plugin.PluginResource in project SpongeCommon by SpongePowered.
the class VanillaPlatformService method runScan.
@Override
public List<Map.Entry<String, Path>> runScan(final IEnvironment environment) {
VanillaPlatformService.pluginPlatform.locatePluginResources();
VanillaPlatformService.pluginPlatform.createPluginCandidates();
final AccessWidenerTransformationService accessWidener = environment.getProperty(AccessWidenerTransformationService.INSTANCE.get()).orElse(null);
final SuperclassChanger superclassChanger = environment.getProperty(SuperclassChanger.INSTANCE.get()).orElse(null);
final ILaunchPluginService mixin = environment.findLaunchPlugin(MixinLaunchPluginLegacy.NAME).orElse(null);
final List<Map.Entry<String, Path>> launchResources = new ArrayList<>();
for (final Map.Entry<String, Set<PluginResource>> resourcesEntry : VanillaPlatformService.pluginPlatform.getResources().entrySet()) {
final Set<PluginResource> resources = resourcesEntry.getValue();
for (final PluginResource resource : resources) {
// Handle Access Transformers
if ((accessWidener != null || mixin != null || superclassChanger != null) && resource instanceof JVMPluginResource) {
if (mixin != null) {
// Offer jar to the Mixin service
mixin.offerResource(((JVMPluginResource) resource).path(), ((JVMPluginResource) resource).path().getFileName().toString());
}
// Offer jar to the AW service
((JVMPluginResource) resource).manifest().ifPresent(manifest -> {
if (accessWidener != null) {
final String atFiles = manifest.getMainAttributes().getValue(Constants.ManifestAttributes.ACCESS_WIDENER);
if (atFiles != null) {
for (final String atFile : atFiles.split(",")) {
if (!atFile.endsWith(AccessWidenerTransformationService.ACCESS_WIDENER_EXTENSION)) {
continue;
}
try {
accessWidener.offerResource(((JVMPluginResource) resource).fileSystem().getPath(atFile).toUri().toURL(), atFile);
} catch (final MalformedURLException ex) {
VanillaPlatformService.pluginPlatform.logger().warn("Failed to read declared access widener {}, from {}:", atFile, resource.locator());
}
}
}
}
if (mixin != null && manifest.getMainAttributes().getValue(org.spongepowered.asm.util.Constants.ManifestAttributes.MIXINCONFIGS) != null) {
if (!VanillaPlatformService.isSponge((JVMPluginResource) resource)) {
VanillaPlatformService.pluginPlatform.logger().warn("Plugin from {} uses Mixins to modify the Minecraft Server. If something breaks, remove it before reporting the " + "problem to Sponge!", ((JVMPluginResource) resource).path());
}
}
if (superclassChanger != null) {
final String superclassChangeFiles = manifest.getMainAttributes().getValue(Constants.ManifestAttributes.SUPERCLASS_CHANGE);
if (superclassChangeFiles != null) {
for (final String superclassChangeFile : superclassChangeFiles.split(",")) {
if (!superclassChangeFile.endsWith(SuperclassChanger.SUPER_CLASS_EXTENSION)) {
continue;
}
try {
superclassChanger.offerResource(((JVMPluginResource) resource).fileSystem().getPath(superclassChangeFile).toUri().toURL(), superclassChangeFile);
} catch (final MalformedURLException ex) {
VanillaPlatformService.pluginPlatform.logger().warn("Failed to read declared superclass changer {}, from {}:", superclassChangeFile, resource.locator());
}
}
}
}
});
final Map.Entry<String, Path> entry = Maps.immutableEntry(((JVMPluginResource) resource).path().getFileName().toString(), ((JVMPluginResource) resource).path());
launchResources.add(entry);
}
}
}
return launchResources;
}
use of org.spongepowered.plugin.PluginResource 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.PluginResource 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.PluginResource in project SpongeCommon by SpongePowered.
the class PluginRepositorySource method loadPacks.
@Override
public void loadPacks(final Consumer<Pack> callback, final Pack.PackConstructor constructor) {
final VanillaPluginManager pluginManager = (VanillaPluginManager) Launch.instance().pluginManager();
// For each plugin, we create a pack. That pack might be empty.
for (final PluginContainer pluginContainer : pluginManager.plugins()) {
// The pack ID is prepended with "plugin-", as this will be the namespace we have to use a valid
// character as a separator
final String id = "plugin-" + pluginContainer.metadata().id();
final PluginResource resource = pluginManager.resource(pluginContainer);
// TODO: provide hook in the resource to return the file system for all resource types?
// Also -- can we fake a FileSystem for a non-Jar (needs thinking about)....
@Nullable Supplier<FileSystem> fileSystemSupplier = null;
if (resource instanceof JVMPluginResource) {
final String extension = FilenameUtils.getExtension(resource.path().getFileName().toString());
if ("jar".equals(extension)) {
fileSystemSupplier = ((JVMPluginResource) resource)::fileSystem;
}
}
final PluginPackResources packResources = new PluginPackResources(id, pluginContainer, fileSystemSupplier);
final Pack pack = Pack.create(id, true, () -> packResources, constructor, Pack.Position.BOTTOM, PackSource.DEFAULT);
((PackRepositoryBridge_Vanilla) this.repository).bridge$registerResourcePack(pluginContainer, pack);
callback.accept(pack);
}
}
use of org.spongepowered.plugin.PluginResource 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();
}
}
}
}
}
Aggregations