pluginmanager.cpp 54.1 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
Eike Ziller's avatar
Eike Ziller committed
3 4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8 9 10 11
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
Eike Ziller's avatar
Eike Ziller committed
12 13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** use the contact form at http://www.qt.io/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
**
Eike Ziller's avatar
Eike Ziller committed
25 26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
con's avatar
con committed
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
hjk's avatar
hjk committed
30

con's avatar
con committed
31 32 33 34 35 36
#include "pluginmanager.h"
#include "pluginmanager_p.h"
#include "pluginspec.h"
#include "pluginspec_p.h"
#include "optionsparser.h"
#include "iplugin.h"
37
#include "plugincollection.h"
con's avatar
con committed
38

39
#include <QCoreApplication>
40 41 42
#include <QEventLoop>
#include <QDateTime>
#include <QDir>
43
#include <QFile>
44
#include <QLibrary>
45 46 47 48 49
#include <QMetaProperty>
#include <QSettings>
#include <QTextStream>
#include <QTime>
#include <QWriteLocker>
hjk's avatar
hjk committed
50
#include <QDebug>
51
#include <QTimer>
52
#include <QSysInfo>
53

54
#include <utils/algorithm.h>
55
#include <utils/executeondestruction.h>
56 57
#include <utils/qtcassert.h>

con's avatar
con committed
58 59 60 61
#ifdef WITH_TESTS
#include <QTest>
#endif

62 63
#include <functional>

64 65
Q_LOGGING_CATEGORY(pluginLog, "qtc.extensionsystem")

66 67 68
const char C_IGNORED_PLUGINS[] = "Plugins/Ignored";
const char C_FORCEENABLED_PLUGINS[] = "Plugins/ForceEnabled";
const int DELAYED_INITIALIZE_INTERVAL = 20; // ms
69

con's avatar
con committed
70 71 72 73
enum { debugLeaks = 0 };

/*!
    \namespace ExtensionSystem
74 75
    \brief The ExtensionSystem namespace provides classes that belong to the
           core plugin system.
con's avatar
con committed
76

77
    The basic extension system contains the plugin manager and its supporting classes,
con's avatar
con committed
78 79 80 81 82 83 84 85 86 87 88 89
    and the IPlugin interface that must be implemented by plugin providers.
*/

/*!
    \namespace ExtensionSystem::Internal
    \internal
*/

/*!
    \class ExtensionSystem::PluginManager
    \mainclass

90 91
    \brief The PluginManager class implements the core plugin system that
    manages the plugins, their life cycle, and their registered objects.
con's avatar
con committed
92 93 94

    The plugin manager is used for the following tasks:
    \list
95 96
    \li Manage plugins and their state
    \li Manipulate a 'common object pool'
con's avatar
con committed
97 98 99
    \endlist

    \section1 Plugins
100
    Plugins consist of an XML descriptor file, and of a library that contains a Qt plugin
101 102
    that must derive from the IPlugin class and has an IID of
    \c "org.qt-project.Qt.QtCreatorPlugin".
con's avatar
con committed
103 104 105
    The plugin manager is used to set a list of file system directories to search for
    plugins, retrieve information about the state of these plugins, and to load them.

106 107
    Usually, the application creates a PluginManager instance and initiates the
    loading.
con's avatar
con committed
108
    \code
109
        // 'plugins' and subdirs will be searched for plugins
110 111
        PluginManager::setPluginPaths(QStringList() << "plugins");
        PluginManager::loadPlugins(); // try to load all the plugins
con's avatar
con committed
112
    \endcode
113 114
    Additionally, it is possible to directly access the plugin specifications
    (the information in the descriptor file), the plugin instances (via PluginSpec),
con's avatar
con committed
115 116 117 118 119 120
    and their state.

    \section1 Object Pool
    Plugins (and everybody else) can add objects to a common 'pool' that is located in
    the plugin manager. Objects in the pool must derive from QObject, there are no other
    prerequisites. All objects of a specified type can be retrieved from the object pool
121
    via the getObjects() and getObject() functions.
con's avatar
con committed
122 123 124 125 126 127 128 129

    Whenever the state of the object pool changes a corresponding signal is emitted by the plugin manager.

    A common usecase for the object pool is that a plugin (or the application) provides
    an "extension point" for other plugins, which is a class / interface that can
    be implemented and added to the object pool. The plugin that provides the
    extension point looks for implementations of the class / interface in the object pool.
    \code
130
        // Plugin A provides a "MimeTypeHandler" extension point
con's avatar
con committed
131 132
        // in plugin B:
        MyMimeTypeHandler *handler = new MyMimeTypeHandler();
133
        PluginManager::instance()->addObject(handler);
134
        // In plugin A:
con's avatar
con committed
135
        QList<MimeTypeHandler *> mimeHandlers =
136
            PluginManager::getObjects<MimeTypeHandler>();
con's avatar
con committed
137 138
    \endcode

139 140 141 142 143 144

    The \c{ExtensionSystem::Invoker} class template provides "syntactic sugar"
    for using "soft" extension points that may or may not be provided by an
    object in the pool. This approach does neither require the "user" plugin being
    linked against the "provider" plugin nor a common shared
    header file. The exposed interface is implicitly given by the
145
    invokable functions of the "provider" object in the object pool.
146 147

    The \c{ExtensionSystem::invoke} function template encapsulates
Orgad Shaneh's avatar
Orgad Shaneh committed
148
    {ExtensionSystem::Invoker} construction for the common case where
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
    the success of the call is not checked.

    \code
        // In the "provide" plugin A:
        namespace PluginA {
        class SomeProvider : public QObject
        {
            Q_OBJECT

        public:
            Q_INVOKABLE QString doit(const QString &msg, int n) {
            {
                qDebug() << "I AM DOING IT " << msg;
                return QString::number(n);
            }
        };
        } // namespace PluginA


        // In the "user" plugin B:
        int someFuntionUsingPluginA()
        {
            using namespace ExtensionSystem;

173
            QObject *target = PluginManager::getObjectByClassName("PluginA::SomeProvider");
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199

            if (target) {
                // Some random argument.
                QString msg = "REALLY.";

                // Plain function call, no return value.
                invoke<void>(target, "doit", msg, 2);

                // Plain function with no return value.
                qDebug() << "Result: " << invoke<QString>(target, "doit", msg, 21);

                // Record success of function call with return value.
                Invoker<QString> in1(target, "doit", msg, 21);
                qDebug() << "Success: (expected)" << in1.wasSuccessful();

                // Try to invoke a non-existing function.
                Invoker<QString> in2(target, "doitWrong", msg, 22);
                qDebug() << "Success (not expected):" << in2.wasSuccessful();

            } else {

                // We have to cope with plugin A's absence.
            }
        };
    \endcode

200
    \note The type of the parameters passed to the \c{invoke()} calls
201 202 203 204 205
    is deduced from the parameters themselves and must match the type of
    the arguments of the called functions \e{exactly}. No conversion or even
    integer promotions are applicable, so to invoke a function with a \c{long}
    parameter explicitly use \c{long(43)} or such.

206
    \note The object pool manipulating functions are thread-safe.
con's avatar
con committed
207 208 209 210
*/

