ExtensionSystem: Refactor PluginSpec

Splits the functionality between plugin type specific and general.

Allows Plugins to be loaded after the first pass, e.g. for Lua scripted
plugins.

Change-Id: If2712817a672c49d554fdc308250cb06ca7eb3f8
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Marcus Tillmanns
2024-01-25 12:21:48 +01:00
parent 1af555ad09
commit b39b192518
16 changed files with 678 additions and 681 deletions

View File

@@ -11,7 +11,7 @@ add_qtc_library(ExtensionSystem
pluginerroroverview.cpp pluginerroroverview.h pluginerroroverview.cpp pluginerroroverview.h
pluginerrorview.cpp pluginerrorview.h pluginerrorview.cpp pluginerrorview.h
pluginmanager.cpp pluginmanager.h pluginmanager_p.h pluginmanager.cpp pluginmanager.h pluginmanager_p.h
pluginspec.cpp pluginspec.h pluginspec_p.h pluginspec.cpp pluginspec.h
pluginview.cpp pluginview.h pluginview.cpp pluginview.h
EXPLICIT_MOC EXPLICIT_MOC
pluginmanager.h pluginmanager.h

View File

@@ -29,7 +29,6 @@ QtcLibrary {
"pluginmanager_p.h", "pluginmanager_p.h",
"pluginspec.cpp", "pluginspec.cpp",
"pluginspec.h", "pluginspec.h",
"pluginspec_p.h",
"pluginview.cpp", "pluginview.cpp",
"pluginview.h", "pluginview.h",
] ]

View File

@@ -6,7 +6,6 @@
#include "extensionsystemtr.h" #include "extensionsystemtr.h"
#include "pluginmanager.h" #include "pluginmanager.h"
#include "pluginmanager_p.h" #include "pluginmanager_p.h"
#include "pluginspec_p.h"
#include <utils/algorithm.h> #include <utils/algorithm.h>
@@ -177,7 +176,7 @@ bool OptionsParser::checkForLoadOption()
if (nextToken(RequiredToken)) { if (nextToken(RequiredToken)) {
if (m_currentArg == QLatin1String("all")) { if (m_currentArg == QLatin1String("all")) {
for (PluginSpec *spec : std::as_const(m_pmPrivate->pluginSpecs)) for (PluginSpec *spec : std::as_const(m_pmPrivate->pluginSpecs))
spec->d->setForceEnabled(true); spec->setForceEnabled(true);
m_isDependencyRefreshNeeded = true; m_isDependencyRefreshNeeded = true;
} else { } else {
PluginSpec *spec = m_pmPrivate->pluginByName(m_currentArg); PluginSpec *spec = m_pmPrivate->pluginByName(m_currentArg);
@@ -186,7 +185,7 @@ bool OptionsParser::checkForLoadOption()
*m_errorString = Tr::tr("The plugin \"%1\" does not exist.").arg(m_currentArg); *m_errorString = Tr::tr("The plugin \"%1\" does not exist.").arg(m_currentArg);
m_hasError = true; m_hasError = true;
} else { } else {
spec->d->setForceEnabled(true); spec->setForceEnabled(true);
m_isDependencyRefreshNeeded = true; m_isDependencyRefreshNeeded = true;
} }
} }
@@ -202,7 +201,7 @@ bool OptionsParser::checkForNoLoadOption()
if (nextToken(RequiredToken)) { if (nextToken(RequiredToken)) {
if (m_currentArg == QLatin1String("all")) { if (m_currentArg == QLatin1String("all")) {
for (PluginSpec *spec : std::as_const(m_pmPrivate->pluginSpecs)) for (PluginSpec *spec : std::as_const(m_pmPrivate->pluginSpecs))
spec->d->setForceDisabled(true); spec->setForceDisabled(true);
m_isDependencyRefreshNeeded = true; m_isDependencyRefreshNeeded = true;
} else { } else {
PluginSpec *spec = m_pmPrivate->pluginByName(m_currentArg); PluginSpec *spec = m_pmPrivate->pluginByName(m_currentArg);
@@ -211,10 +210,10 @@ bool OptionsParser::checkForNoLoadOption()
*m_errorString = Tr::tr("The plugin \"%1\" does not exist.").arg(m_currentArg); *m_errorString = Tr::tr("The plugin \"%1\" does not exist.").arg(m_currentArg);
m_hasError = true; m_hasError = true;
} else { } else {
spec->d->setForceDisabled(true); spec->setForceDisabled(true);
// recursively disable all plugins that require this plugin // recursively disable all plugins that require this plugin
for (PluginSpec *dependantSpec : PluginManager::pluginsRequiringPlugin(spec)) for (PluginSpec *dependantSpec : PluginManager::pluginsRequiringPlugin(spec))
dependantSpec->d->setForceDisabled(true); dependantSpec->setForceDisabled(true);
m_isDependencyRefreshNeeded = true; m_isDependencyRefreshNeeded = true;
} }
} }
@@ -292,10 +291,10 @@ bool OptionsParser::checkForUnknownOption()
void OptionsParser::forceDisableAllPluginsExceptTestedAndForceEnabled() void OptionsParser::forceDisableAllPluginsExceptTestedAndForceEnabled()
{ {
for (const PluginManagerPrivate::TestSpec &testSpec : m_pmPrivate->testSpecs) for (const PluginManagerPrivate::TestSpec &testSpec : m_pmPrivate->testSpecs)
testSpec.pluginSpec->d->setForceEnabled(true); testSpec.pluginSpec->setForceEnabled(true);
for (PluginSpec *spec : std::as_const(m_pmPrivate->pluginSpecs)) { for (PluginSpec *spec : std::as_const(m_pmPrivate->pluginSpecs)) {
if (!spec->isForceEnabled() && !spec->isRequired()) if (!spec->isForceEnabled() && !spec->isRequired())
spec->d->setForceDisabled(true); spec->setForceDisabled(true);
} }
} }

View File

