File indexing completed on 2024-04-28 04:37:21

0001 /*
0002     SPDX-FileCopyrightText: 2004, 2007 Alexander Dymo <adymo@kdevelop.org>
0003     SPDX-FileCopyrightText: 2006 Matt Rogers <mattr@kde.org
0004     SPDX-FileCopyrightText: 2007 Andreas Pakulat <apaku@gmx.de>
0005 
0006     Based on code from Kopete
0007     SPDX-FileCopyrightText: 2002-2003 Martijn Klingens <klingens@kde.org>
0008 
0009     SPDX-License-Identifier: LGPL-2.0-or-later
0010 */
0011 
0012 #include "plugincontroller.h"
0013 
0014 #include <algorithm>
0015 
0016 #include <QElapsedTimer>
0017 #include <QJsonArray>
0018 #include <QJsonObject>
0019 #include <QMap>
0020 
0021 #include <KConfigGroup>
0022 #include <KLocalizedString>
0023 #include <KPluginFactory>
0024 
0025 #include <interfaces/contextmenuextension.h>
0026 #include <interfaces/iplugin.h>
0027 #include <interfaces/isession.h>
0028 #include <interfaces/idebugcontroller.h>
0029 #include <interfaces/idocumentationcontroller.h>
0030 #include <interfaces/ipluginversion.h>
0031 
0032 #include "core.h"
0033 #include "shellextension.h"
0034 #include "runcontroller.h"
0035 #include "debugcontroller.h"
0036 #include "documentationcontroller.h"
0037 #include "sourceformattercontroller.h"
0038 #include "projectcontroller.h"
0039 #include "ktexteditorpluginintegration.h"
0040 #include "debug.h"
0041 
0042 namespace {
0043 
0044 inline QString KEY_Plugins() { return QStringLiteral("Plugins"); }
0045 inline QString KEY_Suffix_Enabled() { return QStringLiteral("Enabled"); }
0046 
0047 inline QString KEY_LoadMode() { return QStringLiteral("X-KDevelop-LoadMode"); }
0048 inline QString KEY_Category() { return QStringLiteral("X-KDevelop-Category"); }
0049 inline QString KEY_Mode() { return QStringLiteral("X-KDevelop-Mode"); }
0050 inline QString KEY_Version() { return QStringLiteral("X-KDevelop-Version"); }
0051 inline QString KEY_Interfaces() { return QStringLiteral("X-KDevelop-Interfaces"); }
0052 inline QString KEY_Required() { return QStringLiteral("X-KDevelop-IRequired"); }
0053 inline QString KEY_Optional() { return QStringLiteral("X-KDevelop-IOptional"); }
0054 inline QString KEY_KPlugin() { return QStringLiteral("KPlugin"); }
0055 inline QString KEY_EnabledByDefault() { return QStringLiteral("EnabledByDefault"); }
0056 
0057 inline QString KEY_Global() { return QStringLiteral("Global"); }
0058 inline QString KEY_Project() { return QStringLiteral("Project"); }
0059 inline QString KEY_Gui() { return QStringLiteral("GUI"); }
0060 inline QString KEY_AlwaysOn() { return QStringLiteral("AlwaysOn"); }
0061 inline QString KEY_UserSelectable() { return QStringLiteral("UserSelectable"); }
0062 
0063 // We really want to access the service types, even though the KPluginMetaData::serviceTypes API is deprecated.
0064 // After talking to Nicolas Fella and Christoph Cullmann this seems to be the suggested approach.
0065 bool hasKDevelopPluginServiceType(const KPluginMetaData& info)
0066 {
0067     const auto pluginData = info.rawData().value(QLatin1String("KPlugin")).toObject();
0068     const auto serviceTypes = pluginData.value(QLatin1String("ServiceTypes")).toArray();
0069     return std::any_of(serviceTypes.begin(), serviceTypes.end(), [](const QJsonValue& value) {
0070         return value.toString() == QLatin1String("KDevelop/Plugin");
0071     });
0072 }
0073 
0074 bool isUserSelectable( const KPluginMetaData& info )
0075 {
0076     QString loadMode = info.value(KEY_LoadMode());
0077     return loadMode.isEmpty() || loadMode == KEY_UserSelectable();
0078 }
0079 
0080 bool isGlobalPlugin( const KPluginMetaData& info )
0081 {
0082     return info.value(KEY_Category()) == KEY_Global();
0083 }
0084 
0085 bool hasMandatoryProperties( const KPluginMetaData& info )
0086 {
0087     QString mode = info.value(KEY_Mode());
0088     if (mode.isEmpty()) {
0089         return false;
0090     }
0091 
0092     // when the plugin is installed into the versioned plugin path, it's good to go
0093     if (info.fileName().contains(QLatin1String("/kdevplatform/" QT_STRINGIFY(KDEVELOP_PLUGIN_VERSION) "/"))) {
0094         return true;
0095     }
0096 
0097     // the version property is only required when the plugin is not installed into the right directory
0098     QVariant version = info.rawData().value(KEY_Version()).toVariant();
0099     if (version.isValid() && version.value<int>() == KDEVELOP_PLUGIN_VERSION) {
0100         return true;
0101     }
0102 
0103     return false;
0104 }
0105 
0106 inline QSet<QString> stringSet(const QVariant& variant)
0107 {
0108     const QStringList list = variant.toStringList();
0109     return QSet<QString>(list.begin(), list.end());
0110 }
0111 
0112 bool constraintsMatch( const KPluginMetaData& info, const QVariantMap& constraints)
0113 {
0114     for (auto it = constraints.begin(); it != constraints.end(); ++it) {
0115         const auto property = info.rawData().value(it.key()).toVariant();
0116 
0117         if (!property.isValid()) {
0118             return false;
0119         } else if (property.canConvert<QStringList>()) {
0120             const QSet<QString> values = stringSet(property);
0121             const QSet<QString> expected = stringSet(it.value());
0122             if (!values.contains(expected)) {
0123                 return false;
0124             }
0125         } else if (it.value() != property) {
0126             return false;
0127         }
0128     }
0129     return true;
0130 }
0131 
0132 struct Dependency
0133 {
0134     explicit Dependency(const QString &dependency)
0135     {
0136         const int pos = dependency.indexOf(QLatin1Char('@'));
0137         if (pos != -1) {
0138             interface = dependency.left(pos);
0139             pluginName = dependency.mid(pos + 1);
0140         } else {
0141             interface = dependency;
0142         }
0143     }
0144 
0145     QString interface;
0146     QString pluginName;
0147 };
0148 
0149 QVector<QString> pluginIds(const QVector<KPluginMetaData> &plugins)
0150 {
0151     QVector<QString> ids(plugins.size());
0152     std::transform(plugins.begin(), plugins.end(), ids.begin(), [](const KPluginMetaData &meta) {
0153         return meta.pluginId();
0154     });
0155     return ids;
0156 }
0157 }
0158 
0159 namespace KDevelop {
0160 
0161 class PluginControllerPrivate
0162 {
0163 public:
0164     explicit PluginControllerPrivate(Core *core)
0165         : core(core)
0166     {}
0167 
0168     QVector<KPluginMetaData> plugins;
0169 
0170     //map plugin infos to currently loaded plugins
0171     using InfoToPluginMap = QHash<KPluginMetaData, IPlugin*>;
0172     InfoToPluginMap loadedPlugins;
0173 
0174     // The plugin manager's mode. The mode is StartingUp until loadAllPlugins()
0175     // has finished loading the plugins, after which it is set to Running.
0176     // ShuttingDown and DoneShutdown are used during shutdown by the
0177     // async unloading of plugins.
0178     enum CleanupMode
0179     {
0180         Running     /**< the plugin manager is running */,
0181         CleaningUp  /**< the plugin manager is cleaning up for shutdown */,
0182         CleanupDone /**< the plugin manager has finished cleaning up */
0183     };
0184     CleanupMode cleanupMode;
0185 
0186     bool canUnload(const KPluginMetaData& plugin)
0187     {
0188         qCDebug(SHELL) << "checking can unload for:" << plugin.name() << plugin.value(KEY_LoadMode());
0189         if (plugin.value(KEY_LoadMode()) == KEY_AlwaysOn()) {
0190             return false;
0191         }
0192         const auto interfaces = plugin.value(KEY_Interfaces(), QStringList());
0193         qCDebug(SHELL) << "checking dependencies:" << interfaces;
0194         for (auto it = loadedPlugins.constBegin(), end = loadedPlugins.constEnd(); it != end; ++it) {
0195             const KPluginMetaData& info = it.key();
0196             if (info.pluginId() != plugin.pluginId()) {
0197                 const auto dependencies =
0198                     plugin.value(KEY_Required(), QStringList()) + plugin.value(KEY_Optional(), QStringList());
0199                 for (const QString& dep : dependencies) {
0200                     Dependency dependency(dep);
0201                     if (!dependency.pluginName.isEmpty() && dependency.pluginName != plugin.pluginId()) {
0202                         continue;
0203                     }
0204                     if (interfaces.contains(dependency.interface) && !canUnload(info)) {
0205                         return false;
0206                     }
0207                 }
0208             }
0209         }
0210         return true;
0211     }
0212 
0213     KPluginMetaData infoForId( const QString& id ) const
0214     {
0215         for (const KPluginMetaData& info : plugins) {
0216             if (info.pluginId() == id) {
0217                 return info;
0218             }
0219         }
0220         return KPluginMetaData();
0221     }
0222 
0223     /**
0224      * Iterate over all cached plugin infos, and call the functor for every enabled plugin.
0225      *
0226      * If an extension and/or pluginName is given, the functor will only be called for
0227      * those plugins matching this information.
0228      *
0229      * The functor should return false when the iteration can be stopped, and true if it
0230      * should be continued.
0231      */
0232     template<typename F>
0233     void foreachEnabledPlugin(F func, const QString &extension = {}, const QVariantMap& constraints = QVariantMap(), const QString &pluginName = {}) const
0234     {
0235         const auto currentPlugins = plugins;
0236         for (const auto& info : currentPlugins) {
0237             if ((pluginName.isEmpty() || info.pluginId() == pluginName)
0238                 && (extension.isEmpty() || info.value(KEY_Interfaces(), QStringList()).contains(extension))
0239                 && constraintsMatch(info, constraints) && isEnabled(info)) {
0240                 if (!func(info)) {
0241                     break;
0242                 }
0243             }
0244         }
0245     }
0246 
0247     enum EnableState {
0248         DisabledByEnv,
0249         DisabledBySetting,
0250         DisabledByUnknown,
0251 
0252         FirstEnabledState,
0253         EnabledBySetting = FirstEnabledState,
0254         AlwaysEnabled
0255     };
0256 
0257     /**
0258      * Estimate enabled state of a plugin
0259      */
0260     EnableState enabledState(const KPluginMetaData& info) const
0261     {
0262         // first check black listing from environment
0263         static const QStringList disabledPlugins = QString::fromLatin1(qgetenv("KDEV_DISABLE_PLUGINS")).split(QLatin1Char(';'));
0264         if (disabledPlugins.contains(info.pluginId())) {
0265             return DisabledByEnv;
0266         }
0267 
0268         if (!isUserSelectable( info ))
0269             return AlwaysEnabled;
0270 
0271         // read stored user preference
0272         const KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() );
0273         const QString pluginEnabledKey = info.pluginId() + KEY_Suffix_Enabled();
0274         if (grp.hasKey(pluginEnabledKey)) {
0275             return grp.readEntry(pluginEnabledKey, true) ? EnabledBySetting : DisabledBySetting;
0276         }
0277 
0278         // should not happen
0279         return DisabledByUnknown;
0280     }
0281 
0282     /**
0283      * Decide whether a plugin is enabled
0284      */
0285     bool isEnabled(const KPluginMetaData& info) const
0286     {
0287         return (enabledState(info) >= FirstEnabledState);
0288     }
0289 
0290     void initKTextEditorIntegration()
0291     {
0292         if (core->setupFlags() == Core::NoUi) {
0293             qCDebug(SHELL) << "Skipping KTextEditor integration in Core::NoUi mode";
0294             return;
0295         }
0296 
0297         KTextEditorIntegration::initialize();
0298         const auto ktePlugins =
0299             KPluginMetaData::findPlugins(QStringLiteral("ktexteditor"), &hasKDevelopPluginServiceType);
0300 
0301         qCDebug(SHELL) << "Found" << ktePlugins.size() << " KTextEditor plugins:" << pluginIds(ktePlugins);
0302 
0303         plugins.reserve(plugins.size() + ktePlugins.size());
0304         for (const auto& info : ktePlugins) {
0305             auto data = info.rawData();
0306             // temporary workaround for Kate's ctags plugin being enabled by default
0307             // see https://mail.kde.org/pipermail/kwrite-devel/2019-July/004821.html
0308             if (info.pluginId() == QLatin1String("katectagsplugin")) {
0309                 auto kpluginData = data[KEY_KPlugin()].toObject();
0310                 kpluginData[KEY_EnabledByDefault()] = false;
0311                 data[KEY_KPlugin()] = kpluginData;
0312             }
0313             // add some KDevelop specific JSON data
0314             data[KEY_Category()] = KEY_Global();
0315             data[KEY_Mode()] = KEY_Gui();
0316             data[KEY_Version()] = KDEVELOP_PLUGIN_VERSION;
0317             plugins.append({data, info.fileName(), info.metaDataFileName()});
0318         }
0319     }
0320 
0321     Core* const core;
0322 };
0323 
0324 PluginController::PluginController(Core *core)
0325     : IPluginController()
0326     , d_ptr(new PluginControllerPrivate(core))
0327 {
0328     Q_D(PluginController);
0329 
0330     setObjectName(QStringLiteral("PluginController"));
0331 
0332     auto newPlugins =
0333         KPluginMetaData::findPlugins(QStringLiteral("kdevplatform/" QT_STRINGIFY(KDEVELOP_PLUGIN_VERSION)));
0334 
0335     qCDebug(SHELL) << "Found" << newPlugins.size() << "plugins:" << pluginIds(newPlugins);
0336     if (newPlugins.isEmpty()) {
0337         qCWarning(SHELL) << "Did not find any plugins, check your environment.";
0338         qCWarning(SHELL) << "  Note: QT_PLUGIN_PATH is set to:" << qgetenv("QT_PLUGIN_PATH");
0339     }
0340 
0341     d->plugins = newPlugins;
0342 
0343     d->initKTextEditorIntegration();
0344 
0345     d->cleanupMode = PluginControllerPrivate::Running;
0346     // Register the KDevelop::IPlugin* metatype so we can properly unload it
0347     qRegisterMetaType<KDevelop::IPlugin*>( "KDevelop::IPlugin*" );
0348 }
0349 
0350 PluginController::~PluginController()
0351 {
0352     Q_D(PluginController);
0353 
0354     if ( d->cleanupMode != PluginControllerPrivate::CleanupDone ) {
0355         qCWarning(SHELL) << "Destructing plugin controller without going through the shutdown process!";
0356     }
0357 }
0358 
0359 KPluginMetaData PluginController::pluginInfo( const IPlugin* plugin ) const
0360 {
0361     Q_D(const PluginController);
0362 
0363     return d->loadedPlugins.key(const_cast<IPlugin*>(plugin));
0364 }
0365 
0366 void PluginController::cleanup()
0367 {
0368     Q_D(PluginController);
0369 
0370     if(d->cleanupMode != PluginControllerPrivate::Running)
0371     {
0372         //qCDebug(SHELL) << "called when not running. state =" << d->cleanupMode;
0373         return;
0374     }
0375 
0376     d->cleanupMode = PluginControllerPrivate::CleaningUp;
0377 
0378     // Ask all plugins to unload
0379     while ( !d->loadedPlugins.isEmpty() )
0380     {
0381         //Let the plugin do some stuff before unloading
0382         unloadPlugin(d->loadedPlugins.begin().value(), Now);
0383     }
0384 
0385     d->cleanupMode = PluginControllerPrivate::CleanupDone;
0386 }
0387 
0388 IPlugin* PluginController::loadPlugin( const QString& pluginName )
0389 {
0390     return loadPluginInternal( pluginName );
0391 }
0392 
0393 void PluginController::initialize()
0394 {
0395     Q_D(PluginController);
0396 
0397     QElapsedTimer timer;
0398     timer.start();
0399 
0400     QMap<QString, bool> pluginMap;
0401     if( ShellExtension::getInstance()->defaultPlugins().isEmpty() )
0402     {
0403         for (const KPluginMetaData& pi : qAsConst(d->plugins)) {
0404             QJsonValue enabledByDefaultValue = pi.rawData()[KEY_KPlugin()].toObject()[KEY_EnabledByDefault()];
0405             // plugins enabled until explicitly specified otherwise
0406             const bool enabledByDefault = (enabledByDefaultValue.isNull() || enabledByDefaultValue.toBool());
0407             pluginMap.insert(pi.pluginId(), enabledByDefault);
0408         }
0409     } else
0410     {
0411         // Get the default from the ShellExtension
0412         const auto defaultPlugins = ShellExtension::getInstance()->defaultPlugins();
0413         for (const QString& s : defaultPlugins) {
0414             pluginMap.insert( s, true );
0415         }
0416     }
0417 
0418     KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() );
0419     QMap<QString, QString> entries = grp.entryMap();
0420 
0421     QMap<QString, QString>::Iterator it;
0422     for ( it = entries.begin(); it != entries.end(); ++it )
0423     {
0424         const QString key = it.key();
0425         if (key.endsWith(KEY_Suffix_Enabled())) {
0426             const QString pluginid = key.left(key.length() - 7);
0427             const bool defValue = pluginMap.value( pluginid, false );
0428             const bool enabled = grp.readEntry(key, defValue);
0429             pluginMap.insert( pluginid, enabled );
0430         }
0431     }
0432 
0433     // store current known set of enabled plugins
0434     for (const KPluginMetaData& pi : qAsConst(d->plugins)) {
0435         if (isUserSelectable(pi)) {
0436             auto it = pluginMap.constFind(pi.pluginId());
0437             if (it != pluginMap.constEnd() && (it.value())) {
0438                 grp.writeEntry(pi.pluginId() + KEY_Suffix_Enabled(), true);
0439             }
0440         } else {
0441             // Backward compat: Remove any now-obsolete entries
0442             grp.deleteEntry(pi.pluginId() + QLatin1String("Disabled"));
0443         }
0444     }
0445     // Synchronize so we're writing out to the file.
0446     grp.sync();
0447 
0448     // load global plugins
0449     for (const KPluginMetaData& pi : qAsConst(d->plugins)) {
0450         if (isGlobalPlugin(pi)) {
0451             loadPluginInternal(pi.pluginId());
0452         }
0453     }
0454 
0455     qCDebug(SHELL) << "Done loading plugins - took:" << timer.elapsed() << "ms";
0456 }
0457 
0458 QList<IPlugin *> PluginController::loadedPlugins() const
0459 {
0460     Q_D(const PluginController);
0461 
0462     return d->loadedPlugins.values();
0463 }
0464 
0465 bool PluginController::unloadPlugin( const QString & pluginId )
0466 {
0467     Q_D(PluginController);
0468 
0469     IPlugin *thePlugin = plugin( pluginId );
0470     bool canUnload = d->canUnload( d->infoForId( pluginId ) );
0471     qCDebug(SHELL) << "Unloading plugin:" << pluginId << "?" << thePlugin << canUnload;
0472     if( thePlugin && canUnload )
0473     {
0474         return unloadPlugin(thePlugin, Later);
0475     }
0476     return (canUnload && thePlugin);
0477 }
0478 
0479 bool PluginController::unloadPlugin(IPlugin* plugin, PluginDeletion deletion)
0480 {
0481     Q_D(PluginController);
0482 
0483     qCDebug(SHELL) << "unloading plugin:" << plugin << pluginInfo( plugin ).name();
0484 
0485     emit unloadingPlugin(plugin);
0486     plugin->unload();
0487     emit pluginUnloaded(plugin);
0488 
0489     //Remove the plugin from our list of plugins so we create a new
0490     //instance when we're asked for it again.
0491     //This is important to do right here, not later when the plugin really
0492     //vanishes. For example project re-opening might try to reload the plugin
0493     //and then would get the "old" pointer which will be deleted in the next
0494     //event loop run and thus causing crashes.
0495     for ( PluginControllerPrivate::InfoToPluginMap::Iterator it = d->loadedPlugins.begin();
0496             it != d->loadedPlugins.end(); ++it )
0497     {
0498         if ( it.value() == plugin )
0499         {
0500             d->loadedPlugins.erase( it );
0501             break;
0502         }
0503     }
0504 
0505     if (deletion == Later)
0506         plugin->deleteLater();
0507     else
0508         delete plugin;
0509     return true;
0510 }
0511 
0512 KPluginMetaData PluginController::infoForPluginId( const QString &pluginId ) const
0513 {
0514     Q_D(const PluginController);
0515 
0516     auto it = std::find_if(d->plugins.constBegin(), d->plugins.constEnd(), [&](const KPluginMetaData& info) {
0517         return (info.pluginId() == pluginId);
0518     });
0519     return (it != d->plugins.constEnd()) ? *it : KPluginMetaData();
0520 }
0521 
0522 IPlugin *PluginController::loadPluginInternal( const QString &pluginId )
0523 {
0524     Q_D(PluginController);
0525 
0526     QElapsedTimer timer;
0527     timer.start();
0528 
0529     KPluginMetaData info = infoForPluginId( pluginId );
0530     if ( !info.isValid() ) {
0531         qCWarning(SHELL) << "Unable to find a plugin named '" << pluginId << "'!" ;
0532         return nullptr;
0533     }
0534 
0535     if ( IPlugin* plugin = d->loadedPlugins.value( info ) ) {
0536         return plugin;
0537     }
0538 
0539     const auto enabledState = d->enabledState(info);
0540     if (enabledState < PluginControllerPrivate::FirstEnabledState) {
0541         // Do not load disabled plugins
0542         qCDebug(SHELL) << "Not loading plugin named" << pluginId << (
0543             (enabledState == PluginControllerPrivate::DisabledByEnv) ?
0544                 "because disabled by KDEV_DISABLE_PLUGINS." :
0545             (enabledState == PluginControllerPrivate::DisabledBySetting) ?
0546                 "because disabled by setting." :
0547             /* else, should not happen */
0548                 "because disabled for unknown reason.");
0549         return nullptr;
0550     }
0551 
0552     if ( !hasMandatoryProperties( info ) ) {
0553         qCWarning(SHELL) << "Unable to load plugin named" << pluginId << "because not all mandatory properties are set.";
0554         return nullptr;
0555     }
0556 
0557     if ( info.value(KEY_Mode()) == KEY_Gui() && Core::self()->setupFlags() == Core::NoUi ) {
0558         qCDebug(SHELL) << "Not loading plugin named" << pluginId
0559                        << "- Running in No-Ui mode, but the plugin says it needs a GUI";
0560         return nullptr;
0561     }
0562 
0563     qCDebug(SHELL) << "Attempting to load" << pluginId << "- name:" << info.name();
0564 
0565     emit loadingPlugin( info.pluginId() );
0566 
0567     // first, ensure all dependencies are available and not disabled
0568     // this is unrelated to whether they are loaded already or not.
0569     // when we depend on e.g. A and B, but B cannot be found, then we
0570     // do not want to load A first and then fail on B and leave A loaded.
0571     // this would happen if we'd skip this step here and directly loadDependencies.
0572     QStringList missingInterfaces;
0573     if ( !hasUnresolvedDependencies( info, missingInterfaces ) ) {
0574         qCWarning(SHELL) << "Can't load plugin" << pluginId
0575                    << "some of its required dependencies could not be fulfilled:"
0576                    << missingInterfaces.join(QLatin1Char(','));
0577         return nullptr;
0578     }
0579 
0580     // now ensure all dependencies are loaded
0581     QString failedDependency;
0582     if( !loadDependencies( info, failedDependency ) ) {
0583         qCWarning(SHELL) << "Can't load plugin" << pluginId
0584                    << "because a required dependency could not be loaded:" << failedDependency;
0585         return nullptr;
0586     }
0587 
0588     // same for optional dependencies, but don't error out if anything fails
0589     loadOptionalDependencies( info );
0590 
0591     // now we can finally load the plugin itself
0592     const auto factory = KPluginFactory::loadFactory(info);
0593     if (!factory) {
0594         qCWarning(SHELL) << "Can't load plugin" << pluginId
0595                          << "because a factory to load the plugin could not be obtained:" << factory.errorText;
0596         return nullptr;
0597     }
0598 
0599     // now create it
0600     auto plugin = factory.plugin->create<IPlugin>(d->core);
0601     if (!plugin) {
0602         if (auto katePlugin = factory.plugin->create<KTextEditor::Plugin>(d->core, QVariantList() << info.pluginId())) {
0603             plugin = new KTextEditorIntegration::Plugin(katePlugin, d->core);
0604         } else {
0605             qCWarning(SHELL) << "Creating plugin" << pluginId << "failed.";
0606             return nullptr;
0607         }
0608     }
0609 
0610     KConfigGroup group = Core::self()->activeSession()->config()->group(KEY_Plugins());
0611     // runtime errors such as missing executables on the system or such get checked now
0612     if (plugin->hasError()) {
0613         qCWarning(SHELL) << "Could not load plugin" << pluginId << ", it reported the error:" << plugin->errorDescription()
0614                     << "Disabling the plugin now.";
0615         group.writeEntry(info.pluginId() + KEY_Suffix_Enabled(), false); // do the same as KPluginInfo did
0616         group.sync();
0617         unloadPlugin(pluginId);
0618         return nullptr;
0619     }
0620 
0621     // yay, it all worked - the plugin is loaded
0622     d->loadedPlugins.insert(info, plugin);
0623     group.writeEntry(info.pluginId() + KEY_Suffix_Enabled(), true); // do the same as KPluginInfo did
0624     group.sync();
0625     qCDebug(SHELL) << "Successfully loaded plugin" << pluginId << "from" << info.fileName()
0626                    << "- took:" << timer.elapsed() << "ms";
0627     emit pluginLoaded( plugin );
0628 
0629     return plugin;
0630 }
0631 
0632 
0633 IPlugin* PluginController::plugin(const QString& pluginId) const
0634 {
0635     Q_D(const PluginController);
0636 
0637     KPluginMetaData info = infoForPluginId( pluginId );
0638     if ( !info.isValid() )
0639         return nullptr;
0640 
0641     return d->loadedPlugins.value( info );
0642 }
0643 
0644 bool PluginController::hasUnresolvedDependencies( const KPluginMetaData& info, QStringList& missing ) const
0645 {
0646     Q_D(const PluginController);
0647 
0648     const auto requiredList = info.value(KEY_Required(), QStringList());
0649     QSet<QString> required(requiredList.begin(), requiredList.end());
0650     if (!required.isEmpty()) {
0651         d->foreachEnabledPlugin([&required](const KPluginMetaData& plugin) -> bool {
0652             const auto interfaces = plugin.value(KEY_Interfaces(), QStringList());
0653             for (const QString& iface : interfaces) {
0654                 required.remove(iface);
0655                 required.remove(iface + QLatin1Char('@') + plugin.pluginId());
0656             }
0657             return !required.isEmpty();
0658         });
0659     }
0660     // if we found all dependencies required should be empty now
0661     if (!required.isEmpty()) {
0662         missing = required.values();
0663         return false;
0664     }
0665     return true;
0666 }
0667 
0668 void PluginController::loadOptionalDependencies( const KPluginMetaData& info )
0669 {
0670     const auto dependencies = info.value(KEY_Optional(), QStringList());
0671     for (const QString& dep : dependencies) {
0672         Dependency dependency(dep);
0673         if (!pluginForExtension(dependency.interface, dependency.pluginName)) {
0674             qCDebug(SHELL) << "Couldn't load optional dependency:" << dep << info.pluginId();
0675         }
0676     }
0677 }
0678 
0679 bool PluginController::loadDependencies( const KPluginMetaData& info, QString& failedDependency )
0680 {
0681     const auto dependencies = info.value(KEY_Required(), QStringList());
0682     for (const QString& value : dependencies) {
0683         Dependency dependency(value);
0684         if (!pluginForExtension(dependency.interface, dependency.pluginName)) {
0685             failedDependency = value;
0686             return false;
0687         }
0688     }
0689     return true;
0690 }
0691 
0692 IPlugin *PluginController::pluginForExtension(const QString &extension, const QString &pluginName, const QVariantMap& constraints)
0693 {
0694     Q_D(PluginController);
0695 
0696     IPlugin* plugin = nullptr;
0697     d->foreachEnabledPlugin([this, &plugin] (const KPluginMetaData& info) -> bool {
0698         Q_D(PluginController);
0699 
0700         plugin = d->loadedPlugins.value( info );
0701         if( !plugin ) {
0702             plugin = loadPluginInternal( info.pluginId() );
0703         }
0704         return !plugin;
0705     }, extension, constraints, pluginName);
0706 
0707     return plugin;
0708 }
0709 
0710 QList<IPlugin*> PluginController::allPluginsForExtension(const QString &extension, const QVariantMap& constraints)
0711 {
0712     Q_D(PluginController);
0713 
0714     //qCDebug(SHELL) << "Finding all Plugins for Extension:" << extension << "|" << constraints;
0715     QList<IPlugin*> plugins;
0716     d->foreachEnabledPlugin([this, &plugins] (const KPluginMetaData& info) -> bool {
0717         Q_D(PluginController);
0718 
0719         IPlugin* plugin = d->loadedPlugins.value( info );
0720         if( !plugin) {
0721             plugin = loadPluginInternal( info.pluginId() );
0722         }
0723         if (plugin && !plugins.contains(plugin)) {
0724             plugins << plugin;
0725         }
0726         return true;
0727     }, extension, constraints);
0728     return plugins;
0729 }
0730 
0731 QVector<KPluginMetaData> PluginController::queryExtensionPlugins(const QString& extension, const QVariantMap& constraints) const
0732 {
0733     Q_D(const PluginController);
0734 
0735     QVector<KPluginMetaData> plugins;
0736     d->foreachEnabledPlugin([&plugins] (const KPluginMetaData& info) -> bool {
0737         plugins << info;
0738         return true;
0739     }, extension, constraints);
0740     return plugins;
0741 }
0742 
0743 QStringList PluginController::allPluginNames() const
0744 {
0745     Q_D(const PluginController);
0746 
0747     QStringList names;
0748     names.reserve(d->plugins.size());
0749     for (const KPluginMetaData& info : qAsConst(d->plugins)) {
0750         names << info.pluginId();
0751     }
0752     return names;
0753 }
0754 
0755 QList<ContextMenuExtension> PluginController::queryPluginsForContextMenuExtensions(KDevelop::Context* context, QWidget* parent) const
0756 {
0757     Q_D(const PluginController);
0758 
0759     // This fixes random order of extension menu items between different runs of KDevelop.
0760     // Without sorting we have random reordering of "Analyze With" submenu for example:
0761     // 1) "Cppcheck" actions, "Vera++" actions - first run
0762     // 2) "Vera++" actions, "Cppcheck" actions - some other run.
0763     QMultiMap<QString, IPlugin*> sortedPlugins;
0764     for (auto it = d->loadedPlugins.constBegin(); it != d->loadedPlugins.constEnd(); ++it) {
0765         sortedPlugins.insert(it.key().name(), it.value());
0766     }
0767 
0768     QList<ContextMenuExtension> exts;
0769     exts.reserve(sortedPlugins.size());
0770     for (IPlugin* plugin : qAsConst(sortedPlugins)) {
0771         exts << plugin->contextMenuExtension(context, parent);
0772     }
0773 
0774     exts << Core::self()->debugControllerInternal()->contextMenuExtension(context, parent);
0775     exts << Core::self()->documentationControllerInternal()->contextMenuExtension(context, parent);
0776     exts << Core::self()->sourceFormatterControllerInternal()->contextMenuExtension(context, parent);
0777     exts << Core::self()->runControllerInternal()->contextMenuExtension(context, parent);
0778     exts << Core::self()->projectControllerInternal()->contextMenuExtension(context, parent);
0779 
0780     return exts;
0781 }
0782 
0783 QStringList PluginController::projectPlugins() const
0784 {
0785     Q_D(const PluginController);
0786 
0787     QStringList names;
0788     for (const KPluginMetaData& info : qAsConst(d->plugins)) {
0789         if (info.value(KEY_Category()) == KEY_Project()) {
0790             names << info.pluginId();
0791         }
0792     }
0793     return names;
0794 }
0795 
0796 void PluginController::loadProjectPlugins()
0797 {
0798     const auto pluginNames = projectPlugins();
0799     for (const QString& name : pluginNames) {
0800         loadPluginInternal( name );
0801     }
0802 }
0803 
0804 void PluginController::unloadProjectPlugins()
0805 {
0806     const auto pluginNames = projectPlugins();
0807     for (const QString& name : pluginNames) {
0808         unloadPlugin( name );
0809     }
0810 }
0811 
0812 QVector<KPluginMetaData> PluginController::allPluginInfos() const
0813 {
0814     Q_D(const PluginController);
0815 
0816     return d->plugins;
0817 }
0818 
0819 void PluginController::updateLoadedPlugins()
0820 {
0821     Q_D(PluginController);
0822 
0823     QStringList defaultPlugins = ShellExtension::getInstance()->defaultPlugins();
0824     KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins() );
0825     for (const KPluginMetaData& info : qAsConst(d->plugins)) {
0826         if( isGlobalPlugin( info ) )
0827         {
0828             bool enabled = grp.readEntry(info.pluginId() + KEY_Suffix_Enabled(), ( defaultPlugins.isEmpty() || defaultPlugins.contains( info.pluginId() ) ) ) || !isUserSelectable( info );
0829             bool loaded = d->loadedPlugins.contains( info );
0830             if( loaded && !enabled )
0831             {
0832                 qCDebug(SHELL) << "unloading" << info.pluginId();
0833                 if( !unloadPlugin( info.pluginId() ) )
0834                 {
0835                     grp.writeEntry( info.pluginId() + KEY_Suffix_Enabled(), false );
0836                 }
0837             } else if( !loaded && enabled )
0838             {
0839                 loadPluginInternal( info.pluginId() );
0840             }
0841         }
0842         // TODO: what about project plugins? what about dependency plugins?
0843     }
0844 }
0845 
0846 void PluginController::resetToDefaults()
0847 {
0848     Q_D(PluginController);
0849 
0850     KSharedConfigPtr cfg = Core::self()->activeSession()->config();
0851     cfg->deleteGroup( KEY_Plugins() );
0852     cfg->sync();
0853     KConfigGroup grp = cfg->group( KEY_Plugins() );
0854     QStringList plugins = ShellExtension::getInstance()->defaultPlugins();
0855     if( plugins.isEmpty() )
0856     {
0857         for (const KPluginMetaData& info : qAsConst(d->plugins)) {
0858             if (!isUserSelectable(info)) {
0859                 continue;
0860             }
0861 
0862             QJsonValue enabledByDefault = info.rawData()[KEY_KPlugin()].toObject()[KEY_EnabledByDefault()];
0863             // plugins enabled until explicitly specified otherwise
0864             if (enabledByDefault.isNull() || enabledByDefault.toBool()) {
0865                 plugins << info.pluginId();
0866             }
0867         }
0868     }
0869     for (const QString& s : qAsConst(plugins)) {
0870         grp.writeEntry(s + KEY_Suffix_Enabled(), true);
0871     }
0872     grp.sync();
0873 }
0874 
0875 }
0876 
0877 #include "moc_plugincontroller.cpp"