mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-04 16:49:11 +00:00
Compile plugins at build-time instead of runtime
Adds gradle tasks to build all plugin scripts under data/plugins with the KotlinPluginCompiler implementation previously used for runtime code generation. In addition to .plugin.kts files, scripts can also declare API code in .kt files which will also be included on the classpath and made available to other plugins.
This commit is contained in:
@@ -1,5 +1,20 @@
|
||||
package org.apollo.game.plugin;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apollo.game.model.World;
|
||||
import org.apollo.game.plugin.kotlin.KotlinPluginCompiler;
|
||||
import org.apollo.game.plugin.kotlin.KotlinPluginScript;
|
||||
@@ -8,17 +23,6 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation;
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity;
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class KotlinPluginEnvironment implements PluginEnvironment, MessageCollector {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(KotlinPluginEnvironment.class.getName());
|
||||
@@ -61,17 +65,56 @@ public class KotlinPluginEnvironment implements PluginEnvironment, MessageCollec
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parse(InputStream is, String name) {
|
||||
//@todo - wait until all plugin classes are loading until running constructors?
|
||||
try {
|
||||
Class<? extends KotlinPluginScript> pluginClass = pluginCompiler.compile(name);
|
||||
Constructor<? extends KotlinPluginScript> pluginConstructor = pluginClass.getConstructor(World.class,
|
||||
PluginContext.class);
|
||||
public void load(Collection<PluginMetaData> plugins) {
|
||||
try (InputStream resource = KotlinPluginEnvironment.class.getResourceAsStream("/manifest.txt")) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(resource));
|
||||
List<String> pluginClassNames = reader.lines().collect(Collectors.toList());
|
||||
|
||||
pluginConstructor.newInstance(world, context);
|
||||
for (String pluginClassName : pluginClassNames) {
|
||||
Class<? extends KotlinPluginScript> pluginClass =
|
||||
(Class<? extends KotlinPluginScript>) Class.forName(pluginClassName);
|
||||
|
||||
Constructor<? extends KotlinPluginScript> pluginConstructor =
|
||||
pluginClass.getConstructor(World.class, PluginContext.class);
|
||||
|
||||
KotlinPluginScript plugin = pluginConstructor.newInstance(world, context);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
List<Class<? extends KotlinPluginScript>> pluginClasses = new ArrayList<>();
|
||||
List<String> sourceRoots = new ArrayList<>();
|
||||
|
||||
for (PluginMetaData plugin : plugins) {
|
||||
List<String> pluginSourceRoots = Arrays.stream(plugin.getScripts())
|
||||
.map(script -> plugin.getBase() + "/" + script)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
sourceRoots.addAll(pluginSourceRoots);
|
||||
}
|
||||
|
||||
for (String scriptSource : sourceRoots) {
|
||||
// try {
|
||||
List<String> dependencySourceRoots = new ArrayList<>(sourceRoots);
|
||||
dependencySourceRoots.remove(scriptSource);
|
||||
|
||||
// pluginClasses.add(pluginCompiler.compile(scriptSource));
|
||||
// } catch (KotlinPluginCompilerException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
}
|
||||
|
||||
for (Class<? extends KotlinPluginScript> pluginClass : pluginClasses) {
|
||||
try {
|
||||
Constructor<? extends KotlinPluginScript> constructor = pluginClass
|
||||
.getConstructor(World.class, PluginContext.class);
|
||||
|
||||
KotlinPluginScript script = constructor.newInstance(world, context);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,7 +132,7 @@ public class KotlinPluginEnvironment implements PluginEnvironment, MessageCollec
|
||||
@NotNull CompilerMessageLocation location) {
|
||||
if (severity.isError()) {
|
||||
logger.log(Level.SEVERE, String.format("%s:%s-%s: %s", location.getPath(), location.getLine(),
|
||||
location.getColumn(), message));
|
||||
location.getColumn(), message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.apollo.game.plugin;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents some sort of environment that plugins could be executed in, e.g. {@code javax.script} or Jython.
|
||||
@@ -10,12 +12,11 @@ import java.io.InputStream;
|
||||
public interface PluginEnvironment {
|
||||
|
||||
/**
|
||||
* Parses the input stream.
|
||||
* Load all of the plugins defined in the given {@link Set} of {@link PluginMetaData}.
|
||||
*
|
||||
* @param is The input stream.
|
||||
* @param name The name of the file.
|
||||
* @param plugins The plugins to be loaded.
|
||||
*/
|
||||
public void parse(InputStream is, String name);
|
||||
void load(Collection<PluginMetaData> plugins);
|
||||
|
||||
/**
|
||||
* Sets the context for this environment.
|
||||
|
||||
@@ -149,43 +149,7 @@ public final class PluginManager {
|
||||
PluginEnvironment env = new KotlinPluginEnvironment(world); // TODO isolate plugins if possible in the future!
|
||||
env.setContext(context);
|
||||
|
||||
for (PluginMetaData plugin : plugins.values()) {
|
||||
start(env, plugin, plugins, started);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a specific plugin.
|
||||
*
|
||||
* @param env The environment.
|
||||
* @param plugin The plugin.
|
||||
* @param plugins The plugin map.
|
||||
* @param started A set of started plugins.
|
||||
* @throws DependencyException If a dependency error occurs.
|
||||
* @throws IOException If an I/O error occurs.
|
||||
*/
|
||||
private void start(PluginEnvironment env, PluginMetaData plugin, Map<String, PluginMetaData> plugins, Set<PluginMetaData> started) throws DependencyException, IOException {
|
||||
// TODO check for cyclic dependencies! this way just won't cut it, we need an exception
|
||||
if (started.contains(plugin)) {
|
||||
return;
|
||||
}
|
||||
started.add(plugin);
|
||||
|
||||
for (String dependencyId : plugin.getDependencies()) {
|
||||
PluginMetaData dependency = plugins.get(dependencyId);
|
||||
if (dependency == null) {
|
||||
throw new DependencyException("Unresolved dependency: " + dependencyId + ".");
|
||||
}
|
||||
start(env, dependency, plugins, started);
|
||||
}
|
||||
|
||||
String[] scripts = plugin.getScripts();
|
||||
|
||||
for (String script : scripts) {
|
||||
File scriptFile = new File(plugin.getBase(), script);
|
||||
InputStream is = new FileInputStream(scriptFile);
|
||||
env.parse(is, scriptFile.getAbsolutePath());
|
||||
}
|
||||
env.load(plugins.values());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package org.apollo.game.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apollo.game.model.World;
|
||||
import org.jruby.embed.ScriptingContainer;
|
||||
|
||||
/**
|
||||
* A {@link PluginEnvironment} which uses Ruby.
|
||||
*
|
||||
* @author Graham
|
||||
*/
|
||||
public final class RubyPluginEnvironment implements PluginEnvironment {
|
||||
|
||||
/**
|
||||
* The scripting container.
|
||||
*/
|
||||
private final ScriptingContainer container = new ScriptingContainer();
|
||||
|
||||
/**
|
||||
* Creates and bootstraps the Ruby plugin environment.
|
||||
*
|
||||
* @param world The {@link World} this RubyPluginEnvironment is for.
|
||||
* @throws IOException If an I/O error occurs during bootstrapping.
|
||||
*/
|
||||
public RubyPluginEnvironment(World world) throws IOException {
|
||||
container.put("$world", world);
|
||||
parseBootstrapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parse(InputStream is, String name) {
|
||||
try {
|
||||
container.runScriptlet(is, name);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Error parsing scriptlet " + name + ".", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the bootstrapper.
|
||||
*
|
||||
* @throws IOException If an I/O error occurs.
|
||||
*/
|
||||
private void parseBootstrapper() throws IOException {
|
||||
File bootstrap = new File("./data/plugins/bootstrap.rb");
|
||||
try (InputStream is = new FileInputStream(bootstrap)) {
|
||||
parse(is, bootstrap.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(PluginContext context) {
|
||||
container.put("$ctx", context);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user