/*!
    \fn void PluginManager::objectAdded(QObject *obj)
211
    Signals that \a obj has been added to the object pool.
con's avatar
con committed
212 213 214 215
*/

/*!
    \fn void PluginManager::aboutToRemoveObject(QObject *obj)
216
    Signals that \a obj will be removed from the object pool.
con's avatar
con committed
217 218 219 220
*/

/*!
    \fn void PluginManager::pluginsChanged()
221
    Signals that the list of available plugins has changed.
con's avatar
con committed
222 223 224 225 226

    \sa plugins()
*/

/*!
227 228
    \fn T *PluginManager::getObject()

229 230
    Retrieves the object of a given type from the object pool.

231
    This function uses \c qobject_cast to determine the type of an object.
con's avatar
con committed
232
    If there are more than one object of the given type in
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
    the object pool, this function will arbitrarily choose one of them.

    \sa addObject()
*/

/*!
    \fn T *PluginManager::getObject(Predicate predicate)

    Retrieves the object of a given type from the object pool that matches
    the \a predicate.

    This function uses \c qobject_cast to determine the type of an object.
    The predicate must be a function taking T * and returning a bool.
    If there is more than one object matching the type and predicate,
    this function will arbitrarily choose one of them.
con's avatar
con committed
248 249 250 251 252

    \sa addObject()
*/

/*!
253
    \fn QList<T *> PluginManager::getObjects()
254 255 256

    Retrieves all objects of a given type from the object pool.

257
    This function uses \c qobject_cast to determine the type of an object.
con's avatar
con committed
258 259 260 261

    \sa addObject()
*/

262 263 264 265 266 267 268 269 270 271 272 273 274 275
/*!
    \fn QList<T *> PluginManager::getObjects(Predicate predicate)

    Retrieves all objects of a given type from the object pool that
    match the \a predicate.

    This function uses \c qobject_cast to determine the type of an object.
    The predicate should be a unary function taking a T* parameter and
    returning a bool.

    \sa addObject()
*/


con's avatar
con committed
276 277 278
using namespace ExtensionSystem;
using namespace ExtensionSystem::Internal;

hjk's avatar
hjk committed
279 280 281
static Internal::PluginManagerPrivate *d = 0;
static PluginManager *m_instance = 0;

282 283 284 285 286
static bool lessThanByPluginName(const PluginSpec *one, const PluginSpec *two)
{
    return one->name() < two->name();
}

con's avatar
con committed
287
/*!
288
    Gets the unique plugin manager instance.
con's avatar
con committed
289 290 291 292 293 294 295
*/
PluginManager *PluginManager::instance()
{
    return m_instance;
}

/*!
296
    Creates a plugin manager. Should be done only once per application.
con's avatar
con committed
297 298 299 300
*/
PluginManager::PluginManager()
{
    m_instance = this;
hjk's avatar
hjk committed
301
    d = new PluginManagerPrivate(this);
con's avatar
con committed
302 303 304 305 306 307 308 309 310 311 312 313
}

/*!
    \internal
*/
PluginManager::~PluginManager()
{
    delete d;
    d = 0;
}

/*!
314 315 316
    Adds the object \a obj to the object pool, so it can be retrieved
    again from the pool by type.

con's avatar
con committed
317 318 319 320 321 322 323 324 325 326 327
    The plugin manager does not do any memory management - added objects
    must be removed from the pool and deleted manually by whoever is responsible for the object.

    Emits the objectAdded() signal.

    \sa PluginManager::removeObject()
    \sa PluginManager::getObject()
    \sa PluginManager::getObjects()
*/
void PluginManager::addObject(QObject *obj)
{
hjk's avatar
hjk committed
328
    d->addObject(obj);
con's avatar
con committed
329 330 331 332 333 334 335 336
}

/*!
    Emits aboutToRemoveObject() and removes the object \a obj from the object pool.
    \sa PluginManager::addObject()
*/
void PluginManager::removeObject(QObject *obj)
{
hjk's avatar
hjk committed
337
    d->removeObject(obj);
con's avatar
con committed
338 339 340
}

