pluginspec.cpp 31.7 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://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
12 13 14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
15
**
16 17 18 19 20 21 22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
con's avatar
con committed
23
**
hjk's avatar
hjk committed
24
****************************************************************************/
hjk's avatar
hjk committed
25

con's avatar
con committed
26
#include "pluginspec.h"
27

con's avatar
con committed
28 29 30 31 32
#include "pluginspec_p.h"
#include "iplugin.h"
#include "iplugin_p.h"
#include "pluginmanager.h"

Eike Ziller's avatar
Eike Ziller committed
33
#include <utils/algorithm.h>
34
#include <utils/qtcassert.h>
35
#include <utils/stringutils.h>
Eike Ziller's avatar
Eike Ziller committed
36

37 38
#include <QCoreApplication>
#include <QDebug>
39 40 41
#include <QDir>
#include <QFile>
#include <QFileInfo>
42 43 44 45
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
46
#include <QPluginLoader>
47
#include <QRegExp>
48

con's avatar
con committed
49 50
/*!
    \class ExtensionSystem::PluginDependency
51 52
    \brief The PluginDependency class contains the name and required compatible
    version number of a plugin's dependency.
con's avatar
con committed
53

54 55
    This reflects the data of a dependency object in the plugin's meta data.
    The name and version are used to resolve the dependency. That is,
56
    a plugin with the given name and
con's avatar
con committed
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
    plugin \c {compatibility version <= dependency version <= plugin version} is searched for.

    See also ExtensionSystem::IPlugin for more information about plugin dependencies and
    version matching.
*/

/*!
    \variable ExtensionSystem::PluginDependency::name
    String identifier of the plugin.
*/

/*!
    \variable ExtensionSystem::PluginDependency::version
    Version string that a plugin must match to fill this dependency.
*/

con's avatar
con committed
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
/*!
    \variable ExtensionSystem::PluginDependency::type
    Defines whether the dependency is required or optional.
    \sa ExtensionSystem::PluginDependency::Type
*/

/*!
    \enum ExtensionSystem::PluginDependency::Type
    Whether the dependency is required or optional.
    \value Required
           Dependency needs to be there.
    \value Optional
           Dependency is not necessarily needed. You need to make sure that
           the plugin is able to load without this dependency installed, so
           for example you may not link to the dependency's library.
88 89
    \value Test
           Dependency needs to be force-loaded for running tests of the plugin.
con's avatar
con committed
90 91
*/

con's avatar
con committed
92 93
/*!
    \class ExtensionSystem::PluginSpec
94 95
    \brief The PluginSpec class contains the information of the plugin's embedded meta data
    and information about the plugin's current state.
con's avatar
con committed
96 97

    The plugin spec is also filled with more information as the plugin
Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
98
    goes through its loading process (see PluginSpec::State).
con's avatar
con committed
99 100 101 102 103 104
    If an error occurs, the plugin spec is the place to look for the
    error details.
*/

/*!
    \enum ExtensionSystem::PluginSpec::State
105 106
    The State enum indicates the states the plugin goes through while
    it is being loaded.
con's avatar
con committed
107 108 109 110

    The state gives a hint on what went wrong in case of an error.

    \value  Invalid
111
            Starting point: Even the plugin meta data was not read.
con's avatar
con committed
112
    \value  Read
113
            The plugin meta data has been successfully read, and its
con's avatar
con committed
114 115 116
            information is available via the PluginSpec.
    \value  Resolved
            The dependencies given in the description file have been
117
            successfully found, and are available via the dependencySpecs() function.
con's avatar
con committed
118 119 120 121
    \value  Loaded
            The plugin's library is loaded and the plugin instance created
            (available through plugin()).
    \value  Initialized
122
            The plugin instance's IPlugin::initialize() function has been called
con's avatar
con committed
123 124 125 126 127 128
            and returned a success value.
    \value  Running
            The plugin's dependencies are successfully initialized and
            extensionsInitialized has been called. The loading process is
            complete.
    \value Stopped
129
            The plugin has been shut down, i.e. the plugin's IPlugin::aboutToShutdown() function has been called.
con's avatar
con committed
130 131 132
    \value Deleted
            The plugin instance has been deleted.
*/
con's avatar
con committed
133

con's avatar
con committed
134 135 136
using namespace ExtensionSystem;
using namespace ExtensionSystem::Internal;

con's avatar
con committed
137 138 139
/*!
    \internal
*/
140
uint ExtensionSystem::qHash(const PluginDependency &value)
con's avatar
con committed
141 142 143 144
{
    return qHash(value.name);
}

con's avatar
con committed
145 146 147
/*!
    \internal
*/
con's avatar
con committed
148
bool PluginDependency::operator==(const PluginDependency &other) const
con's avatar
con committed
149
{
con's avatar
con committed
150
    return name == other.name && version == other.version && type == other.type;
con's avatar
con committed
151 152
}

