Merge pull request #2611 from swazrgb/externalplugins-developmentmode
This commit is contained in:
@@ -24,6 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
package net.runelite.client;
|
package net.runelite.client;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
@@ -43,6 +44,7 @@ public class RuneLiteProperties
|
|||||||
private static final String PATREON_LINK = "runelite.patreon.link";
|
private static final String PATREON_LINK = "runelite.patreon.link";
|
||||||
private static final String LAUNCHER_VERSION_PROPERTY = "launcher.version";
|
private static final String LAUNCHER_VERSION_PROPERTY = "launcher.version";
|
||||||
private static final String PLUGIN_PATH = "plugin.path";
|
private static final String PLUGIN_PATH = "plugin.path";
|
||||||
|
private static final String PLUGIN_DEVELOPMENT_PATH = "plugin.development.path";
|
||||||
private static final String TROUBLESHOOTING_LINK = "runelite.wiki.troubleshooting.link";
|
private static final String TROUBLESHOOTING_LINK = "runelite.wiki.troubleshooting.link";
|
||||||
private static final String BUILDING_LINK = "runelite.wiki.building.link";
|
private static final String BUILDING_LINK = "runelite.wiki.building.link";
|
||||||
private static final String DNS_CHANGE_LINK = "runelite.dnschange.link";
|
private static final String DNS_CHANGE_LINK = "runelite.dnschange.link";
|
||||||
@@ -146,6 +148,20 @@ public class RuneLiteProperties
|
|||||||
return pluginPath.equals("") ? null : pluginPath;
|
return pluginPath.equals("") ? null : pluginPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String[] getPluginDevelopmentPath()
|
||||||
|
{
|
||||||
|
// First check if property supplied as environment variable PLUGIN_DEVELOPMENT_PATHS
|
||||||
|
String developmentPluginPaths = System.getenv(PLUGIN_DEVELOPMENT_PATH.replace('.', '_').toUpperCase());
|
||||||
|
|
||||||
|
if (Strings.isNullOrEmpty(developmentPluginPaths))
|
||||||
|
{
|
||||||
|
// Otherwise check the property file
|
||||||
|
developmentPluginPaths = properties.getProperty(PLUGIN_DEVELOPMENT_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Strings.isNullOrEmpty(developmentPluginPaths) ? new String[0] : developmentPluginPaths.split(";");
|
||||||
|
}
|
||||||
|
|
||||||
public static String getImgurClientId()
|
public static String getImgurClientId()
|
||||||
{
|
{
|
||||||
return properties.getProperty(IMGUR_CLIENT_ID);
|
return properties.getProperty(IMGUR_CLIENT_ID);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import com.google.inject.Key;
|
|||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
@@ -40,6 +41,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -80,8 +82,13 @@ import net.runelite.client.util.Groups;
|
|||||||
import net.runelite.client.util.MiscUtils;
|
import net.runelite.client.util.MiscUtils;
|
||||||
import net.runelite.client.util.SwingUtil;
|
import net.runelite.client.util.SwingUtil;
|
||||||
import org.jgroups.Message;
|
import org.jgroups.Message;
|
||||||
|
import org.pf4j.BasePluginLoader;
|
||||||
|
import org.pf4j.CompoundPluginLoader;
|
||||||
|
import org.pf4j.CompoundPluginRepository;
|
||||||
import org.pf4j.DefaultPluginManager;
|
import org.pf4j.DefaultPluginManager;
|
||||||
import org.pf4j.DependencyResolver;
|
import org.pf4j.DependencyResolver;
|
||||||
|
import org.pf4j.DevelopmentPluginClasspath;
|
||||||
|
import org.pf4j.DevelopmentPluginRepository;
|
||||||
import org.pf4j.JarPluginLoader;
|
import org.pf4j.JarPluginLoader;
|
||||||
import org.pf4j.JarPluginRepository;
|
import org.pf4j.JarPluginRepository;
|
||||||
import org.pf4j.ManifestPluginDescriptorFinder;
|
import org.pf4j.ManifestPluginDescriptorFinder;
|
||||||
@@ -114,6 +121,8 @@ public class ExternalPluginManager
|
|||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
private final ConfigManager configManager;
|
private final ConfigManager configManager;
|
||||||
private final Map<String, String> pluginsMap = new HashMap<>();
|
private final Map<String, String> pluginsMap = new HashMap<>();
|
||||||
|
@Getter
|
||||||
|
private final boolean developmentMode = RuneLiteProperties.getPluginDevelopmentPath().length > 0;
|
||||||
@Getter(AccessLevel.PUBLIC)
|
@Getter(AccessLevel.PUBLIC)
|
||||||
private final Map<String, Map<String, String>> pluginsInfoMap = new HashMap<>();
|
private final Map<String, Map<String, String>> pluginsInfoMap = new HashMap<>();
|
||||||
private final Groups groups;
|
private final Groups groups;
|
||||||
@@ -153,45 +162,182 @@ public class ExternalPluginManager
|
|||||||
@Override
|
@Override
|
||||||
protected PluginDescriptorFinder createPluginDescriptorFinder()
|
protected PluginDescriptorFinder createPluginDescriptorFinder()
|
||||||
{
|
{
|
||||||
return new ManifestPluginDescriptorFinder();
|
return new ManifestPluginDescriptorFinder()
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PluginRepository createPluginRepository()
|
|
||||||
{
|
|
||||||
return new JarPluginRepository(getPluginsRoot())
|
|
||||||
{
|
{
|
||||||
@Override
|
protected Path getManifestPath(Path pluginPath)
|
||||||
public List<Path> getPluginPaths()
|
|
||||||
{
|
{
|
||||||
File[] files = pluginsRoot.toFile().listFiles(filter);
|
if (isDevelopment())
|
||||||
|
|
||||||
if ((files == null) || files.length == 0)
|
|
||||||
{
|
{
|
||||||
return Collections.emptyList();
|
// The superclass performs a find, which is slow in development mode since we're pointing
|
||||||
|
// at a sources directory, which can have a lot of files. The external plugin template
|
||||||
|
// will always output the manifest at the following location, so we can hardcode this path.
|
||||||
|
return pluginPath.resolve("build/tmp/jar/MANIFEST.MF");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Path> paths = new ArrayList<>(files.length);
|
return super.getManifestPath(pluginPath);
|
||||||
for (File file : files)
|
|
||||||
{
|
|
||||||
paths.add(file.toPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
return paths;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PluginLoader createPluginLoader()
|
protected PluginRepository createPluginRepository()
|
||||||
{
|
{
|
||||||
return new JarPluginLoader(this);
|
CompoundPluginRepository compoundPluginRepository = new CompoundPluginRepository();
|
||||||
|
|
||||||
|
if (isNotDevelopment())
|
||||||
|
{
|
||||||
|
JarPluginRepository jarPluginRepository = new JarPluginRepository(getPluginsRoot())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public List<Path> getPluginPaths()
|
||||||
|
{
|
||||||
|
File[] files = pluginsRoot.toFile().listFiles(filter);
|
||||||
|
|
||||||
|
if ((files == null) || files.length == 0)
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Path> paths = new ArrayList<>(files.length);
|
||||||
|
for (File file : files)
|
||||||
|
{
|
||||||
|
paths.add(file.toPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
compoundPluginRepository.add(jarPluginRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDevelopment())
|
||||||
|
{
|
||||||
|
FileFilter pluginsFilter = new FileFilter()
|
||||||
|
{
|
||||||
|
private final List<String> blacklist = Arrays.asList(
|
||||||
|
".git",
|
||||||
|
"build",
|
||||||
|
"target"
|
||||||
|
);
|
||||||
|
|
||||||
|
private final List<String> buildFiles = Arrays.asList(
|
||||||
|
"%s.gradle.kts",
|
||||||
|
"%s.gradle"
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(File pathName)
|
||||||
|
{
|
||||||
|
// Check if this path looks like a plugin development directory
|
||||||
|
if (!pathName.isDirectory())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String dirName = pathName.getName();
|
||||||
|
if (blacklist.contains(dirName))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isPlugin = false;
|
||||||
|
|
||||||
|
// By convention plugins their directory is $name and they have a $name.gradle.kts or $name.gradle file in their root
|
||||||
|
for (String buildFile : buildFiles)
|
||||||
|
{
|
||||||
|
if (new File(pathName, String.format(buildFile, dirName)).exists())
|
||||||
|
{
|
||||||
|
isPlugin = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is a plugin directory, but we should also check if it can actually be loaded
|
||||||
|
if (!new File(pathName, "build/tmp/jar/MANIFEST.MF").exists())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isPlugin;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (String developmentPluginPath : RuneLiteProperties.getPluginDevelopmentPath())
|
||||||
|
{
|
||||||
|
DevelopmentPluginRepository developmentPluginRepository = new DevelopmentPluginRepository(Paths.get(developmentPluginPath))
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean deletePluginPath(Path pluginPath)
|
||||||
|
{
|
||||||
|
// Do nothing, because we'd be deleting our sources!
|
||||||
|
return filter.accept(pluginPath.toFile());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
developmentPluginRepository.setFilter(pluginsFilter);
|
||||||
|
compoundPluginRepository.add(developmentPluginRepository);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return compoundPluginRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RuntimeMode getRuntimeMode()
|
protected PluginLoader createPluginLoader()
|
||||||
{
|
{
|
||||||
return RuneLiteProperties.getLauncherVersion() == null ? RuntimeMode.DEVELOPMENT : RuntimeMode.DEPLOYMENT;
|
return new CompoundPluginLoader()
|
||||||
|
.add(new BasePluginLoader(this, new DevelopmentPluginClasspath().addJarsDirectories("build/deps")), this::isDevelopment)
|
||||||
|
.add(new JarPluginLoader(this), this::isNotDevelopment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadPlugins()
|
||||||
|
{
|
||||||
|
if (Files.notExists(pluginsRoot) || !Files.isDirectory(pluginsRoot))
|
||||||
|
{
|
||||||
|
log.warn("No '{}' root", pluginsRoot);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Path> pluginPaths = pluginRepository.getPluginPaths();
|
||||||
|
|
||||||
|
if (pluginPaths.isEmpty())
|
||||||
|
{
|
||||||
|
log.warn("No plugins");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Found {} possible plugins: {}", pluginPaths.size(), pluginPaths);
|
||||||
|
|
||||||
|
for (Path pluginPath : pluginPaths)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
loadPluginFromPath(pluginPath);
|
||||||
|
}
|
||||||
|
catch (PluginRuntimeException e)
|
||||||
|
{
|
||||||
|
if (!(e instanceof PluginAlreadyLoadedException))
|
||||||
|
{
|
||||||
|
log.error("Could not load plugin {}", pluginPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
resolvePlugins();
|
||||||
|
}
|
||||||
|
catch (PluginRuntimeException e)
|
||||||
|
{
|
||||||
|
if (e instanceof DependencyResolver.DependenciesNotFoundException)
|
||||||
|
{
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -258,52 +404,9 @@ public class ExternalPluginManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadPlugins()
|
public RuntimeMode getRuntimeMode()
|
||||||
{
|
{
|
||||||
if (Files.notExists(pluginsRoot) || !Files.isDirectory(pluginsRoot))
|
return developmentMode ? RuntimeMode.DEVELOPMENT : RuntimeMode.DEPLOYMENT;
|
||||||
{
|
|
||||||
log.warn("No '{}' root", pluginsRoot);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Path> pluginPaths = pluginRepository.getPluginPaths();
|
|
||||||
|
|
||||||
if (pluginPaths.isEmpty())
|
|
||||||
{
|
|
||||||
log.warn("No plugins");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("Found {} possible plugins: {}", pluginPaths.size(), pluginPaths);
|
|
||||||
|
|
||||||
for (Path pluginPath : pluginPaths)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
loadPluginFromPath(pluginPath);
|
|
||||||
}
|
|
||||||
catch (PluginRuntimeException e)
|
|
||||||
{
|
|
||||||
if (!(e instanceof PluginAlreadyLoadedException))
|
|
||||||
{
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
resolvePlugins();
|
|
||||||
}
|
|
||||||
catch (PluginRuntimeException e)
|
|
||||||
{
|
|
||||||
if (e instanceof DependencyResolver.DependenciesNotFoundException)
|
|
||||||
{
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1038,30 +1141,39 @@ public class ExternalPluginManager
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Null version returns the last release version of this plugin for given system version
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PluginInfo.PluginRelease latest = updateManager.getLastPluginRelease(pluginId);
|
if (!developmentMode)
|
||||||
|
|
||||||
if (latest == null)
|
|
||||||
{
|
{
|
||||||
try
|
PluginInfo.PluginRelease latest = updateManager.getLastPluginRelease(pluginId);
|
||||||
|
|
||||||
|
// Null version returns the last release version of this plugin for given system version
|
||||||
|
if (latest == null)
|
||||||
{
|
{
|
||||||
SwingUtil.syncExec(() ->
|
try
|
||||||
JOptionPane.showMessageDialog(ClientUI.getFrame(),
|
{
|
||||||
pluginId + " is outdated and cannot be installed",
|
SwingUtil.syncExec(() ->
|
||||||
"Installation error",
|
JOptionPane.showMessageDialog(ClientUI.getFrame(),
|
||||||
JOptionPane.ERROR_MESSAGE));
|
pluginId + " is outdated and cannot be installed",
|
||||||
}
|
"Installation error",
|
||||||
catch (InvocationTargetException | InterruptedException ignored)
|
JOptionPane.ERROR_MESSAGE));
|
||||||
{
|
}
|
||||||
return false;
|
catch (InvocationTargetException | InterruptedException ignored)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
updateManager.installPlugin(pluginId, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// In development mode our plugin will already be present in a repository, so we can just load it
|
||||||
|
externalPluginManager.loadPlugins();
|
||||||
|
externalPluginManager.startPlugin(pluginId);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateManager.installPlugin(pluginId, null);
|
|
||||||
|
|
||||||
scanAndInstantiate(loadPlugin(pluginId), true, true);
|
scanAndInstantiate(loadPlugin(pluginId), true, true);
|
||||||
|
|
||||||
|
|||||||
@@ -147,32 +147,37 @@ public class PluginListItem extends JPanel
|
|||||||
String pluginId = pluginInfo.get("id");
|
String pluginId = pluginInfo.get("id");
|
||||||
|
|
||||||
hotSwapButton.setIcon(REFRESH_ICON);
|
hotSwapButton.setIcon(REFRESH_ICON);
|
||||||
externalPluginManager.uninstall(pluginId);
|
|
||||||
|
|
||||||
SwingWorker<Boolean, Void> worker = new SwingWorker<>()
|
new SwingWorker<>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground()
|
protected Boolean doInBackground()
|
||||||
{
|
{
|
||||||
return externalPluginManager.uninstall(pluginId);
|
return externalPluginManager.uninstall(pluginId);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
worker.execute();
|
|
||||||
|
|
||||||
JOptionPane.showMessageDialog(ClientUI.getFrame(),
|
|
||||||
pluginId + " is unloaded, put the new jar file in the externalmanager folder and click `ok`",
|
|
||||||
"Hotswap " + pluginId,
|
|
||||||
JOptionPane.INFORMATION_MESSAGE);
|
|
||||||
|
|
||||||
worker = new SwingWorker<>()
|
|
||||||
{
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground()
|
protected void done()
|
||||||
{
|
{
|
||||||
return externalPluginManager.reloadStart(pluginId);
|
// In development mode our plugins will be loaded directly from sources, so we don't need to prompt
|
||||||
|
if (!externalPluginManager.isDevelopmentMode())
|
||||||
|
{
|
||||||
|
JOptionPane.showMessageDialog(ClientUI.getFrame(),
|
||||||
|
pluginId + " is unloaded, put the new jar file in the externalmanager folder and click `ok`",
|
||||||
|
"Hotswap " + pluginId,
|
||||||
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
new SwingWorker<>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground()
|
||||||
|
{
|
||||||
|
return externalPluginManager.reloadStart(pluginId);
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
}
|
}
|
||||||
};
|
}.execute();
|
||||||
worker.execute();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
hotSwapButton.setVisible(true);
|
hotSwapButton.setVisible(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user