Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement plugin dependencies, loaded in dependency first order #1701

Merged
merged 6 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ Chunky uses the following 3rd party libraries:
The library is covered by the Apache License, version 2.0.
See the file `licenses/Apache-2.0.txt` for the full license text.
See the file `licenses/commons-math.txt` for the copyright notices.
- **Apache Maven Artifact by the Apache Software Foundation**
The library is covered by the Apache License, version 2.0.
See the file `licenses/Apache-2.0.txt` for the full license text.
- **FastUtil by Sebastiano Vigna**
FastUtil is covered by Apache License, version 2.0.
See the file `licenses/Apache-2.0.txt` for the full license text.
Expand Down
1 change: 1 addition & 0 deletions chunky/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies {
implementation 'org.apache.commons:commons-math3:3.2'
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'org.lz4:lz4-java:1.8.0'
implementation 'org.apache.maven:maven-artifact:3.9.9'
implementation project(':lib')
}

Expand Down
Binary file added chunky/lib/maven-artifact-3.9.9.jar
Binary file not shown.
68 changes: 31 additions & 37 deletions chunky/src/java/se/llbit/chunky/main/Chunky.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
import se.llbit.chunky.block.MinecraftBlockProvider;
import se.llbit.chunky.block.legacy.LegacyMinecraftBlockProvider;
import se.llbit.chunky.main.CommandLineOptions.Mode;
import se.llbit.chunky.plugin.ContextMenuTransformer;
import se.llbit.chunky.plugin.PluginApi;
import se.llbit.chunky.plugin.ChunkyPlugin;
import se.llbit.chunky.plugin.TabTransformer;
import se.llbit.chunky.plugin.*;
import se.llbit.chunky.plugin.loader.PluginManager;
import se.llbit.chunky.plugin.loader.JarPluginLoader;
import se.llbit.chunky.plugin.manifest.PluginManifest;
import se.llbit.chunky.renderer.*;
import se.llbit.chunky.renderer.RenderManager;
import se.llbit.chunky.renderer.export.PictureExportFormat;
Expand All @@ -43,7 +43,6 @@
import se.llbit.chunky.ui.render.RenderControlsTabTransformer;
import se.llbit.chunky.world.MaterialStore;
import se.llbit.json.JsonArray;
import se.llbit.json.JsonValue;
import se.llbit.log.ConsoleReceiver;
import se.llbit.log.Level;
import se.llbit.log.Log;
Expand All @@ -55,11 +54,10 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Chunky is a Minecraft mapping and rendering tool created byJesper Öqvist ([email protected]).
Expand Down Expand Up @@ -265,35 +263,31 @@ private void loadPlugins() {
}
Path pluginsPath = pluginsDirectory.toPath();
JsonArray plugins = PersistentSettings.getPlugins();
Set<String> loadedPlugins = new HashSet<>();
for (JsonValue value : plugins) {
String jarName = value.asString("");
if (!jarName.isEmpty()) {
Log.info("Loading plugin: " + value);
try {
ChunkyPlugin
.load(pluginsPath.resolve(jarName).toRealPath().toFile(), (plugin, manifest) -> {
CreditsController.addPlugin(manifest);
String pluginName = manifest.get("name").asString("");
if (loadedPlugins.contains(pluginName)) {
Log.warnf(
"Multiple plugins with the same name (\"%s\") are enabled. Loading multiple versions of the same plugin can lead to strange behavior.",
pluginName);
}
loadedPlugins.add(pluginName);
try {
plugin.attach(this);
} catch (Throwable t) {
Log.error("Plugin " + jarName + " failed to load.", t);
}
Log.infof("Plugin loaded: %s %s", manifest.get("name").asString(""),
manifest.get("version").asString(""));
});
} catch (Throwable t) {
Log.error("Plugin " + jarName + " failed to load.", t);
}
// TODO: allow plugins to implement a custom plugin loader.
PluginManager pluginManager = new PluginManager(new JarPluginLoader());

// Parse plugin manifests
Set<PluginManifest> pluginManifests = plugins.elements.stream()
.map(value -> value.asString(""))
.filter(jarName -> !jarName.isEmpty())
.map(jarName -> pluginsPath.resolve(jarName).toAbsolutePath().toFile())
.map(PluginManager::parsePluginManifest)
.flatMap(Optional::stream)
.collect(Collectors.toSet());

// Load plugins
pluginManager.load(pluginManifests, (plugin, manifest) -> {
String jarName = manifest.pluginJar.getName();
Log.infof("Loading plugin: %s", jarName);
CreditsController.addPlugin(manifest.name, manifest.version.toString(), manifest.author, manifest.description);

try {
plugin.attach(this);
} catch (Throwable t) {
Log.error("Plugin " + jarName + " failed to load.", t);
}
}
Log.infof("Plugin loaded: %s %s", manifest.name, manifest.version);
});
}

/**
Expand Down
102 changes: 0 additions & 102 deletions chunky/src/java/se/llbit/chunky/plugin/ChunkyPlugin.java

This file was deleted.

45 changes: 45 additions & 0 deletions chunky/src/java/se/llbit/chunky/plugin/loader/JarPluginLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package se.llbit.chunky.plugin.loader;

import se.llbit.chunky.Plugin;
import se.llbit.chunky.plugin.PluginApi;
import se.llbit.chunky.plugin.manifest.PluginManifest;
import se.llbit.log.Log;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.function.BiConsumer;

@PluginApi
public class JarPluginLoader implements PluginLoader {
public void load(BiConsumer<Plugin, PluginManifest> onLoad, PluginManifest pluginManifest) {
try {
Class<?> pluginClass = loadPluginClass(pluginManifest.main, pluginManifest.pluginJar);
Plugin plugin = (Plugin) pluginClass.getDeclaredConstructor().newInstance();
onLoad.accept(plugin, pluginManifest);
} catch (IOException | ClassNotFoundException e) {
Log.error("Could not load the plugin", e);
} catch (ClassCastException e) {
Log.error("Plugin main class has wrong type (must implement se.llbit.chunky.Plugin)", e);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
Log.error("Could not create plugin instance", e);
}
}

/**
* This method is {@link PluginApi} to allow plugins to override only classloading functionality of the default plugin loader.
leMaik marked this conversation as resolved.
Show resolved Hide resolved
*
* @param pluginMainClass The plugin's main class to load.
* @param pluginJarFile The jar file to load classes from.
* @return The loaded plugin's main class
* @throws ClassNotFoundException If the main class doesn't exist
* @throws MalformedURLException If the jar file cannot be converted to a URL
*/
@PluginApi
protected Class<?> loadPluginClass(String pluginMainClass, File pluginJarFile) throws ClassNotFoundException, MalformedURLException {
return new URLClassLoader(new URL[] { pluginJarFile.toURI().toURL() }).loadClass(pluginMainClass);
}
}
18 changes: 18 additions & 0 deletions chunky/src/java/se/llbit/chunky/plugin/loader/PluginLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package se.llbit.chunky.plugin.loader;

import se.llbit.chunky.Plugin;
import se.llbit.chunky.plugin.PluginApi;
import se.llbit.chunky.plugin.manifest.PluginManifest;

import java.util.function.BiConsumer;

@PluginApi
public interface PluginLoader {
/**
* Load the plugin specified in the manifest
* @param onLoad The consumer to call with the loaded plugin
* @param pluginManifest The plugin to load.
*/
@PluginApi
void load(BiConsumer<Plugin, PluginManifest> onLoad, PluginManifest pluginManifest);
}
Loading