@@ -8,7 +8,6 @@
#include "optionsparser.h" #include "optionsparser.h"
#include "pluginmanager_p.h" #include "pluginmanager_p.h"
#include "pluginspec.h" #include "pluginspec.h"
#include "pluginspec_p.h"
#include <nanotrace/nanotrace.h> #include <nanotrace/nanotrace.h>
@@ -35,6 +34,7 @@
#include <QLibraryInfo> #include <QLibraryInfo>
#include <QMessageBox> #include <QMessageBox>
#include <QMetaProperty> #include <QMetaProperty>
#include <QPluginLoader>
#include <QPushButton> #include <QPushButton>
#include <QScopeGuard> #include <QScopeGuard>
#include <QSysInfo> #include <QSysInfo>
@@ -327,6 +327,11 @@ void PluginManager::loadPluginsAtRuntime(const QSet<PluginSpec *> &plugins)
d->loadPluginsAtRuntime(plugins); d->loadPluginsAtRuntime(plugins);
} }
void PluginManager::addPlugins(const QVector<PluginSpec *> &specs)
{
d->addPlugins(specs);
}
/*! /*!
Returns \c true if any plugin has errors even though it is enabled. Returns \c true if any plugin has errors even though it is enabled.
Most useful to call after loadPlugins(). Most useful to call after loadPlugins().
@@ -903,14 +908,6 @@ QVector<PluginSpec *> PluginManager::loadQueue()
//============PluginManagerPrivate=========== //============PluginManagerPrivate===========
/*!
\internal
*/
PluginSpec *PluginManagerPrivate::createSpec()
{
return new PluginSpec();
}
/*! /*!
\internal \internal
*/ */
@@ -935,14 +932,6 @@ void PluginManagerPrivate::setGlobalSettings(QtcSettings *s)
globalSettings->setParent(this); globalSettings->setParent(this);
} }
/*!
\internal
*/
PluginSpecPrivate *PluginManagerPrivate::privateSpec(PluginSpec *spec)
{
return spec->d;
}
void PluginManagerPrivate::startDelayedInitialize() void PluginManagerPrivate::startDelayedInitialize()
{ {
Utils::setMimeStartupPhase(MimeStartupPhase::PluginsDelayedInitializing); Utils::setMimeStartupPhase(MimeStartupPhase::PluginsDelayedInitializing);
@@ -954,8 +943,8 @@ void PluginManagerPrivate::startDelayedInitialize()
delayedInitializeQueue.pop(); delayedInitializeQueue.pop();
NANOTRACE_SCOPE(specName, specName + "::delayedInitialized"); NANOTRACE_SCOPE(specName, specName + "::delayedInitialized");
profilingReport(">delayedInitialize", spec); profilingReport(">delayedInitialize", spec);
bool delay = spec->d->delayedInitialize(); bool delay = spec->delayedInitialize();
profilingReport("<delayedInitialize", spec, &spec->d->performanceData.delayedInitialize); profilingReport("<delayedInitialize", spec, &spec->performanceData().delayedInitialize);
if (delay) // give UI a bit of breathing space, but prevent user interaction if (delay) // give UI a bit of breathing space, but prevent user interaction
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
} }
@@ -1068,10 +1057,8 @@ void PluginManagerPrivate::checkForDuplicatePlugins()
if (spec->isEffectivelyEnabled() && other->isEffectivelyEnabled()) { if (spec->isEffectivelyEnabled() && other->isEffectivelyEnabled()) {
const QString error = Tr::tr( const QString error = Tr::tr(
"Multiple versions of the same plugin have been found."); "Multiple versions of the same plugin have been found.");
spec->d->hasError = true; spec->setError(error);
spec->d->errorString = error; other->setError(error);
other->d->hasError = true;
other->d->errorString = error;
} }
} else { } else {
seen.insert(spec->name(), spec); seen.insert(spec->name(), spec);
@@ -1403,7 +1390,7 @@ void PluginManagerPrivate::loadPlugins()
delayedInitializeQueue.push(spec); delayedInitializeQueue.push(spec);
} else { } else {
// Plugin initialization failed, so cleanup after it // Plugin initialization failed, so cleanup after it
spec->d->kill(); spec->kill();
} }
}); });
} }
@@ -1421,10 +1408,23 @@ void PluginManagerPrivate::loadPlugins()
void PluginManagerPrivate::loadPluginsAtRuntime(const QSet<PluginSpec *> &plugins) void PluginManagerPrivate::loadPluginsAtRuntime(const QSet<PluginSpec *> &plugins)
{ {
QTC_CHECK(allOf(plugins, [](PluginSpec *spec) { return spec->isSoftLoadable(); })); QTC_CHECK(allOf(plugins, [](PluginSpec *spec) { return spec->isSoftLoadable(); }));
// load the plugins ordered by dependency
// load the plugins and their dependencies (if possible) ordered by dependency
const QList<PluginSpec *> queue = filtered(loadQueue(), [&plugins](PluginSpec *spec) { const QList<PluginSpec *> queue = filtered(loadQueue(), [&plugins](PluginSpec *spec) {
return plugins.contains(spec); // Is the current plugin already running, or not soft loadable?
if (spec->state() == PluginSpec::State::Running || !spec->isSoftLoadable())
return false;
// Is the current plugin in the list of plugins to load?
if (plugins.contains(spec))
return true;
// Is the current plugin a dependency of any of the plugins we want to load?
return plugins.contains(spec) || Utils::anyOf(plugins, [spec](PluginSpec *other) {
return other->requiresAny({spec});
}); });
});
std::queue<PluginSpec *> localDelayedInitializeQueue; std::queue<PluginSpec *> localDelayedInitializeQueue;
for (PluginSpec *spec : queue) for (PluginSpec *spec : queue)
loadPlugin(spec, PluginSpec::Loaded); loadPlugin(spec, PluginSpec::Loaded);
@@ -1434,12 +1434,12 @@ void PluginManagerPrivate::loadPluginsAtRuntime(const QSet<PluginSpec *> &plugin
[this](PluginSpec *spec) { loadPlugin(spec, PluginSpec::Running); }); [this](PluginSpec *spec) { loadPlugin(spec, PluginSpec::Running); });
Utils::reverseForeach(queue, [](PluginSpec *spec) { Utils::reverseForeach(queue, [](PluginSpec *spec) {
if (spec->state() == PluginSpec::Running) { if (spec->state() == PluginSpec::Running) {
const bool delay = spec->d->delayedInitialize(); const bool delay = spec->delayedInitialize();
if (delay) if (delay)
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
} else { } else {
// Plugin initialization failed, so cleanup after it // Plugin initialization failed, so cleanup after it
spec->d->kill(); spec->kill();
} }
}); });
emit q->pluginsChanged(); emit q->pluginsChanged();
@@ -1494,17 +1494,17 @@ bool PluginManagerPrivate::loadQueue(PluginSpec *spec,
return true; return true;
// check for circular dependencies // check for circular dependencies
if (circularityCheckQueue.contains(spec)) { if (circularityCheckQueue.contains(spec)) {
spec->d->hasError = true; QString errorString = Tr::tr("Circular dependency detected:");
spec->d->errorString = Tr::tr("Circular dependency detected:"); errorString += QLatin1Char('\n');
spec->d->errorString += QLatin1Char('\n');
int index = circularityCheckQueue.indexOf(spec); int index = circularityCheckQueue.indexOf(spec);
for (int i = index; i < circularityCheckQueue.size(); ++i) { for (int i = index; i < circularityCheckQueue.size(); ++i) {
const PluginSpec *depSpec = circularityCheckQueue.at(i); const PluginSpec *depSpec = circularityCheckQueue.at(i);
spec->d->errorString.append(Tr::tr("%1 (%2) depends on") errorString.append(
.arg(depSpec->name(), depSpec->version())); Tr::tr("%1 (%2) depends on").arg(depSpec->name(), depSpec->version()));
spec->d->errorString += QLatin1Char('\n'); errorString += QLatin1Char('\n');
} }
spec->d->errorString.append(Tr::tr("%1 (%2)").arg(spec->name(), spec->version())); errorString.append(Tr::tr("%1 (%2)").arg(spec->name(), spec->version()));
spec->setError(errorString);
return false; return false;
} }
circularityCheckQueue.append(spec); circularityCheckQueue.append(spec);
@@ -1523,10 +1523,9 @@ bool PluginManagerPrivate::loadQueue(PluginSpec *spec,
continue; continue;
PluginSpec *depSpec = it.value(); PluginSpec *depSpec = it.value();
if (!loadQueue(depSpec, queue, circularityCheckQueue)) { if (!loadQueue(depSpec, queue, circularityCheckQueue)) {
spec->d->hasError = true; spec->setError(
spec->d->errorString =
Tr::tr("Cannot load plugin because dependency failed to load: %1 (%2)\nReason: %3") Tr::tr("Cannot load plugin because dependency failed to load: %1 (%2)\nReason: %3")
.arg(depSpec->name(), depSpec->version(), depSpec->errorString()); .arg(depSpec->name(), depSpec->version(), depSpec->errorString()));
return false; return false;
} }
} }
@@ -1619,9 +1618,9 @@ void PluginManagerPrivate::checkForProblematicPlugins()
dialog.addButton(Tr::tr("Continue"), QMessageBox::RejectRole); dialog.addButton(Tr::tr("Continue"), QMessageBox::RejectRole);
dialog.exec(); dialog.exec();
if (dialog.clickedButton() == disableButton) { if (dialog.clickedButton() == disableButton) {
spec->d->setForceDisabled(true); spec->setForceDisabled(true);
for (PluginSpec *other : dependents) for (PluginSpec *other : dependents)
other->d->setForceDisabled(true); other->setForceDisabled(true);
enableDependenciesIndirectly(); enableDependenciesIndirectly();
} }
} }
@@ -1664,15 +1663,15 @@ void PluginManagerPrivate::loadPlugin(PluginSpec *spec, PluginSpec::State destSt
case PluginSpec::Running: { case PluginSpec::Running: {
NANOTRACE_SCOPE(specName, specName + "::extensionsInitialized"); NANOTRACE_SCOPE(specName, specName + "::extensionsInitialized");
profilingReport(">initializeExtensions", spec); profilingReport(">initializeExtensions", spec);
spec->d->initializeExtensions(); spec->initializeExtensions();
profilingReport("<initializeExtensions", profilingReport("<initializeExtensions",
spec, spec,
&spec->d->performanceData.extensionsInitialized); &spec->performanceData().extensionsInitialized);
return; return;
} }
case PluginSpec::Deleted: case PluginSpec::Deleted:
profilingReport(">delete", spec); profilingReport(">delete", spec);
spec->d->kill(); spec->kill();
profilingReport("<delete", spec); profilingReport("<delete", spec);
return; return;
default: default:
@@ -1686,10 +1685,10 @@ void PluginManagerPrivate::loadPlugin(PluginSpec *spec, PluginSpec::State destSt
continue; continue;
PluginSpec *depSpec = it.value(); PluginSpec *depSpec = it.value();
if (depSpec->state() != destState) { if (depSpec->state() != destState) {
spec->d->hasError = true; spec->setError(
spec->d->errorString = Tr::tr(
Tr::tr("Cannot load plugin because dependency failed to load: %1(%2)\nReason: %3") "Cannot load plugin because dependency failed to load: %1(%2)\nReason: %3")
.arg(depSpec->name(), depSpec->version(), depSpec->errorString()); .arg(depSpec->name(), depSpec->version(), depSpec->errorString()));
return; return;
} }
} }
@@ -1698,20 +1697,20 @@ void PluginManagerPrivate::loadPlugin(PluginSpec *spec, PluginSpec::State destSt
case PluginSpec::Loaded: { case PluginSpec::Loaded: {
NANOTRACE_SCOPE(specName, specName + "::load"); NANOTRACE_SCOPE(specName, specName + "::load");
profilingReport(">loadLibrary", spec); profilingReport(">loadLibrary", spec);
spec->d->loadLibrary(); spec->loadLibrary();
profilingReport("<loadLibrary", spec, &spec->d->performanceData.load); profilingReport("<loadLibrary", spec, &spec->performanceData().load);
break; break;
} }
case PluginSpec::Initialized: { case PluginSpec::Initialized: {
NANOTRACE_SCOPE(specName, specName + "::initialize"); NANOTRACE_SCOPE(specName, specName + "::initialize");
profilingReport(">initializePlugin", spec); profilingReport(">initializePlugin", spec);
spec->d->initializePlugin(); spec->initializePlugin();
profilingReport("<initializePlugin", spec, &spec->d->performanceData.initialize); profilingReport("<initializePlugin", spec, &spec->performanceData().initialize);
break; break;
} }
case PluginSpec::Stopped: case PluginSpec::Stopped:
profilingReport(">stop", spec); profilingReport(">stop", spec);
if (spec->d->stop() == IPlugin::AsynchronousShutdown) { if (spec->stop() == IPlugin::AsynchronousShutdown) {
asynchronousPlugins << spec; asynchronousPlugins << spec;
connect(spec->plugin(), &IPlugin::asynchronousShutdownFinished, this, [this, spec] { connect(spec->plugin(), &IPlugin::asynchronousShutdownFinished, this, [this, spec] {
asynchronousPlugins.remove(spec); asynchronousPlugins.remove(spec);
@@ -1753,45 +1752,24 @@ static const QStringList pluginFiles(const QStringList &pluginPaths)
return pluginFiles; return pluginFiles;
} }
/*! void PluginManagerPrivate::addPlugins(const QVector<PluginSpec *> &specs)
\internal
*/
void PluginManagerPrivate::readPluginPaths()
{ {
qDeleteAll(pluginSpecs); pluginSpecs += specs;
pluginSpecs.clear();
pluginCategories.clear();
// default for (PluginSpec *spec : specs) {
pluginCategories.insert(QString(), QVector<PluginSpec *>());
// from the file system
for (const QString &pluginFile : pluginFiles(pluginPaths)) {
PluginSpec *spec = PluginSpec::read(pluginFile);
if (spec) // Qt Creator plugin
pluginSpecs.append(spec);
}
// static
for (const QStaticPlugin &plugin : QPluginLoader::staticPlugins()) {
PluginSpec *spec = PluginSpec::read(plugin);
if (spec) // Qt Creator plugin
pluginSpecs.append(spec);
}
for (PluginSpec *spec : pluginSpecs) {
// defaultDisabledPlugins and defaultEnabledPlugins from install settings // defaultDisabledPlugins and defaultEnabledPlugins from install settings
// is used to override the defaults read from the plugin spec // is used to override the defaults read from the plugin spec
if (spec->isEnabledByDefault() && defaultDisabledPlugins.contains(spec->name())) { if (spec->isEnabledByDefault() && defaultDisabledPlugins.contains(spec->name())) {
spec->d->setEnabledByDefault(false); spec->setEnabledByDefault(false);
spec->d->setEnabledBySettings(false); spec->setEnabledBySettings(false);
} else if (!spec->isEnabledByDefault() && defaultEnabledPlugins.contains(spec->name())) { } else if (!spec->isEnabledByDefault() && defaultEnabledPlugins.contains(spec->name())) {
spec->d->setEnabledByDefault(true); spec->setEnabledByDefault(true);
spec->d->setEnabledBySettings(true); spec->setEnabledBySettings(true);
} }
if (!spec->isEnabledByDefault() && forceEnabledPlugins.contains(spec->name())) if (!spec->isEnabledByDefault() && forceEnabledPlugins.contains(spec->name()))
spec->d->setEnabledBySettings(true); spec->setEnabledBySettings(true);
if (spec->isEnabledByDefault() && disabledPlugins.contains(spec->name())) if (spec->isEnabledByDefault() && disabledPlugins.contains(spec->name()))
spec->d->setEnabledBySettings(false); spec->setEnabledBySettings(false);
pluginCategories[spec->category()].append(spec); pluginCategories[spec->category()].append(spec);
} }
@@ -1803,21 +1781,49 @@ void PluginManagerPrivate::readPluginPaths()
emit q->pluginsChanged(); emit q->pluginsChanged();
} }
/*!
\internal
*/
void PluginManagerPrivate::readPluginPaths()
{
QVector<PluginSpec *> newSpecs;
// from the file system
for (const QString &pluginFile : pluginFiles(pluginPaths)) {
expected_str<PluginSpec *> spec = PluginSpecImpl::read(pluginFile);
if (!spec) {
qCWarning(pluginLog).noquote()
<< QString("Ignoring plugin \"%1\" because: %2").arg(pluginFile).arg(spec.error());
continue;
}
newSpecs.append(*spec);
}
// static
for (const QStaticPlugin &plugin : QPluginLoader::staticPlugins()) {
expected_str<PluginSpec *> spec = PluginSpecImpl::read(plugin);
QTC_ASSERT_EXPECTED(spec, continue);
newSpecs.append(*spec);
}
addPlugins(newSpecs);
}
void PluginManagerPrivate::resolveDependencies() void PluginManagerPrivate::resolveDependencies()
{ {
for (PluginSpec *spec : std::as_const(pluginSpecs)) for (PluginSpec *spec : std::as_const(pluginSpecs))
spec->d->resolveDependencies(pluginSpecs); spec->resolveDependencies(pluginSpecs);
} }
void PluginManagerPrivate::enableDependenciesIndirectly() void PluginManagerPrivate::enableDependenciesIndirectly()
{ {
for (PluginSpec *spec : std::as_const(pluginSpecs)) for (PluginSpec *spec : std::as_const(pluginSpecs))
spec->d->enabledIndirectly = false; spec->setEnabledIndirectly(false);
// cannot use reverse loadQueue here, because test dependencies can introduce circles // cannot use reverse loadQueue here, because test dependencies can introduce circles
QVector<PluginSpec *> queue = Utils::filtered(pluginSpecs, &PluginSpec::isEffectivelyEnabled); QVector<PluginSpec *> queue = Utils::filtered(pluginSpecs, &PluginSpec::isEffectivelyEnabled);
while (!queue.isEmpty()) { while (!queue.isEmpty()) {
PluginSpec *spec = queue.takeFirst(); PluginSpec *spec = queue.takeFirst();
queue += spec->d->enableDependenciesIndirectly(containsTestSpec(spec)); queue += spec->enableDependenciesIndirectly(containsTestSpec(spec));
} }
} }

View File

@@ -82,6 +82,8 @@ public:
static void checkForProblematicPlugins(); static void checkForProblematicPlugins();
static PluginSpec *specForPlugin(IPlugin *plugin); static PluginSpec *specForPlugin(IPlugin *plugin);
static void addPlugins(const QVector<PluginSpec *> &specs);
// Settings // Settings
static void setSettings(Utils::QtcSettings *settings); static void setSettings(Utils::QtcSettings *settings);
static Utils::QtcSettings *settings(); static Utils::QtcSettings *settings();

View File

@@ -36,8 +36,6 @@ class PluginManager;
namespace Internal { namespace Internal {
class PluginSpecPrivate;
class EXTENSIONSYSTEM_TEST_EXPORT PluginManagerPrivate : public QObject class EXTENSIONSYSTEM_TEST_EXPORT PluginManagerPrivate : public QObject
{ {
public: public:
@@ -52,6 +50,8 @@ public:
void checkForProblematicPlugins(); void checkForProblematicPlugins();
void loadPlugins(); void loadPlugins();
void loadPluginsAtRuntime(const QSet<PluginSpec *> &plugins); void loadPluginsAtRuntime(const QSet<PluginSpec *> &plugins);
void addPlugins(const QVector<PluginSpec *> &specs);
void shutdown(); void shutdown();
void setPluginPaths(const QStringList &paths); void setPluginPaths(const QStringList &paths);
const QVector<ExtensionSystem::PluginSpec *> loadQueue(); const QVector<ExtensionSystem::PluginSpec *> loadQueue();
@@ -119,10 +119,6 @@ public:
PluginSpec *pluginForOption(const QString &option, bool *requiresArgument) const; PluginSpec *pluginForOption(const QString &option, bool *requiresArgument) const;
PluginSpec *pluginByName(const QString &name) const; PluginSpec *pluginByName(const QString &name) const;
// used by tests
static PluginSpec *createSpec();
static PluginSpecPrivate *privateSpec(PluginSpec *spec);
static void addTestCreator(IPlugin *plugin, const std::function<QObject *()> &testCreator); static void addTestCreator(IPlugin *plugin, const std::function<QObject *()> &testCreator);
mutable QReadWriteLock m_lock; mutable QReadWriteLock m_lock;

View File

@@ -6,7 +6,6 @@
#include "extensionsystemtr.h" #include "extensionsystemtr.h"
#include "iplugin.h" #include "iplugin.h"
#include "pluginmanager.h" #include "pluginmanager.h"
#include "pluginspec_p.h"
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
@@ -25,6 +24,7 @@
#include <QPluginLoader> #include <QPluginLoader>
using namespace ExtensionSystem::Internal; using namespace ExtensionSystem::Internal;
using namespace Utils;
namespace ExtensionSystem { namespace ExtensionSystem {
@@ -168,22 +168,78 @@ QString PluginDependency::toString() const
return name + " (" + version + typeString(type) + ")"; return name + " (" + version + typeString(type) + ")";
} }
/*! namespace Internal {
\internal class PluginSpecImplPrivate
*/
PluginSpec::PluginSpec()
: d(new PluginSpecPrivate(this))
{ {
} public:
std::optional<QPluginLoader> loader;
std::optional<QStaticPlugin> staticPlugin;
IPlugin *plugin;
};
class PluginSpecPrivate
{
public:
ExtensionSystem::PerformanceData performanceData;
QString name;
QString version;
QString compatVersion;
QString vendor;
QString category;
QString description;
QString longDescription;
QString url;
QString license;
QString revision;
QString copyright;
QStringList arguments;
QRegularExpression platformSpecification;
QVector<ExtensionSystem::PluginDependency> dependencies;
PluginSpecImpl::PluginArgumentDescriptions argumentDescriptions;
QString location;
QString filePath;
bool experimental{false};
bool deprecated{false};
bool required{false};
bool enabledByDefault{false};
bool enabledBySettings{true};
bool enabledIndirectly{false};
bool forceEnabled{false};
bool forceDisabled{false};
bool softLoadable{false};
std::optional<QString> errorString;
PluginSpec::State state;
QHash<PluginDependency, PluginSpec *> dependencySpecs;
QJsonObject metaData;
Utils::expected_str<void> readMetaData(const QJsonObject &metaData);
Utils::expected_str<void> reportError(const QString &error)
{
errorString = error;
return {};
};
};
} // namespace Internal
/*! /*!
\internal \internal
*/ */
PluginSpec::~PluginSpec() PluginSpecImpl::PluginSpecImpl()
{ : d(new PluginSpecImplPrivate)
delete d; {}
d = nullptr;
} /*!
\internal
*/
PluginSpecImpl::~PluginSpecImpl() = default;
/*! /*!
Returns the plugin name. This is valid after the PluginSpec::Read state is Returns the plugin name. This is valid after the PluginSpec::Read state is
@@ -278,10 +334,7 @@ QString PluginSpec::category() const
QString PluginSpec::revision() const QString PluginSpec::revision() const
{ {
const QJsonValue revision = metaData().value("Revision"); return d->revision;
if (revision.isString())
return revision.toString();
return QString();
} }
/*! /*!
@@ -419,7 +472,7 @@ QJsonObject PluginSpec::metaData() const
return d->metaData; return d->metaData;
} }
const PerformanceData &PluginSpec::performanceData() const PerformanceData &PluginSpec::performanceData() const
{ {
return d->performanceData; return d->performanceData;
} }
@@ -491,7 +544,7 @@ PluginSpec::State PluginSpec::state() const
*/ */
bool PluginSpec::hasError() const bool PluginSpec::hasError() const
{ {
return d->hasError; return d->errorString.has_value();
} }
/*! /*!
@@ -500,7 +553,7 @@ bool PluginSpec::hasError() const
*/ */
QString PluginSpec::errorString() const QString PluginSpec::errorString() const
{ {
return d->errorString; return d->errorString.value_or(QString());
} }
/*! /*!
@@ -509,9 +562,13 @@ QString PluginSpec::errorString() const
\sa PluginSpec::dependencies() \sa PluginSpec::dependencies()
*/ */
bool PluginSpec::provides(const QString &pluginName, const QString &version) const bool PluginSpec::provides(const QString &pluginName, const QString &pluginVersion) const
{ {
return d->provides(pluginName, version); if (QString::compare(pluginName, name(), Qt::CaseInsensitive) != 0)
return false;
return (versionCompare(version(), pluginVersion) >= 0)
&& (versionCompare(compatVersion(), pluginVersion) <= 0);
} }
/*! /*!
@@ -519,7 +576,7 @@ bool PluginSpec::provides(const QString &pluginName, const QString &version) con
already been successfully loaded. That is, the PluginSpec::Loaded state already been successfully loaded. That is, the PluginSpec::Loaded state
is reached. is reached.
*/ */
IPlugin *PluginSpec::plugin() const IPlugin *PluginSpecImpl::plugin() const
{ {
return d->plugin; return d->plugin;
} }
@@ -548,6 +605,11 @@ bool PluginSpec::requiresAny(const QSet<PluginSpec *> &plugins) const
return false; return false;
} }
void PluginSpec::setEnabledByDefault(bool value)
{
d->enabledByDefault = value;
}
/*! /*!
Sets whether the plugin should be loaded at startup to \a value. Sets whether the plugin should be loaded at startup to \a value.
@@ -555,27 +617,40 @@ bool PluginSpec::requiresAny(const QSet<PluginSpec *> &plugins) const
*/ */
void PluginSpec::setEnabledBySettings(bool value) void PluginSpec::setEnabledBySettings(bool value)
{ {
d->setEnabledBySettings(value); d->enabledBySettings = value;
}
void PluginSpec::setEnabledIndirectly(bool value)
{
d->enabledIndirectly = value;
}
void PluginSpec::setForceDisabled(bool value)
{
d->forceDisabled = value;
}
void PluginSpec::setForceEnabled(bool value)
{
d->forceEnabled = value;
} }
PluginSpec *PluginSpec::read(const QString &filePath) // returns the plugins that it actually indirectly enabled
QVector<PluginSpec *> PluginSpec::enableDependenciesIndirectly(bool enableTestDependencies)
{ {
auto spec = new PluginSpec; if (!isEffectivelyEnabled()) // plugin not enabled, nothing to do
if (!spec->d->read(filePath)) { // not a Qt Creator plugin return {};
delete spec;
return nullptr;
}
return spec;
}
PluginSpec *PluginSpec::read(const QStaticPlugin &plugin) QVector<PluginSpec *> enabled;
{ for (auto it = d->dependencySpecs.cbegin(), end = d->dependencySpecs.cend(); it != end; ++it) {
auto spec = new PluginSpec; if (it.key().type != PluginDependency::Required
if (!spec->d->read(plugin)) { // not a Qt Creator plugin && (!enableTestDependencies || it.key().type != PluginDependency::Test))
delete spec; continue;
return nullptr;
PluginSpec *dependencySpec = it.value();
if (!dependencySpec->isEffectivelyEnabled()) {
dependencySpec->setEnabledIndirectly(true);
enabled << dependencySpec;
} }
return spec; }
return enabled;
} }
//==========PluginSpecPrivate================== //==========PluginSpecPrivate==================
@@ -610,112 +685,45 @@ namespace {
const char ARGUMENT_PARAMETER[] = "Parameter"; const char ARGUMENT_PARAMETER[] = "Parameter";
const char ARGUMENT_DESCRIPTION[] = "Description"; const char ARGUMENT_DESCRIPTION[] = "Description";
} }
/*!
\internal
*/
PluginSpecPrivate::PluginSpecPrivate(PluginSpec *spec)
: q(spec)
{}
void PluginSpecPrivate::reset()
{
name.clear();
version.clear();
compatVersion.clear();
vendor.clear();
copyright.clear();
license.clear();
description.clear();
longDescription.clear();
url.clear();
category.clear();
location.clear();
filePath.clear();
state = PluginSpec::Invalid;
hasError = false;
errorString.clear();
dependencies.clear();
metaData = QJsonObject();
loader.reset();
staticPlugin.reset();
}
/*! /*!
\internal \internal
Returns false if the file does not represent a Qt Creator plugin. Returns false if the file does not represent a Qt Creator plugin.
*/ */
bool PluginSpecPrivate::read(const QString &fileName) expected_str<PluginSpec *> PluginSpecImpl::read(const QString &fileName)
{ {
qCDebug(pluginLog) << "\nReading meta data of" << fileName; auto spec = new PluginSpecImpl;
reset();
QFileInfo fileInfo(fileName); QFileInfo fileInfo(fileName);
location = fileInfo.absolutePath(); spec->setLocation(fileInfo.absolutePath());
filePath = fileInfo.absoluteFilePath(); spec->setFilePath(fileInfo.absoluteFilePath());
loader.emplace(); spec->d->loader.emplace();
if (Utils::HostOsInfo::isMacHost()) if (Utils::HostOsInfo::isMacHost())
loader->setLoadHints(QLibrary::ExportExternalSymbolsHint); spec->d->loader->setLoadHints(QLibrary::ExportExternalSymbolsHint);
loader->setFileName(filePath);
if (loader->fileName().isEmpty()) {
qCDebug(pluginLog) << "Cannot open file";
return false;
}
if (!readMetaData(loader->metaData())) spec->d->loader->setFileName(fileInfo.absoluteFilePath());
return false; if (spec->d->loader->fileName().isEmpty())
return make_unexpected(::ExtensionSystem::Tr::tr("Cannot open file"));
state = PluginSpec::Read; expected_str<void> r = spec->readMetaData(spec->d->loader->metaData());
return true; if (!r)
return make_unexpected(r.error());
return spec;
} }
bool PluginSpecPrivate::read(const QStaticPlugin &plugin) expected_str<PluginSpec *> PluginSpecImpl::read(const QStaticPlugin &plugin)
{ {
auto spec = new PluginSpecImpl;
qCDebug(pluginLog) << "\nReading meta data of static plugin"; qCDebug(pluginLog) << "\nReading meta data of static plugin";
reset(); spec->d->staticPlugin = plugin;
staticPlugin = plugin; expected_str<void> r = spec->readMetaData(plugin.metaData());
if (!readMetaData(plugin.metaData())) if (!r)
return false; return make_unexpected(r.error());
state = PluginSpec::Read; return spec;
return true;
}
void PluginSpecPrivate::setEnabledBySettings(bool value)
{
enabledBySettings = value;
}
void PluginSpecPrivate::setEnabledByDefault(bool value)
{
enabledByDefault = value;
}
void PluginSpecPrivate::setForceEnabled(bool value)
{
forceEnabled = value;
if (value)
forceDisabled = false;
}
void PluginSpecPrivate::setForceDisabled(bool value)
{
if (value)
forceEnabled = false;
forceDisabled = value;
}
void PluginSpecPrivate::setSoftLoadable(bool value)
{
softLoadable = value;
}
/*!
\internal
*/
bool PluginSpecPrivate::reportError(const QString &err)
{
errorString = err;
hasError = true;
return true;
} }
static inline QString msgValueMissing(const char *key) static inline QString msgValueMissing(const char *key)
@@ -725,20 +733,17 @@ static inline QString msgValueMissing(const char *key)
static inline QString msgValueIsNotAString(const char *key) static inline QString msgValueIsNotAString(const char *key)
{ {
return Tr::tr("Value for key \"%1\" is not a string") return Tr::tr("Value for key \"%1\" is not a string").arg(QLatin1String(key));
.arg(QLatin1String(key));
} }
static inline QString msgValueIsNotABool(const char *key) static inline QString msgValueIsNotABool(const char *key)
{ {
return Tr::tr("Value for key \"%1\" is not a bool") return Tr::tr("Value for key \"%1\" is not a bool").arg(QLatin1String(key));
.arg(QLatin1String(key));
} }
static inline QString msgValueIsNotAObjectArray(const char *key) static inline QString msgValueIsNotAObjectArray(const char *key)
{ {
return Tr::tr("Value for key \"%1\" is not an array of objects") return Tr::tr("Value for key \"%1\" is not an array of objects").arg(QLatin1String(key));
.arg(QLatin1String(key));
} }
static inline QString msgValueIsNotAMultilineString(const char *key) static inline QString msgValueIsNotAMultilineString(const char *key)
@@ -749,34 +754,46 @@ static inline QString msgValueIsNotAMultilineString(const char *key)
static inline QString msgInvalidFormat(const char *key, const QString &content) static inline QString msgInvalidFormat(const char *key, const QString &content)
{ {
return Tr::tr("Value \"%2\" for key \"%1\" has invalid format") return Tr::tr("Value \"%2\" for key \"%1\" has invalid format").arg(QLatin1String(key), content);
.arg(QLatin1String(key), content); }
Utils::expected_str<void> PluginSpec::readMetaData(const QJsonObject &metaData)
{
return d->readMetaData(metaData);
}
Utils::expected_str<void> PluginSpec::reportError(const QString &error)
{
return d->reportError(error);
} }
/*! /*!
\internal \internal
*/ */
bool PluginSpecPrivate::readMetaData(const QJsonObject &pluginMetaData) expected_str<void> PluginSpecImpl::readMetaData(const QJsonObject &pluginMetaData)
{ {
qCDebug(pluginLog) << "MetaData:" << QJsonDocument(pluginMetaData).toJson(); qCDebug(pluginLog).noquote() << "MetaData:" << QJsonDocument(pluginMetaData).toJson();
QJsonValue value; QJsonValue value;
value = pluginMetaData.value(QLatin1String("IID")); value = pluginMetaData.value(QLatin1String("IID"));
if (!value.isString()) { if (!value.isString())
qCDebug(pluginLog) << "Not a plugin (no string IID found)"; return make_unexpected(::ExtensionSystem::Tr::tr("No IID found"));
return false;
} if (value.toString() != PluginManager::pluginIID())
if (value.toString() != PluginManager::pluginIID()) { return make_unexpected(::ExtensionSystem::Tr::tr("Expected IID \"%1\", but found \"%2\"")
qCDebug(pluginLog) << "Plugin ignored (IID does not match)"; .arg(PluginManager::pluginIID())
return false; .arg(value.toString()));
}
value = pluginMetaData.value(QLatin1String(PLUGIN_METADATA)); value = pluginMetaData.value(QLatin1String(PLUGIN_METADATA));
if (!value.isObject()) { if (!value.isObject())
return reportError(::ExtensionSystem::Tr::tr("Plugin meta data not found")); return reportError(::ExtensionSystem::Tr::tr("Plugin meta data not found"));
}
metaData = value.toObject();
value = metaData.value(QLatin1String(PLUGIN_NAME)); return PluginSpec::readMetaData(value.toObject());
}
Utils::expected_str<void> PluginSpecPrivate::readMetaData(const QJsonObject &data)
{
metaData = data;
QJsonValue value = metaData.value(QLatin1String(PLUGIN_NAME));
if (value.isUndefined()) if (value.isUndefined())
return reportError(msgValueMissing(PLUGIN_NAME)); return reportError(msgValueMissing(PLUGIN_NAME));
if (!value.isString()) if (!value.isString())
@@ -789,14 +806,14 @@ bool PluginSpecPrivate::readMetaData(const QJsonObject &pluginMetaData)
if (!value.isString()) if (!value.isString())
return reportError(msgValueIsNotAString(PLUGIN_VERSION)); return reportError(msgValueIsNotAString(PLUGIN_VERSION));
version = value.toString(); version = value.toString();
if (!isValidVersion(version)) if (!PluginSpec::isValidVersion(version))
return reportError(msgInvalidFormat(PLUGIN_VERSION, version)); return reportError(msgInvalidFormat(PLUGIN_VERSION, version));
value = metaData.value(QLatin1String(PLUGIN_COMPATVERSION)); value = metaData.value(QLatin1String(PLUGIN_COMPATVERSION));
if (!value.isUndefined() && !value.isString()) if (!value.isUndefined() && !value.isString())
return reportError(msgValueIsNotAString(PLUGIN_COMPATVERSION)); return reportError(msgValueIsNotAString(PLUGIN_COMPATVERSION));
compatVersion = value.toString(version); compatVersion = value.toString(version);
if (!value.isUndefined() && !isValidVersion(compatVersion)) if (!value.isUndefined() && !PluginSpec::isValidVersion(compatVersion))
return reportError(msgInvalidFormat(PLUGIN_COMPATVERSION, compatVersion)); return reportError(msgInvalidFormat(PLUGIN_COMPATVERSION, compatVersion));
value = metaData.value(QLatin1String(PLUGIN_REQUIRED)); value = metaData.value(QLatin1String(PLUGIN_REQUIRED));
@@ -844,11 +861,11 @@ bool PluginSpecPrivate::readMetaData(const QJsonObject &pluginMetaData)
copyright = value.toString(); copyright = value.toString();
value = metaData.value(QLatin1String(DESCRIPTION)); value = metaData.value(QLatin1String(DESCRIPTION));
if (!value.isUndefined() && !Utils::readMultiLineString(value, &description)) if (!value.isUndefined() && !readMultiLineString(value, &description))
return reportError(msgValueIsNotAString(DESCRIPTION)); return reportError(msgValueIsNotAString(DESCRIPTION));
value = metaData.value(QLatin1String(LONGDESCRIPTION)); value = metaData.value(QLatin1String(LONGDESCRIPTION));
if (!value.isUndefined() && !Utils::readMultiLineString(value, &longDescription)) if (!value.isUndefined() && !readMultiLineString(value, &longDescription))
return reportError(msgValueIsNotAString(LONGDESCRIPTION)); return reportError(msgValueIsNotAString(LONGDESCRIPTION));
value = metaData.value(QLatin1String(URL)); value = metaData.value(QLatin1String(URL));
@@ -862,9 +879,14 @@ bool PluginSpecPrivate::readMetaData(const QJsonObject &pluginMetaData)
category = value.toString(); category = value.toString();
value = metaData.value(QLatin1String(LICENSE)); value = metaData.value(QLatin1String(LICENSE));
if (!value.isUndefined() && !Utils::readMultiLineString(value, &license)) if (!value.isUndefined() && !readMultiLineString(value, &license))
return reportError(msgValueIsNotAMultilineString(LICENSE)); return reportError(msgValueIsNotAMultilineString(LICENSE));
value = metaData.value("Revision");
if (!value.isUndefined() && !value.isString())
return reportError(msgValueIsNotAString("Revision"));
revision = value.toString();
value = metaData.value(QLatin1String(PLATFORM)); value = metaData.value(QLatin1String(PLATFORM));
if (!value.isUndefined() && !value.isString()) if (!value.isUndefined() && !value.isString())
return reportError(msgValueIsNotAString(PLATFORM)); return reportError(msgValueIsNotAString(PLATFORM));
@@ -906,7 +928,7 @@ bool PluginSpecPrivate::readMetaData(const QJsonObject &pluginMetaData)
.arg(msgValueIsNotAString(DEPENDENCY_VERSION))); .arg(msgValueIsNotAString(DEPENDENCY_VERSION)));
} }
dep.version = value.toString(); dep.version = value.toString();
if (!isValidVersion(dep.version)) { if (!PluginSpec::isValidVersion(dep.version)) {
return reportError( return reportError(
::ExtensionSystem::Tr::tr("Dependency: %1") ::ExtensionSystem::Tr::tr("Dependency: %1")
.arg(msgInvalidFormat(DEPENDENCY_VERSION, dep.version))); .arg(msgInvalidFormat(DEPENDENCY_VERSION, dep.version)));
@@ -987,23 +1009,15 @@ bool PluginSpecPrivate::readMetaData(const QJsonObject &pluginMetaData)
} }
} }
return true; state = PluginSpecImpl::Read;
return {};
} }
/*! /*!
\internal \internal
*/ */
bool PluginSpecPrivate::provides(const QString &pluginName, const QString &pluginVersion) const static const QRegularExpression &versionRegExp()
{
if (QString::compare(pluginName, name, Qt::CaseInsensitive) != 0)
return false;
return (versionCompare(version, pluginVersion) >= 0) && (versionCompare(compatVersion, pluginVersion) <= 0);
}
/*!
\internal
*/
const QRegularExpression &PluginSpecPrivate::versionRegExp()
{ {
static const QRegularExpression reg("^([0-9]+)(?:[.]([0-9]+))?(?:[.]([0-9]+))?(?:_([0-9]+))?$"); static const QRegularExpression reg("^([0-9]+)(?:[.]([0-9]+))?(?:[.]([0-9]+))?(?:_([0-9]+))?$");
return reg; return reg;
@@ -1011,7 +1025,7 @@ const QRegularExpression &PluginSpecPrivate::versionRegExp()
/*! /*!
\internal \internal
*/ */
bool PluginSpecPrivate::isValidVersion(const QString &version) bool PluginSpec::isValidVersion(const QString &version)
{ {
return versionRegExp().match(version).hasMatch(); return versionRegExp().match(version).hasMatch();
} }
@@ -1019,7 +1033,7 @@ bool PluginSpecPrivate::isValidVersion(const QString &version)
/*! /*!
\internal \internal
*/ */
int PluginSpecPrivate::versionCompare(const QString &version1, const QString &version2) int PluginSpec::versionCompare(const QString &version1, const QString &version2)
{ {
const QRegularExpressionMatch match1 = versionRegExp().match(version1); const QRegularExpressionMatch match1 = versionRegExp().match(version1);
const QRegularExpressionMatch match2 = versionRegExp().match(version2); const QRegularExpressionMatch match2 = versionRegExp().match(version2);
@@ -1041,196 +1055,205 @@ int PluginSpecPrivate::versionCompare(const QString &version1, const QString &ve
/*! /*!
\internal \internal
*/ */
bool PluginSpecPrivate::resolveDependencies(const QVector<PluginSpec *> &specs) bool PluginSpec::resolveDependencies(const QVector<PluginSpec *> &specs)
{ {
if (hasError) if (hasError())
return false; return false;
if (state == PluginSpec::Resolved) if (state() > PluginSpecImpl::Resolved)
state = PluginSpec::Read; // Go back, so we just re-resolve the dependencies. return true; // We are resolved already.
if (state != PluginSpec::Read) { if (state() == PluginSpecImpl::Resolved)
errorString = ::ExtensionSystem::Tr::tr("Resolving dependencies failed because state != Read"); setState(PluginSpecImpl::Read); // Go back, so we just re-resolve the dependencies.
hasError = true; if (state() != PluginSpecImpl::Read) {
setError(::ExtensionSystem::Tr::tr("Resolving dependencies failed because state != Read"));
return false; return false;
} }
QHash<PluginDependency, PluginSpec *> resolvedDependencies; QHash<PluginDependency, PluginSpec *> resolvedDependencies;
for (const PluginDependency &dependency : std::as_const(dependencies)) { for (const PluginDependency &dependency : d->dependencies) {
PluginSpec * const found = Utils::findOrDefault(specs, [&dependency](PluginSpec *spec) { PluginSpec *const found = findOrDefault(specs, [&dependency](PluginSpec *spec) {
return spec->provides(dependency.name, dependency.version); return spec->provides(dependency.name, dependency.version);
}); });
if (!found) { if (!found) {
if (dependency.type == PluginDependency::Required) { if (dependency.type == PluginDependency::Required) {
hasError = true; const QString error = ::ExtensionSystem::Tr::tr(
if (!errorString.isEmpty()) "Could not resolve dependency '%1(%2)'")
errorString.append(QLatin1Char('\n')); .arg(dependency.name, dependency.version);
errorString.append(::ExtensionSystem::Tr::tr("Could not resolve dependency '%1(%2)'") if (hasError())
.arg(dependency.name, dependency.version)); setError(errorString() + '\n' + error);
else
setError(error);
} }
continue; continue;
} }
resolvedDependencies.insert(dependency, found); resolvedDependencies.insert(dependency, found);
} }
if (hasError) if (hasError())
return false; return false;
dependencySpecs = resolvedDependencies; d->dependencySpecs = resolvedDependencies;
state = PluginSpec::Resolved; d->state = PluginSpecImpl::Resolved;
return true; return true;
} }
// returns the plugins that it actually indirectly enabled PluginSpec::PluginSpec()
QVector<PluginSpec *> PluginSpecPrivate::enableDependenciesIndirectly(bool enableTestDependencies) : d(new PluginSpecPrivate())
{}
PluginSpec::~PluginSpec() = default;
void PluginSpec::setState(State state)
{ {
if (!q->isEffectivelyEnabled()) // plugin not enabled, nothing to do d->state = state;
return {}; }
QVector<PluginSpec *> enabled;
for (auto it = dependencySpecs.cbegin(), end = dependencySpecs.cend(); it != end; ++it) { void PluginSpec::setLocation(const QString &location)
if (it.key().type != PluginDependency::Required {
&& (!enableTestDependencies || it.key().type != PluginDependency::Test)) d->location = location;
continue; }
PluginSpec *dependencySpec = it.value();
if (!dependencySpec->isEffectivelyEnabled()) { void PluginSpec::setFilePath(const QString &filePath)
dependencySpec->d->enabledIndirectly = true; {
enabled << dependencySpec; d->filePath = filePath;
} }
}
return enabled; void PluginSpec::setError(const QString &errorString)
{
d->errorString = errorString;
} }
/*! /*!
\internal \internal
*/ */
bool PluginSpecPrivate::loadLibrary() bool PluginSpecImpl::loadLibrary()
{ {
if (hasError) if (hasError())
return false; return false;
if (state != PluginSpec::Resolved) {
if (state == PluginSpec::Loaded) if (state() != PluginSpecImpl::Resolved) {
if (state() == PluginSpecImpl::Loaded)
return true; return true;
errorString = setError(::ExtensionSystem::Tr::tr("Loading the library failed because state != Resolved"));
::ExtensionSystem::Tr::tr("Loading the library failed because state != Resolved");
hasError = true;
return false; return false;
} }
if (loader && !loader->load()) { if (d->loader && !d->loader->load()) {
hasError = true; setError(QDir::toNativeSeparators(filePath()) + QString::fromLatin1(": ")
errorString = QDir::toNativeSeparators(filePath) + QString::fromLatin1(": ") + d->loader->errorString());
+ loader->errorString();
return false; return false;
} }
auto *pluginObject = loader ? qobject_cast<IPlugin *>(loader->instance()) auto *pluginObject = d->loader ? qobject_cast<IPlugin *>(d->loader->instance())
: qobject_cast<IPlugin *>(staticPlugin->instance()); : qobject_cast<IPlugin *>(d->staticPlugin->instance());
if (!pluginObject) { if (!pluginObject) {
hasError = true; setError(::ExtensionSystem::Tr::tr("Plugin is not valid (does not derive from IPlugin)"));
errorString = if (d->loader)
::ExtensionSystem::Tr::tr("Plugin is not valid (does not derive from IPlugin)"); d->loader->unload();
if (loader)
loader->unload();
return false; return false;
} }
state = PluginSpec::Loaded; setState(PluginSpecImpl::Loaded);
plugin = pluginObject; d->plugin = pluginObject;
return true; return true;
} }
/*! /*!
\internal \internal
*/ */
bool PluginSpecPrivate::initializePlugin() bool PluginSpecImpl::initializePlugin()
{ {
if (hasError) if (hasError())
return false; return false;
if (state != PluginSpec::Loaded) {
if (state == PluginSpec::Initialized) if (state() != PluginSpecImpl::Loaded) {
if (state() == PluginSpecImpl::Initialized)
return true; return true;
errorString = ::ExtensionSystem::Tr::tr( setError(
"Initializing the plugin failed because state != Loaded"); ::ExtensionSystem::Tr::tr("Initializing the plugin failed because state != Loaded"));
hasError = true;
return false; return false;
} }
if (!plugin) { if (!d->plugin) {
errorString = ::ExtensionSystem::Tr::tr( setError(
"Internal error: have no plugin instance to initialize"); ::ExtensionSystem::Tr::tr("Internal error: have no plugin instance to initialize"));
hasError = true;
return false; return false;
} }
QString err; QString err;
if (!plugin->initialize(arguments, &err)) { if (!d->plugin->initialize(arguments(), &err)) {
errorString = ::ExtensionSystem::Tr::tr("Plugin initialization failed: %1").arg(err); setError(::ExtensionSystem::Tr::tr("Plugin initialization failed: %1").arg(err));
hasError = true;
return false; return false;
} }
state = PluginSpec::Initialized; setState(PluginSpecImpl::Initialized);
return true; return true;
} }
/*! /*!
\internal \internal
*/ */
bool PluginSpecPrivate::initializeExtensions() bool PluginSpecImpl::initializeExtensions()
{ {
if (hasError) if (hasError())
return false; return false;
if (state != PluginSpec::Initialized) {
if (state == PluginSpec::Running) if (state() != PluginSpecImpl::Initialized) {
if (state() == PluginSpecImpl::Running)
return true; return true;
errorString = ::ExtensionSystem::Tr::tr( setError(::ExtensionSystem::Tr::tr(
"Cannot perform extensionsInitialized because state != Initialized"); "Cannot perform extensionsInitialized because state != Initialized"));
hasError = true;
return false; return false;
} }
if (!plugin) { if (!d->plugin) {
errorString = ::ExtensionSystem::Tr::tr( setError(::ExtensionSystem::Tr::tr(
"Internal error: have no plugin instance to perform extensionsInitialized"); "Internal error: have no plugin instance to perform extensionsInitialized"));
hasError = true;
return false; return false;
} }
plugin->extensionsInitialized(); d->plugin->extensionsInitialized();
state = PluginSpec::Running; setState(PluginSpecImpl::Running);
return true; return true;
} }
/*! /*!
\internal \internal
*/ */
bool PluginSpecPrivate::delayedInitialize() bool PluginSpecImpl::delayedInitialize()
{ {
if (hasError) if (hasError())
return true;
if (state() != PluginSpecImpl::Running)
return false; return false;
if (state != PluginSpec::Running) if (!d->plugin) {
return false; setError(::ExtensionSystem::Tr::tr(
if (!plugin) { "Internal error: have no plugin instance to perform delayedInitialize"));
errorString = ::ExtensionSystem::Tr::tr(
"Internal error: have no plugin instance to perform delayedInitialize");
hasError = true;
return false; return false;
} }
const bool res = plugin->delayedInitialize(); const bool res = d->plugin->delayedInitialize();
return res; return res;
} }
/*! /*!
\internal \internal
*/ */
IPlugin::ShutdownFlag PluginSpecPrivate::stop() IPlugin::ShutdownFlag PluginSpecImpl::stop()
{ {
if (!plugin) if (hasError())
return IPlugin::ShutdownFlag::SynchronousShutdown;
if (!d->plugin)
return IPlugin::SynchronousShutdown; return IPlugin::SynchronousShutdown;
state = PluginSpec::Stopped; setState(PluginSpecImpl::Stopped);
return plugin->aboutToShutdown(); return d->plugin->aboutToShutdown();
} }
/*! /*!
\internal \internal
*/ */
void PluginSpecPrivate::kill() void PluginSpecImpl::kill()
{ {
if (!plugin) if (hasError())
return; return;
delete plugin;
plugin = nullptr;
state = PluginSpec::Deleted;
}
} // ExtensionSystem if (!d->plugin)
return;
delete d->plugin;
d->plugin = nullptr;
setState(PluginSpecImpl::Deleted);
}
} // namespace ExtensionSystem

View File

@@ -5,6 +5,10 @@
#include "extensionsystem_global.h" #include "extensionsystem_global.h"
#include "iplugin.h"
#include <utils/expected.h>
#include <QHash> #include <QHash>
#include <QStaticPlugin> #include <QStaticPlugin>
#include <QString> #include <QString>
@@ -14,17 +18,19 @@ QT_BEGIN_NAMESPACE
class QRegularExpression; class QRegularExpression;
QT_END_NAMESPACE QT_END_NAMESPACE
class tst_PluginSpec;
namespace ExtensionSystem { namespace ExtensionSystem {
namespace Internal { namespace Internal {
class OptionsParser; class OptionsParser;
class PluginSpecPrivate; class PluginSpecImplPrivate;
class PluginManagerPrivate; class PluginManagerPrivate;
class PluginSpecPrivate;
} // Internal } // Internal
class IPlugin;
class PluginView; class PluginView;
struct EXTENSIONSYSTEM_EXPORT PluginDependency struct EXTENSIONSYSTEM_EXPORT PluginDependency
@@ -37,8 +43,6 @@ struct EXTENSIONSYSTEM_EXPORT PluginDependency
PluginDependency() : type(Required) {} PluginDependency() : type(Required) {}
friend size_t qHash(const PluginDependency &value);
QString name; QString name;
QString version; QString version;
Type type; Type type;
@@ -46,6 +50,8 @@ struct EXTENSIONSYSTEM_EXPORT PluginDependency
QString toString() const; QString toString() const;
}; };
size_t EXTENSIONSYSTEM_EXPORT qHash(const PluginDependency &value);
struct EXTENSIONSYSTEM_EXPORT PluginArgumentDescription struct EXTENSIONSYSTEM_EXPORT PluginArgumentDescription
{ {
QString name; QString name;
@@ -73,77 +79,126 @@ struct EXTENSIONSYSTEM_EXPORT PerformanceData
class EXTENSIONSYSTEM_EXPORT PluginSpec class EXTENSIONSYSTEM_EXPORT PluginSpec
{ {
friend class ::tst_PluginSpec;
friend class Internal::PluginManagerPrivate;
friend class Internal::OptionsParser;
public: public:
enum State { Invalid, Read, Resolved, Loaded, Initialized, Running, Stopped, Deleted}; PluginSpec();
virtual ~PluginSpec();
~PluginSpec();
// information from the xml file, valid after 'Read' state is reached
QString name() const;
QString version() const;
QString compatVersion() const;
QString vendor() const;
QString copyright() const;
QString license() const;
QString description() const;
QString longDescription() const;
QString url() const;
QString category() const;
QString revision() const;
QRegularExpression platformSpecification() const;
bool isAvailableForHostPlatform() const;
bool isRequired() const;
bool isExperimental() const;
bool isDeprecated() const;
bool isEnabledByDefault() const;
bool isEnabledBySettings() const;
bool isEffectivelyEnabled() const;
bool isEnabledIndirectly() const;
bool isForceEnabled() const;
bool isForceDisabled() const;
bool isSoftLoadable() const;
QVector<PluginDependency> dependencies() const;
QJsonObject metaData() const;
const PerformanceData &performanceData() const;
using PluginArgumentDescriptions = QVector<PluginArgumentDescription>; using PluginArgumentDescriptions = QVector<PluginArgumentDescription>;
PluginArgumentDescriptions argumentDescriptions() const; enum State { Invalid, Read, Resolved, Loaded, Initialized, Running, Stopped, Deleted};
// other information, valid after 'Read' state is reached // information read from the plugin, valid after 'Read' state is reached
QString location() const; virtual QString name() const;
QString filePath() const; virtual QString version() const;
virtual QString compatVersion() const;
virtual QString vendor() const;
virtual QString copyright() const;
virtual QString license() const;
virtual QString description() const;
virtual QString longDescription() const;
virtual QString url() const;
virtual QString category() const;
virtual QString revision() const;
virtual QRegularExpression platformSpecification() const;
QStringList arguments() const; virtual bool isAvailableForHostPlatform() const;
void setArguments(const QStringList &arguments); virtual bool isRequired() const;
void addArgument(const QString &argument); virtual bool isExperimental() const;
virtual bool isDeprecated() const;
virtual bool isEnabledByDefault() const;
virtual bool isEnabledBySettings() const;
virtual bool isEffectivelyEnabled() const;
virtual bool isEnabledIndirectly() const;
virtual bool isForceEnabled() const;
virtual bool isForceDisabled() const;
virtual bool isSoftLoadable() const;
bool provides(const QString &pluginName, const QString &version) const; virtual QVector<PluginDependency> dependencies() const;
virtual QJsonObject metaData() const;
virtual PerformanceData &performanceData() const;
virtual PluginArgumentDescriptions argumentDescriptions() const;
virtual QString location() const;
virtual QString filePath() const;
virtual QStringList arguments() const;
virtual void setArguments(const QStringList &arguments);
virtual void addArgument(const QString &argument);
virtual QHash<PluginDependency, PluginSpec *> dependencySpecs() const;
// dependency specs, valid after 'Resolved' state is reached virtual bool provides(const QString &pluginName, const QString &pluginVersion) const;
QHash<PluginDependency, PluginSpec *> dependencySpecs() const; virtual bool requiresAny(const QSet<PluginSpec *> &plugins) const;
bool requiresAny(const QSet<PluginSpec *> &plugins) const; virtual QVector<PluginSpec *> enableDependenciesIndirectly(bool enableTestDependencies);
virtual bool resolveDependencies(const QVector<PluginSpec *> &pluginSpecs);
// linked plugin instance, valid after 'Loaded' state is reached virtual IPlugin *plugin() const = 0;
IPlugin *plugin() const; virtual State state() const;
virtual bool hasError() const;
virtual QString errorString() const;
// state static bool isValidVersion(const QString &version);
State state() const; static int versionCompare(const QString &version1, const QString &version2);
bool hasError() const;
QString errorString() const;
void setEnabledBySettings(bool value); virtual void setEnabledBySettings(bool value);
static PluginSpec *read(const QString &filePath); protected:
static PluginSpec *read(const QStaticPlugin &plugin); virtual void setEnabledByDefault(bool value);
virtual void setEnabledIndirectly(bool value);
virtual void setForceDisabled(bool value);
virtual void setForceEnabled(bool value);
virtual bool loadLibrary() = 0;
virtual bool initializePlugin() = 0;
virtual bool initializeExtensions() = 0;
virtual bool delayedInitialize() = 0;
virtual IPlugin::ShutdownFlag stop() = 0;
virtual void kill() = 0;
virtual void setError(const QString &errorString);
protected:
virtual void setState(State state);
virtual void setLocation(const QString &location);
virtual void setFilePath(const QString &filePath);
virtual Utils::expected_str<void> readMetaData(const QJsonObject &metaData);
Utils::expected_str<void> reportError(const QString &error);
private: private:
PluginSpec(); std::unique_ptr<Internal::PluginSpecPrivate> d;
};
Internal::PluginSpecPrivate *d; class EXTENSIONSYSTEM_TEST_EXPORT PluginSpecImpl : public PluginSpec
{
public:
~PluginSpecImpl() override;
// linked plugin instance, valid after 'Loaded' state is reached
IPlugin *plugin() const override;
bool loadLibrary() override;
bool initializePlugin() override;
bool initializeExtensions() override;
bool delayedInitialize() override;
IPlugin::ShutdownFlag stop() override;
void kill() override;
static Utils::expected_str<PluginSpec *> read(const QString &filePath);
static Utils::expected_str<PluginSpec *> read(const QStaticPlugin &plugin);
Utils::expected_str<void> readMetaData(const QJsonObject &pluginMetaData) override;
protected:
PluginSpecImpl();
private:
std::unique_ptr<Internal::PluginSpecImplPrivate> d;
friend class PluginView; friend class PluginView;
friend class Internal::OptionsParser; friend class Internal::OptionsParser;
friend class Internal::PluginManagerPrivate; friend class Internal::PluginManagerPrivate;
friend class Internal::PluginSpecPrivate; friend class Internal::PluginSpecImplPrivate;
friend class ::tst_PluginSpec;
}; };
} // namespace ExtensionSystem } // namespace ExtensionSystem

View File

@@ -1,107 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "pluginspec.h"
#include "iplugin.h"
#include <QJsonObject>
#include <QObject>
#include <QPluginLoader>
#include <QRegularExpression>
#include <QStringList>
#include <QVector>
#include <QXmlStreamReader>
#include <optional>
namespace ExtensionSystem {
class IPlugin;
namespace Internal {
class EXTENSIONSYSTEM_TEST_EXPORT PluginSpecPrivate : public QObject
{
Q_OBJECT
public:
PluginSpecPrivate(PluginSpec *spec);
void reset();
bool read(const QString &fileName);
bool read(const QStaticPlugin &plugin);
bool provides(const QString &pluginName, const QString &version) const;
bool resolveDependencies(const QVector<PluginSpec *> &specs);
bool loadLibrary();
bool initializePlugin();
bool initializeExtensions();
bool delayedInitialize();
IPlugin::ShutdownFlag stop();
void kill();
void setEnabledBySettings(bool value);
void setEnabledByDefault(bool value);
void setForceEnabled(bool value);
void setForceDisabled(bool value);
void setSoftLoadable(bool value);
std::optional<QPluginLoader> loader;
std::optional<QStaticPlugin> staticPlugin;
QString name;
QString version;
QString compatVersion;
bool required = false;
bool experimental = false;
bool enabledByDefault = true;
bool deprecated = false;
QString vendor;
QString copyright;
QString license;
QString description;
QString longDescription;
QString url;
QString category;
QRegularExpression platformSpecification;
QVector<PluginDependency> dependencies;
QJsonObject metaData;
bool enabledBySettings = true;
bool enabledIndirectly = false;
bool forceEnabled = false;
bool forceDisabled = false;
bool softLoadable = false;
QString location;
QString filePath;
QStringList arguments;
QHash<PluginDependency, PluginSpec *> dependencySpecs;
PluginSpec::PluginArgumentDescriptions argumentDescriptions;
IPlugin *plugin = nullptr;
QList<TestCreator> registeredPluginTests;
PluginSpec::State state = PluginSpec::Invalid;
bool hasError = false;
QString errorString;
PerformanceData performanceData;
static bool isValidVersion(const QString &version);
static int versionCompare(const QString &version1, const QString &version2);
QVector<PluginSpec *> enableDependenciesIndirectly(bool enableTestDependencies = false);
bool readMetaData(const QJsonObject &pluginMetaData);
private:
PluginSpec *q;
bool reportError(const QString &err);
static const QRegularExpression &versionRegExp();
};
} // namespace Internal
} // namespace ExtensionSystem

View File

@@ -5,7 +5,7 @@
#include "extensionsystemtr.h" #include "extensionsystemtr.h"
#include "pluginmanager.h" #include "pluginmanager.h"
#include "pluginspec_p.h" #include "pluginspec.h"
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/categorysortfiltermodel.h> #include <utils/categorysortfiltermodel.h>
@@ -416,8 +416,8 @@ bool PluginView::setPluginsEnabled(const QSet<PluginSpec *> &plugins, bool enabl
}); });
QTC_ASSERT(item, continue); QTC_ASSERT(item, continue);
if (m_affectedPlugins.find(spec) == m_affectedPlugins.end()) if (m_affectedPlugins.find(spec) == m_affectedPlugins.end())
m_affectedPlugins[spec] = spec->d->enabledBySettings; m_affectedPlugins[spec] = spec->isEnabledBySettings();
spec->d->setEnabledBySettings(enable); spec->setEnabledBySettings(enable);
item->updateColumn(LoadedColumn); item->updateColumn(LoadedColumn);
item->parent()->updateColumn(LoadedColumn); item->parent()->updateColumn(LoadedColumn);
} }
@@ -428,7 +428,7 @@ bool PluginView::setPluginsEnabled(const QSet<PluginSpec *> &plugins, bool enabl
void PluginView::cancelChanges() void PluginView::cancelChanges()
{ {
for (auto element : m_affectedPlugins) for (auto element : m_affectedPlugins)
element.first->d->setEnabledBySettings(element.second); element.first->setEnabledBySettings(element.second);
} }
} // namespace ExtensionSystem } // namespace ExtensionSystem

