-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement plugin dependencies, loaded in dependency first order (#1701)
* Implement plugin dependencies, loaded in dependency first order # Conflicts: # chunky/src/java/se/llbit/chunky/main/Chunky.java * Add additional documentation * Move plugin loader api out into a separate class. This also makes PluginManager much easier to test. * Add tests to verify load order of plugins with dependencies. * Apply review suggestions * Add maven-artifact:3.9.9 dependency. --------- Co-authored-by: Maik Marschner <[email protected]>
- Loading branch information
1 parent
6f3fe7a
commit 0bc1302
Showing
14 changed files
with
613 additions
and
140 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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; | ||
|
@@ -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]). | ||
|
@@ -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); | ||
}); | ||
} | ||
|
||
/** | ||
|
102 changes: 0 additions & 102 deletions
102
chunky/src/java/se/llbit/chunky/plugin/ChunkyPlugin.java
This file was deleted.
Oops, something went wrong.
45 changes: 45 additions & 0 deletions
45
chunky/src/java/se/llbit/chunky/plugin/loader/JarPluginLoader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
* | ||
* @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
18
chunky/src/java/se/llbit/chunky/plugin/loader/PluginLoader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
Oops, something went wrong.