153 154 155 156
static QString typeString(PluginDependency::Type type)
{
    switch (type) {
    case PluginDependency::Optional:
157
        return QString(", optional");
158
    case PluginDependency::Test:
159 160 161 162
        return QString(", test");
    case PluginDependency::Required:
    default:
        return QString();
163 164 165 166 167 168 169 170
    }
}

QString PluginDependency::toString() const
{
    return name + " (" + version + typeString(type) + ")";
}

con's avatar
con committed
171 172 173 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 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
/*!
    \internal
*/
PluginSpec::PluginSpec()
    : d(new PluginSpecPrivate(this))
{
}

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

/*!
    The plugin name. This is valid after the PluginSpec::Read state is reached.
*/
QString PluginSpec::name() const
{
    return d->name;
}

/*!
    The plugin version. This is valid after the PluginSpec::Read state is reached.
*/
QString PluginSpec::version() const
{
    return d->version;
}

/*!
    The plugin compatibility version. This is valid after the PluginSpec::Read state is reached.
*/
QString PluginSpec::compatVersion() const
{
    return d->compatVersion;
}

/*!
    The plugin vendor. This is valid after the PluginSpec::Read state is reached.
*/
QString PluginSpec::vendor() const
{
    return d->vendor;
}

/*!
    The plugin copyright. This is valid after the PluginSpec::Read state is reached.
*/
QString PluginSpec::copyright() const
{
    return d->copyright;
}

/*!
    The plugin license. This is valid after the PluginSpec::Read state is reached.
*/
QString PluginSpec::license() const
{
    return d->license;
}

/*!
    The plugin description. This is valid after the PluginSpec::Read state is reached.
*/
QString PluginSpec::description() const
{
    return d->description;
}

/*!
245 246
    The plugin URL where you can find more information about the plugin.
    This is valid after the PluginSpec::Read state is reached.
con's avatar
con committed
247 248 249 250 251 252
*/
QString PluginSpec::url() const
{
    return d->url;
}

253 254 255 256 257 258 259 260 261
/*!
    The category that the plugin belongs to. Categories are groups of plugins which allow for keeping them together in the UI.
    Returns an empty string if the plugin does not belong to a category.
*/
QString PluginSpec::category() const
{
    return d->category;
}

262 263 264 265 266 267 268 269 270 271
/*!
    A QRegExp matching the platforms this plugin works on. An empty pattern implies all platforms.
    \since 3.0
*/

QRegExp PluginSpec::platformSpecification() const
{
    return d->platformSpecification;
}

272 273 274 275 276
bool PluginSpec::isAvailableForHostPlatform() const
{
    return d->platformSpecification.isEmpty() || d->platformSpecification.exactMatch(PluginManager::platformName());
}

277 278 279 280 281
bool PluginSpec::isRequired() const
{
    return d->required;
}

Eike Ziller's avatar
Eike Ziller committed
282 283 284 285 286
bool PluginSpec::isHiddenByDefault() const
{
    return d->hiddenByDefault;
}

287
/*!
288
    Returns whether the plugin has its experimental flag set.
289 290 291 292 293 294
*/
bool PluginSpec::isExperimental() const
{
    return d->experimental;
}

295
/*!
296 297 298
    Returns whether the plugin is enabled by default.
    A plugin might be disabled because the plugin is experimental, or because
    the install settings define it as disabled by default.
299
*/
300
bool PluginSpec::isEnabledByDefault() const
301
{
302
    return d->enabledByDefault;
303 304
}

305
/*!
306 307
    Returns whether the plugin should be loaded at startup,
    taking into account the default enabled state, and the user's settings.
308

309 310 311
    \note This function might return false even if the plugin is loaded as a requirement of another
    enabled plugin.
    \sa PluginSpec::isEffectivelyEnabled
312
*/
313
bool PluginSpec::isEnabledBySettings() const
314
{
315
    return d->enabledBySettings;
316 317
}

318
/*!
319
    Returns whether the plugin is loaded at startup.
320
    \see PluginSpec::isEnabledBySettings
321 322 323
*/
bool PluginSpec::isEffectivelyEnabled() const
{
324
    if (!isAvailableForHostPlatform())
325
        return false;
326 327 328 329 330
    if (isForceEnabled() || isEnabledIndirectly())
        return true;
    if (isForceDisabled())
        return false;
    return isEnabledBySettings();
331 332
}

333
/*!
334
    Returns true if loading was not done due to user unselecting this plugin or its dependencies.
335
*/
336
bool PluginSpec::isEnabledIndirectly() const
337
{
338
    return d->enabledIndirectly;
339 340
}

