ExtensionSystem: Allow opt-in plugin loading without restart

Change-Id: I682e51d047c43ff5bf4647ef7e015222707f3204
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
hjk
2023-06-20 16:06:50 +02:00
committed by Marcus Tillmanns
parent 845337436c
commit 32914fe66b
9 changed files with 63 additions and 19 deletions

View File

@@ -322,6 +322,13 @@ void PluginManager::loadPlugins()
d->loadPlugins(); d->loadPlugins();
} }
void PluginManager::loadPlugin(PluginSpec *spec)
{
d->loadPlugin(spec, PluginSpec::Loaded);
d->loadPlugin(spec, PluginSpec::Initialized);
d->loadPlugin(spec, PluginSpec::Running);
}
/*! /*!
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().
@@ -1616,6 +1623,7 @@ void PluginManagerPrivate::loadPlugin(PluginSpec *spec, PluginSpec::State destSt
break; break;
} }
// check if dependencies have loaded without error // check if dependencies have loaded without error
if (!spec->isSoftLoadable()) {
const QHash<PluginDependency, PluginSpec *> deps = spec->dependencySpecs(); const QHash<PluginDependency, PluginSpec *> deps = spec->dependencySpecs();
for (auto it = deps.cbegin(), end = deps.cend(); it != end; ++it) { for (auto it = deps.cbegin(), end = deps.cend(); it != end; ++it) {
if (it.key().type != PluginDependency::Required) if (it.key().type != PluginDependency::Required)
@@ -1629,6 +1637,7 @@ void PluginManagerPrivate::loadPlugin(PluginSpec *spec, PluginSpec::State destSt
return; return;
} }
} }
}
switch (destState) { switch (destState) {
case PluginSpec::Loaded: { case PluginSpec::Loaded: {
NANOTRACE_SCOPE(specName, specName + "::load"); NANOTRACE_SCOPE(specName, specName + "::load");

View File

@@ -68,6 +68,7 @@ public:
// Plugin operations // Plugin operations
static QVector<PluginSpec *> loadQueue(); static QVector<PluginSpec *> loadQueue();
static void loadPlugins(); static void loadPlugins();
static void loadPlugin(PluginSpec *);
static QStringList pluginPaths(); static QStringList pluginPaths();
static void setPluginPaths(const QStringList &paths); static void setPluginPaths(const QStringList &paths);
static QString pluginIID(); static QString pluginIID();

View File

@@ -386,6 +386,11 @@ bool PluginSpec::isForceDisabled() const
return d->forceDisabled; return d->forceDisabled;
} }
bool PluginSpec::isSoftLoadable() const
{
return d->softLoadable;
}
/*! /*!
The plugin dependencies. This is valid after the PluginSpec::Read state is reached. The plugin dependencies. This is valid after the PluginSpec::Read state is reached.
*/ */
@@ -570,6 +575,7 @@ namespace {
const char PLUGIN_REQUIRED[] = "Required"; const char PLUGIN_REQUIRED[] = "Required";
const char PLUGIN_EXPERIMENTAL[] = "Experimental"; const char PLUGIN_EXPERIMENTAL[] = "Experimental";
const char PLUGIN_DISABLED_BY_DEFAULT[] = "DisabledByDefault"; const char PLUGIN_DISABLED_BY_DEFAULT[] = "DisabledByDefault";
const char PLUGIN_SOFTLOADABLE[] = "SoftLoadable";
const char VENDOR[] = "Vendor"; const char VENDOR[] = "Vendor";
const char COPYRIGHT[] = "Copyright"; const char COPYRIGHT[] = "Copyright";
const char LICENSE[] = "License"; const char LICENSE[] = "License";
@@ -683,6 +689,11 @@ void PluginSpecPrivate::setForceDisabled(bool value)
forceDisabled = value; forceDisabled = value;
} }
void PluginSpecPrivate::setSoftLoadable(bool value)
{
softLoadable = value;
}
/*! /*!
\internal \internal
*/ */
@@ -796,6 +807,12 @@ bool PluginSpecPrivate::readMetaData(const QJsonObject &pluginMetaData)
enabledByDefault = false; enabledByDefault = false;
enabledBySettings = enabledByDefault; enabledBySettings = enabledByDefault;
value = metaData.value(QLatin1String(PLUGIN_SOFTLOADABLE));
if (!value.isUndefined() && !value.isBool())
return reportError(msgValueIsNotABool(PLUGIN_SOFTLOADABLE));
softLoadable = value.toBool(false);
qCDebug(pluginLog) << "softLoadable =" << softLoadable;
value = metaData.value(QLatin1String(VENDOR)); value = metaData.value(QLatin1String(VENDOR));
if (!value.isUndefined() && !value.isString()) if (!value.isUndefined() && !value.isString())
return reportError(msgValueIsNotAString(VENDOR)); return reportError(msgValueIsNotAString(VENDOR));

View File

@@ -100,6 +100,7 @@ public:
bool isEnabledIndirectly() const; bool isEnabledIndirectly() const;
bool isForceEnabled() const; bool isForceEnabled() const;
bool isForceDisabled() const; bool isForceDisabled() const;
bool isSoftLoadable() const;
QVector<PluginDependency> dependencies() const; QVector<PluginDependency> dependencies() const;
QJsonObject metaData() const; QJsonObject metaData() const;
const PerformanceData &performanceData() const; const PerformanceData &performanceData() const;

View File