/*!
341 342 343 344
    Retrieves the list of all objects in the pool, unfiltered.

    Usually, clients do not need to call this function.

con's avatar
con committed
345 346 347
    \sa PluginManager::getObject()
    \sa PluginManager::getObjects()
*/
348
QList<QObject *> PluginManager::allObjects()
con's avatar
con committed
349
{
hjk's avatar
hjk committed
350 351 352 353 354 355
    return d->allObjects;
}

QReadWriteLock *PluginManager::listLock()
{
    return &d->m_lock;
con's avatar
con committed
356 357 358 359 360 361 362 363 364 365 366 367
}

/*!
    Tries to load all the plugins that were previously found when
    setting the plugin search paths. The plugin specs of the plugins
    can be used to retrieve error and state information about individual plugins.

    \sa setPluginPaths()
    \sa plugins()
*/
void PluginManager::loadPlugins()
{
hjk's avatar
hjk committed
368
    return d->loadPlugins();
con's avatar
con committed
369 370
}

371 372 373 374
/*!
    Returns true if any plugin has errors even though it is enabled.
    Most useful to call after loadPlugins().
*/
375
bool PluginManager::hasError()
376 377 378
{
    foreach (PluginSpec *spec, plugins()) {
        // only show errors on startup if plugin is enabled.
379
        if (spec->hasError() && spec->isEffectivelyEnabled())
380 381 382 383 384
            return true;
    }
    return false;
}

385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
/*!
    Returns all plugins that require \a spec to be loaded. Recurses into dependencies.
 */
QSet<PluginSpec *> PluginManager::pluginsRequiringPlugin(PluginSpec *spec)
{
    QSet<PluginSpec *> dependingPlugins;
    dependingPlugins.insert(spec);
    foreach (PluginSpec *checkSpec, d->loadQueue()) {
        QHashIterator<PluginDependency, PluginSpec *> depIt(checkSpec->dependencySpecs());
        while (depIt.hasNext()) {
            depIt.next();
            if (depIt.key().type != PluginDependency::Required)
                continue;
            if (dependingPlugins.contains(depIt.value())) {
                dependingPlugins.insert(checkSpec);
                break; // no use to check other dependencies, continue with load queue
            }
        }
    }
    dependingPlugins.remove(spec);
    return dependingPlugins;
}

/*!
    Returns all plugins that \a spec requires to be loaded. Recurses into dependencies.
 */
QSet<PluginSpec *> PluginManager::pluginsRequiredByPlugin(PluginSpec *spec)
{
    QSet<PluginSpec *> recursiveDependencies;
    recursiveDependencies.insert(spec);
    QList<PluginSpec *> queue;
    queue.append(spec);
    while (!queue.isEmpty()) {
        PluginSpec *checkSpec = queue.takeFirst();
        QHashIterator<PluginDependency, PluginSpec *> depIt(checkSpec->dependencySpecs());
        while (depIt.hasNext()) {
            depIt.next();
            if (depIt.key().type != PluginDependency::Required)
                continue;
            PluginSpec *depSpec = depIt.value();
            if (!recursiveDependencies.contains(depSpec)) {
                recursiveDependencies.insert(depSpec);
                queue.append(depSpec);
            }
        }
    }
    recursiveDependencies.remove(spec);
    return recursiveDependencies;
}

435 436 437 438 439 440 441 442
/*!
    Shuts down and deletes all plugins.
*/
void PluginManager::shutdown()
{
    d->shutdown();
}

con's avatar
con committed
443 444 445 446 447
/*!
    The list of paths were the plugin manager searches for plugins.

    \sa setPluginPaths()
*/
448
QStringList PluginManager::pluginPaths()
con's avatar
con committed
449
{
hjk's avatar
hjk committed
450
    return d->pluginPaths;
con's avatar
con committed
451 452 453 454 455 456 457 458 459 460 461 462
}

/*!
    Sets the plugin search paths, i.e. the file system paths where the plugin manager
    looks for plugin descriptions. All given \a paths and their sub directory trees
    are searched for plugin xml description files.

    \sa pluginPaths()
    \sa loadPlugins()
*/
void PluginManager::setPluginPaths(const QStringList &paths)
{
hjk's avatar
hjk committed
463
    d->setPluginPaths(paths);
con's avatar
con committed
464 465 466
}

/*!
467
    The IID that valid plugins must have.
con's avatar
con committed
468

469
    \sa setPluginIID()
con's avatar
con committed
470
*/
471
QString PluginManager::pluginIID()
con's avatar
con committed
472
{
473
    return d->pluginIID;
con's avatar
con committed
474 475 476
}

/*!
477 478 479
    Sets the IID that valid plugins must have. Only plugins with this IID are loaded, others are
    silently ignored.

con's avatar
con committed
480
    At the moment this must be called before setPluginPaths() is called.
481
    // ### TODO let this + setPluginPaths read the plugin meta data lazyly whenever loadPlugins() or plugins() is called.
con's avatar
con committed
482
*/
483
void PluginManager::setPluginIID(const QString &iid)
con's avatar
con committed
484
{
485
    d->pluginIID = iid;
con's avatar
con committed
486 487
}

488
/*!
489 490
    Defines the user specific settings to use for information about enabled and
    disabled plugins.
491 492
    Needs to be set before the plugin search path is set with setPluginPaths().
*/
493
void PluginManager::setSettings(QSettings *settings)
494
{
hjk's avatar
hjk committed
495
    d->setSettings(settings);
496 497
}

498
/*!
499 500
    Defines the global (user-independent) settings to use for information about
    default disabled plugins.
501 502 503 504
    Needs to be set before the plugin search path is set with setPluginPaths().
*/
void PluginManager::setGlobalSettings(QSettings *settings)
{
hjk's avatar
hjk committed
505
    d->setGlobalSettings(settings);
506 507 508
}