341
/*!
342
    Returns whether the plugin is enabled via the -load option on the command line.
343 344 345 346 347 348 349
*/
bool PluginSpec::isForceEnabled() const
{
    return d->forceEnabled;
}

/*!
350
    Returns whether the plugin is disabled via the -noload option on the command line.
351 352 353 354 355 356
*/
bool PluginSpec::isForceDisabled() const
{
    return d->forceDisabled;
}

con's avatar
con committed
357 358 359
/*!
    The plugin dependencies. This is valid after the PluginSpec::Read state is reached.
*/
hjk's avatar
hjk committed
360
QVector<PluginDependency> PluginSpec::dependencies() const
con's avatar
con committed
361 362 363 364
{
    return d->dependencies;
}

365 366 367 368 369
QJsonObject PluginSpec::metaData() const
{
    return d->metaData;
}

con's avatar
con committed
370 371 372 373 374 375 376 377 378 379
/*!
    Returns a list of descriptions of command line arguments the plugin processes.
*/

PluginSpec::PluginArgumentDescriptions PluginSpec::argumentDescriptions() const
{
    return d->argumentDescriptions;
}

/*!
380
    The absolute path to the directory containing the plugin XML description file
con's avatar
con committed
381 382 383 384 385 386 387 388
    this PluginSpec corresponds to.
*/
QString PluginSpec::location() const
{
    return d->location;
}

/*!
389
    The absolute path to the plugin XML description file (including the file name)
con's avatar
con committed
390 391 392 393 394 395 396 397
    this PluginSpec corresponds to.
*/
QString PluginSpec::filePath() const
{
    return d->filePath;
}

/*!
398
    Command line arguments specific to the plugin. Set at startup.
con's avatar
con committed
399 400 401 402 403 404 405 406
*/

QStringList PluginSpec::arguments() const
{
    return d->arguments;
}

/*!
407
    Sets the command line arguments specific to the plugin to \a arguments.
con's avatar
con committed
408 409 410 411 412 413 414 415
*/

void PluginSpec::setArguments(const QStringList &arguments)
{
    d->arguments = arguments;
}

/*!
416
    Adds \a argument to the command line arguments specific to the plugin.
con's avatar
con committed
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
*/

void PluginSpec::addArgument(const QString &argument)
{
    d->arguments.push_back(argument);
}


/*!
    The state in which the plugin currently is.
    See the description of the PluginSpec::State enum for details.
*/
PluginSpec::State PluginSpec::state() const
{
    return d->state;
}

/*!
    Returns whether an error occurred while reading/starting the plugin.
*/
bool PluginSpec::hasError() const
{
    return d->hasError;
}

/*!
    Detailed, possibly multi-line, error description in case of an error.
*/
QString PluginSpec::errorString() const
{
    return d->errorString;
}

/*!
451
    Returns whether this plugin can be used to fill in a dependency of the given
con's avatar
con committed
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
    \a pluginName and \a version.

        \sa PluginSpec::dependencies()
*/
bool PluginSpec::provides(const QString &pluginName, const QString &version) const
{
    return d->provides(pluginName, version);
}

/*!
    The corresponding IPlugin instance, if the plugin library has already been successfully loaded,
    i.e. the PluginSpec::Loaded state is reached.
*/
IPlugin *PluginSpec::plugin() const
{
    return d->plugin;
}

/*!
    Returns the list of dependencies, already resolved to existing plugin specs.
    Valid if PluginSpec::Resolved state is reached.

    \sa PluginSpec::dependencies()
*/
con's avatar
con committed
476
QHash<PluginDependency, PluginSpec *> PluginSpec::dependencySpecs() const
con's avatar
con committed
477 478 479 480
{
    return d->dependencySpecs;
}

Eike Ziller's avatar
Eike Ziller committed
481 482 483 484 485 486 487 488
bool PluginSpec::requiresAny(const QSet<PluginSpec *> &plugins) const
{
    return Utils::anyOf(d->dependencySpecs.keys(), [this, &plugins](const PluginDependency &dep) {
        return dep.type == PluginDependency::Required
               && plugins.contains(d->dependencySpecs.value(dep));
    });
}

con's avatar
con committed
489 490 491
//==========PluginSpecPrivate==================

