- Merged 292 changed files from dlbo snapshot - Fixed IDL enum collisions (PortType, PdmEventType) - Fixed Grantlee template for C++11 scoped enums - Fixed CMake VERSION_GREATER syntax - Added missing directories (THP, LtsRestClient, etc.) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
465 lines
14 KiB
C++
Executable File
465 lines
14 KiB
C++
Executable File
/**
|
|
* @RDE_HEADER@
|
|
* plugin loader helper function
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
*
|
|
* Created by Christian Ehrlicher, SLSE, 2016-07-07
|
|
*/
|
|
|
|
#include "lts_plugin_loader.h"
|
|
|
|
#include <NGLLogger/NGLLoggerBootstrap.h>
|
|
#include <LtsAppDescriptor/lts_app_descriptor.h>
|
|
|
|
#include <QCoreApplication>
|
|
#include <QDirIterator>
|
|
#include <QFileInfo>
|
|
#include <QJsonObject>
|
|
#include <QPluginLoader>
|
|
#include <QMutableListIterator>
|
|
|
|
#include <QDebug>
|
|
|
|
|
|
namespace LtsPluginManager
|
|
{
|
|
|
|
struct LtsPluginLoaderHandler
|
|
{
|
|
LtsPluginLoader ltsPluginLoader;
|
|
static NGL::INGLLogger &logger() { return NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("LtsPluginLoader"); }
|
|
static void cleanupPluginLoader();
|
|
};
|
|
Q_GLOBAL_STATIC(LtsPluginLoaderHandler, s_ltsPluginLoader)
|
|
|
|
void LtsPluginLoaderHandler::cleanupPluginLoader()
|
|
{
|
|
if (s_ltsPluginLoader.exists())
|
|
s_ltsPluginLoader->ltsPluginLoader.unloadAllPlugins();
|
|
}
|
|
|
|
|
|
LtsPluginLoader *LtsPluginLoader::getInstance()
|
|
{
|
|
if (!s_ltsPluginLoader.exists())
|
|
qAddPostRoutine(&LtsPluginLoaderHandler::cleanupPluginLoader);
|
|
return &s_ltsPluginLoader->ltsPluginLoader;
|
|
}
|
|
|
|
|
|
LtsPluginLoader::~LtsPluginLoader()
|
|
{
|
|
unloadAllPlugins();
|
|
}
|
|
|
|
void LtsPluginLoader::unloadAllPlugins()
|
|
{
|
|
for (auto &pi : qAsConst(m_loadedPlugins))
|
|
{
|
|
QPluginLoader *pluginLoader = pi.pluginLoader;
|
|
|
|
delete pluginLoader->instance();
|
|
|
|
if (!pluginLoader->unload())
|
|
qWarning() << "~LtsPluginLoader: could not unload" << pi.pluginName << pluginLoader->errorString();
|
|
|
|
delete pluginLoader;
|
|
}
|
|
m_loadedPlugins.clear();
|
|
}
|
|
|
|
|
|
void LtsPluginLoader::checkPlugins(const QVector<LtsAppDescriptor::AppDescription> &appVector,
|
|
QStringList &errors /* out */)
|
|
{
|
|
NGL::INGLLogger &logger = LtsPluginLoaderHandler::logger();
|
|
|
|
if (appVector.isEmpty())
|
|
{
|
|
errors += QStringLiteral("No Apps available");
|
|
return;
|
|
}
|
|
|
|
|
|
QVector<PluginInstance> dataPlugins;
|
|
QVector<PluginInstance> guiPlugins;
|
|
auto addDataPluginFn = [&](const PluginInstance &pi)
|
|
{
|
|
dataPlugins.append(pi);
|
|
return true;
|
|
};
|
|
auto addGuiPluginFn = [&](const PluginInstance &pi)
|
|
{
|
|
guiPlugins.append(pi);
|
|
return true;
|
|
};
|
|
|
|
if (!loadPlugins(PluginType::DATA, addDataPluginFn))
|
|
{
|
|
errors += QStringLiteral("failed to load data plugins");
|
|
return;
|
|
}
|
|
|
|
if (!loadPlugins(PluginType::GUI, addGuiPluginFn))
|
|
{
|
|
errors += QStringLiteral("failed to load gui plugins");
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
// check for duplicate iids per plugin type
|
|
auto checkDuplicateIidsFn = [&](const QVector<PluginInstance> &plugins, const QString &pluginType)
|
|
{
|
|
typedef QMap<QString /*iid*/, QString /*filename*/> IidMap;
|
|
IidMap iids;
|
|
|
|
for (auto i3 = plugins.constBegin(); i3 != plugins.constEnd(); ++i3)
|
|
{
|
|
const PluginInstance &pin = *i3;
|
|
auto iidFound = iids.constFind(pin.pluginName);
|
|
|
|
if (iidFound != iids.constEnd())
|
|
errors += QStringLiteral("Iid duplicate for %1 plugin '%2'").arg(pluginType, pin.pluginName);
|
|
else
|
|
iids.insert(pin.pluginName, pin.fullFileName);
|
|
}
|
|
};
|
|
|
|
checkDuplicateIidsFn(dataPlugins, QStringLiteral("data"));
|
|
checkDuplicateIidsFn(guiPlugins, QStringLiteral("gui"));
|
|
|
|
|
|
|
|
QVector<PluginInstance> plugins(dataPlugins + guiPlugins);
|
|
|
|
|
|
// check if all in apps.json requested plugins are available
|
|
for (auto i1 = appVector.constBegin(); i1 != appVector.constEnd(); ++i1) // Apps
|
|
{
|
|
const LtsAppDescriptor::AppDescription &app = *i1;
|
|
|
|
//WARNING_LOG(logger) << "Checking app '"<<app.appName<<"'";
|
|
|
|
const QVector<LtsAppDescriptor::PluginInfo> &piVector = app.pluginInfos;
|
|
|
|
for (auto i2 = piVector.constBegin(); i2 != piVector.constEnd(); i2++) // App's Required Plugins
|
|
{
|
|
const LtsAppDescriptor::PluginInfo &pi = *i2;
|
|
bool found = false;
|
|
|
|
for (auto i3 = plugins.constBegin(); i3 != plugins.constEnd(); ++i3) // Available Plugins
|
|
{
|
|
const PluginInstance &pin = *i3;
|
|
|
|
if (pi.iid == pin.pluginName)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
ERROR_LOG(logger) << "Plugin with iid '" << pi.iid << "' was requested but was not loaded!";
|
|
errors += QStringLiteral("App '%1' requires plugin '%2' that could not be found.").arg(app.appName, pi.iid);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
bool LtsPluginLoader::loadPlugins(PluginType requestedPluginType,
|
|
const std::function<bool(const PluginInstance &)> &loadPlugin,
|
|
const QStringList &requiredPlugins,
|
|
const QString &specialTopicTypeFilter,
|
|
const QStringList &optionalPlugins)
|
|
{
|
|
NGL::INGLLogger &logger = LtsPluginLoaderHandler::logger();
|
|
Q_ASSERT(requestedPluginType != PluginType::UNKNOWN); // do not allow to look for unknown
|
|
|
|
QStringList requiredPluginsList = requiredPlugins; // copy ...
|
|
QStringList optionalPluginsList = optionalPlugins; // copy ...
|
|
bool checkPluginsLists = (!requiredPluginsList.isEmpty()) || (!optionalPluginsList.isEmpty());
|
|
|
|
if (checkPluginsLists && !m_loadedPlugins.isEmpty()) // only lookup of there is something to lookup TODO: faster to NOT do that check?
|
|
{
|
|
{
|
|
QMutableListIterator<QString> i1(requiredPluginsList); // delete all plugins from the to-load list that were cached
|
|
|
|
while (i1.hasNext())
|
|
{
|
|
const QString pluginName = i1.next().toLower();
|
|
|
|
QMap<PluginKey, PluginInstance>::iterator i2 = m_loadedPlugins.find(PluginKey(requestedPluginType, pluginName));
|
|
|
|
if (i2 != m_loadedPlugins.end())
|
|
{
|
|
DEBUG_LOG(logger) << "Plugin " << pluginName << " already loaded, using that instance";
|
|
|
|
if (loadPlugin)
|
|
loadPlugin(i2.value());
|
|
|
|
i1.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
{
|
|
QMutableListIterator<QString> i1(optionalPluginsList); // delete all plugins from the to-load list that were cached
|
|
|
|
while (i1.hasNext())
|
|
{
|
|
const QString pluginName = i1.next().toLower();
|
|
|
|
QMap<PluginKey, PluginInstance>::iterator i2 = m_loadedPlugins.find(PluginKey(requestedPluginType, pluginName));
|
|
|
|
if (i2 != m_loadedPlugins.end())
|
|
{
|
|
DEBUG_LOG(logger) << "Plugin " << pluginName << " already loaded, using that instance";
|
|
|
|
if (loadPlugin)
|
|
loadPlugin(i2.value());
|
|
|
|
i1.remove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
QFileInfo libPathInfo(QCoreApplication::applicationDirPath() + QStringLiteral("/../lib/"));
|
|
QString libPath(libPathInfo.absolutePath());
|
|
if (!libPathInfo.exists())
|
|
{
|
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
* In case of windows systems all libs are located in application directory. But can't
|
|
* use QCoreApplication::applicationDirPath here, because it points some times to the
|
|
* wrong directory.
|
|
* 2016/10/18 JT
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
libPathInfo.setFile(QCoreApplication::applicationFilePath());
|
|
libPath = libPathInfo.absolutePath();
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
const QStringList nameFilter = QStringList(QStringLiteral("*Plugin*.dll"));
|
|
#else
|
|
const QStringList nameFilter = QStringList(QStringLiteral("*Plugin*.so"));
|
|
#endif
|
|
|
|
static const QStringList pluginTypes({QStringLiteral("data"),
|
|
QStringLiteral("gui"),
|
|
QStringLiteral("topic_type"),
|
|
QStringLiteral("project"),
|
|
QStringLiteral("style"),
|
|
QStringLiteral("special"),
|
|
QStringLiteral("gui_element")}); // #warning TODO: recordreplay is a data plugin ?
|
|
|
|
QDirIterator pluginDir(libPath, nameFilter, QDir::Files);
|
|
while (pluginDir.hasNext())
|
|
{
|
|
pluginDir.next();
|
|
const QString filename = pluginDir.fileName();
|
|
if (filename.contains(QStringLiteral("PluginInterface"), Qt::CaseSensitive))
|
|
continue;
|
|
|
|
const QString absoluteFilePath = pluginDir.filePath();
|
|
|
|
DEBUG_LOG(logger) << "File Path: " << absoluteFilePath;
|
|
|
|
// if (!filename.contains(type))
|
|
// {
|
|
// pluginInfo.resultType = ResultType::WRONG_TYPE;
|
|
// pluginInfos.append(pluginInfo);
|
|
// continue;
|
|
// }
|
|
|
|
QScopedPointer<QPluginLoader> pluginLoader(new QPluginLoader);
|
|
pluginLoader->setFileName(absoluteFilePath);
|
|
|
|
const QJsonObject jObj = pluginLoader->metaData();
|
|
QJsonObject::const_iterator iid = jObj.find(QStringLiteral("IID"));
|
|
|
|
if (iid == jObj.end())
|
|
{
|
|
DEBUG_LOG(logger) << "Plugin '" << filename << "' has no IID !";
|
|
continue;
|
|
}
|
|
|
|
const QString jsonIid = iid.value().toString().toLower(); // LOWERCASE ! (pun intended)
|
|
const QVector<QStringRef> iidParameters = jsonIid.splitRef(QLatin1Char(':'));
|
|
|
|
DEBUG_LOG(logger) << "Plugin " << absoluteFilePath << " provides iid '" << jsonIid;
|
|
|
|
if (iidParameters.count() != 3)
|
|
{
|
|
WARNING_LOG(logger) << "Invalid iid "<<jsonIid<<" for plugin " << filename;
|
|
continue;
|
|
}
|
|
|
|
bool ok = false;
|
|
int pluginLoaderVersion = iidParameters.at(1).toInt(&ok);
|
|
|
|
if (!ok || pluginLoaderVersion != PLUGIN_LOADER_VERSION)
|
|
{
|
|
WARNING_LOG(logger) << "Invalid plugin loader version" << pluginLoaderVersion << "for plugin:" << filename;
|
|
continue;
|
|
}
|
|
|
|
PluginType pluginType = PluginType::UNKNOWN;
|
|
|
|
const QString pluginName = iidParameters.at(2).toString();
|
|
const QString pluginTypeString = iidParameters.at(0).toString();
|
|
switch (pluginTypes.indexOf(pluginTypeString))
|
|
{
|
|
case 0: // data
|
|
pluginType = PluginType::DATA;
|
|
break;
|
|
case 1: // gui
|
|
pluginType = PluginType::GUI;
|
|
break;
|
|
case 2: // topic_type
|
|
pluginType = PluginType::TOPIC_TYPE;
|
|
break;
|
|
case 3: // project
|
|
pluginType = PluginType::PROJECT;
|
|
break;
|
|
case 4: // style
|
|
pluginType = PluginType::STYLE;
|
|
break;
|
|
case 5: // special
|
|
pluginType = PluginType::SPECIAL;
|
|
break;
|
|
case 6: // gui_element
|
|
pluginType = PluginType::GUI_ELEMENT;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (pluginType != requestedPluginType)
|
|
{
|
|
DEBUG_LOG(logger) << "Plugin provides not the requested type " << (int)requestedPluginType << " is: " << (int)pluginType;
|
|
continue;
|
|
}
|
|
|
|
if (pluginType == PluginType::SPECIAL && !specialTopicTypeFilter.isEmpty())
|
|
{
|
|
if (!pluginName.startsWith(specialTopicTypeFilter))
|
|
{
|
|
DEBUG_LOG(logger) << "Special plugin, but filter does not match: " << pluginName << " - " << specialTopicTypeFilter;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (checkPluginsLists)
|
|
{
|
|
bool found = false;
|
|
|
|
{
|
|
QMutableListIterator<QString> i (requiredPluginsList);
|
|
|
|
while (i.hasNext())
|
|
{
|
|
const QString requiredPlugin = i.next().toLower();
|
|
|
|
if (pluginName == requiredPlugin)
|
|
{
|
|
found = true;
|
|
i.remove();
|
|
break; // no need to look further, just a waste of time, like writing this long unnecessary explanation
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (!found)
|
|
{
|
|
for (auto i = optionalPluginsList.constBegin(); i != optionalPluginsList.constEnd(); i++)
|
|
{
|
|
const QString optionalPlugin = (*i).toLower();
|
|
|
|
if (pluginName == optionalPlugin)
|
|
{
|
|
found = true;
|
|
break; // no need to look further, just a waste of time, like writing this long unnecessary explanation
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (!found)
|
|
{
|
|
DEBUG_LOG(logger) << "Plugin " << pluginName << " was not requested";
|
|
continue; // plugin was not requested to be loaded
|
|
}
|
|
}
|
|
|
|
|
|
// before we create a new instance of a plugin, which is not needed as one instance is perfectly fine to use for many apps, look it up in the loaded plugins list
|
|
|
|
if (loadPlugin && !m_loadedPlugins.isEmpty()) // only lookup if there is something to lookup TODO: faster to NOT do that check?
|
|
{
|
|
QMap<PluginKey, PluginInstance>::iterator i = m_loadedPlugins.find(PluginKey(requestedPluginType, pluginName));
|
|
|
|
if (i != m_loadedPlugins.end())
|
|
{
|
|
DEBUG_LOG(logger) << "Plugin " << pluginName << " already loaded, using that instance ("<<i.value().plugin->metaObject()->className()<<")";
|
|
|
|
loadPlugin(i.value());
|
|
continue; // found, next plugin!
|
|
}
|
|
}
|
|
|
|
// plugin not already loaded:
|
|
QObject *plugin = pluginLoader->instance();
|
|
|
|
if (!plugin)
|
|
{
|
|
ERROR_LOG(logger) << "Failed to load " << filename << " - " << pluginLoader->errorString();
|
|
continue;
|
|
}
|
|
|
|
PluginInstance pi;
|
|
pi.pluginName = pluginName;
|
|
pi.fileName = filename;
|
|
pi.fullFileName = absoluteFilePath;
|
|
pi.plugin = plugin;
|
|
pi.pluginLoader = pluginLoader.data();
|
|
|
|
if (loadPlugin && !loadPlugin(pi)) // does the dev want this plugin? if no, unload it! TODO: really unload? or still cache it??
|
|
{
|
|
DEBUG_LOG(logger) << "Plugin " << pluginName << " unloaded";
|
|
delete pluginLoader->instance();
|
|
pluginLoader->unload();
|
|
continue; // next plugin
|
|
}
|
|
pi.pluginLoader = pluginLoader.take();
|
|
|
|
// the dev decided to use that plugin, so cache it
|
|
m_loadedPlugins.insert(PluginKey(requestedPluginType, pluginName), pi);
|
|
|
|
// if (m_loadedPlugins.count() > 100)
|
|
// WARNING_LOG(logger) << "There are more than 100 plugins cached!";
|
|
|
|
DEBUG_LOG(logger) << "Loaded " << jsonIid << " - File: " << filename;
|
|
} // end of >> pluginDir.hasNext();
|
|
|
|
|
|
if (checkPluginsLists && !requiredPluginsList.isEmpty())
|
|
{
|
|
WARNING_LOG(logger) << "These plugins could not be found for Plugin Type " << (int)requestedPluginType << ": " << requiredPluginsList.join(QStringLiteral(", "));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace LtsPluginManager
|