/*!
509 510
    Returns the user specific settings used for information about enabled and
    disabled plugins.
511
*/
512
QSettings *PluginManager::settings()
513
{
hjk's avatar
hjk committed
514
    return d->settings;
515 516
}

517 518 519
/*!
    Returns the global (user-independent) settings used for information about default disabled plugins.
*/
520
QSettings *PluginManager::globalSettings()
521
{
hjk's avatar
hjk committed
522
    return d->globalSettings;
523 524 525 526
}

void PluginManager::writeSettings()
{
hjk's avatar
hjk committed
527
    d->writeSettings();
528 529
}

con's avatar
con committed
530
/*!
531
    The arguments left over after parsing (that were neither startup nor plugin
con's avatar
con committed
532 533
    arguments). Typically, this will be the list of files to open.
*/
534
QStringList PluginManager::arguments()
con's avatar
con committed
535
{
hjk's avatar
hjk committed
536
    return d->arguments;
con's avatar
con committed
537 538 539 540 541 542 543 544 545 546 547
}

/*!
    List of all plugin specifications that have been found in the plugin search paths.
    This list is valid directly after the setPluginPaths() call.
    The plugin specifications contain the information from the plugins' xml description files
    and the current state of the plugins. If a plugin's library has been already successfully loaded,
    the plugin specification has a reference to the created plugin instance as well.

    \sa setPluginPaths()
*/
548
QList<PluginSpec *> PluginManager::plugins()
con's avatar
con committed
549
{
hjk's avatar
hjk committed
550
    return d->pluginSpecs;
con's avatar
con committed
551 552
}

553
QHash<QString, PluginCollection *> PluginManager::pluginCollections()
554
{
hjk's avatar
hjk committed
555
    return d->pluginCategories;
556 557
}

558
static const char argumentKeywordC[] = ":arguments";
559
static const char pwdKeywordC[] = ":pwd";
560 561

/*!
562
    Serializes plugin options and arguments for sending in a single string
563 564 565 566 567 568
    via QtSingleApplication:
    ":myplugin|-option1|-option2|:arguments|argument1|argument2",
    as a list of lists started by a keyword with a colon. Arguments are last.

    \sa setPluginPaths()
*/
569
QString PluginManager::serializedArguments()
570 571 572 573 574 575 576 577 578 579
{
    const QChar separator = QLatin1Char('|');
    QString rc;
    foreach (const PluginSpec *ps, plugins()) {
        if (!ps->arguments().isEmpty()) {
            if (!rc.isEmpty())
                rc += separator;
            rc += QLatin1Char(':');
            rc += ps->name();
            rc += separator;
hjk's avatar
hjk committed
580
            rc +=  ps->arguments().join(separator);
581 582
        }
    }
583 584 585
    if (!rc.isEmpty())
        rc += separator;
    rc += QLatin1String(pwdKeywordC) + separator + QDir::currentPath();
hjk's avatar
hjk committed
586
    if (!d->arguments.isEmpty()) {
587 588 589
        if (!rc.isEmpty())
            rc += separator;
        rc += QLatin1String(argumentKeywordC);
590 591
        foreach (const QString &argument, d->arguments)
            rc += separator + argument;
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
    }
    return rc;
}

/* Extract a sublist from the serialized arguments
 * indicated by a keyword starting with a colon indicator:
 * ":a,i1,i2,:b:i3,i4" with ":a" -> "i1,i2"
 */
static QStringList subList(const QStringList &in, const QString &key)
{
    QStringList rc;
    // Find keyword and copy arguments until end or next keyword
    const QStringList::const_iterator inEnd = in.constEnd();
    QStringList::const_iterator it = qFind(in.constBegin(), inEnd, key);
    if (it != inEnd) {
        const QChar nextIndicator = QLatin1Char(':');
        for (++it; it != inEnd && !it->startsWith(nextIndicator); ++it)
            rc.append(*it);
    }
    return rc;
}

/*!
    Parses the options encoded by serializedArguments() const
    and passes them on to the respective plugins along with the arguments.
617 618 619

    \a socket is passed for disconnecting the peer when the operation is done (for example,
    document is closed) for supporting the -block flag.
620 621
*/

622
void PluginManager::remoteArguments(const QString &serializedArgument, QObject *socket)
623 624 625 626
{
    if (serializedArgument.isEmpty())
        return;
    QStringList serializedArguments = serializedArgument.split(QLatin1Char('|'));
627 628
    const QStringList pwdValue = subList(serializedArguments, QLatin1String(pwdKeywordC));
    const QString workingDirectory = pwdValue.isEmpty() ? QString() : pwdValue.first();
629 630 631 632
    const QStringList arguments = subList(serializedArguments, QLatin1String(argumentKeywordC));
    foreach (const PluginSpec *ps, plugins()) {
        if (ps->state() == PluginSpec::Running) {
            const QStringList pluginOptions = subList(serializedArguments, QLatin1Char(':') + ps->name());
633 634
            QObject *socketParent = ps->plugin()->remoteCommand(pluginOptions, workingDirectory,
                                                                arguments);
635 636 637 638
            if (socketParent && socket) {
                socket->setParent(socketParent);
                socket = 0;
            }
639 640
        }
    }
641 642
    if (socket)
        delete socket;
643 644
}

con's avatar
con committed
645 646 647 648 649 650 651
/*!
    Takes the list of command line options in \a args and parses them.
    The plugin manager itself might process some options itself directly (-noload <plugin>), and
    adds options that are registered by plugins to their plugin specs.
    The caller (the application) may register itself for options via the \a appOptions list, containing pairs
    of "option string" and a bool that indicates if the option requires an argument.
    Application options always override any plugin's options.
652

con's avatar
con committed
653
    \a foundAppOptions is set to pairs of ("option string", "argument") for any application options that were found.
654
    The command line options that were not processed can be retrieved via the arguments() function.
con's avatar
con committed
655 656
    If an error occurred (like missing argument for an option that requires one), \a errorString contains
    a descriptive message of the error.
657

con's avatar
con committed
658 659 660 661 662 663 664
    Returns if there was an error.
 */