namespace {
492 493 494 495 496
    const char PLUGIN_METADATA[] = "MetaData";
    const char PLUGIN_NAME[] = "Name";
    const char PLUGIN_VERSION[] = "Version";
    const char PLUGIN_COMPATVERSION[] = "CompatVersion";
    const char PLUGIN_REQUIRED[] = "Required";
Eike Ziller's avatar
Eike Ziller committed
497
    const char PLUGIN_HIDDEN_BY_DEFAULT[] = "HiddenByDefault";
498 499 500 501 502 503 504 505 506 507 508 509 510
    const char PLUGIN_EXPERIMENTAL[] = "Experimental";
    const char PLUGIN_DISABLED_BY_DEFAULT[] = "DisabledByDefault";
    const char VENDOR[] = "Vendor";
    const char COPYRIGHT[] = "Copyright";
    const char LICENSE[] = "License";
    const char DESCRIPTION[] = "Description";
    const char URL[] = "Url";
    const char CATEGORY[] = "Category";
    const char PLATFORM[] = "Platform";
    const char DEPENDENCIES[] = "Dependencies";
    const char DEPENDENCY_NAME[] = "Name";
    const char DEPENDENCY_VERSION[] = "Version";
    const char DEPENDENCY_TYPE[] = "Type";
511 512
    const char DEPENDENCY_TYPE_SOFT[] = "optional";
    const char DEPENDENCY_TYPE_HARD[] = "required";
513
    const char DEPENDENCY_TYPE_TEST[] = "test";
514 515 516 517
    const char ARGUMENTS[] = "Arguments";
    const char ARGUMENT_NAME[] = "Name";
    const char ARGUMENT_PARAMETER[] = "Parameter";
    const char ARGUMENT_DESCRIPTION[] = "Description";
con's avatar
con committed
518 519 520 521 522
}
/*!
    \internal
*/
PluginSpecPrivate::PluginSpecPrivate(PluginSpec *spec)
523
    : q(spec)
con's avatar
con committed
524 525 526 527 528
{
}

/*!
    \internal
529
    Returns false if the file does not represent a Qt Creator plugin.
con's avatar
con committed
530 531 532
*/
bool PluginSpecPrivate::read(const QString &fileName)
{
533
    qCDebug(pluginLog) << "\nReading meta data of" << fileName;
con's avatar
con committed
534 535 536 537 538 539 540 541
    name
        = version
        = compatVersion
        = vendor
        = copyright
        = license
        = description
        = url
542
        = category
con's avatar
con committed
543
        = location
544
        = QString();
con's avatar
con committed
545 546
    state = PluginSpec::Invalid;
    hasError = false;
547
    errorString.clear();
con's avatar
con committed
548
    dependencies.clear();
549
    metaData = QJsonObject();
550
    QFileInfo fileInfo(fileName);
con's avatar
con committed
551 552
    location = fileInfo.absolutePath();
    filePath = fileInfo.absoluteFilePath();
553 554 555 556
    loader.setFileName(filePath);
    if (loader.fileName().isEmpty()) {
        qCDebug(pluginLog) << "Cannot open file";
        return false;
con's avatar
con committed
557
    }
558 559 560 561

    if (!readMetaData(loader.metaData()))
        return false;

con's avatar
con committed
562 563 564 565
    state = PluginSpec::Read;
    return true;
}

566
void PluginSpecPrivate::setEnabledBySettings(bool value)
567
{
568
    enabledBySettings = value;
569 570
}

571
void PluginSpecPrivate::setEnabledByDefault(bool value)
572
{
573
    enabledByDefault = value;
574 575
}

576
void PluginSpecPrivate::setForceEnabled(bool value)
577
{
578
    forceEnabled = value;
579
    if (value)
580
        forceDisabled = false;
581 582
}

583
void PluginSpecPrivate::setForceDisabled(bool value)
584 585
{
    if (value)
586 587
        forceEnabled = false;
    forceDisabled = value;
588 589
}

con's avatar
con committed
590 591 592 593 594 595 596
/*!
    \internal
*/
bool PluginSpecPrivate::reportError(const QString &err)
{
    errorString = err;
    hasError = true;
597
    return true;
con's avatar
con committed
598 599
}

600
static inline QString msgValueMissing(const char *key)
con's avatar
con committed
601
{
602
    return QCoreApplication::translate("PluginSpec", "\"%1\" is missing").arg(QLatin1String(key));
con's avatar
con committed
603 604
}

605
static inline QString msgValueIsNotAString(const char *key)
con's avatar
con committed
606
{
607 608
    return QCoreApplication::translate("PluginSpec", "Value for key \"%1\" is not a string")
            .arg(QLatin1String(key));
con's avatar
con committed
609 610
}

611
static inline QString msgValueIsNotABool(const char *key)
con's avatar
con committed
612
{
613 614
    return QCoreApplication::translate("PluginSpec", "Value for key \"%1\" is not a bool")
            .arg(QLatin1String(key));
con's avatar
con committed
615 616
}

617
static inline QString msgValueIsNotAObjectArray(const char *key)
con's avatar
con committed
618
{
619 620
    return QCoreApplication::translate("PluginSpec", "Value for key \"%1\" is not an array of objects")
            .arg(QLatin1String(key));
con's avatar
con committed
621 622
}

