/** * @RDE_HEADER@ * plugin loader helper function * $Id$ * $HeadURL$ * $Author$ * * Created by Christian Ehrlicher, SLSE, 2016-07-07 */ #include "lts_plugin_loader.h" #include #include #include #include #include #include #include #include #include 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 &appVector, QStringList &errors /* out */) { NGL::INGLLogger &logger = LtsPluginLoaderHandler::logger(); if (appVector.isEmpty()) { errors += QStringLiteral("No Apps available"); return; } QVector dataPlugins; QVector 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 &plugins, const QString &pluginType) { typedef QMap 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 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 '"< &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 &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 i1(requiredPluginsList); // delete all plugins from the to-load list that were cached while (i1.hasNext()) { const QString pluginName = i1.next().toLower(); QMap::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 i1(optionalPluginsList); // delete all plugins from the to-load list that were cached while (i1.hasNext()) { const QString pluginName = i1.next().toLower(); QMap::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 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 iidParameters = jsonIid.splitRef(QLatin1Char(':')); DEBUG_LOG(logger) << "Plugin " << absoluteFilePath << " provides iid '" << jsonIid; if (iidParameters.count() != 3) { WARNING_LOG(logger) << "Invalid iid "< 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::iterator i = m_loadedPlugins.find(PluginKey(requestedPluginType, pluginName)); if (i != m_loadedPlugins.end()) { DEBUG_LOG(logger) << "Plugin " << pluginName << " already loaded, using that instance ("<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