bool PluginManager::parseOptions(const QStringList &args,
    const QMap<QString, bool> &appOptions,
    QMap<QString, QString> *foundAppOptions,
    QString *errorString)
{
hjk's avatar
hjk committed
665
    OptionsParser options(args, appOptions, foundAppOptions, errorString, d);
con's avatar
con committed
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688
    return options.parse();
}



static inline void indent(QTextStream &str, int indent)
{
    const QChar blank = QLatin1Char(' ');
    for (int i = 0 ; i < indent; i++)
        str << blank;
}

static inline void formatOption(QTextStream &str,
                                const QString &opt, const QString &parm, const QString &description,
                                int optionIndentation, int descriptionIndentation)
{
    int remainingIndent = descriptionIndentation - optionIndentation - opt.size();
    indent(str, optionIndentation);
    str << opt;
    if (!parm.isEmpty()) {
        str << " <" << parm << '>';
        remainingIndent -= 3 + parm.size();
    }
689
    indent(str, qMax(1, remainingIndent));
con's avatar
con committed
690 691 692 693
    str << description << '\n';
}

/*!
694
    Formats the startup options of the plugin manager for command line help.
con's avatar
con committed
695 696 697 698
*/

void PluginManager::formatOptions(QTextStream &str, int optionIndentation, int descriptionIndentation)
{
699
    formatOption(str, QLatin1String(OptionsParser::LOAD_OPTION),
700
                 QLatin1String("plugin"), QLatin1String("Load <plugin> and all plugins that it requires"),
701
                 optionIndentation, descriptionIndentation);
702 703 704
    formatOption(str, QLatin1String(OptionsParser::LOAD_OPTION) + QLatin1String(" all"),
                 QString(), QLatin1String("Load all available plugins"),
                 optionIndentation, descriptionIndentation);
con's avatar
con committed
705
    formatOption(str, QLatin1String(OptionsParser::NO_LOAD_OPTION),
706
                 QLatin1String("plugin"), QLatin1String("Do not load <plugin> and all plugins that require it"),
con's avatar
con committed
707
                 optionIndentation, descriptionIndentation);
708 709 710 711 712
    formatOption(str, QLatin1String(OptionsParser::NO_LOAD_OPTION) + QLatin1String(" all"),
                 QString(), QString::fromLatin1("Do not load any plugin (useful when "
                                                "followed by one or more \"%1\" arguments)")
                 .arg(QLatin1String(OptionsParser::LOAD_OPTION)),
                 optionIndentation, descriptionIndentation);
713 714 715
    formatOption(str, QLatin1String(OptionsParser::PROFILE_OPTION),
                 QString(), QLatin1String("Profile plugin loading"),
                 optionIndentation, descriptionIndentation);
Eike Ziller's avatar
Eike Ziller committed
716
#ifdef WITH_TESTS
717
    formatOption(str, QString::fromLatin1(OptionsParser::TEST_OPTION)
718
                 + QLatin1String(" <plugin>[,testfunction[:testdata]]..."), QString(),
719 720
                 QLatin1String("Run plugin's tests (by default a separate settings path is used)"),
                 optionIndentation, descriptionIndentation);
721 722
    formatOption(str, QString::fromLatin1(OptionsParser::TEST_OPTION) + QLatin1String(" all"),
                 QString(), QLatin1String("Run tests from all plugins"),
Eike Ziller's avatar
Eike Ziller committed
723 724
                 optionIndentation, descriptionIndentation);
#endif
con's avatar
con committed
725 726 727
}

/*!
728
    Formats the plugin options of the plugin specs for command line help.
con's avatar
con committed
729 730
*/

731
void PluginManager::formatPluginOptions(QTextStream &str, int optionIndentation, int descriptionIndentation)
con's avatar
con committed
732 733
{
    // Check plugins for options
hjk's avatar
hjk committed
734 735
    foreach (PluginSpec *ps, d->pluginSpecs) {
        const PluginSpec::PluginArgumentDescriptions pargs = ps->argumentDescriptions();
con's avatar
con committed
736
        if (!pargs.empty()) {
hjk's avatar
hjk committed
737 738 739
            str << "\nPlugin: " <<  ps->name() << '\n';
            foreach (PluginArgumentDescription pad, pargs)
                formatOption(str, pad.name, pad.parameter, pad.description, optionIndentation, descriptionIndentation);
con's avatar
con committed
740 741 742 743 744
        }
    }
}

/*!
745
    Formats the version of the plugin specs for command line help.
con's avatar
con committed
746
*/
747
void PluginManager::formatPluginVersions(QTextStream &str)
con's avatar
con committed
748
{
hjk's avatar
hjk committed
749
    foreach (PluginSpec *ps, d->pluginSpecs)
con's avatar
con committed
750 751 752
        str << "  " << ps->name() << ' ' << ps->version() << ' ' << ps->description() <<  '\n';
}

753 754 755
/*!
 * \internal
 */
756
bool PluginManager::testRunRequested()
con's avatar
con committed
757
{
hjk's avatar
hjk committed
758
    return !d->testSpecs.isEmpty();
con's avatar
con committed
759 760
}

761
/*!
762 763
    Creates a profiling entry showing the elapsed time if profiling is
    activated.
764 765 766 767
*/

void PluginManager::profilingReport(const char *what, const PluginSpec *spec)
{
hjk's avatar
hjk committed
768
    d->profilingReport(what, spec);
769 770
}

771 772 773 774 775 776