View File

@@ -154,11 +154,11 @@ void checkContents(QPromise<ArchiveIssue> &promise, const FilePath &tempDir)
if (promise.isCanceled()) if (promise.isCanceled())
return; return;
it.next(); it.next();
PluginSpec *spec = PluginSpec::read(it.filePath()); expected_str<PluginSpec *> spec = PluginSpecImpl::read(it.filePath());
if (spec) { if (spec) {
// Is a Qt Creator plugin. Let's see if we find a Core dependency and check the // Is a Qt Creator plugin. Let's see if we find a Core dependency and check the
// version // version
const QVector<PluginDependency> dependencies = spec->dependencies(); const QVector<PluginDependency> dependencies = (*spec)->dependencies();
const auto found = std::find_if(dependencies.constBegin(), dependencies.constEnd(), const auto found = std::find_if(dependencies.constBegin(), dependencies.constEnd(),
[coreplugin](const PluginDependency &d) { return d.name == coreplugin->name(); }); [coreplugin](const PluginDependency &d) { return d.name == coreplugin->name(); });
if (found == dependencies.constEnd()) if (found == dependencies.constEnd())

View File

@@ -11,6 +11,7 @@
"end of terms" "end of terms"
], ],
"Description" : [ "Description" : [
"This spec is broken because no name is set.",
"This plugin is just a test.", "This plugin is just a test.",
" it demonstrates the great use of the plugin spec." " it demonstrates the great use of the plugin spec."
], ],