623
static inline QString msgValueIsNotAMultilineString(const char *key)
con's avatar
con committed
624
{
625 626
    return QCoreApplication::translate("PluginSpec", "Value for key \"%1\" is not a string and not an array of strings")
            .arg(QLatin1String(key));
con's avatar
con committed
627 628
}

629
static inline QString msgInvalidFormat(const char *key, const QString &content)
con's avatar
con committed
630
{
631 632
    return QCoreApplication::translate("PluginSpec", "Value \"%2\" for key \"%1\" has invalid format")
            .arg(QLatin1String(key), content);
con's avatar
con committed
633 634 635 636 637
}

/*!
    \internal
*/
638
bool PluginSpecPrivate::readMetaData(const QJsonObject &pluginMetaData)
con's avatar
con committed
639
{
640
    qCDebug(pluginLog) << "MetaData:" << QJsonDocument(pluginMetaData).toJson();
641
    QJsonValue value;
642
    value = pluginMetaData.value(QLatin1String("IID"));
643 644 645 646 647 648 649
    if (!value.isString()) {
        qCDebug(pluginLog) << "Not a plugin (no string IID found)";
        return false;
    }
    if (value.toString() != PluginManager::pluginIID()) {
        qCDebug(pluginLog) << "Plugin ignored (IID does not match)";
        return false;
con's avatar
con committed
650 651
    }

652
    value = pluginMetaData.value(QLatin1String(PLUGIN_METADATA));
653 654
    if (!value.isObject())
        return reportError(tr("Plugin meta data not found"));
655
    metaData = value.toObject();
656

657
    value = metaData.value(QLatin1String(PLUGIN_NAME));
658 659 660 661 662 663
    if (value.isUndefined())
        return reportError(msgValueMissing(PLUGIN_NAME));
    if (!value.isString())
        return reportError(msgValueIsNotAString(PLUGIN_NAME));
    name = value.toString();

664
    value = metaData.value(QLatin1String(PLUGIN_VERSION));
665 666 667 668 669 670 671 672
    if (value.isUndefined())
        return reportError(msgValueMissing(PLUGIN_VERSION));
    if (!value.isString())
        return reportError(msgValueIsNotAString(PLUGIN_VERSION));
    version = value.toString();
    if (!isValidVersion(version))
        return reportError(msgInvalidFormat(PLUGIN_VERSION, version));

673
    value = metaData.value(QLatin1String(PLUGIN_COMPATVERSION));
674 675 676 677 678 679
    if (!value.isUndefined() && !value.isString())
        return reportError(msgValueIsNotAString(PLUGIN_COMPATVERSION));
    compatVersion = value.toString(version);
    if (!value.isUndefined() && !isValidVersion(compatVersion))
        return reportError(msgInvalidFormat(PLUGIN_COMPATVERSION, compatVersion));

680
    value = metaData.value(QLatin1String(PLUGIN_REQUIRED));
681 682 683 684 685
    if (!value.isUndefined() && !value.isBool())
        return reportError(msgValueIsNotABool(PLUGIN_REQUIRED));
    required = value.toBool(false);
    qCDebug(pluginLog) << "required =" << required;

686
    value = metaData.value(QLatin1String(PLUGIN_HIDDEN_BY_DEFAULT));
Eike Ziller's avatar
Eike Ziller committed
687 688 689 690 691
    if (!value.isUndefined() && !value.isBool())
        return reportError(msgValueIsNotABool(PLUGIN_HIDDEN_BY_DEFAULT));
    hiddenByDefault = value.toBool(false);
    qCDebug(pluginLog) << "hiddenByDefault =" << hiddenByDefault;

692
    value = metaData.value(QLatin1String(PLUGIN_EXPERIMENTAL));
693 694 695 696 697
    if (!value.isUndefined() && !value.isBool())
        return reportError(msgValueIsNotABool(PLUGIN_EXPERIMENTAL));
    experimental = value.toBool(false);
    qCDebug(pluginLog) << "experimental =" << experimental;

698
    value = metaData.value(QLatin1String(PLUGIN_DISABLED_BY_DEFAULT));
699 700
    if (!value.isUndefined() && !value.isBool())
        return reportError(msgValueIsNotABool(PLUGIN_DISABLED_BY_DEFAULT));
701 702
    enabledByDefault = !value.toBool(false);
    qCDebug(pluginLog) << "enabledByDefault =" << enabledByDefault;
703 704

    if (experimental)
705 706
        enabledByDefault = false;
    enabledBySettings = enabledByDefault;
707

708
    value = metaData.value(QLatin1String(VENDOR));
709 710 711 712
    if (!value.isUndefined() && !value.isString())
        return reportError(msgValueIsNotAString(VENDOR));
    vendor = value.toString();

713
    value = metaData.value(QLatin1String(COPYRIGHT));
714 715 716 717
    if (!value.isUndefined() && !value.isString())
        return reportError(msgValueIsNotAString(COPYRIGHT));
    copyright = value.toString();

718
    value = metaData.value(QLatin1String(DESCRIPTION));
719
    if (!value.isUndefined() && !Utils::readMultiLineString(value, &description))
720 721
        return reportError(msgValueIsNotAString(DESCRIPTION));

722
    value = metaData.value(QLatin1String(URL));
723 724 725 726
    if (!value.isUndefined() && !value.isString())
        return reportError(msgValueIsNotAString(URL));
    url = value.toString();

727
    value = metaData.value(QLatin1String(CATEGORY));
728 729 730 731
    if (!value.isUndefined() && !value.isString())
        return reportError(msgValueIsNotAString(CATEGORY));
    category = value.toString();

732
    value = metaData.value(QLatin1String(LICENSE));
733
    if (!value.isUndefined() && !Utils::readMultiLineString(value, &license))
734 735
        return reportError(msgValueIsNotAMultilineString(LICENSE));

736
    value = metaData.value(QLatin1String(PLATFORM));
737 738 739 740 741 742 743 744
    if (!value.isUndefined() && !value.isString())
        return reportError(msgValueIsNotAString(PLATFORM));
    const QString platformSpec = value.toString().trimmed();
    if (!platformSpec.isEmpty()) {
        platformSpecification.setPattern(platformSpec);
        if (!platformSpecification.isValid())
            return reportError(tr("Invalid platform specification \"%1\": %2")
                               .arg(platformSpec, platformSpecification.errorString()));
745 746
    }

747
    value = metaData.value(QLatin1String(DEPENDENCIES));
748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779
    if (!value.isUndefined() && !value.isArray())
        return reportError(msgValueIsNotAObjectArray(DEPENDENCIES));
    if (!value.isUndefined()) {
        QJsonArray array = value.toArray();
        foreach (const QJsonValue &v, array) {
            if (!v.isObject())
                return reportError(msgValueIsNotAObjectArray(DEPENDENCIES));
            QJsonObject dependencyObject = v.toObject();
            PluginDependency dep;
            value = dependencyObject.value(QLatin1String(DEPENDENCY_NAME));
            if (value.isUndefined())
                return reportError(tr("Dependency: %1").arg(msgValueMissing(DEPENDENCY_NAME)));
            if (!value.isString())
                return reportError(tr("Dependency: %1").arg(msgValueIsNotAString(DEPENDENCY_NAME)));
            dep.name = value.toString();
            value = dependencyObject.value(QLatin1String(DEPENDENCY_VERSION));
            if (!value.isUndefined() && !value.isString())
                return reportError(tr("Dependency: %1").arg(msgValueIsNotAString(DEPENDENCY_VERSION)));
            dep.version = value.toString();
            if (!isValidVersion(dep.version))
                return reportError(tr("Dependency: %1").arg(msgInvalidFormat(DEPENDENCY_VERSION,
                                                                             dep.version)));
            dep.type = PluginDependency::Required;
            value = dependencyObject.value(QLatin1String(DEPENDENCY_TYPE));
            if (!value.isUndefined() && !value.isString())
                return reportError(tr("Dependency: %1").arg(msgValueIsNotAString(DEPENDENCY_TYPE)));
            if (!value.isUndefined()) {
                const QString typeValue = value.toString();
                if (typeValue.toLower() == QLatin1String(DEPENDENCY_TYPE_HARD)) {
                    dep.type = PluginDependency::Required;
                } else if (typeValue.toLower() == QLatin1String(DEPENDENCY_TYPE_SOFT)) {
                    dep.type = PluginDependency::Optional;
780 781
                } else if (typeValue.toLower() == QLatin1String(DEPENDENCY_TYPE_TEST)) {
                    dep.type = PluginDependency::Test;
782
                } else {
783
                    return reportError(tr("Dependency: \"%1\" must be \"%2\" or \"%3\" (is \"%4\").")
784 785 786 787 788 789 790
                                       .arg(QLatin1String(DEPENDENCY_TYPE),
                                            QLatin1String(DEPENDENCY_TYPE_HARD),
                                            QLatin1String(DEPENDENCY_TYPE_SOFT),
                                            typeValue));
                }
            }
            dependencies.append(dep);
con's avatar
con committed
791 792 793
        }
    }

794
    value = metaData.value(QLatin1String(ARGUMENTS));
795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822
    if (!value.isUndefined() && !value.isArray())
        return reportError(msgValueIsNotAObjectArray(ARGUMENTS));
    if (!value.isUndefined()) {
        QJsonArray array = value.toArray();
        foreach (const QJsonValue &v, array) {
            if (!v.isObject())
                return reportError(msgValueIsNotAObjectArray(ARGUMENTS));
            QJsonObject argumentObject = v.toObject();
            PluginArgumentDescription arg;
            value = argumentObject.value(QLatin1String(ARGUMENT_NAME));
            if (value.isUndefined())
                return reportError(tr("Argument: %1").arg(msgValueMissing(ARGUMENT_NAME)));
            if (!value.isString())
                return reportError(tr("Argument: %1").arg(msgValueIsNotAString(ARGUMENT_NAME)));
            arg.name = value.toString();
            if (arg.name.isEmpty())
                return reportError(tr("Argument: \"%1\" is empty").arg(QLatin1String(ARGUMENT_NAME)));
            value = argumentObject.value(QLatin1String(ARGUMENT_DESCRIPTION));
            if (!value.isUndefined() && !value.isString())
                return reportError(tr("Argument: %1").arg(msgValueIsNotAString(ARGUMENT_DESCRIPTION)));
            arg.description = value.toString();
            value = argumentObject.value(QLatin1String(ARGUMENT_PARAMETER));
            if (!value.isUndefined() && !value.isString())
                return reportError(tr("Argument: %1").arg(msgValueIsNotAString(ARGUMENT_PARAMETER)));
            arg.parameter = value.toString();
            argumentDescriptions.append(arg);
            qCDebug(pluginLog) << "Argument:" << arg.name << "Parameter:" << arg.parameter
                               << "Description:" << arg.description;
con's avatar
con committed
823 824
        }
    }
825 826

    return true;
con's avatar
con committed
827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843
}