/*!
    Returns a list of plugins in load order.
*/
QList<PluginSpec *> PluginManager::loadQueue()
{
hjk's avatar
hjk committed
777
    return d->loadQueue();
778 779
}

con's avatar
con committed
780 781 782 783 784 785 786 787 788 789
//============PluginManagerPrivate===========

/*!
    \internal
*/
PluginSpec *PluginManagerPrivate::createSpec()
{
    return new PluginSpec();
}

790 791 792 793 794 795 796 797 798 799 800 801
/*!
    \internal
*/
void PluginManagerPrivate::setSettings(QSettings *s)
{
    if (settings)
        delete settings;
    settings = s;
    if (settings)
        settings->setParent(this);
}

802 803 804 805 806 807 808 809 810 811 812 813
/*!
    \internal
*/
void PluginManagerPrivate::setGlobalSettings(QSettings *s)
{
    if (globalSettings)
        delete globalSettings;
    globalSettings = s;
    if (globalSettings)
        globalSettings->setParent(this);
}

con's avatar
con committed
814 815 816 817 818 819 820 821
/*!
    \internal
*/
PluginSpecPrivate *PluginManagerPrivate::privateSpec(PluginSpec *spec)
{
    return spec->d;
}

822 823 824 825 826 827 828 829 830 831 832 833 834
void PluginManagerPrivate::nextDelayedInitialize()
{
    while (!delayedInitializeQueue.isEmpty()) {
        PluginSpec *spec = delayedInitializeQueue.takeFirst();
        profilingReport(">delayedInitialize", spec);
        bool delay = spec->d->delayedInitialize();
        profilingReport("<delayedInitialize", spec);
        if (delay)
            break; // do next delayedInitialize after a delay
    }
    if (delayedInitializeQueue.isEmpty()) {
        delete delayedInitializeTimer;
        delayedInitializeTimer = 0;
835
        profilingSummary();
Tobias Hunger's avatar
Tobias Hunger committed
836
        emit q->initializationDone();
837 838
#ifdef WITH_TESTS
        if (q->testRunRequested())
Nikolai Kosjar's avatar
Nikolai Kosjar committed
839
            startTests();
840
#endif
841 842 843 844 845
    } else {
        delayedInitializeTimer->start();
    }
}

con's avatar
con committed
846 847 848
/*!
    \internal
*/
849
PluginManagerPrivate::PluginManagerPrivate(PluginManager *pluginManager) :
850
    m_failedTests(0),
851 852
    delayedInitializeTimer(0),
    shutdownEventLoop(0),
853
    m_profileElapsedMS(0),
854
    m_profilingVerbosity(0),
855
    settings(0),
856
    globalSettings(0),
857
    q(pluginManager)
con's avatar
con committed
858 859 860
{
}

861

con's avatar
con committed
862 863 864 865 866 867
/*!
    \internal
*/
PluginManagerPrivate::~PluginManagerPrivate()
{
    qDeleteAll(pluginSpecs);
868
    qDeleteAll(pluginCategories);
con's avatar
con committed
869 870
}

871 872 873
/*!
    \internal
*/
874 875
void PluginManagerPrivate::writeSettings()
{
876 877
    if (!settings)
        return;
con's avatar
con committed
878
    QStringList tempDisabledPlugins;
879
    QStringList tempForceEnabledPlugins;
880
    foreach (PluginSpec *spec, pluginSpecs) {
881
        if (spec->isEnabledByDefault() && !spec->isEnabledBySettings())
con's avatar
con committed
882
            tempDisabledPlugins.append(spec->name());
883
        if (!spec->isEnabledByDefault() && spec->isEnabledBySettings())
884
            tempForceEnabledPlugins.append(spec->name());
885 886
    }

887 888
    settings->setValue(QLatin1String(C_IGNORED_PLUGINS), tempDisabledPlugins);
    settings->setValue(QLatin1String(C_FORCEENABLED_PLUGINS), tempForceEnabledPlugins);
889 890
}

891 892 893
/*!
    \internal
*/
894
void PluginManagerPrivate::readSettings()
895
{
896
    if (globalSettings) {
897
        defaultDisabledPlugins = globalSettings->value(QLatin1String(C_IGNORED_PLUGINS)).toStringList();
898 899
        defaultEnabledPlugins = globalSettings->value(QLatin1String(C_FORCEENABLED_PLUGINS)).toStringList();
    }
900 901 902 903
    if (settings) {
        disabledPlugins = settings->value(QLatin1String(C_IGNORED_PLUGINS)).toStringList();
        forceEnabledPlugins = settings->value(QLatin1String(C_FORCEENABLED_PLUGINS)).toStringList();
    }
904 905
}

906 907 908
/*!
    \internal
*/
con's avatar
con committed
909 910
void PluginManagerPrivate::stopAll()
{
911 912 913 914 915
    if (delayedInitializeTimer && delayedInitializeTimer->isActive()) {
        delayedInitializeTimer->stop();
        delete delayedInitializeTimer;
        delayedInitializeTimer = 0;
    }
con's avatar
con committed
916 917 918 919
    QList<PluginSpec *> queue = loadQueue();
    foreach (PluginSpec *spec, queue) {
        loadPlugin(spec, PluginSpec::Stopped);
    }
920 921 922 923 924 925 926 927
}

/*!
    \internal
*/
void PluginManagerPrivate::deleteAll()
{
    QList<PluginSpec *> queue = loadQueue();
con's avatar
con committed
928 929 930 931 932 933 934
    QListIterator<PluginSpec *> it(queue);
    it.toBack();
    while (it.hasPrevious()) {
        loadPlugin(it.previous(), PluginSpec::Deleted);
    }
}

935
#ifdef WITH_TESTS
936 937 938 939 940 941