View File

@@ -11,6 +11,7 @@
"end of terms" "end of terms"
], ],
"Description" : [ "Description" : [
"This spec is wrong because no version is set.",
"This plugin is just a test.", "This plugin is just a test.",
" it demonstrates the great use of the plugin spec." " it demonstrates the great use of the plugin spec."
], ],

View File

@@ -12,6 +12,7 @@
"end of terms" "end of terms"
], ],
"Description" : [ "Description" : [
"This spec is wrong because the first dependency has no name.",
"This plugin is just a test.", "This plugin is just a test.",
" it demonstrates the great use of the plugin spec." " it demonstrates the great use of the plugin spec."
], ],

View File

@@ -12,6 +12,7 @@
"end of terms" "end of terms"
], ],
"Description" : [ "Description" : [
"This spec is wrong because the first dependencies version is invalid.",
"This plugin is just a test.", "This plugin is just a test.",
" it demonstrates the great use of the plugin spec." " it demonstrates the great use of the plugin spec."
], ],

View File

@@ -1,10 +1,9 @@
// Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <extensionsystem/pluginspec.h>
#include <extensionsystem/pluginspec_p.h>
#include <extensionsystem/pluginmanager_p.h>
#include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginmanager.h>
#include <extensionsystem/pluginmanager_p.h>
#include <extensionsystem/pluginspec.h>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
@@ -85,109 +84,110 @@ void tst_PluginSpec::cleanupTestCase()
void tst_PluginSpec::read() void tst_PluginSpec::read()
{ {
Internal::PluginSpecPrivate spec(0); PluginSpecImpl spec;
QCOMPARE(spec.state, PluginSpec::Invalid); QCOMPARE(spec.state(), PluginSpec::Invalid);
QVERIFY(spec.readMetaData(metaData("testspecs/spec1.json"))); QVERIFY(spec.readMetaData(metaData("testspecs/spec1.json")));
QVERIFY(!spec.hasError); QVERIFY(!spec.hasError());
QVERIFY(spec.errorString.isEmpty()); QVERIFY(spec.errorString().isEmpty());
QCOMPARE(spec.name, QString("test")); QCOMPARE(spec.name(), QString("test"));
QCOMPARE(spec.version, QString("1.0.1")); QCOMPARE(spec.version(), QString("1.0.1"));
QCOMPARE(spec.compatVersion, QString("1.0.0")); QCOMPARE(spec.compatVersion(), QString("1.0.0"));
QCOMPARE(spec.required, false); QCOMPARE(spec.isRequired(), false);
QCOMPARE(spec.experimental, false); QCOMPARE(spec.isExperimental(), false);
QCOMPARE(spec.enabledBySettings, true); QCOMPARE(spec.isEnabledBySettings(), true);
QCOMPARE(spec.vendor, QString("The Qt Company Ltd")); QCOMPARE(spec.vendor(), QString("The Qt Company Ltd"));
QCOMPARE(spec.copyright, QString("(C) 2015 The Qt Company Ltd")); QCOMPARE(spec.copyright(), QString("(C) 2015 The Qt Company Ltd"));
QCOMPARE(spec.license, QString("This is a default license bla\nblubbblubb\nend of terms")); QCOMPARE(spec.license(), QString("This is a default license bla\nblubbblubb\nend of terms"));
QCOMPARE(spec.description, QString("This plugin is just a test.")); QCOMPARE(spec.description(), QString("This plugin is just a test."));
QCOMPARE( QCOMPARE(
spec.longDescription, spec.longDescription(),
QString( QString(
"This plugin is just a test.\n it demonstrates the great use of the plugin spec.")); "This plugin is just a test.\n it demonstrates the great use of the plugin spec."));
QCOMPARE(spec.url, QString("http://www.qt.io")); QCOMPARE(spec.url(), QString("http://www.qt.io"));
PluginDependency dep1; PluginDependency dep1;
dep1.name = QString("SomeOtherPlugin"); dep1.name = QString("SomeOtherPlugin");
dep1.version = QString("2.3.0_2"); dep1.version = QString("2.3.0_2");
PluginDependency dep2; PluginDependency dep2;
dep2.name = QString("EvenOther"); dep2.name = QString("EvenOther");
dep2.version = QString("1.0.0"); dep2.version = QString("1.0.0");
QCOMPARE(spec.dependencies, QVector<PluginDependency>() << dep1 << dep2); QCOMPARE(spec.dependencies(), QVector<PluginDependency>() << dep1 << dep2);
// test missing compatVersion behavior // test missing compatVersion behavior
// and 'required' attribute // and 'required' attribute
QVERIFY(spec.readMetaData(metaData("testspecs/spec2.json"))); QVERIFY(spec.readMetaData(metaData("testspecs/spec2.json")));
QCOMPARE(spec.version, QString("3.1.4_10")); QCOMPARE(spec.version(), QString("3.1.4_10"));
QCOMPARE(spec.compatVersion, QString("3.1.4_10")); QCOMPARE(spec.compatVersion(), QString("3.1.4_10"));
QCOMPARE(spec.required, true); QCOMPARE(spec.isRequired(), true);
} }
void tst_PluginSpec::readError() void tst_PluginSpec::readError()
{ {
Internal::PluginSpecPrivate spec(0); PluginSpecImpl spec;
QCOMPARE(spec.state, PluginSpec::Invalid); QCOMPARE(spec.state(), PluginSpec::Invalid);
QVERIFY(!spec.readMetaData(metaData("non-existing-file.json"))); QVERIFY(!spec.readMetaData(metaData("non-existing-file.json")));
QCOMPARE(spec.state, PluginSpec::Invalid); QCOMPARE(spec.state(), PluginSpec::Invalid);
QVERIFY(!spec.hasError); QVERIFY(!spec.hasError());
QVERIFY(spec.errorString.isEmpty()); QVERIFY(spec.errorString().isEmpty());
QVERIFY(spec.readMetaData(metaData("testspecs/spec_wrong2.json"))); QVERIFY(spec.readMetaData(metaData("testspecs/spec_wrong2.json")));
QCOMPARE(spec.state, PluginSpec::Invalid); QCOMPARE(spec.state(), PluginSpec::Invalid);
QVERIFY(spec.hasError); QVERIFY(spec.hasError());
QVERIFY(!spec.errorString.isEmpty()); QVERIFY(!spec.errorString().isEmpty());
QVERIFY(spec.readMetaData(metaData("testspecs/spec_wrong3.json"))); QVERIFY(spec.readMetaData(metaData("testspecs/spec_wrong3.json")));
QCOMPARE(spec.state, PluginSpec::Invalid); QCOMPARE(spec.state(), PluginSpec::Invalid);
QVERIFY(spec.hasError); QVERIFY(spec.hasError());
QVERIFY(!spec.errorString.isEmpty()); QVERIFY(!spec.errorString().isEmpty());
QVERIFY(spec.readMetaData(metaData("testspecs/spec_wrong4.json"))); QVERIFY(spec.readMetaData(metaData("testspecs/spec_wrong4.json")));
QCOMPARE(spec.state, PluginSpec::Invalid); QCOMPARE(spec.state(), PluginSpec::Invalid);
QVERIFY(spec.hasError); QVERIFY(spec.hasError());
QVERIFY(!spec.errorString.isEmpty()); QVERIFY(!spec.errorString().isEmpty());
QVERIFY(spec.readMetaData(metaData("testspecs/spec_wrong5.json"))); QVERIFY(spec.readMetaData(metaData("testspecs/spec_wrong5.json")));
QCOMPARE(spec.state, PluginSpec::Invalid); QCOMPARE(spec.state(), PluginSpec::Invalid);
QVERIFY(spec.hasError); QVERIFY(spec.hasError());
QVERIFY(!spec.errorString.isEmpty()); QVERIFY(!spec.errorString().isEmpty());
} }
void tst_PluginSpec::isValidVersion() void tst_PluginSpec::isValidVersion()
{ {
QVERIFY(Internal::PluginSpecPrivate::isValidVersion("2")); QVERIFY(PluginSpec::isValidVersion("2"));
QVERIFY(Internal::PluginSpecPrivate::isValidVersion("53")); QVERIFY(PluginSpec::isValidVersion("53"));
QVERIFY(Internal::PluginSpecPrivate::isValidVersion("52_1")); QVERIFY(PluginSpec::isValidVersion("52_1"));
QVERIFY(Internal::PluginSpecPrivate::isValidVersion("3.12")); QVERIFY(PluginSpec::isValidVersion("3.12"));
QVERIFY(Internal::PluginSpecPrivate::isValidVersion("31.1_12")); QVERIFY(PluginSpec::isValidVersion("31.1_12"));
QVERIFY(Internal::PluginSpecPrivate::isValidVersion("31.1.0")); QVERIFY(PluginSpec::isValidVersion("31.1.0"));
QVERIFY(Internal::PluginSpecPrivate::isValidVersion("1.0.2_1")); QVERIFY(PluginSpec::isValidVersion("1.0.2_1"));
QVERIFY(!Internal::PluginSpecPrivate::isValidVersion("")); QVERIFY(!PluginSpec::isValidVersion(""));
QVERIFY(!Internal::PluginSpecPrivate::isValidVersion("1..0")); QVERIFY(!PluginSpec::isValidVersion("1..0"));
QVERIFY(!Internal::PluginSpecPrivate::isValidVersion("1.0_")); QVERIFY(!PluginSpec::isValidVersion("1.0_"));
QVERIFY(!Internal::PluginSpecPrivate::isValidVersion("1.0.0.0")); QVERIFY(!PluginSpec::isValidVersion("1.0.0.0"));
} }
void tst_PluginSpec::versionCompare() void tst_PluginSpec::versionCompare()
{ {
QVERIFY(Internal::PluginSpecPrivate::versionCompare("3", "3") == 0); QVERIFY(PluginSpec::versionCompare("3", "3") == 0);
QVERIFY(Internal::PluginSpecPrivate::versionCompare("3.0.0", "3") == 0); QVERIFY(PluginSpec::versionCompare("3.0.0", "3") == 0);
QVERIFY(Internal::PluginSpecPrivate::versionCompare("3.0", "3") == 0); QVERIFY(PluginSpec::versionCompare("3.0", "3") == 0);
QVERIFY(Internal::PluginSpecPrivate::versionCompare("3.0.0_1", "3_1") == 0); QVERIFY(PluginSpec::versionCompare("3.0.0_1", "3_1") == 0);
QVERIFY(Internal::PluginSpecPrivate::versionCompare("3.0_21", "3_21") == 0); QVERIFY(PluginSpec::versionCompare("3.0_21", "3_21") == 0);
QVERIFY(Internal::PluginSpecPrivate::versionCompare("3", "1") > 0); QVERIFY(PluginSpec::versionCompare("3", "1") > 0);
QVERIFY(Internal::PluginSpecPrivate::versionCompare("3", "1.0_12") > 0); QVERIFY(PluginSpec::versionCompare("3", "1.0_12") > 0);
QVERIFY(Internal::PluginSpecPrivate::versionCompare("3_1", "3") > 0); QVERIFY(PluginSpec::versionCompare("3_1", "3") > 0);
QVERIFY(Internal::PluginSpecPrivate::versionCompare("3.1.0_23", "3.1") > 0); QVERIFY(PluginSpec::versionCompare("3.1.0_23", "3.1") > 0);
QVERIFY(Internal::PluginSpecPrivate::versionCompare("3.1_23", "3.1_12") > 0); QVERIFY(PluginSpec::versionCompare("3.1_23", "3.1_12") > 0);
QVERIFY(Internal::PluginSpecPrivate::versionCompare("1", "3") < 0); QVERIFY(PluginSpec::versionCompare("1", "3") < 0);
QVERIFY(Internal::PluginSpecPrivate::versionCompare("1.0_12", "3") < 0); QVERIFY(PluginSpec::versionCompare("1.0_12", "3") < 0);
QVERIFY(Internal::PluginSpecPrivate::versionCompare("3", "3_1") < 0); QVERIFY(PluginSpec::versionCompare("3", "3_1") < 0);
QVERIFY(Internal::PluginSpecPrivate::versionCompare("3.1", "3.1.0_23") < 0); QVERIFY(PluginSpec::versionCompare("3.1", "3.1.0_23") < 0);
QVERIFY(Internal::PluginSpecPrivate::versionCompare("3.1_12", "3.1_23") < 0); QVERIFY(PluginSpec::versionCompare("3.1_12", "3.1_23") < 0);
} }
void tst_PluginSpec::provides() void tst_PluginSpec::provides()
{ {
Internal::PluginSpecPrivate spec(0); PluginSpecImpl spec;
QVERIFY(spec.readMetaData(metaData("testspecs/simplespec.json"))); QVERIFY(spec.readMetaData(metaData("testspecs/simplespec.json")));
QVERIFY(!spec.provides("SomeOtherPlugin", "2.2.3_9")); QVERIFY(!spec.provides("SomeOtherPlugin", "2.2.3_9"));
QVERIFY(!spec.provides("MyPlugin", "2.2.3_10")); QVERIFY(!spec.provides("MyPlugin", "2.2.3_10"));
QVERIFY(!spec.provides("MyPlugin", "2.2.4")); QVERIFY(!spec.provides("MyPlugin", "2.2.4"));
@@ -211,112 +211,132 @@ void tst_PluginSpec::provides()
void tst_PluginSpec::experimental() void tst_PluginSpec::experimental()
{ {
Internal::PluginSpecPrivate spec(0); PluginSpecImpl spec;
QVERIFY(spec.readMetaData(metaData("testspecs/simplespec_experimental.json"))); QVERIFY(spec.readMetaData(metaData("testspecs/simplespec_experimental.json")));
QCOMPARE(spec.experimental, true);
QCOMPARE(spec.enabledBySettings, false); QCOMPARE(spec.isExperimental(), true);
QCOMPARE(spec.isEnabledBySettings(), false);
} }
void tst_PluginSpec::locationAndPath() void tst_PluginSpec::locationAndPath()
{ {
Internal::PluginSpecPrivate spec(0); Utils::expected_str<PluginSpec *> ps = PluginSpecImpl::read(
QVERIFY(spec.read(QLatin1String(PLUGIN_DIR) + QLatin1String("/testplugin/") + libraryName(QLatin1String("test")))); QLatin1String(PLUGIN_DIR) + QLatin1String("/testplugin/")
QCOMPARE(spec.location, QString(QLatin1String(PLUGIN_DIR) + QLatin1String("/testplugin"))); + libraryName(QLatin1String("test")));
QCOMPARE(spec.filePath, QString(QLatin1String(PLUGIN_DIR) + QLatin1String("/testplugin/") + libraryName(QLatin1String("test")))); QVERIFY(ps);
PluginSpecImpl *spec = static_cast<PluginSpecImpl *>(ps.value());
QCOMPARE(spec->location(), QString(QLatin1String(PLUGIN_DIR) + QLatin1String("/testplugin")));
QCOMPARE(spec->filePath(),
QString(QLatin1String(PLUGIN_DIR) + QLatin1String("/testplugin/")
+ libraryName(QLatin1String("test"))));
} }
void tst_PluginSpec::resolveDependencies() void tst_PluginSpec::resolveDependencies()
{ {
QVector<PluginSpec *> specs; QVector<PluginSpec *> specs;
PluginSpec *spec1 = Internal::PluginManagerPrivate::createSpec(); PluginSpec *spec1 = new PluginSpecImpl();
specs.append(spec1); specs.append(spec1);
Internal::PluginSpecPrivate *spec1Priv = Internal::PluginManagerPrivate::privateSpec(spec1); spec1->readMetaData(metaData("testdependencies/spec1.json"));
spec1Priv->readMetaData(metaData("testdependencies/spec1.json")); spec1->setState(PluginSpec::Read); // fake read state for plugin resolving
spec1Priv->state = PluginSpec::Read; // fake read state for plugin resolving
PluginSpec *spec2 = Internal::PluginManagerPrivate::createSpec(); PluginSpec *spec2 = new PluginSpecImpl();
specs.append(spec2); specs.append(spec2);
Internal::PluginSpecPrivate *spec2Priv = Internal::PluginManagerPrivate::privateSpec(spec2); spec2->readMetaData(metaData("testdependencies/spec2.json"));
spec2Priv->readMetaData(metaData("testdependencies/spec2.json")); spec2->setState(PluginSpec::Read); // fake read state for plugin resolving
spec2Priv->state = PluginSpec::Read; // fake read state for plugin resolving
PluginSpec *spec3 = Internal::PluginManagerPrivate::createSpec(); PluginSpec *spec3 = new PluginSpecImpl();
specs.append(spec3); specs.append(spec3);
Internal::PluginSpecPrivate *spec3Priv = Internal::PluginManagerPrivate::privateSpec(spec3); spec3->readMetaData(metaData("testdependencies/spec3.json"));
spec3Priv->readMetaData(metaData("testdependencies/spec3.json")); spec3->setState(PluginSpec::Read); // fake read state for plugin resolving
spec3Priv->state = PluginSpec::Read; // fake read state for plugin resolving
PluginSpec *spec4 = Internal::PluginManagerPrivate::createSpec(); PluginSpec *spec4 = new PluginSpecImpl();
specs.append(spec4); specs.append(spec4);
Internal::PluginSpecPrivate *spec4Priv = Internal::PluginManagerPrivate::privateSpec(spec4); spec4->readMetaData(metaData("testdependencies/spec4.json"));
spec4Priv->readMetaData(metaData("testdependencies/spec4.json")); spec4->setState(PluginSpec::Read); // fake read state for plugin resolving
spec4Priv->state = PluginSpec::Read; // fake read state for plugin resolving
PluginSpec *spec5 = Internal::PluginManagerPrivate::createSpec(); PluginSpec *spec5 = new PluginSpecImpl();
specs.append(spec5); specs.append(spec5);
Internal::PluginSpecPrivate *spec5Priv = Internal::PluginManagerPrivate::privateSpec(spec5); spec5->readMetaData(metaData("testdependencies/spec5.json"));
spec5Priv->readMetaData(metaData("testdependencies/spec5.json")); spec5->setState(PluginSpec::Read); // fake read state for plugin resolving
spec5Priv->state = PluginSpec::Read; // fake read state for plugin resolving
QVERIFY(spec1Priv->resolveDependencies(specs)); QVERIFY(spec1->resolveDependencies(specs));
QCOMPARE(spec1Priv->dependencySpecs.size(), 2); QCOMPARE(spec1->dependencySpecs().size(), 2);
QVERIFY(!spec1Priv->dependencySpecs.key(spec2).name.isEmpty()); QVERIFY(!spec1->dependencySpecs().key(spec2).name.isEmpty());
QVERIFY(!spec1Priv->dependencySpecs.key(spec3).name.isEmpty()); QVERIFY(!spec1->dependencySpecs().key(spec3).name.isEmpty());
QCOMPARE(spec1Priv->state, PluginSpec::Resolved); QCOMPARE(spec1->state(), PluginSpec::Resolved);
QVERIFY(!spec4Priv->resolveDependencies(specs)); QVERIFY(!spec4->resolveDependencies(specs));
QVERIFY(spec4Priv->hasError); QVERIFY(spec4->hasError());
QCOMPARE(spec4Priv->state, PluginSpec::Read); QCOMPARE(spec4->state(), PluginSpec::Read);
} }
void tst_PluginSpec::loadLibrary() void tst_PluginSpec::loadLibrary()
{ {
PluginSpec *ps = Internal::PluginManagerPrivate::createSpec(); Utils::expected_str<PluginSpec *> ps = PluginSpecImpl::read(
Internal::PluginSpecPrivate *spec = Internal::PluginManagerPrivate::privateSpec(ps); QLatin1String(PLUGIN_DIR) + QLatin1String("/testplugin/")
QVERIFY(spec->read(QLatin1String(PLUGIN_DIR) + QLatin1String("/testplugin/") + libraryName(QLatin1String("test")))); + libraryName(QLatin1String("test")));
QVERIFY(ps);
PluginSpecImpl *spec = static_cast<PluginSpecImpl *>(ps.value());
QVERIFY(spec->resolveDependencies(QVector<PluginSpec *>())); QVERIFY(spec->resolveDependencies(QVector<PluginSpec *>()));
QVERIFY2(spec->loadLibrary(), qPrintable(spec->errorString)); QVERIFY2(spec->loadLibrary(), qPrintable(spec->errorString()));
QVERIFY(spec->plugin != 0); QVERIFY(spec->plugin() != 0);
QVERIFY(QLatin1String(spec->plugin->metaObject()->className()) == QLatin1String("MyPlugin::MyPluginImpl")); QVERIFY(QLatin1String(spec->plugin()->metaObject()->className())
QCOMPARE(spec->state, PluginSpec::Loaded); == QLatin1String("MyPlugin::MyPluginImpl"));
QVERIFY(!spec->hasError); QCOMPARE(spec->state(), PluginSpec::Loaded);
QCOMPARE(spec->plugin, ps->plugin()); QVERIFY(!spec->hasError());
delete ps;
delete *ps;
} }
void tst_PluginSpec::initializePlugin() void tst_PluginSpec::initializePlugin()
{ {
Internal::PluginSpecPrivate spec(0); Utils::expected_str<PluginSpec *> ps = PluginSpecImpl::read(
QVERIFY(spec.read(QLatin1String(PLUGIN_DIR) + QLatin1String("/testplugin/") + libraryName(QLatin1String("test")))); QLatin1String(PLUGIN_DIR) + QLatin1String("/testplugin/")
QVERIFY(spec.resolveDependencies(QVector<PluginSpec *>())); + libraryName(QLatin1String("test")));
QVERIFY2(spec.loadLibrary(), qPrintable(spec.errorString)); QVERIFY(ps);
PluginSpecImpl *spec = static_cast<PluginSpecImpl *>(ps.value());
QVERIFY(spec->resolveDependencies(QVector<PluginSpec *>()));
QVERIFY2(spec->loadLibrary(), qPrintable(spec->errorString()));
bool isInitialized; bool isInitialized;
QMetaObject::invokeMethod(spec.plugin, "isInitialized", QMetaObject::invokeMethod(spec->plugin(),
Qt::DirectConnection, Q_RETURN_ARG(bool, isInitialized)); "isInitialized",
Qt::DirectConnection,
Q_RETURN_ARG(bool, isInitialized));
QVERIFY(!isInitialized); QVERIFY(!isInitialized);
QVERIFY(spec.initializePlugin()); QVERIFY(spec->initializePlugin());
QCOMPARE(spec.state, PluginSpec::Initialized); QCOMPARE(spec->state(), PluginSpec::Initialized);
QVERIFY(!spec.hasError); QVERIFY(!spec->hasError());
QMetaObject::invokeMethod(spec.plugin, "isInitialized", QMetaObject::invokeMethod(spec->plugin(),
Qt::DirectConnection, Q_RETURN_ARG(bool, isInitialized)); "isInitialized",
Qt::DirectConnection,
Q_RETURN_ARG(bool, isInitialized));
QVERIFY(isInitialized); QVERIFY(isInitialized);
} }
void tst_PluginSpec::initializeExtensions() void tst_PluginSpec::initializeExtensions()
{ {
Internal::PluginSpecPrivate spec(0); Utils::expected_str<PluginSpec *> ps = PluginSpecImpl::read(
QVERIFY(spec.read(QLatin1String(PLUGIN_DIR) + QLatin1String("/testplugin/") + libraryName(QLatin1String("test")))); QLatin1String(PLUGIN_DIR) + QLatin1String("/testplugin/")
QVERIFY(spec.resolveDependencies(QVector<PluginSpec *>())); + libraryName(QLatin1String("test")));
QVERIFY2(spec.loadLibrary(), qPrintable(spec.errorString)); QVERIFY(ps);
PluginSpecImpl *spec = static_cast<PluginSpecImpl *>(ps.value());
QVERIFY(spec->resolveDependencies(QVector<PluginSpec *>()));
QVERIFY2(spec->loadLibrary(), qPrintable(spec->errorString()));
bool isExtensionsInitialized; bool isExtensionsInitialized;
QVERIFY(spec.initializePlugin()); QVERIFY(spec->initializePlugin());
QMetaObject::invokeMethod(spec.plugin, "isExtensionsInitialized", QMetaObject::invokeMethod(spec->plugin(),
Qt::DirectConnection, Q_RETURN_ARG(bool, isExtensionsInitialized)); "isExtensionsInitialized",
Qt::DirectConnection,
Q_RETURN_ARG(bool, isExtensionsInitialized));
QVERIFY(!isExtensionsInitialized); QVERIFY(!isExtensionsInitialized);
QVERIFY(spec.initializeExtensions()); QVERIFY(spec->initializeExtensions());
QCOMPARE(spec.state, PluginSpec::Running); QCOMPARE(spec->state(), PluginSpec::Running);
QVERIFY(!spec.hasError); QVERIFY(!spec->hasError());
QMetaObject::invokeMethod(spec.plugin, "isExtensionsInitialized", QMetaObject::invokeMethod(spec->plugin(),
Qt::DirectConnection, Q_RETURN_ARG(bool, isExtensionsInitialized)); "isExtensionsInitialized",
Qt::DirectConnection,
Q_RETURN_ARG(bool, isExtensionsInitialized));
QVERIFY(isExtensionsInitialized); QVERIFY(isExtensionsInitialized);
} }