/*!
    \internal
*/
bool PluginSpecPrivate::provides(const QString &pluginName, const QString &pluginVersion) const
{
    if (QString::compare(pluginName, name, Qt::CaseInsensitive) != 0)
        return false;
    return (versionCompare(version, pluginVersion) >= 0) && (versionCompare(compatVersion, pluginVersion) <= 0);
}

/*!
    \internal
*/
QRegExp &PluginSpecPrivate::versionRegExp()
{
844
    static QRegExp reg(QLatin1String("([0-9]+)(?:[.]([0-9]+))?(?:[.]([0-9]+))?(?:_([0-9]+))?"));
con's avatar
con committed
845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881
    return reg;
}
/*!
    \internal
*/
bool PluginSpecPrivate::isValidVersion(const QString &version)
{
    return versionRegExp().exactMatch(version);
}

/*!
    \internal
*/
int PluginSpecPrivate::versionCompare(const QString &version1, const QString &version2)
{
    QRegExp reg1 = versionRegExp();
    QRegExp reg2 = versionRegExp();
    if (!reg1.exactMatch(version1))
        return 0;
    if (!reg2.exactMatch(version2))
        return 0;
    int number1;
    int number2;
    for (int i = 0; i < 4; ++i) {
        number1 = reg1.cap(i+1).toInt();
        number2 = reg2.cap(i+1).toInt();
        if (number1 < number2)
            return -1;
        if (number1 > number2)
            return 1;
    }
    return 0;
}