typedef QMap<QObject *, QStringList> TestPlan; // Object -> selected test functions
typedef QMapIterator<QObject *, QStringList> TestPlanIterator;

static bool isTestFunction(const QMetaMethod &metaMethod)
{
Orgad Shaneh's avatar
Orgad Shaneh committed
942
    static const QList<QByteArray> blackList = QList<QByteArray>()
943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963
        << "initTestCase()" << "cleanupTestCase()" << "init()" << "cleanup()";

    if (metaMethod.methodType() != QMetaMethod::Slot)
        return false;

    if (metaMethod.access() != QMetaMethod::Private)
        return false;

    const QByteArray signature = metaMethod.methodSignature();
    if (blackList.contains(signature))
        return false;

    if (!signature.startsWith("test"))
        return false;

    if (signature.endsWith("_data()"))
        return false;

    return true;
}

964 965
static QStringList testFunctions(const QMetaObject *metaObject)
{
966 967

    QStringList functions;
968 969

    for (int i = metaObject->methodOffset(); i < metaObject->methodCount(); ++i) {
970 971 972
        const QMetaMethod metaMethod = metaObject->method(i);
        if (isTestFunction(metaMethod)) {
            const QByteArray signature = metaMethod.methodSignature();
973 974
            const QString method = QString::fromLatin1(signature);
            const QString methodName = method.left(method.size() - 2);
975
            functions.append(methodName);
976 977 978
        }
    }

979
    return functions;
980 981
}

982 983
static QStringList matchingTestFunctions(const QStringList &testFunctions,
                                         const QString &matchText)
984
{
985 986 987 988 989 990 991 992
    // There might be a test data suffix like in "testfunction:testdata1".
    QString testFunctionName = matchText;
    QString testDataSuffix;
    const int index = testFunctionName.indexOf(QLatin1Char(':'));
    if (index != -1) {
        testDataSuffix = testFunctionName.mid(index);
        testFunctionName = testFunctionName.left(index);
    }
993

994 995 996 997
    const QRegExp regExp(testFunctionName, Qt::CaseSensitive, QRegExp::Wildcard);
    QStringList matchingFunctions;
    foreach (const QString &testFunction, testFunctions) {
        if (regExp.exactMatch(testFunction)) {
998 999
            // If the specified test data is invalid, the QTest framework will
            // print a reasonable error message for us.
1000 1001 1002 1003 1004 1005 1006 1007 1008
            matchingFunctions.append(testFunction + testDataSuffix);
        }
    }

    return matchingFunctions;
}

static QObject *objectWithClassName(const QList<QObject *> &objects, const QString &className)
{
Orgad Shaneh's avatar
Orgad Shaneh committed
1009
    return Utils::findOr(objects, 0, [className] (QObject *object) -> bool {
1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
        QString candidate = QString::fromUtf8(object->metaObject()->className());
        const int colonIndex = candidate.lastIndexOf(QLatin1Char(':'));
        if (colonIndex != -1 && colonIndex < candidate.size() - 1)
            candidate = candidate.mid(colonIndex + 1);
        return candidate == className;
    });
}

static int executeTestPlan(const TestPlan &testPlan)
{
    int failedTests = 0;

    TestPlanIterator it(testPlan);
    while (it.hasNext()) {
        it.next();
        QObject *testObject = it.key();
        QStringList functions = it.value();

        // Don't run QTest::qExec without any test functions, that'd run *all* slots as tests.
        if (functions.isEmpty())
            continue;

        functions.removeDuplicates();

        // QTest::qExec() expects basically QCoreApplication::arguments(),
        QStringList qExecArguments = QStringList()
                << QLatin1String("arg0") // fake application name
                << QLatin1String("-maxwarnings") << QLatin1String("0"); // unlimit output
        qExecArguments << functions;
        failedTests += QTest::qExec(testObject, qExecArguments);
    }

    return failedTests;
}

/// Resulting plan consists of all test functions of the plugin object and
/// all test functions of all test objects of the plugin.
static TestPlan generateCompleteTestPlan(IPlugin *plugin, const QList<QObject *> &testObjects)
{
    TestPlan testPlan;

    testPlan.insert(plugin, testFunctions(plugin->metaObject()));
    foreach (QObject *testObject, testObjects) {
        const QStringList allFunctions = testFunctions(testObject->metaObject());
        testPlan.insert(testObject, allFunctions);
    }

    return testPlan;
}

/// Resulting plan consists of all matching test functions of the plugin object
/// and all matching functions of all test objects of the plugin. However, if a
/// match text denotes a test class, all test functions of that will be
/// included and the class will not be considered further.
///
/// Since multiple match texts can match the same function, a test function might
/// be included multiple times for a test object.
static TestPlan generateCustomTestPlan(IPlugin *plugin, const QList<QObject *> &testObjects,
                                       const QStringList &matchTexts)
{
    TestPlan testPlan;

    const QStringList testFunctionsOfPluginObject = testFunctions(plugin->metaObject());
    QStringList matchedTestFunctionsOfPluginObject;
    QStringList remainingMatchTexts = matchTexts;
    QList<QObject *> remainingTestObjectsOfPlugin = testObjects;

    while (!remainingMatchTexts.isEmpty()) {
        const QString matchText = remainingMatchTexts.takeFirst();
        bool matched = false;

        if (QObject *testObject = objectWithClassName(remainingTestObjectsOfPlugin, matchText)) {
            // Add all functions of the matching test object
            matched = true;
            testPlan.insert(testObject, testFunctions(testObject->metaObject()));
            remainingTestObjectsOfPlugin.removeAll(testObject);

1087
        } else {
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107
            // Add all matching test functions of all remaining test objects
            foreach (QObject *testObject, remainingTestObjectsOfPlugin) {
                const QStringList allFunctions = testFunctions(testObject->metaObject());
                const QStringList matchingFunctions = matchingTestFunctions(allFunctions,
                                                                            matchText);
                if (!matchingFunctions.isEmpty()) {
                    matched = true;
                    testPlan[testObject] += matchingFunctions;
                }
            }
        }

        const QStringList currentMatchedTestFunctionsOfPluginObject
            = matchingTestFunctions(testFunctionsOfPluginObject, matchText);
        if (!currentMatchedTestFunctionsOfPluginObject.isEmpty()) {
            matched = true;
            matchedTestFunctionsOfPluginObject += currentMatchedTestFunctionsOfPluginObject;
        }

        if (!matched) {
1108
            QTextStream out(stdout);
1109 1110
            out << "No test function or class matches \"" << matchText
                << "\" in plugin \"" << plugin->metaObject()->className() << "\"." << endl;
1111 1112 1113
        }
    }

1114 1115 1116 1117 1118
    // Add all matching test functions of plugin
    if (!matchedTestFunctionsOfPluginObject.isEmpty())
        testPlan.insert(plugin, matchedTestFunctionsOfPluginObject);

    return testPlan;
1119 1120
}

