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"