/*!
    \internal
*/
882
bool PluginSpecPrivate::resolveDependencies(const QList<PluginSpec *> &specs)
con's avatar
con committed
883 884 885 886 887 888 889 890 891 892
{
    if (hasError)
        return false;
    if (state == PluginSpec::Resolved)
        state = PluginSpec::Read; // Go back, so we just re-resolve the dependencies.
    if (state != PluginSpec::Read) {
        errorString = QCoreApplication::translate("PluginSpec", "Resolving dependencies failed because state != Read");
        hasError = true;
        return false;
    }
con's avatar
con committed
893
    QHash<PluginDependency, PluginSpec *> resolvedDependencies;
con's avatar
con committed
894
    foreach (const PluginDependency &dependency, dependencies) {
Eike Ziller's avatar
Eike Ziller committed
895 896 897
        PluginSpec * const found = Utils::findOrDefault(specs, [&dependency](PluginSpec *spec) {
            return spec->provides(dependency.name, dependency.version);
        });
con's avatar
con committed
898
        if (!found) {
con's avatar
con committed
899 900 901 902 903 904 905
            if (dependency.type == PluginDependency::Required) {
                hasError = true;
                if (!errorString.isEmpty())
                    errorString.append(QLatin1Char('\n'));
                errorString.append(QCoreApplication::translate("PluginSpec", "Could not resolve dependency '%1(%2)'")
                    .arg(dependency.name).arg(dependency.version));
            }
con's avatar
con committed
906 907
            continue;
        }
con's avatar
con committed
908
        resolvedDependencies.insert(dependency, found);
con's avatar
con committed
909 910 911
    }
    if (hasError)
        return false;
912

con's avatar
con committed
913
    dependencySpecs = resolvedDependencies;
914

915
    state = PluginSpec::Resolved;
916

con's avatar
con committed
917 918 919
    return true;
}

