Merge pull request #2611 from swazrgb/externalplugins-developmentmode

This commit is contained in:
Owain van Brakel
2020-05-24 21:09:50 +02:00
committed by GitHub
3 changed files with 234 additions and 101 deletions

View File

@@ -24,6 +24,7 @@
*/
package net.runelite.client;
import com.google.common.base.Strings;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
@@ -43,6 +44,7 @@ public class RuneLiteProperties
private static final String PATREON_LINK = "runelite.patreon.link";
private static final String LAUNCHER_VERSION_PROPERTY = "launcher.version";
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 BUILDING_LINK = "runelite.wiki.building.link";
private static final String DNS_CHANGE_LINK = "runelite.dnschange.link";
@@ -146,6 +148,20 @@ public class RuneLiteProperties
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()
{
return properties.getProperty(IMGUR_CLIENT_ID);

View File

@@ -32,6 +32,7 @@ import com.google.inject.Key;
import com.google.inject.Module;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
@@ -40,6 +41,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
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.SwingUtil;
import org.jgroups.Message;
import org.pf4j.BasePluginLoader;
import org.pf4j.CompoundPluginLoader;
import org.pf4j.CompoundPluginRepository;
import org.pf4j.DefaultPluginManager;
import org.pf4j.DependencyResolver;
import org.pf4j.DevelopmentPluginClasspath;
import org.pf4j.DevelopmentPluginRepository;
import org.pf4j.JarPluginLoader;
import org.pf4j.JarPluginRepository;
import org.pf4j.ManifestPluginDescriptorFinder;
@@ -114,6 +121,8 @@ public class ExternalPluginManager
private final EventBus eventBus;
private final ConfigManager configManager;
private final Map<String, String> pluginsMap = new HashMap<>();
@Getter
private final boolean developmentMode = RuneLiteProperties.getPluginDevelopmentPath().length > 0;
@Getter(AccessLevel.PUBLIC)
private final Map<String, Map<String, String>> pluginsInfoMap = new HashMap<>();
private final Groups groups;
@@ -153,45 +162,182 @@ public class ExternalPluginManager
@Override
protected PluginDescriptorFinder createPluginDescriptorFinder()
{
return new ManifestPluginDescriptorFinder();
}
@Override
protected PluginRepository createPluginRepository()
{
return new JarPluginRepository(getPluginsRoot())
return new ManifestPluginDescriptorFinder()
{
@Override
public List<Path> getPluginPaths()
protected Path getManifestPath(Path pluginPath)
{
File[] files = pluginsRoot.toFile().listFiles(filter);
if ((files == null) || files.length == 0)
if (isDevelopment())
{
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);
for (File file : files)
{
paths.add(file.toPath());
}
return paths;
return super.getManifestPath(pluginPath);
}
};
}
@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
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
@@ -258,52 +404,9 @@ public class ExternalPluginManager
}
@Override
public void loadPlugins()
public RuntimeMode getRuntimeMode()
{
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(e.getMessage(), e);
}
}
}
try
{
resolvePlugins();
}
catch (PluginRuntimeException e)
{
if (e instanceof DependencyResolver.DependenciesNotFoundException)
{
throw e;
}
log.error(e.getMessage(), e);
}
return developmentMode ? RuntimeMode.DEVELOPMENT : RuntimeMode.DEPLOYMENT;
}
@Override
@@ -1038,30 +1141,39 @@ public class ExternalPluginManager
return true;
}
// Null version returns the last release version of this plugin for given system version
try
{
PluginInfo.PluginRelease latest = updateManager.getLastPluginRelease(pluginId);
if (latest == null)
if (!developmentMode)
{
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(() ->
JOptionPane.showMessageDialog(ClientUI.getFrame(),
pluginId + " is outdated and cannot be installed",
"Installation error",
JOptionPane.ERROR_MESSAGE));
}
catch (InvocationTargetException | InterruptedException ignored)
{
return false;
try
{
SwingUtil.syncExec(() ->
JOptionPane.showMessageDialog(ClientUI.getFrame(),
pluginId + " is outdated and cannot be installed",
"Installation error",
JOptionPane.ERROR_MESSAGE));
}
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);

View File

@@ -147,32 +147,37 @@ public class PluginListItem extends JPanel
String pluginId = pluginInfo.get("id");
hotSwapButton.setIcon(REFRESH_ICON);
externalPluginManager.uninstall(pluginId);
SwingWorker<Boolean, Void> worker = new SwingWorker<>()
new SwingWorker<>()
{
@Override
protected Boolean doInBackground()
{
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
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();
}
};
worker.execute();
}.execute();
});
hotSwapButton.setVisible(true);