1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "pluginmanager.h"
27 #include "pluginmanager_p.h"
28 #include "pluginspec.h"
29 #include "pluginspec_p.h"
30 #include "optionsparser.h"
31 #include "iplugin.h"
32
33 #include <QCoreApplication>
34 #include <QCryptographicHash>
35 #include <QDateTime>
36 #include <QDebug>
37 #include <QDir>
38 #include <QEventLoop>
39 #include <QFile>
40 #include <QGuiApplication>
41 #include <QLibrary>
42 #include <QLibraryInfo>
43 #include <QMessageBox>
44 #include <QMetaProperty>
45 #include <QPushButton>
46 #include <QSysInfo>
47 #include <QTextStream>
48 #include <QTimer>
49 #include <QWriteLocker>
50
51 #include <utils/algorithm.h>
52 #include <utils/benchmarker.h>
53 #include <utils/executeondestruction.h>
54 #include <utils/fileutils.h>
55 #include <utils/hostosinfo.h>
56 #include <utils/mimetypes/mimedatabase.h>
57 #include <utils/qtcassert.h>
58 #include <utils/qtcprocess.h>
59 #include <utils/qtcsettings.h>
60
61 #ifdef WITH_TESTS
62 #include <utils/hostosinfo.h>
63 #include <QTest>
64 #include <QThread>
65 #endif
66
67 #include <functional>
68 #include <memory>
69
70 Q_LOGGING_CATEGORY(pluginLog, "qtc.extensionsystem", QtWarningMsg)
71
72 const char C_IGNORED_PLUGINS[] = "Plugins/Ignored";
73 const char C_FORCEENABLED_PLUGINS[] = "Plugins/ForceEnabled";
74 const int DELAYED_INITIALIZE_INTERVAL = 20; // ms
75
76 enum { debugLeaks = 0 };
77
78 /*!
79 \namespace ExtensionSystem
80 \inmodule QtCreator
81 \brief The ExtensionSystem namespace provides classes that belong to the
82 core plugin system.
83
84 The basic extension system contains the plugin manager and its supporting classes,
85 and the IPlugin interface that must be implemented by plugin providers.
86 */
87
88 /*!
89 \namespace ExtensionSystem::Internal
90 \internal
91 */
92
93 /*!
94 \class ExtensionSystem::PluginManager
95 \inheaderfile extensionsystem/pluginmanager.h
96 \inmodule QtCreator
97 \ingroup mainclasses
98
99 \brief The PluginManager class implements the core plugin system that
100 manages the plugins, their life cycle, and their registered objects.
101
102 The plugin manager is used for the following tasks:
103 \list
104 \li Manage plugins and their state
105 \li Manipulate a \e {common object pool}
106 \endlist
107
108 \section1 Plugins
109 Plugins must derive from the IPlugin class and have the IID
110 \c "org.qt-project.Qt.QtCreatorPlugin".
111
112 The plugin manager is used to set a list of file system directories to search for
113 plugins, retrieve information about the state of these plugins, and to load them.
114
115 Usually, the application creates a PluginManager instance and initiates the
116 loading.
117 \code
118 // 'plugins' and subdirs will be searched for plugins
119 PluginManager::setPluginPaths(QStringList("plugins"));
120 PluginManager::loadPlugins(); // try to load all the plugins
121 \endcode
122 Additionally, it is possible to directly access plugin meta data, instances,
123 and state.
124
125 \section1 Object Pool
126 Plugins (and everybody else) can add objects to a common \e pool that is located in
127 the plugin manager. Objects in the pool must derive from QObject, there are no other
128 prerequisites. Objects can be retrieved from the object pool via the getObject()
129 and getObjectByName() functions.
130
131 Whenever the state of the object pool changes, a corresponding signal is
132 emitted by the plugin manager.
133
134 A common usecase for the object pool is that a plugin (or the application) provides
135 an \e {extension point} for other plugins, which is a class or interface that can
136 be implemented and added to the object pool. The plugin that provides the
137 extension point looks for implementations of the class or interface in the object pool.
138 \code
139 // Plugin A provides a "MimeTypeHandler" extension point
140 // in plugin B:
141 MyMimeTypeHandler *handler = new MyMimeTypeHandler();
142 PluginManager::instance()->addObject(handler);
143 // In plugin A:
144 MimeTypeHandler *mimeHandler =
145 PluginManager::getObject<MimeTypeHandler>();
146 \endcode
147
148
149 The ExtensionSystem::Invoker class template provides \e {syntactic sugar}
150 for using \e soft extension points that may or may not be provided by an
151 object in the pool. This approach neither requires the \e user plugin being
152 linked against the \e provider plugin nor a common shared
153 header file. The exposed interface is implicitly given by the
154 invokable functions of the provider object in the object pool.
155
156 The ExtensionSystem::invoke() function template encapsulates
157 ExtensionSystem::Invoker construction for the common case where
158 the success of the call is not checked.
159
160 \code
161 // In the "provide" plugin A:
162 namespace PluginA {
163 class SomeProvider : public QObject
164 {
165 Q_OBJECT
166
167 public:
168 Q_INVOKABLE QString doit(const QString &msg, int n) {
169 {
170 qDebug() << "I AM DOING IT " << msg;
171 return QString::number(n);
172 }
173 };
174 } // namespace PluginA
175
176
177 // In the "user" plugin B:
178 int someFuntionUsingPluginA()
179 {
180 using namespace ExtensionSystem;
181
182 QObject *target = PluginManager::getObjectByClassName("PluginA::SomeProvider");
183
184 if (target) {
185 // Some random argument.
186 QString msg = "REALLY.";
187
188 // Plain function call, no return value.
189 invoke<void>(target, "doit", msg, 2);
190
191 // Plain function with no return value.
192 qDebug() << "Result: " << invoke<QString>(target, "doit", msg, 21);
193
194 // Record success of function call with return value.
195 Invoker<QString> in1(target, "doit", msg, 21);
196 qDebug() << "Success: (expected)" << in1.wasSuccessful();
197
198 // Try to invoke a non-existing function.
199 Invoker<QString> in2(target, "doitWrong", msg, 22);
200 qDebug() << "Success (not expected):" << in2.wasSuccessful();
201
202 } else {
203
204 // We have to cope with plugin A's absence.
205 }
206 };
207 \endcode
208
209 \note The type of the parameters passed to the \c{invoke()} calls
210 is deduced from the parameters themselves and must match the type of
211 the arguments of the called functions \e{exactly}. No conversion or even
212 integer promotions are applicable, so to invoke a function with a \c{long}
213 parameter explicitly, use \c{long(43)} or such.
214
215 \note The object pool manipulating functions are thread-safe.
216 */
217
218 /*!
219 \fn template <typename T> *ExtensionSystem::PluginManager::getObject()
220
221 Retrieves the object of a given type from the object pool.
222
223 This function uses \c qobject_cast to determine the type of an object.
224 If there are more than one objects of the given type in
225 the object pool, this function will arbitrarily choose one of them.
226
227 \sa addObject()
228 */
229
230 /*!
231 \fn template <typename T, typename Predicate> *ExtensionSystem::PluginManager::getObject(Predicate predicate)
232
233 Retrieves the object of a given type from the object pool that matches
234 the \a predicate.
235
236 This function uses \c qobject_cast to determine the type of an object.
237 The predicate must be a function taking T * and returning a bool.
238 If there is more than one object matching the type and predicate,
239 this function will arbitrarily choose one of them.
240
241 \sa addObject()
242 */
243
244
245 using namespace Utils;
246
247 namespace ExtensionSystem {
248
249 using namespace Internal;
250
251 static Internal::PluginManagerPrivate *d = nullptr;
252 static PluginManager *m_instance = nullptr;
253
254 /*!
255 Gets the unique plugin manager instance.
256 */
instance()257 PluginManager *PluginManager::instance()
258 {
259 return m_instance;
260 }
261
262 /*!
263 Creates a plugin manager. Should be done only once per application.
264 */
PluginManager()265 PluginManager::PluginManager()
266 {
267 m_instance = this;
268 d = new PluginManagerPrivate(this);
269 }
270
271 /*!
272 \internal
273 */
~PluginManager()274 PluginManager::~PluginManager()
275 {
276 delete d;
277 d = nullptr;
278 }
279
280 /*!
281 Adds the object \a obj to the object pool, so it can be retrieved
282 again from the pool by type.
283
284 The plugin manager does not do any memory management. Added objects
285 must be removed from the pool and deleted manually by whoever is responsible for the object.
286
287 Emits the \c objectAdded() signal.
288
289 \sa PluginManager::removeObject()
290 \sa PluginManager::getObject()
291 \sa PluginManager::getObjectByName()
292 */
addObject(QObject * obj)293 void PluginManager::addObject(QObject *obj)
294 {
295 d->addObject(obj);
296 }
297
298 /*!
299 Emits the \c aboutToRemoveObject() signal and removes the object \a obj
300 from the object pool.
301 \sa PluginManager::addObject()
302 */
removeObject(QObject * obj)303 void PluginManager::removeObject(QObject *obj)
304 {
305 d->removeObject(obj);
306 }
307
308 /*!
309 Retrieves the list of all objects in the pool, unfiltered.
310
311 Usually, clients do not need to call this function.
312
313 \sa PluginManager::getObject()
314 */
allObjects()315 QVector<QObject *> PluginManager::allObjects()
316 {
317 return d->allObjects;
318 }
319
320 /*!
321 \internal
322 */
listLock()323 QReadWriteLock *PluginManager::listLock()
324 {
325 return &d->m_lock;
326 }
327
328 /*!
329 Tries to load all the plugins that were previously found when
330 setting the plugin search paths. The plugin specs of the plugins
331 can be used to retrieve error and state information about individual plugins.
332
333 \sa setPluginPaths()
334 \sa plugins()
335 */
loadPlugins()336 void PluginManager::loadPlugins()
337 {
338 d->loadPlugins();
339 }
340
341 /*!
342 Returns \c true if any plugin has errors even though it is enabled.
343 Most useful to call after loadPlugins().
344 */
hasError()345 bool PluginManager::hasError()
346 {
347 return Utils::anyOf(plugins(), [](PluginSpec *spec) {
348 // only show errors on startup if plugin is enabled.
349 return spec->hasError() && spec->isEffectivelyEnabled();
350 });
351 }
352
allErrors()353 const QStringList PluginManager::allErrors()
354 {
355 return Utils::transform<QStringList>(Utils::filtered(plugins(), [](const PluginSpec *spec) {
356 return spec->hasError() && spec->isEffectivelyEnabled();
357 }), [](const PluginSpec *spec) {
358 return spec->name().append(": ").append(spec->errorString());
359 });
360 }
361
362 /*!
363 Returns all plugins that require \a spec to be loaded. Recurses into dependencies.
364 */
pluginsRequiringPlugin(PluginSpec * spec)365 const QSet<PluginSpec *> PluginManager::pluginsRequiringPlugin(PluginSpec *spec)
366 {
367 QSet<PluginSpec *> dependingPlugins({spec});
368 // recursively add plugins that depend on plugins that.... that depend on spec
369 for (PluginSpec *spec : d->loadQueue()) {
370 if (spec->requiresAny(dependingPlugins))
371 dependingPlugins.insert(spec);
372 }
373 dependingPlugins.remove(spec);
374 return dependingPlugins;
375 }
376
377 /*!
378 Returns all plugins that \a spec requires to be loaded. Recurses into dependencies.
379 */
pluginsRequiredByPlugin(PluginSpec * spec)380 const QSet<PluginSpec *> PluginManager::pluginsRequiredByPlugin(PluginSpec *spec)
381 {
382 QSet<PluginSpec *> recursiveDependencies;
383 recursiveDependencies.insert(spec);
384 std::queue<PluginSpec *> queue;
385 queue.push(spec);
386 while (!queue.empty()) {
387 PluginSpec *checkSpec = queue.front();
388 queue.pop();
389 const QHash<PluginDependency, PluginSpec *> deps = checkSpec->dependencySpecs();
390 for (auto depIt = deps.cbegin(), end = deps.cend(); depIt != end; ++depIt) {
391 if (depIt.key().type != PluginDependency::Required)
392 continue;
393 PluginSpec *depSpec = depIt.value();
394 if (!recursiveDependencies.contains(depSpec)) {
395 recursiveDependencies.insert(depSpec);
396 queue.push(depSpec);
397 }
398 }
399 }
400 recursiveDependencies.remove(spec);
401 return recursiveDependencies;
402 }
403
404 /*!
405 Shuts down and deletes all plugins.
406 */
shutdown()407 void PluginManager::shutdown()
408 {
409 d->shutdown();
410 }
411
filled(const QString & s,int min)412 static QString filled(const QString &s, int min)
413 {
414 return s + QString(qMax(0, min - s.size()), ' ');
415 }
416
systemInformation()417 QString PluginManager::systemInformation()
418 {
419 QString result;
420 CommandLine qtDiag(HostOsInfo::withExecutableSuffix(
421 QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qtdiag"));
422 QtcProcess qtDiagProc;
423 qtDiagProc.setCommand(qtDiag);
424 qtDiagProc.runBlocking();
425 if (qtDiagProc.result() == QtcProcess::FinishedWithSuccess)
426 result += qtDiagProc.allOutput() + "\n";
427 result += "Plugin information:\n\n";
428 auto longestSpec = std::max_element(d->pluginSpecs.cbegin(), d->pluginSpecs.cend(),
429 [](const PluginSpec *left, const PluginSpec *right) {
430 return left->name().size() < right->name().size();
431 });
432 int size = (*longestSpec)->name().size();
433 for (const PluginSpec *spec : plugins()) {
434 result += QLatin1String(spec->isEffectivelyEnabled() ? "+ " : " ") + filled(spec->name(), size) +
435 " " + spec->version() + "\n";
436 }
437 QString settingspath = QFileInfo(settings()->fileName()).path();
438 if (settingspath.startsWith(QDir::homePath()))
439 settingspath.replace(QDir::homePath(), "~");
440 result += "\nUsed settingspath: " + settingspath + "\n";
441 return result;
442 }
443
444 /*!
445 The list of paths were the plugin manager searches for plugins.
446
447 \sa setPluginPaths()
448 */
pluginPaths()449 QStringList PluginManager::pluginPaths()
450 {
451 return d->pluginPaths;
452 }
453
454 /*!
455 Sets the plugin paths. All the specified \a paths and their subdirectory
456 trees are searched for plugins.
457
458 \sa pluginPaths()
459 \sa loadPlugins()
460 */
setPluginPaths(const QStringList & paths)461 void PluginManager::setPluginPaths(const QStringList &paths)
462 {
463 d->setPluginPaths(paths);
464 }
465
466 /*!
467 The IID that valid plugins must have.
468
469 \sa setPluginIID()
470 */
pluginIID()471 QString PluginManager::pluginIID()
472 {
473 return d->pluginIID;
474 }
475
476 /*!
477 Sets the IID that valid plugins must have to \a iid. Only plugins with this
478 IID are loaded, others are silently ignored.
479
480 At the moment this must be called before setPluginPaths() is called.
481
482 \omit
483 // ### TODO let this + setPluginPaths read the plugin meta data lazyly whenever loadPlugins() or plugins() is called.
484 \endomit
485 */
setPluginIID(const QString & iid)486 void PluginManager::setPluginIID(const QString &iid)
487 {
488 d->pluginIID = iid;
489 }
490
491 /*!
492 Defines the user specific \a settings to use for information about enabled and
493 disabled plugins.
494 Needs to be set before the plugin search path is set with setPluginPaths().
495 */
setSettings(QtcSettings * settings)496 void PluginManager::setSettings(QtcSettings *settings)
497 {
498 d->setSettings(settings);
499 }
500
501 /*!
502 Defines the global (user-independent) \a settings to use for information about
503 default disabled plugins.
504 Needs to be set before the plugin search path is set with setPluginPaths().
505 */
setGlobalSettings(QtcSettings * settings)506 void PluginManager::setGlobalSettings(QtcSettings *settings)
507 {
508 d->setGlobalSettings(settings);
509 }
510
511 /*!
512 Returns the user specific settings used for information about enabled and
513 disabled plugins.
514 */
settings()515 QtcSettings *PluginManager::settings()
516 {
517 return d->settings;
518 }
519
520 /*!
521 Returns the global (user-independent) settings used for information about default disabled plugins.
522 */
globalSettings()523 QtcSettings *PluginManager::globalSettings()
524 {
525 return d->globalSettings;
526 }
527
writeSettings()528 void PluginManager::writeSettings()
529 {
530 d->writeSettings();
531 }
532
533 /*!
534 The arguments left over after parsing (that were neither startup nor plugin
535 arguments). Typically, this will be the list of files to open.
536 */
arguments()537 QStringList PluginManager::arguments()
538 {
539 return d->arguments;
540 }
541
542 /*!
543 The arguments that should be used when automatically restarting the application.
544 This includes plugin manager related options for enabling or disabling plugins,
545 but excludes others, like the arguments returned by arguments() and the appOptions
546 passed to the parseOptions() method.
547 */
argumentsForRestart()548 QStringList PluginManager::argumentsForRestart()
549 {
550 return d->argumentsForRestart;
551 }
552
553 /*!
554 List of all plugins that have been found in the plugin search paths.
555 This list is valid directly after the setPluginPaths() call.
556 The plugin specifications contain plugin metadata and the current state
557 of the plugins. If a plugin's library has been already successfully loaded,
558 the plugin specification has a reference to the created plugin instance as well.
559
560 \sa setPluginPaths()
561 */
plugins()562 const QVector<PluginSpec *> PluginManager::plugins()
563 {
564 return d->pluginSpecs;
565 }
566
pluginCollections()567 QHash<QString, QVector<PluginSpec *>> PluginManager::pluginCollections()
568 {
569 return d->pluginCategories;
570 }
571
572 static const char argumentKeywordC[] = ":arguments";
573 static const char pwdKeywordC[] = ":pwd";
574
575 /*!
576 Serializes plugin options and arguments for sending in a single string
577 via QtSingleApplication:
578 ":myplugin|-option1|-option2|:arguments|argument1|argument2",
579 as a list of lists started by a keyword with a colon. Arguments are last.
580
581 \sa setPluginPaths()
582 */
serializedArguments()583 QString PluginManager::serializedArguments()
584 {
585 const QChar separator = QLatin1Char('|');
586 QString rc;
587 for (const PluginSpec *ps : plugins()) {
588 if (!ps->arguments().isEmpty()) {
589 if (!rc.isEmpty())
590 rc += separator;
591 rc += QLatin1Char(':');
592 rc += ps->name();
593 rc += separator;
594 rc += ps->arguments().join(separator);
595 }
596 }
597 if (!rc.isEmpty())
598 rc += separator;
599 rc += QLatin1String(pwdKeywordC) + separator + QDir::currentPath();
600 if (!d->arguments.isEmpty()) {
601 if (!rc.isEmpty())
602 rc += separator;
603 rc += QLatin1String(argumentKeywordC);
604 for (const QString &argument : qAsConst(d->arguments))
605 rc += separator + argument;
606 }
607 return rc;
608 }
609
610 /* Extract a sublist from the serialized arguments
611 * indicated by a keyword starting with a colon indicator:
612 * ":a,i1,i2,:b:i3,i4" with ":a" -> "i1,i2"
613 */
subList(const QStringList & in,const QString & key)614 static QStringList subList(const QStringList &in, const QString &key)
615 {
616 QStringList rc;
617 // Find keyword and copy arguments until end or next keyword
618 const QStringList::const_iterator inEnd = in.constEnd();
619 QStringList::const_iterator it = std::find(in.constBegin(), inEnd, key);
620 if (it != inEnd) {
621 const QChar nextIndicator = QLatin1Char(':');
622 for (++it; it != inEnd && !it->startsWith(nextIndicator); ++it)
623 rc.append(*it);
624 }
625 return rc;
626 }
627
628 /*!
629 Parses the options encoded in \a serializedArgument
630 and passes them on to the respective plugins along with the arguments.
631
632 \a socket is passed for disconnecting the peer when the operation is done (for example,
633 document is closed) for supporting the \c -block flag.
634 */
635
remoteArguments(const QString & serializedArgument,QObject * socket)636 void PluginManager::remoteArguments(const QString &serializedArgument, QObject *socket)
637 {
638 if (serializedArgument.isEmpty())
639 return;
640 QStringList serializedArguments = serializedArgument.split(QLatin1Char('|'));
641 const QStringList pwdValue = subList(serializedArguments, QLatin1String(pwdKeywordC));
642 const QString workingDirectory = pwdValue.isEmpty() ? QString() : pwdValue.first();
643 const QStringList arguments = subList(serializedArguments, QLatin1String(argumentKeywordC));
644 for (const PluginSpec *ps : plugins()) {
645 if (ps->state() == PluginSpec::Running) {
646 const QStringList pluginOptions = subList(serializedArguments, QLatin1Char(':') + ps->name());
647 QObject *socketParent = ps->plugin()->remoteCommand(pluginOptions, workingDirectory,
648 arguments);
649 if (socketParent && socket) {
650 socket->setParent(socketParent);
651 socket = nullptr;
652 }
653 }
654 }
655 if (socket)
656 delete socket;
657 }
658
659 /*!
660 Takes the list of command line options in \a args and parses them.
661 The plugin manager itself might process some options itself directly
662 (\c {-noload <plugin>}), and adds options that are registered by
663 plugins to their plugin specs.
664
665 The caller (the application) may register itself for options via the
666 \a appOptions list, containing pairs of \e {option string} and a bool
667 that indicates whether the option requires an argument.
668 Application options always override any plugin's options.
669
670 \a foundAppOptions is set to pairs of (\e {option string}, \e argument)
671 for any application options that were found.
672 The command line options that were not processed can be retrieved via the arguments() function.
673 If an error occurred (like missing argument for an option that requires one), \a errorString contains
674 a descriptive message of the error.
675
676 Returns if there was an error.
677 */
parseOptions(const QStringList & args,const QMap<QString,bool> & appOptions,QMap<QString,QString> * foundAppOptions,QString * errorString)678 bool PluginManager::parseOptions(const QStringList &args,
679 const QMap<QString, bool> &appOptions,
680 QMap<QString, QString> *foundAppOptions,
681 QString *errorString)
682 {
683 OptionsParser options(args, appOptions, foundAppOptions, errorString, d);
684 return options.parse();
685 }
686
687
688
indent(QTextStream & str,int indent)689 static inline void indent(QTextStream &str, int indent)
690 {
691 str << QString(indent, ' ');
692 }
693
formatOption(QTextStream & str,const QString & opt,const QString & parm,const QString & description,int optionIndentation,int descriptionIndentation)694 static inline void formatOption(QTextStream &str,
695 const QString &opt, const QString &parm, const QString &description,
696 int optionIndentation, int descriptionIndentation)
697 {
698 int remainingIndent = descriptionIndentation - optionIndentation - opt.size();
699 indent(str, optionIndentation);
700 str << opt;
701 if (!parm.isEmpty()) {
702 str << " <" << parm << '>';
703 remainingIndent -= 3 + parm.size();
704 }
705 if (remainingIndent >= 1) {
706 indent(str, remainingIndent);
707 } else {
708 str << '\n';
709 indent(str, descriptionIndentation);
710 }
711 str << description << '\n';
712 }
713
714 /*!
715 Formats the startup options of the plugin manager for command line help with the specified
716 \a optionIndentation and \a descriptionIndentation.
717 Adds the result to \a str.
718 */
719
formatOptions(QTextStream & str,int optionIndentation,int descriptionIndentation)720 void PluginManager::formatOptions(QTextStream &str, int optionIndentation, int descriptionIndentation)
721 {
722 formatOption(str, QLatin1String(OptionsParser::LOAD_OPTION),
723 QLatin1String("plugin"), QLatin1String("Load <plugin> and all plugins that it requires"),
724 optionIndentation, descriptionIndentation);
725 formatOption(str, QLatin1String(OptionsParser::LOAD_OPTION) + QLatin1String(" all"),
726 QString(), QLatin1String("Load all available plugins"),
727 optionIndentation, descriptionIndentation);
728 formatOption(str, QLatin1String(OptionsParser::NO_LOAD_OPTION),
729 QLatin1String("plugin"), QLatin1String("Do not load <plugin> and all plugins that require it"),
730 optionIndentation, descriptionIndentation);
731 formatOption(str, QLatin1String(OptionsParser::NO_LOAD_OPTION) + QLatin1String(" all"),
732 QString(), QString::fromLatin1("Do not load any plugin (useful when "
733 "followed by one or more \"%1\" arguments)")
734 .arg(QLatin1String(OptionsParser::LOAD_OPTION)),
735 optionIndentation, descriptionIndentation);
736 formatOption(str, QLatin1String(OptionsParser::PROFILE_OPTION),
737 QString(), QLatin1String("Profile plugin loading"),
738 optionIndentation, descriptionIndentation);
739 formatOption(str,
740 QLatin1String(OptionsParser::NO_CRASHCHECK_OPTION),
741 QString(),
742 QLatin1String("Disable startup check for previously crashed instance"),
743 optionIndentation,
744 descriptionIndentation);
745 #ifdef WITH_TESTS
746 formatOption(str, QString::fromLatin1(OptionsParser::TEST_OPTION)
747 + QLatin1String(" <plugin>[,testfunction[:testdata]]..."), QString(),
748 QLatin1String("Run plugin's tests (by default a separate settings path is used)"),
749 optionIndentation, descriptionIndentation);
750 formatOption(str, QString::fromLatin1(OptionsParser::TEST_OPTION) + QLatin1String(" all"),
751 QString(), QLatin1String("Run tests from all plugins"),
752 optionIndentation, descriptionIndentation);
753 formatOption(str, QString::fromLatin1(OptionsParser::NOTEST_OPTION),
754 QLatin1String("plugin"), QLatin1String("Exclude all of the plugin's tests from the test run"),
755 optionIndentation, descriptionIndentation);
756 formatOption(str, QString::fromLatin1(OptionsParser::SCENARIO_OPTION),
757 QString("scenarioname"), QLatin1String("Run given scenario"),
758 optionIndentation, descriptionIndentation);
759 #endif
760 }
761
762 /*!
763 Formats the plugin options of the plugin specs for command line help with the specified
764 \a optionIndentation and \a descriptionIndentation.
765 Adds the result to \a str.
766 */
767
formatPluginOptions(QTextStream & str,int optionIndentation,int descriptionIndentation)768 void PluginManager::formatPluginOptions(QTextStream &str, int optionIndentation, int descriptionIndentation)
769 {
770 // Check plugins for options
771 for (PluginSpec *ps : qAsConst(d->pluginSpecs)) {
772 const PluginSpec::PluginArgumentDescriptions pargs = ps->argumentDescriptions();
773 if (!pargs.empty()) {
774 str << "\nPlugin: " << ps->name() << '\n';
775 for (const PluginArgumentDescription &pad : pargs)
776 formatOption(str, pad.name, pad.parameter, pad.description, optionIndentation, descriptionIndentation);
777 }
778 }
779 }
780
781 /*!
782 Formats the version of the plugin specs for command line help and adds it to \a str.
783 */
formatPluginVersions(QTextStream & str)784 void PluginManager::formatPluginVersions(QTextStream &str)
785 {
786 for (PluginSpec *ps : qAsConst(d->pluginSpecs))
787 str << " " << ps->name() << ' ' << ps->version() << ' ' << ps->description() << '\n';
788 }
789
790 /*!
791 \internal
792 */
testRunRequested()793 bool PluginManager::testRunRequested()
794 {
795 return !d->testSpecs.empty();
796 }
797
798 #ifdef WITH_TESTS
799 // Called in plugin initialization, the scenario function will be called later, from main
registerScenario(const QString & scenarioId,std::function<bool ()> scenarioStarter)800 bool PluginManager::registerScenario(const QString &scenarioId, std::function<bool()> scenarioStarter)
801 {
802 if (d->m_scenarios.contains(scenarioId)) {
803 const QString warning = QString("Can't register scenario \"%1\" as the other scenario was "
804 "already registered with this name.").arg(scenarioId);
805 qWarning("%s", qPrintable(warning));
806 return false;
807 }
808
809 d->m_scenarios.insert(scenarioId, scenarioStarter);
810 return true;
811 }
812
813 // Called from main
isScenarioRequested()814 bool PluginManager::isScenarioRequested()
815 {
816 return !d->m_requestedScenario.isEmpty();
817 }
818
819 // Called from main (may be squashed with the isScenarioRequested: runScenarioIfRequested).
820 // Returns false if scenario couldn't run (e.g. no Qt version set)
runScenario()821 bool PluginManager::runScenario()
822 {
823 if (d->m_isScenarioRunning) {
824 qWarning("Scenario is already running. Can't run scenario recursively.");
825 return false;
826 }
827
828 if (d->m_requestedScenario.isEmpty()) {
829 qWarning("Can't run any scenario since no scenario was requested.");
830 return false;
831 }
832
833 if (!d->m_scenarios.contains(d->m_requestedScenario)) {
834 const QString warning = QString("Requested scenario \"%1\" was not registered.").arg(d->m_requestedScenario);
835 qWarning("%s", qPrintable(warning));
836 return false;
837 }
838
839 d->m_isScenarioRunning = true;
840 // The return value comes now from scenarioStarted() function. It may fail e.g. when
841 // no Qt version is set. Initializing the scenario may take some time, that's why
842 // waitForScenarioFullyInitialized() was added.
843 bool ret = d->m_scenarios[d->m_requestedScenario]();
844
845 QMutexLocker locker(&d->m_scenarioMutex);
846 d->m_scenarioFullyInitialized = true;
847 d->m_scenarioWaitCondition.wakeAll();
848
849 return ret;
850 }
851
852 // Called from scenario point (and also from runScenario - don't run scenarios recursively).
853 // This may be called from non-main thread. We assume that m_requestedScenario
854 // may only be changed from the main thread.
isScenarioRunning(const QString & scenarioId)855 bool PluginManager::isScenarioRunning(const QString &scenarioId)
856 {
857 return d->m_isScenarioRunning && d->m_requestedScenario == scenarioId;
858 }
859
860 // This may be called from non-main thread.
finishScenario()861 bool PluginManager::finishScenario()
862 {
863 if (!d->m_isScenarioRunning)
864 return false; // Can't finish not running scenario
865
866 if (d->m_isScenarioFinished.exchange(true))
867 return false; // Finish was already called before. We return false, as we didn't finish it right now.
868
869 QMetaObject::invokeMethod(d, []() { emit m_instance->scenarioFinished(0); });
870 return true; // Finished successfully.
871 }
872
873 // Waits until the running scenario is fully initialized
waitForScenarioFullyInitialized()874 void PluginManager::waitForScenarioFullyInitialized()
875 {
876 if (QThread::currentThread() == qApp->thread()) {
877 qWarning("The waitForScenarioFullyInitialized() function can't be called from main thread.");
878 return;
879 }
880 QMutexLocker locker(&d->m_scenarioMutex);
881 if (d->m_scenarioFullyInitialized)
882 return;
883
884 d->m_scenarioWaitCondition.wait(&d->m_scenarioMutex);
885 }
886 #endif
887
setCreatorProcessData(const PluginManager::ProcessData & data)888 void PluginManager::setCreatorProcessData(const PluginManager::ProcessData &data)
889 {
890 d->m_creatorProcessData = data;
891 }
892
creatorProcessData()893 PluginManager::ProcessData PluginManager::creatorProcessData()
894 {
895 return d->m_creatorProcessData;
896 }
897
898 /*!
899 \internal
900 */
901
profilingReport(const char * what,const PluginSpec * spec)902 void PluginManager::profilingReport(const char *what, const PluginSpec *spec)
903 {
904 d->profilingReport(what, spec);
905 }
906
907
908 /*!
909 Returns a list of plugins in load order.
910 */
loadQueue()911 QVector<PluginSpec *> PluginManager::loadQueue()
912 {
913 return d->loadQueue();
914 }
915
916 //============PluginManagerPrivate===========
917
918 /*!
919 \internal
920 */
createSpec()921 PluginSpec *PluginManagerPrivate::createSpec()
922 {
923 return new PluginSpec();
924 }
925
926 /*!
927 \internal
928 */
setSettings(QtcSettings * s)929 void PluginManagerPrivate::setSettings(QtcSettings *s)
930 {
931 if (settings)
932 delete settings;
933 settings = s;
934 if (settings)
935 settings->setParent(this);
936 }
937
938 /*!
939 \internal
940 */
setGlobalSettings(QtcSettings * s)941 void PluginManagerPrivate::setGlobalSettings(QtcSettings *s)
942 {
943 if (globalSettings)
944 delete globalSettings;
945 globalSettings = s;
946 if (globalSettings)
947 globalSettings->setParent(this);
948 }
949
950 /*!
951 \internal
952 */
privateSpec(PluginSpec * spec)953 PluginSpecPrivate *PluginManagerPrivate::privateSpec(PluginSpec *spec)
954 {
955 return spec->d;
956 }
957
nextDelayedInitialize()958 void PluginManagerPrivate::nextDelayedInitialize()
959 {
960 while (!delayedInitializeQueue.empty()) {
961 PluginSpec *spec = delayedInitializeQueue.front();
962 delayedInitializeQueue.pop();
963 profilingReport(">delayedInitialize", spec);
964 bool delay = spec->d->delayedInitialize();
965 profilingReport("<delayedInitialize", spec);
966 if (delay)
967 break; // do next delayedInitialize after a delay
968 }
969 if (delayedInitializeQueue.empty()) {
970 m_isInitializationDone = true;
971 delete delayedInitializeTimer;
972 delayedInitializeTimer = nullptr;
973 profilingSummary();
974 emit q->initializationDone();
975 #ifdef WITH_TESTS
976 if (PluginManager::testRunRequested())
977 startTests();
978 else if (PluginManager::isScenarioRequested()) {
979 if (PluginManager::runScenario()) {
980 const QString info = QString("Successfully started scenario \"%1\"...").arg(d->m_requestedScenario);
981 qInfo("%s", qPrintable(info));
982 } else {
983 QMetaObject::invokeMethod(this, []() { emit m_instance->scenarioFinished(1); });
984 }
985 }
986 #endif
987 } else {
988 delayedInitializeTimer->start();
989 }
990 }
991
992 /*!
993 \internal
994 */
PluginManagerPrivate(PluginManager * pluginManager)995 PluginManagerPrivate::PluginManagerPrivate(PluginManager *pluginManager) :
996 q(pluginManager)
997 {
998 }
999
1000
1001 /*!
1002 \internal
1003 */
~PluginManagerPrivate()1004 PluginManagerPrivate::~PluginManagerPrivate()
1005 {
1006 qDeleteAll(pluginSpecs);
1007 }
1008
1009 /*!
1010 \internal
1011 */
writeSettings()1012 void PluginManagerPrivate::writeSettings()
1013 {
1014 if (!settings)
1015 return;
1016 QStringList tempDisabledPlugins;
1017 QStringList tempForceEnabledPlugins;
1018 for (PluginSpec *spec : qAsConst(pluginSpecs)) {
1019 if (spec->isEnabledByDefault() && !spec->isEnabledBySettings())
1020 tempDisabledPlugins.append(spec->name());
1021 if (!spec->isEnabledByDefault() && spec->isEnabledBySettings())
1022 tempForceEnabledPlugins.append(spec->name());
1023 }
1024
1025 settings->setValueWithDefault(C_IGNORED_PLUGINS, tempDisabledPlugins);
1026 settings->setValueWithDefault(C_FORCEENABLED_PLUGINS, tempForceEnabledPlugins);
1027 }
1028
1029 /*!
1030 \internal
1031 */
readSettings()1032 void PluginManagerPrivate::readSettings()
1033 {
1034 if (globalSettings) {
1035 defaultDisabledPlugins = globalSettings->value(QLatin1String(C_IGNORED_PLUGINS)).toStringList();
1036 defaultEnabledPlugins = globalSettings->value(QLatin1String(C_FORCEENABLED_PLUGINS)).toStringList();
1037 }
1038 if (settings) {
1039 disabledPlugins = settings->value(QLatin1String(C_IGNORED_PLUGINS)).toStringList();
1040 forceEnabledPlugins = settings->value(QLatin1String(C_FORCEENABLED_PLUGINS)).toStringList();
1041 }
1042 }
1043
1044 /*!
1045 \internal
1046 */
stopAll()1047 void PluginManagerPrivate::stopAll()
1048 {
1049 if (delayedInitializeTimer && delayedInitializeTimer->isActive()) {
1050 delayedInitializeTimer->stop();
1051 delete delayedInitializeTimer;
1052 delayedInitializeTimer = nullptr;
1053 }
1054
1055 const QVector<PluginSpec *> queue = loadQueue();
1056 for (PluginSpec *spec : queue)
1057 loadPlugin(spec, PluginSpec::Stopped);
1058 }
1059
1060 /*!
1061 \internal
1062 */
deleteAll()1063 void PluginManagerPrivate::deleteAll()
1064 {
1065 Utils::reverseForeach(loadQueue(), [this](PluginSpec *spec) {
1066 loadPlugin(spec, PluginSpec::Deleted);
1067 });
1068 }
1069
1070 #ifdef WITH_TESTS
1071
1072 using TestPlan = QMap<QObject *, QStringList>; // Object -> selected test functions
1073
isTestFunction(const QMetaMethod & metaMethod)1074 static bool isTestFunction(const QMetaMethod &metaMethod)
1075 {
1076 static const QVector<QByteArray> blackList = {"initTestCase()",
1077 "cleanupTestCase()",
1078 "init()",
1079 "cleanup()"};
1080
1081 if (metaMethod.methodType() != QMetaMethod::Slot)
1082 return false;
1083
1084 if (metaMethod.access() != QMetaMethod::Private)
1085 return false;
1086
1087 const QByteArray signature = metaMethod.methodSignature();
1088 if (blackList.contains(signature))
1089 return false;
1090
1091 if (!signature.startsWith("test"))
1092 return false;
1093
1094 if (signature.endsWith("_data()"))
1095 return false;
1096
1097 return true;
1098 }
1099
testFunctions(const QMetaObject * metaObject)1100 static QStringList testFunctions(const QMetaObject *metaObject)
1101 {
1102
1103 QStringList functions;
1104
1105 for (int i = metaObject->methodOffset(); i < metaObject->methodCount(); ++i) {
1106 const QMetaMethod metaMethod = metaObject->method(i);
1107 if (isTestFunction(metaMethod)) {
1108 const QByteArray signature = metaMethod.methodSignature();
1109 const QString method = QString::fromLatin1(signature);
1110 const QString methodName = method.left(method.size() - 2);
1111 functions.append(methodName);
1112 }
1113 }
1114
1115 return functions;
1116 }
1117
matchingTestFunctions(const QStringList & testFunctions,const QString & matchText)1118 static QStringList matchingTestFunctions(const QStringList &testFunctions,
1119 const QString &matchText)
1120 {
1121 // There might be a test data suffix like in "testfunction:testdata1".
1122 QString testFunctionName = matchText;
1123 QString testDataSuffix;
1124 const int index = testFunctionName.indexOf(QLatin1Char(':'));
1125 if (index != -1) {
1126 testDataSuffix = testFunctionName.mid(index);
1127 testFunctionName = testFunctionName.left(index);
1128 }
1129
1130 const QRegularExpression regExp(
1131 QRegularExpression::wildcardToRegularExpression(testFunctionName));
1132 QStringList matchingFunctions;
1133 for (const QString &testFunction : testFunctions) {
1134 if (regExp.match(testFunction).hasMatch()) {
1135 // If the specified test data is invalid, the QTest framework will
1136 // print a reasonable error message for us.
1137 matchingFunctions.append(testFunction + testDataSuffix);
1138 }
1139 }
1140
1141 return matchingFunctions;
1142 }
1143
objectWithClassName(const QVector<QObject * > & objects,const QString & className)1144 static QObject *objectWithClassName(const QVector<QObject *> &objects, const QString &className)
1145 {
1146 return Utils::findOr(objects, nullptr, [className] (QObject *object) -> bool {
1147 QString candidate = QString::fromUtf8(object->metaObject()->className());
1148 const int colonIndex = candidate.lastIndexOf(QLatin1Char(':'));
1149 if (colonIndex != -1 && colonIndex < candidate.size() - 1)
1150 candidate = candidate.mid(colonIndex + 1);
1151 return candidate == className;
1152 });
1153 }
1154
executeTestPlan(const TestPlan & testPlan)1155 static int executeTestPlan(const TestPlan &testPlan)
1156 {
1157 int failedTests = 0;
1158
1159 for (auto it = testPlan.cbegin(), end = testPlan.cend(); it != end; ++it) {
1160 QObject *testObject = it.key();
1161 QStringList functions = it.value();
1162
1163 // Don't run QTest::qExec without any test functions, that'd run *all* slots as tests.
1164 if (functions.isEmpty())
1165 continue;
1166
1167 functions.removeDuplicates();
1168
1169 // QTest::qExec() expects basically QCoreApplication::arguments(),
1170 QStringList qExecArguments = QStringList()
1171 << QLatin1String("arg0") // fake application name
1172 << QLatin1String("-maxwarnings") << QLatin1String("0"); // unlimit output
1173 qExecArguments << functions;
1174 // avoid being stuck in QTBUG-24925
1175 if (!HostOsInfo::isWindowsHost())
1176 qExecArguments << "-nocrashhandler";
1177 failedTests += QTest::qExec(testObject, qExecArguments);
1178 }
1179
1180 return failedTests;
1181 }
1182
1183 /// Resulting plan consists of all test functions of the plugin object and
1184 /// all test functions of all test objects of the plugin.
generateCompleteTestPlan(IPlugin * plugin,const QVector<QObject * > & testObjects)1185 static TestPlan generateCompleteTestPlan(IPlugin *plugin, const QVector<QObject *> &testObjects)
1186 {
1187 TestPlan testPlan;
1188
1189 testPlan.insert(plugin, testFunctions(plugin->metaObject()));
1190 for (QObject *testObject : testObjects) {
1191 const QStringList allFunctions = testFunctions(testObject->metaObject());
1192 testPlan.insert(testObject, allFunctions);
1193 }
1194
1195 return testPlan;
1196 }
1197
1198 /// Resulting plan consists of all matching test functions of the plugin object
1199 /// and all matching functions of all test objects of the plugin. However, if a
1200 /// match text denotes a test class, all test functions of that will be
1201 /// included and the class will not be considered further.
1202 ///
1203 /// Since multiple match texts can match the same function, a test function might
1204 /// be included multiple times for a test object.
generateCustomTestPlan(IPlugin * plugin,const QVector<QObject * > & testObjects,const QStringList & matchTexts)1205 static TestPlan generateCustomTestPlan(IPlugin *plugin,
1206 const QVector<QObject *> &testObjects,
1207 const QStringList &matchTexts)
1208 {
1209 TestPlan testPlan;
1210
1211 const QStringList testFunctionsOfPluginObject = testFunctions(plugin->metaObject());
1212 QStringList matchedTestFunctionsOfPluginObject;
1213 QStringList remainingMatchTexts = matchTexts;
1214 QVector<QObject *> remainingTestObjectsOfPlugin = testObjects;
1215
1216 while (!remainingMatchTexts.isEmpty()) {
1217 const QString matchText = remainingMatchTexts.takeFirst();
1218 bool matched = false;
1219
1220 if (QObject *testObject = objectWithClassName(remainingTestObjectsOfPlugin, matchText)) {
1221 // Add all functions of the matching test object
1222 matched = true;
1223 testPlan.insert(testObject, testFunctions(testObject->metaObject()));
1224 remainingTestObjectsOfPlugin.removeAll(testObject);
1225
1226 } else {
1227 // Add all matching test functions of all remaining test objects
1228 for (QObject *testObject : qAsConst(remainingTestObjectsOfPlugin)) {
1229 const QStringList allFunctions = testFunctions(testObject->metaObject());
1230 const QStringList matchingFunctions = matchingTestFunctions(allFunctions,
1231 matchText);
1232 if (!matchingFunctions.isEmpty()) {
1233 matched = true;
1234 testPlan[testObject] += matchingFunctions;
1235 }
1236 }
1237 }
1238
1239 const QStringList currentMatchedTestFunctionsOfPluginObject
1240 = matchingTestFunctions(testFunctionsOfPluginObject, matchText);
1241 if (!currentMatchedTestFunctionsOfPluginObject.isEmpty()) {
1242 matched = true;
1243 matchedTestFunctionsOfPluginObject += currentMatchedTestFunctionsOfPluginObject;
1244 }
1245
1246 if (!matched) {
1247 QTextStream out(stdout);
1248 out << "No test function or class matches \"" << matchText
1249 << "\" in plugin \"" << plugin->metaObject()->className()
1250 << "\".\nAvailable functions:\n";
1251 for (const QString &f : testFunctionsOfPluginObject)
1252 out << " " << f << '\n';
1253 out << '\n';
1254 }
1255 }
1256
1257 // Add all matching test functions of plugin
1258 if (!matchedTestFunctionsOfPluginObject.isEmpty())
1259 testPlan.insert(plugin, matchedTestFunctionsOfPluginObject);
1260
1261 return testPlan;
1262 }
1263
startTests()1264 void PluginManagerPrivate::startTests()
1265 {
1266 if (PluginManager::hasError()) {
1267 qWarning("Errors occurred while loading plugins, skipping test run.");
1268 for (const QString &pluginError : PluginManager::allErrors())
1269 qWarning("%s", qPrintable(pluginError));
1270 QTimer::singleShot(1, QCoreApplication::instance(), &QCoreApplication::quit);
1271 return;
1272 }
1273
1274 int failedTests = 0;
1275 for (const TestSpec &testSpec : qAsConst(testSpecs)) {
1276 IPlugin *plugin = testSpec.pluginSpec->plugin();
1277 if (!plugin)
1278 continue; // plugin not loaded
1279
1280 const QVector<QObject *> testObjects = plugin->createTestObjects();
1281 ExecuteOnDestruction deleteTestObjects([&]() { qDeleteAll(testObjects); });
1282 Q_UNUSED(deleteTestObjects)
1283
1284 const bool hasDuplicateTestObjects = testObjects.size()
1285 != Utils::filteredUnique(testObjects).size();
1286 QTC_ASSERT(!hasDuplicateTestObjects, continue);
1287 QTC_ASSERT(!testObjects.contains(plugin), continue);
1288
1289 const TestPlan testPlan = testSpec.testFunctionsOrObjects.isEmpty()
1290 ? generateCompleteTestPlan(plugin, testObjects)
1291 : generateCustomTestPlan(plugin, testObjects, testSpec.testFunctionsOrObjects);
1292
1293 failedTests += executeTestPlan(testPlan);
1294 }
1295
1296 QTimer::singleShot(0, this, [failedTests]() { emit m_instance->testsFinished(failedTests); });
1297 }
1298 #endif
1299
1300 /*!
1301 \internal
1302 */
addObject(QObject * obj)1303 void PluginManagerPrivate::addObject(QObject *obj)
1304 {
1305 {
1306 QWriteLocker lock(&m_lock);
1307 if (obj == nullptr) {
1308 qWarning() << "PluginManagerPrivate::addObject(): trying to add null object";
1309 return;
1310 }
1311 if (allObjects.contains(obj)) {
1312 qWarning() << "PluginManagerPrivate::addObject(): trying to add duplicate object";
1313 return;
1314 }
1315
1316 if (debugLeaks)
1317 qDebug() << "PluginManagerPrivate::addObject" << obj << obj->objectName();
1318
1319 if (m_profilingVerbosity && !m_profileTimer.isNull()) {
1320 // Report a timestamp when adding an object. Useful for profiling
1321 // its initialization time.
1322 const int absoluteElapsedMS = int(m_profileTimer->elapsed());
1323 qDebug(" %-43s %8dms", obj->metaObject()->className(), absoluteElapsedMS);
1324 }
1325
1326 allObjects.append(obj);
1327 }
1328 emit q->objectAdded(obj);
1329 }
1330
1331 /*!
1332 \internal
1333 */
removeObject(QObject * obj)1334 void PluginManagerPrivate::removeObject(QObject *obj)
1335 {
1336 if (obj == nullptr) {
1337 qWarning() << "PluginManagerPrivate::removeObject(): trying to remove null object";
1338 return;
1339 }
1340
1341 if (!allObjects.contains(obj)) {
1342 qWarning() << "PluginManagerPrivate::removeObject(): object not in list:"
1343 << obj << obj->objectName();
1344 return;
1345 }
1346 if (debugLeaks)
1347 qDebug() << "PluginManagerPrivate::removeObject" << obj << obj->objectName();
1348
1349 emit q->aboutToRemoveObject(obj);
1350 QWriteLocker lock(&m_lock);
1351 allObjects.removeAll(obj);
1352 }
1353
1354 /*!
1355 \internal
1356 */
loadPlugins()1357 void PluginManagerPrivate::loadPlugins()
1358 {
1359 const QVector<PluginSpec *> queue = loadQueue();
1360 Utils::setMimeStartupPhase(MimeStartupPhase::PluginsLoading);
1361 for (PluginSpec *spec : queue)
1362 loadPlugin(spec, PluginSpec::Loaded);
1363
1364 Utils::setMimeStartupPhase(MimeStartupPhase::PluginsInitializing);
1365 for (PluginSpec *spec : queue)
1366 loadPlugin(spec, PluginSpec::Initialized);
1367
1368 Utils::setMimeStartupPhase(MimeStartupPhase::PluginsDelayedInitializing);
1369 Utils::reverseForeach(queue, [this](PluginSpec *spec) {
1370 loadPlugin(spec, PluginSpec::Running);
1371 if (spec->state() == PluginSpec::Running) {
1372 delayedInitializeQueue.push(spec);
1373 } else {
1374 // Plugin initialization failed, so cleanup after it
1375 spec->d->kill();
1376 }
1377 });
1378 emit q->pluginsChanged();
1379 Utils::setMimeStartupPhase(MimeStartupPhase::UpAndRunning);
1380
1381 delayedInitializeTimer = new QTimer;
1382 delayedInitializeTimer->setInterval(DELAYED_INITIALIZE_INTERVAL);
1383 delayedInitializeTimer->setSingleShot(true);
1384 connect(delayedInitializeTimer, &QTimer::timeout,
1385 this, &PluginManagerPrivate::nextDelayedInitialize);
1386 delayedInitializeTimer->start();
1387 }
1388
1389 /*!
1390 \internal
1391 */
shutdown()1392 void PluginManagerPrivate::shutdown()
1393 {
1394 stopAll();
1395 if (!asynchronousPlugins.isEmpty()) {
1396 shutdownEventLoop = new QEventLoop;
1397 shutdownEventLoop->exec();
1398 }
1399 deleteAll();
1400 #ifdef WITH_TESTS
1401 if (PluginManager::isScenarioRunning("TestModelManagerInterface")) {
1402 qDebug() << "Point 2: Expect the next call to Point 3 triggers a crash";
1403 QThread::currentThread()->sleep(5);
1404 }
1405 #endif
1406 if (!allObjects.isEmpty()) {
1407 qDebug() << "There are" << allObjects.size() << "objects left in the plugin manager pool.";
1408 // Intentionally split debug info here, since in case the list contains
1409 // already deleted object we get at least the info about the number of objects;
1410 qDebug() << "The following objects left in the plugin manager pool:" << allObjects;
1411 }
1412 }
1413
1414 /*!
1415 \internal
1416 */
asyncShutdownFinished()1417 void PluginManagerPrivate::asyncShutdownFinished()
1418 {
1419 auto *plugin = qobject_cast<IPlugin *>(sender());
1420 Q_ASSERT(plugin);
1421 asynchronousPlugins.remove(plugin->pluginSpec());
1422 if (asynchronousPlugins.isEmpty())
1423 shutdownEventLoop->exit();
1424 }
1425
1426 /*!
1427 \internal
1428 */
loadQueue()1429 const QVector<PluginSpec *> PluginManagerPrivate::loadQueue()
1430 {
1431 QVector<PluginSpec *> queue;
1432 for (PluginSpec *spec : qAsConst(pluginSpecs)) {
1433 QVector<PluginSpec *> circularityCheckQueue;
1434 loadQueue(spec, queue, circularityCheckQueue);
1435 }
1436 return queue;
1437 }
1438
1439 /*!
1440 \internal
1441 */
loadQueue(PluginSpec * spec,QVector<PluginSpec * > & queue,QVector<PluginSpec * > & circularityCheckQueue)1442 bool PluginManagerPrivate::loadQueue(PluginSpec *spec,
1443 QVector<PluginSpec *> &queue,
1444 QVector<PluginSpec *> &circularityCheckQueue)
1445 {
1446 if (queue.contains(spec))
1447 return true;
1448 // check for circular dependencies
1449 if (circularityCheckQueue.contains(spec)) {
1450 spec->d->hasError = true;
1451 spec->d->errorString = PluginManager::tr("Circular dependency detected:");
1452 spec->d->errorString += QLatin1Char('\n');
1453 int index = circularityCheckQueue.indexOf(spec);
1454 for (int i = index; i < circularityCheckQueue.size(); ++i) {
1455 spec->d->errorString.append(PluginManager::tr("%1 (%2) depends on")
1456 .arg(circularityCheckQueue.at(i)->name()).arg(circularityCheckQueue.at(i)->version()));
1457 spec->d->errorString += QLatin1Char('\n');
1458 }
1459 spec->d->errorString.append(PluginManager::tr("%1 (%2)").arg(spec->name()).arg(spec->version()));
1460 return false;
1461 }
1462 circularityCheckQueue.append(spec);
1463 // check if we have the dependencies
1464 if (spec->state() == PluginSpec::Invalid || spec->state() == PluginSpec::Read) {
1465 queue.append(spec);
1466 return false;
1467 }
1468
1469 // add dependencies
1470 const QHash<PluginDependency, PluginSpec *> deps = spec->dependencySpecs();
1471 for (auto it = deps.cbegin(), end = deps.cend(); it != end; ++it) {
1472 // Skip test dependencies since they are not real dependencies but just force-loaded
1473 // plugins when running tests
1474 if (it.key().type == PluginDependency::Test)
1475 continue;
1476 PluginSpec *depSpec = it.value();
1477 if (!loadQueue(depSpec, queue, circularityCheckQueue)) {
1478 spec->d->hasError = true;
1479 spec->d->errorString =
1480 PluginManager::tr("Cannot load plugin because dependency failed to load: %1 (%2)\nReason: %3")
1481 .arg(depSpec->name()).arg(depSpec->version()).arg(depSpec->errorString());
1482 return false;
1483 }
1484 }
1485 // add self
1486 queue.append(spec);
1487 return true;
1488 }
1489
1490 class LockFile
1491 {
1492 public:
filePath(PluginManagerPrivate * pm)1493 static QString filePath(PluginManagerPrivate *pm)
1494 {
1495 return QFileInfo(pm->settings->fileName()).absolutePath() + '/'
1496 + QCoreApplication::applicationName() + '.'
1497 + QCryptographicHash::hash(QCoreApplication::applicationDirPath().toUtf8(),
1498 QCryptographicHash::Sha1)
1499 .left(8)
1500 .toHex()
1501 + ".lock";
1502 }
1503
lockedPluginName(PluginManagerPrivate * pm)1504 static Utils::optional<QString> lockedPluginName(PluginManagerPrivate *pm)
1505 {
1506 const QString lockFilePath = LockFile::filePath(pm);
1507 if (QFile::exists(lockFilePath)) {
1508 QFile f(lockFilePath);
1509 if (f.open(QIODevice::ReadOnly)) {
1510 const auto pluginName = QString::fromUtf8(f.readLine()).trimmed();
1511 f.close();
1512 return pluginName;
1513 } else {
1514 qCDebug(pluginLog) << "Lock file" << lockFilePath << "exists but is not readable";
1515 }
1516 }
1517 return {};
1518 }
1519
LockFile(PluginManagerPrivate * pm,PluginSpec * spec)1520 LockFile(PluginManagerPrivate *pm, PluginSpec *spec)
1521 : m_filePath(filePath(pm))
1522 {
1523 QDir().mkpath(QFileInfo(m_filePath).absolutePath());
1524 QFile f(m_filePath);
1525 if (f.open(QIODevice::WriteOnly)) {
1526 f.write(spec->name().toUtf8());
1527 f.write("\n");
1528 f.close();
1529 } else {
1530 qCDebug(pluginLog) << "Cannot write lock file" << m_filePath;
1531 }
1532 }
1533
~LockFile()1534 ~LockFile() { QFile::remove(m_filePath); }
1535
1536 private:
1537 QString m_filePath;
1538 };
1539
checkForProblematicPlugins()1540 void PluginManagerPrivate::checkForProblematicPlugins()
1541 {
1542 if (!enableCrashCheck)
1543 return;
1544 const Utils::optional<QString> pluginName = LockFile::lockedPluginName(this);
1545 if (pluginName) {
1546 PluginSpec *spec = pluginByName(*pluginName);
1547 if (spec && !spec->isRequired()) {
1548 const QSet<PluginSpec *> dependents = PluginManager::pluginsRequiringPlugin(spec);
1549 auto dependentsNames = Utils::transform<QStringList>(dependents, &PluginSpec::name);
1550 std::sort(dependentsNames.begin(), dependentsNames.end());
1551 const QString dependentsList = dependentsNames.join(", ");
1552 const QString pluginsMenu = HostOsInfo::isMacHost()
1553 ? tr("%1 > About Plugins")
1554 .arg(QGuiApplication::applicationDisplayName())
1555 : tr("Help > About Plugins");
1556 const QString otherPluginsText = tr("The following plugins depend on "
1557 "%1 and are also disabled: %2.\n\n")
1558 .arg(spec->name(), dependentsList);
1559 const QString detailsText = (dependents.isEmpty() ? QString() : otherPluginsText)
1560 + tr("Disable plugins permanently in %1.").arg(pluginsMenu);
1561 const QString text = tr("It looks like %1 closed because of a problem with the \"%2\" "
1562 "plugin. Temporarily disable the plugin?")
1563 .arg(QGuiApplication::applicationDisplayName(), spec->name());
1564 QMessageBox dialog;
1565 dialog.setIcon(QMessageBox::Question);
1566 dialog.setText(text);
1567 dialog.setDetailedText(detailsText);
1568 QPushButton *disableButton = dialog.addButton(tr("Disable Plugin"),
1569 QMessageBox::AcceptRole);
1570 dialog.addButton(tr("Continue"), QMessageBox::RejectRole);
1571 dialog.exec();
1572 if (dialog.clickedButton() == disableButton) {
1573 spec->d->setForceDisabled(true);
1574 for (PluginSpec *other : dependents)
1575 other->d->setForceDisabled(true);
1576 enableDependenciesIndirectly();
1577 }
1578 }
1579 }
1580 }
1581
checkForProblematicPlugins()1582 void PluginManager::checkForProblematicPlugins()
1583 {
1584 d->checkForProblematicPlugins();
1585 }
1586
1587 /*!
1588 \internal
1589 */
loadPlugin(PluginSpec * spec,PluginSpec::State destState)1590 void PluginManagerPrivate::loadPlugin(PluginSpec *spec, PluginSpec::State destState)
1591 {
1592 if (spec->hasError() || spec->state() != destState-1)
1593 return;
1594
1595 // don't load disabled plugins.
1596 if (!spec->isEffectivelyEnabled() && destState == PluginSpec::Loaded)
1597 return;
1598
1599 std::unique_ptr<LockFile> lockFile;
1600 if (enableCrashCheck && destState < PluginSpec::Stopped)
1601 lockFile.reset(new LockFile(this, spec));
1602
1603 switch (destState) {
1604 case PluginSpec::Running:
1605 profilingReport(">initializeExtensions", spec);
1606 spec->d->initializeExtensions();
1607 profilingReport("<initializeExtensions", spec);
1608 return;
1609 case PluginSpec::Deleted:
1610 profilingReport(">delete", spec);
1611 spec->d->kill();
1612 profilingReport("<delete", spec);
1613 return;
1614 default:
1615 break;
1616 }
1617 // check if dependencies have loaded without error
1618 const QHash<PluginDependency, PluginSpec *> deps = spec->dependencySpecs();
1619 for (auto it = deps.cbegin(), end = deps.cend(); it != end; ++it) {
1620 if (it.key().type != PluginDependency::Required)
1621 continue;
1622 PluginSpec *depSpec = it.value();
1623 if (depSpec->state() != destState) {
1624 spec->d->hasError = true;
1625 spec->d->errorString =
1626 PluginManager::tr("Cannot load plugin because dependency failed to load: %1(%2)\nReason: %3")
1627 .arg(depSpec->name()).arg(depSpec->version()).arg(depSpec->errorString());
1628 return;
1629 }
1630 }
1631 switch (destState) {
1632 case PluginSpec::Loaded:
1633 profilingReport(">loadLibrary", spec);
1634 spec->d->loadLibrary();
1635 profilingReport("<loadLibrary", spec);
1636 break;
1637 case PluginSpec::Initialized:
1638 profilingReport(">initializePlugin", spec);
1639 spec->d->initializePlugin();
1640 profilingReport("<initializePlugin", spec);
1641 break;
1642 case PluginSpec::Stopped:
1643 profilingReport(">stop", spec);
1644 if (spec->d->stop() == IPlugin::AsynchronousShutdown) {
1645 asynchronousPlugins << spec;
1646 connect(spec->plugin(), &IPlugin::asynchronousShutdownFinished,
1647 this, &PluginManagerPrivate::asyncShutdownFinished);
1648 }
1649 profilingReport("<stop", spec);
1650 break;
1651 default:
1652 break;
1653 }
1654 }
1655
1656 /*!
1657 \internal
1658 */
setPluginPaths(const QStringList & paths)1659 void PluginManagerPrivate::setPluginPaths(const QStringList &paths)
1660 {
1661 qCDebug(pluginLog) << "Plugin search paths:" << paths;
1662 qCDebug(pluginLog) << "Required IID:" << pluginIID;
1663 pluginPaths = paths;
1664 readSettings();
1665 readPluginPaths();
1666 }
1667
pluginFiles(const QStringList & pluginPaths)1668 static const QStringList pluginFiles(const QStringList &pluginPaths)
1669 {
1670 QStringList pluginFiles;
1671 QStringList searchPaths = pluginPaths;
1672 while (!searchPaths.isEmpty()) {
1673 const QDir dir(searchPaths.takeFirst());
1674 const QFileInfoList files = dir.entryInfoList(QDir::Files | QDir::NoSymLinks);
1675 const QStringList absoluteFilePaths = Utils::transform(files, &QFileInfo::absoluteFilePath);
1676 pluginFiles += Utils::filtered(absoluteFilePaths, [](const QString &path) { return QLibrary::isLibrary(path); });
1677 const QFileInfoList dirs = dir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot);
1678 searchPaths += Utils::transform(dirs, &QFileInfo::absoluteFilePath);
1679 }
1680 return pluginFiles;
1681 }
1682
1683 /*!
1684 \internal
1685 */
readPluginPaths()1686 void PluginManagerPrivate::readPluginPaths()
1687 {
1688 qDeleteAll(pluginSpecs);
1689 pluginSpecs.clear();
1690 pluginCategories.clear();
1691
1692 // default
1693 pluginCategories.insert(QString(), QVector<PluginSpec *>());
1694
1695 for (const QString &pluginFile : pluginFiles(pluginPaths)) {
1696 PluginSpec *spec = PluginSpec::read(pluginFile);
1697 if (!spec) // not a Qt Creator plugin
1698 continue;
1699
1700 // defaultDisabledPlugins and defaultEnabledPlugins from install settings
1701 // is used to override the defaults read from the plugin spec
1702 if (spec->isEnabledByDefault() && defaultDisabledPlugins.contains(spec->name())) {
1703 spec->d->setEnabledByDefault(false);
1704 spec->d->setEnabledBySettings(false);
1705 } else if (!spec->isEnabledByDefault() && defaultEnabledPlugins.contains(spec->name())) {
1706 spec->d->setEnabledByDefault(true);
1707 spec->d->setEnabledBySettings(true);
1708 }
1709 if (!spec->isEnabledByDefault() && forceEnabledPlugins.contains(spec->name()))
1710 spec->d->setEnabledBySettings(true);
1711 if (spec->isEnabledByDefault() && disabledPlugins.contains(spec->name()))
1712 spec->d->setEnabledBySettings(false);
1713
1714 pluginCategories[spec->category()].append(spec);
1715 pluginSpecs.append(spec);
1716 }
1717 resolveDependencies();
1718 enableDependenciesIndirectly();
1719 // ensure deterministic plugin load order by sorting
1720 Utils::sort(pluginSpecs, &PluginSpec::name);
1721 emit q->pluginsChanged();
1722 }
1723
resolveDependencies()1724 void PluginManagerPrivate::resolveDependencies()
1725 {
1726 for (PluginSpec *spec : qAsConst(pluginSpecs))
1727 spec->d->resolveDependencies(pluginSpecs);
1728 }
1729
enableDependenciesIndirectly()1730 void PluginManagerPrivate::enableDependenciesIndirectly()
1731 {
1732 for (PluginSpec *spec : qAsConst(pluginSpecs))
1733 spec->d->enabledIndirectly = false;
1734 // cannot use reverse loadQueue here, because test dependencies can introduce circles
1735 QVector<PluginSpec *> queue = Utils::filtered(pluginSpecs, &PluginSpec::isEffectivelyEnabled);
1736 while (!queue.isEmpty()) {
1737 PluginSpec *spec = queue.takeFirst();
1738 queue += spec->d->enableDependenciesIndirectly(containsTestSpec(spec));
1739 }
1740 }
1741
1742 // Look in argument descriptions of the specs for the option.
pluginForOption(const QString & option,bool * requiresArgument) const1743 PluginSpec *PluginManagerPrivate::pluginForOption(const QString &option, bool *requiresArgument) const
1744 {
1745 // Look in the plugins for an option
1746 *requiresArgument = false;
1747 for (PluginSpec *spec : qAsConst(pluginSpecs)) {
1748 PluginArgumentDescription match = Utils::findOrDefault(spec->argumentDescriptions(),
1749 [option](PluginArgumentDescription pad) {
1750 return pad.name == option;
1751 });
1752 if (!match.name.isEmpty()) {
1753 *requiresArgument = !match.parameter.isEmpty();
1754 return spec;
1755 }
1756 }
1757 return nullptr;
1758 }
1759
pluginByName(const QString & name) const1760 PluginSpec *PluginManagerPrivate::pluginByName(const QString &name) const
1761 {
1762 return Utils::findOrDefault(pluginSpecs, [name](PluginSpec *spec) { return spec->name() == name; });
1763 }
1764
initProfiling()1765 void PluginManagerPrivate::initProfiling()
1766 {
1767 if (m_profileTimer.isNull()) {
1768 m_profileTimer.reset(new QElapsedTimer);
1769 m_profileTimer->start();
1770 m_profileElapsedMS = 0;
1771 qDebug("Profiling started");
1772 } else {
1773 m_profilingVerbosity++;
1774 }
1775 }
1776
profilingReport(const char * what,const PluginSpec * spec)1777 void PluginManagerPrivate::profilingReport(const char *what, const PluginSpec *spec /* = 0 */)
1778 {
1779 if (!m_profileTimer.isNull()) {
1780 const int absoluteElapsedMS = int(m_profileTimer->elapsed());
1781 const int elapsedMS = absoluteElapsedMS - m_profileElapsedMS;
1782 m_profileElapsedMS = absoluteElapsedMS;
1783 if (spec)
1784 qDebug("%-22s %-22s %8dms (%8dms)", what, qPrintable(spec->name()), absoluteElapsedMS, elapsedMS);
1785 else
1786 qDebug("%-45s %8dms (%8dms)", what, absoluteElapsedMS, elapsedMS);
1787 if (what && *what == '<') {
1788 QString tc;
1789 if (spec) {
1790 m_profileTotal[spec] += elapsedMS;
1791 tc = spec->name() + '_';
1792 }
1793 tc += QString::fromUtf8(QByteArray(what + 1));
1794 Utils::Benchmarker::report("loadPlugins", tc, elapsedMS);
1795 }
1796 }
1797 }
1798
profilingSummary() const1799 void PluginManagerPrivate::profilingSummary() const
1800 {
1801 if (!m_profileTimer.isNull()) {
1802 QMultiMap<int, const PluginSpec *> sorter;
1803 int total = 0;
1804
1805 auto totalEnd = m_profileTotal.constEnd();
1806 for (auto it = m_profileTotal.constBegin(); it != totalEnd; ++it) {
1807 sorter.insert(it.value(), it.key());
1808 total += it.value();
1809 }
1810
1811 auto sorterEnd = sorter.constEnd();
1812 for (auto it = sorter.constBegin(); it != sorterEnd; ++it)
1813 qDebug("%-22s %8dms ( %5.2f%% )", qPrintable(it.value()->name()),
1814 it.key(), 100.0 * it.key() / total);
1815 qDebug("Total: %8dms", total);
1816 Utils::Benchmarker::report("loadPlugins", "Total", total);
1817 }
1818 }
1819
getPlatformName()1820 static inline QString getPlatformName()
1821 {
1822 if (HostOsInfo::isMacHost())
1823 return QLatin1String("OS X");
1824 else if (HostOsInfo::isAnyUnixHost())
1825 return QLatin1String(HostOsInfo::isLinuxHost() ? "Linux" : "Unix");
1826 else if (HostOsInfo::isWindowsHost())
1827 return QLatin1String("Windows");
1828 return QLatin1String("Unknown");
1829 }
1830
platformName()1831 QString PluginManager::platformName()
1832 {
1833 static const QString result = getPlatformName() + " (" + QSysInfo::prettyProductName() + ')';
1834 return result;
1835 }
1836
isInitializationDone()1837 bool PluginManager::isInitializationDone()
1838 {
1839 return d->m_isInitializationDone;
1840 }
1841
1842 /*!
1843 Retrieves one object with \a name from the object pool.
1844 \sa addObject()
1845 */
1846
getObjectByName(const QString & name)1847 QObject *PluginManager::getObjectByName(const QString &name)
1848 {
1849 QReadLocker lock(&d->m_lock);
1850 return Utils::findOrDefault(allObjects(), [&name](const QObject *obj) {
1851 return obj->objectName() == name;
1852 });
1853 }
1854
1855 } // ExtensionSystem
1856