920
void PluginSpecPrivate::enableDependenciesIndirectly()
921
{
922
    if (!q->isEffectivelyEnabled()) // plugin not enabled, nothing to do
923
        return;
con's avatar
con committed
924 925 926
    QHashIterator<PluginDependency, PluginSpec *> it(dependencySpecs);
    while (it.hasNext()) {
        it.next();
927
        if (it.key().type != PluginDependency::Required)
con's avatar
con committed
928 929
            continue;
        PluginSpec *dependencySpec = it.value();
930 931
        if (!dependencySpec->isEffectivelyEnabled())
            dependencySpec->d->enabledIndirectly = true;
932 933 934
    }
}

con's avatar
con committed
935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950
/*!
    \internal
*/
bool PluginSpecPrivate::loadLibrary()
{
    if (hasError)
        return false;
    if (state != PluginSpec::Resolved) {
        if (state == PluginSpec::Loaded)
            return true;
        errorString = QCoreApplication::translate("PluginSpec", "Loading the library failed because state != Resolved");
        hasError = true;
        return false;
    }
    if (!loader.load()) {
        hasError = true;
951
        errorString = QDir::toNativeSeparators(filePath)
Alessandro Portale's avatar
Alessandro Portale committed
952
            + QString::fromLatin1(": ") + loader.errorString();
con's avatar
con committed
953 954 955 956 957
        return false;
    }
    IPlugin *pluginObject = qobject_cast<IPlugin*>(loader.instance());
    if (!pluginObject) {
        hasError = true;
958
        errorString = QCoreApplication::translate("PluginSpec", "Plugin is not valid (does not derive from IPlugin)");
con's avatar
con committed
959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
        loader.unload();
        return false;
    }
    state = PluginSpec::Loaded;
    plugin = pluginObject;
    plugin->d->pluginSpec = q;
    return true;
}

/*!
    \internal
*/
bool PluginSpecPrivate::initializePlugin()
{
    if (hasError)
        return false;
    if (state != PluginSpec::Loaded) {
        if (state == PluginSpec::Initialized)
            return true;
        errorString = QCoreApplication::translate("PluginSpec", "Initializing the plugin failed because state != Loaded");
        hasError = true;
        return false;
    }
    if (!plugin) {
        errorString = QCoreApplication::translate("PluginSpec", "Internal error: have no plugin instance to initialize");
        hasError = true;
        return false;
    }
    QString err;
    if (!plugin->initialize(arguments, &err)) {
        errorString = QCoreApplication::translate("PluginSpec", "Plugin initialization failed: %1").arg(err);
        hasError = true;
        return false;
    }
    state = PluginSpec::Initialized;
    return true;
}

/*!
    \internal
*/
bool PluginSpecPrivate::initializeExtensions()
{
    if (hasError)
        return false;
    if (state != PluginSpec::Initialized) {
        if (state == PluginSpec::Running)
            return true;
        errorString = QCoreApplication::translate("PluginSpec", "Cannot perform extensionsInitialized because state != Initialized");
        hasError = true;
        return false;
    }
    if (!plugin) {
        errorString = QCoreApplication::translate("PluginSpec", "Internal error: have no plugin instance to perform extensionsInitialized");
        hasError = true;
        return false;
    }
    plugin->extensionsInitialized();
    state = PluginSpec::Running;
    return true;
}

1021 1022 1023 1024 1025 1026 1027
/*!
    \internal
*/
bool PluginSpecPrivate::delayedInitialize()
{
    if (hasError)
        return false;
1028
    if (state != PluginSpec::Running)
1029 1030 1031 1032 1033 1034 1035 1036 1037
        return false;
    if (!plugin) {
        errorString = QCoreApplication::translate("PluginSpec", "Internal error: have no plugin instance to perform delayedInitialize");
        hasError = true;
        return false;
    }
    return plugin->delayedInitialize();
}

con's avatar
con committed
1038 1039 1040
/*!
    \internal
*/
1041
IPlugin::ShutdownFlag PluginSpecPrivate::stop()
con's avatar
con committed
1042 1043
{
    if (!plugin)
1044
        return IPlugin::SynchronousShutdown;
con's avatar
con committed
1045
    state = PluginSpec::Stopped;
1046
    return plugin->aboutToShutdown();
con's avatar
con committed
1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
}

/*!
    \internal
*/
void PluginSpecPrivate::kill()
{
    if (!plugin)
        return;
    delete plugin;
    plugin = 0;
    state = PluginSpec::Deleted;
}