Nikolai Kosjar's avatar
Nikolai Kosjar committed
1121 1122 1123 1124 1125 1126 1127 1128 1129 1130
void PluginManagerPrivate::startTests()
{
    if (PluginManager::hasError()) {
        qWarning("Errors occurred while loading plugins, skipping test run. "
                 "For details, start without \"-test\" option.");
        QTimer::singleShot(1, QCoreApplication::instance(), SLOT(quit()));
        return;
    }

    foreach (const PluginManagerPrivate::TestSpec &testSpec, testSpecs) {
1131
        IPlugin *plugin = testSpec.pluginSpec->plugin();
1132 1133
        if (!plugin)
            continue; // plugin not loaded
Nikolai Kosjar's avatar
Nikolai Kosjar committed
1134

1135
        const QList<QObject *> testObjects = plugin->createTestObjects();
1136 1137
        Utils::ExecuteOnDestruction deleteTestObjects([&]() { qDeleteAll(testObjects); });
        Q_UNUSED(deleteTestObjects)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
1138

1139 1140 1141
        const bool hasDuplicateTestObjects = testObjects.size() != testObjects.toSet().size();
        QTC_ASSERT(!hasDuplicateTestObjects, continue);
        QTC_ASSERT(!testObjects.contains(plugin), continue);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
1142

1143 1144 1145
        const TestPlan testPlan = testSpec.testFunctionsOrObjects.isEmpty()
                ? generateCompleteTestPlan(plugin, testObjects)
                : generateCustomTestPlan(plugin, testObjects, testSpec.testFunctionsOrObjects);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
1146

1147
        m_failedTests += executeTestPlan(testPlan);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
1148 1149
    }
    if (!testSpecs.isEmpty())
1150
        QTimer::singleShot(1, this, SLOT(exitWithNumberOfFailedTests()));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
1151
}
1152
#endif
Nikolai Kosjar's avatar
Nikolai Kosjar committed
1153

con's avatar
con committed
1154 1155 1156 1157 1158 1159
/*!
    \internal
*/
void PluginManagerPrivate::addObject(QObject *obj)
{
    {
hjk's avatar
hjk committed
1160
        QWriteLocker lock(&m_lock);
con's avatar
con committed
1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172
        if (obj == 0) {
            qWarning() << "PluginManagerPrivate::addObject(): trying to add null object";
            return;
        }
        if (allObjects.contains(obj)) {
            qWarning() << "PluginManagerPrivate::addObject(): trying to add duplicate object";
            return;
        }

        if (debugLeaks)
            qDebug() << "PluginManagerPrivate::addObject" << obj << obj->objectName();

1173 1174 1175 1176 1177 1178 1179
        if (m_profilingVerbosity && !m_profileTimer.isNull()) {
            // Report a timestamp when adding an object. Useful for profiling
            // its initialization time.
            const int absoluteElapsedMS = m_profileTimer->elapsed();
            qDebug("  %-43s %8dms", obj->metaObject()->className(), absoluteElapsedMS);
        }

con's avatar
con committed
1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203
        allObjects.append(obj);
    }
    emit q->objectAdded(obj);
}

/*!
    \internal
*/
void PluginManagerPrivate::removeObject(QObject *obj)
{
    if (obj == 0) {
        qWarning() << "PluginManagerPrivate::removeObject(): trying to remove null object";
        return;
    }

    if (!allObjects.contains(obj)) {
        qWarning() << "PluginManagerPrivate::removeObject(): object not in list:"
            << obj << obj->objectName();
        return;
    }
    if (debugLeaks)
        qDebug() << "PluginManagerPrivate::removeObject" << obj << obj->objectName();

    emit q->aboutToRemoveObject(obj);
hjk's avatar
hjk committed
1204
    QWriteLocker lock(&m_lock);
con's avatar
con committed
1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222
    allObjects.removeAll(obj);
}

/*!
    \internal
*/
void PluginManagerPrivate::loadPlugins()
{
    QList<PluginSpec *> queue = loadQueue();
    foreach (PluginSpec *spec, queue) {
        loadPlugin(spec, PluginSpec::Loaded);
    }
    foreach (PluginSpec *spec, queue) {
        loadPlugin(spec, PluginSpec::Initialized);
    }
    QListIterator<PluginSpec *> it(queue);
    it.toBack();
    while (it.hasPrevious()) {
1223 1224
        PluginSpec *spec = it.previous();
        loadPlugin(spec, PluginSpec::Running);
1225
        if (spec->state() == PluginSpec::Running) {