@@ -45,6 +45,7 @@ public:
void setEnabledByDefault(bool value); void setEnabledByDefault(bool value);
void setForceEnabled(bool value); void setForceEnabled(bool value);
void setForceDisabled(bool value); void setForceDisabled(bool value);
void setSoftLoadable(bool value);
std::optional<QPluginLoader> loader; std::optional<QPluginLoader> loader;
std::optional<QStaticPlugin> staticPlugin; std::optional<QStaticPlugin> staticPlugin;
@@ -69,6 +70,7 @@ public:
bool enabledIndirectly = false; bool enabledIndirectly = false;
bool forceEnabled = false; bool forceEnabled = false;
bool forceDisabled = false; bool forceDisabled = false;
bool softLoadable = false;
QString location; QString location;
QString filePath; QString filePath;

View File

@@ -237,9 +237,7 @@ public:
if (column == LoadedColumn && role == Qt::CheckStateRole) { if (column == LoadedColumn && role == Qt::CheckStateRole) {
const QVector<PluginSpec *> affectedPlugins const QVector<PluginSpec *> affectedPlugins
= Utils::filtered(m_plugins, [](PluginSpec *spec) { return !spec->isRequired(); }); = Utils::filtered(m_plugins, [](PluginSpec *spec) { return !spec->isRequired(); });
if (m_view->setPluginsEnabled(Utils::transform<QSet>(affectedPlugins, if (m_view->setPluginsEnabled(toSet(affectedPlugins), data.toBool())) {
[](PluginSpec *s) { return s; }),
data.toBool())) {
update(); update();
return true; return true;
} }
@@ -419,8 +417,8 @@ bool PluginView::setPluginsEnabled(const QSet<PluginSpec *> &plugins, bool enabl
spec->d->setEnabledBySettings(enable); spec->d->setEnabledBySettings(enable);
item->updateColumn(LoadedColumn); item->updateColumn(LoadedColumn);
item->parent()->updateColumn(LoadedColumn); item->parent()->updateColumn(LoadedColumn);
emit pluginSettingsChanged(spec);
} }
emit pluginsChanged(affectedPlugins, enable);
return true; return true;
} }

View File

@@ -7,6 +7,7 @@
#include <utils/treemodel.h> #include <utils/treemodel.h>
#include <QSet>
#include <QWidget> #include <QWidget>
#include <unordered_map> #include <unordered_map>
@@ -40,7 +41,7 @@ public:
signals: signals:
void currentPluginChanged(ExtensionSystem::PluginSpec *spec); void currentPluginChanged(ExtensionSystem::PluginSpec *spec);
void pluginActivated(ExtensionSystem::PluginSpec *spec); void pluginActivated(ExtensionSystem::PluginSpec *spec);
void pluginSettingsChanged(ExtensionSystem::PluginSpec *spec); void pluginsChanged(const QSet<ExtensionSystem::PluginSpec *> &spec, bool enabled);
private: private:
PluginSpec *pluginForIndex(const QModelIndex &index) const; PluginSpec *pluginForIndex(const QModelIndex &index) const;

View File

@@ -14,6 +14,7 @@
#include <extensionsystem/pluginspec.h> #include <extensionsystem/pluginspec.h>
#include <extensionsystem/pluginview.h> #include <extensionsystem/pluginview.h>
#include <utils/algorithm.h>
#include <utils/fancylineedit.h> #include <utils/fancylineedit.h>
#include <utils/layoutbuilder.h> #include <utils/layoutbuilder.h>
@@ -21,6 +22,7 @@
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QPushButton> #include <QPushButton>
using namespace ExtensionSystem;
using namespace Utils; using namespace Utils;
namespace Core { namespace Core {
@@ -59,8 +61,16 @@ PluginDialog::PluginDialog(QWidget *parent)
this, &PluginDialog::updateButtons); this, &PluginDialog::updateButtons);
connect(m_view, &ExtensionSystem::PluginView::pluginActivated, connect(m_view, &ExtensionSystem::PluginView::pluginActivated,
this, &PluginDialog::openDetails); this, &PluginDialog::openDetails);
connect(m_view, &ExtensionSystem::PluginView::pluginSettingsChanged, this, [this] { connect(m_view, &ExtensionSystem::PluginView::pluginsChanged,
this, [this](const QSet<PluginSpec *> &plugins, bool enable) {
for (PluginSpec *plugin : plugins) {
if (enable && plugin->isSoftLoadable()) {
m_softLoad.insert(plugin);
} else {
m_softLoad.remove(plugin); // In case it was added, harmless otherwise.
m_isRestartRequired = true; m_isRestartRequired = true;
}
}
}); });
connect(m_detailsButton, &QAbstractButton::clicked, this, connect(m_detailsButton, &QAbstractButton::clicked, this,
[this] { openDetails(m_view->currentPlugin()); }); [this] { openDetails(m_view->currentPlugin()); });
@@ -75,7 +85,11 @@ PluginDialog::PluginDialog(QWidget *parent)
void PluginDialog::closeDialog() void PluginDialog::closeDialog()
{ {
ExtensionSystem::PluginManager::writeSettings(); PluginManager::writeSettings();
for (PluginSpec *plugin : m_softLoad)
PluginManager::loadPlugin(plugin);
if (m_isRestartRequired) { if (m_isRestartRequired) {
RestartDialog restartDialog(ICore::dialogParent(), RestartDialog restartDialog(ICore::dialogParent(),
Tr::tr("Plugin changes will take effect after restart.")); Tr::tr("Plugin changes will take effect after restart."));

View File

@@ -38,6 +38,7 @@ private:
QPushButton *m_errorDetailsButton; QPushButton *m_errorDetailsButton;
QPushButton *m_installButton; QPushButton *m_installButton;
bool m_isRestartRequired = false; bool m_isRestartRequired = false;
QSet<ExtensionSystem::PluginSpec *> m_softLoad;
}; };
} // namespace Internal } // namespace Internal