diff --git a/src/libs/extensionsystem/pluginmanager.cpp b/src/libs/extensionsystem/pluginmanager.cpp index 015a9384c98..2f804004a54 100644 --- a/src/libs/extensionsystem/pluginmanager.cpp +++ b/src/libs/extensionsystem/pluginmanager.cpp @@ -1559,11 +1559,9 @@ void PluginManagerPrivate::readPluginPaths() pluginCategories.insert(QString(), QVector()); for (const QString &pluginFile : pluginFiles(pluginPaths)) { - auto *spec = new PluginSpec; - if (!spec->d->read(pluginFile)) { // not a Qt Creator plugin - delete spec; + PluginSpec *spec = PluginSpec::read(pluginFile); + if (!spec) // not a Qt Creator plugin continue; - } // defaultDisabledPlugins and defaultEnabledPlugins from install settings // is used to override the defaults read from the plugin spec diff --git a/src/libs/extensionsystem/pluginspec.cpp b/src/libs/extensionsystem/pluginspec.cpp index 8e5b3e02ecb..ea83933b7ed 100644 --- a/src/libs/extensionsystem/pluginspec.cpp +++ b/src/libs/extensionsystem/pluginspec.cpp @@ -547,6 +547,16 @@ void PluginSpec::setEnabledBySettings(bool value) d->setEnabledBySettings(value); } +PluginSpec *PluginSpec::read(const QString &filePath) +{ + auto spec = new PluginSpec; + if (!spec->d->read(filePath)) { // not a Qt Creator plugin + delete spec; + return nullptr; + } + return spec; +} + //==========PluginSpecPrivate================== namespace { diff --git a/src/libs/extensionsystem/pluginspec.h b/src/libs/extensionsystem/pluginspec.h index 2180d916dd5..4ad702b2163 100644 --- a/src/libs/extensionsystem/pluginspec.h +++ b/src/libs/extensionsystem/pluginspec.h @@ -133,6 +133,8 @@ public: void setEnabledBySettings(bool value); + static PluginSpec *read(const QString &filePath); + private: PluginSpec(); diff --git a/src/plugins/coreplugin/plugininstallwizard.cpp b/src/plugins/coreplugin/plugininstallwizard.cpp index 8d0f628f658..59380bd191c 100644 --- a/src/plugins/coreplugin/plugininstallwizard.cpp +++ b/src/plugins/coreplugin/plugininstallwizard.cpp @@ -25,13 +25,18 @@ #include "plugininstallwizard.h" +#include "coreplugin.h" #include "icore.h" +#include + #include #include #include #include #include +#include +#include #include #include #include @@ -40,6 +45,7 @@ #include #include +#include #include #include #include @@ -50,6 +56,7 @@ #include +using namespace ExtensionSystem; using namespace Utils; struct Data @@ -59,6 +66,15 @@ struct Data bool installIntoApplication; }; +static QStringList libraryNameFilter() +{ + if (HostOsInfo().isWindowsHost()) + return {"*.dll"}; + if (HostOsInfo().isLinuxHost()) + return {"*.so"}; + return {"*.dylib"}; +} + static bool hasLibSuffix(const FilePath &path) { return (HostOsInfo().isWindowsHost() && path.endsWith(".dll")) @@ -146,6 +162,12 @@ public: class CheckArchivePage : public WizardPage { public: + struct ArchiveIssue + { + QString message; + InfoLabel::InfoType type; + }; + CheckArchivePage(Data *data, QWidget *parent) : WizardPage(parent) , m_data(data) @@ -155,6 +177,8 @@ public: setLayout(vlayout); m_label = new InfoLabel; + m_label->setElideMode(Qt::ElideNone); + m_label->setWordWrap(true); m_cancelButton = new QPushButton(PluginInstallWizard::tr("Cancel")); m_output = new QTextEdit; m_output->setReadOnly(true); @@ -187,18 +211,17 @@ public: if (!m_archive) { m_label->setType(InfoLabel::Error); m_label->setText(PluginInstallWizard::tr("The file is not an archive.")); + return; } QObject::connect(m_archive, &Archive::outputReceived, this, [this](const QString &output) { m_output->append(output); }); QObject::connect(m_archive, &Archive::finished, this, [this](bool success) { - m_cancelButton->setVisible(false); - m_isComplete = success; - if (success) { - m_label->setType(InfoLabel::Ok); - m_label->setText(PluginInstallWizard::tr("Archive is ok.")); - } else { + m_archive = nullptr; // we don't own it + m_cancelButton->disconnect(); + if (!success) { // unarchiving failed + m_cancelButton->setVisible(false); if (m_canceled) { m_label->setType(InfoLabel::Information); m_label->setText(PluginInstallWizard::tr("Canceled.")); @@ -207,9 +230,31 @@ public: m_label->setText( PluginInstallWizard::tr("There was an error while unarchiving.")); } + } else { // unarchiving was successful, run a check + m_archiveCheck = Utils::runAsync( + [this](QFutureInterface &fi) { return checkContents(fi); }); + Utils::onFinished(m_archiveCheck, this, [this](const QFuture &f) { + m_cancelButton->setVisible(false); + m_cancelButton->disconnect(); + const bool ok = f.resultCount() == 0 && !f.isCanceled(); + if (f.isCanceled()) { + m_label->setType(InfoLabel::Information); + m_label->setText(PluginInstallWizard::tr("Canceled.")); + } else if (ok) { + m_label->setType(InfoLabel::Ok); + m_label->setText(PluginInstallWizard::tr("Archive is OK.")); + } else { + const ArchiveIssue issue = f.result(); + m_label->setType(issue.type); + m_label->setText(issue.message); + } + m_isComplete = ok; + emit completeChanged(); + }); + QObject::connect(m_cancelButton, &QPushButton::clicked, this, [this] { + m_archiveCheck.cancel(); + }); } - m_archive = nullptr; // we don't own it - emit completeChanged(); }); QObject::connect(m_cancelButton, &QPushButton::clicked, m_archive, [this] { m_canceled = true; @@ -217,15 +262,60 @@ public: }); } + // Async. Result is set if any issue was found. + void checkContents(QFutureInterface &fi) + { + QTC_ASSERT(m_tempDir.get(), return ); + + PluginSpec *coreplugin = CorePlugin::instance()->pluginSpec(); + + // look for plugin + QDirIterator it(m_tempDir->path(), libraryNameFilter(), QDir::Files | QDir::NoSymLinks); + while (it.hasNext()) { + if (fi.isCanceled()) + return; + it.next(); + PluginSpec *spec = PluginSpec::read(it.filePath()); + if (spec) { + // Is a Qt Creator plugin. Let's see if we find a Core dependency and check the + // version + const QVector dependencies = spec->dependencies(); + const auto found = std::find_if(dependencies.constBegin(), + dependencies.constEnd(), + [coreplugin](const PluginDependency &d) { + return d.name == coreplugin->name(); + }); + if (found != dependencies.constEnd()) { + if (!coreplugin->provides(found->name, found->version)) { + fi.reportResult({PluginInstallWizard::tr( + "Plugin requires an incompatible version of %1 (%2).") + .arg(Constants::IDE_DISPLAY_NAME) + .arg(found->version), + InfoLabel::Error}); + return; + } + } + return; // successful / no error + } + } + fi.reportResult({PluginInstallWizard::tr("Did not find %1 plugin in toplevel directory.") + .arg(Constants::IDE_DISPLAY_NAME), + InfoLabel::Error}); + } + void cleanupPage() { // back button pressed + m_cancelButton->disconnect(); if (m_archive) { - m_cancelButton->disconnect(); m_archive->disconnect(); m_archive->cancel(); m_archive = nullptr; // we don't own it } + if (m_archiveCheck.isRunning()) { + m_archiveCheck.cancel(); + m_archiveCheck.waitForFinished(); + } m_tempDir.reset(); } @@ -233,6 +323,7 @@ public: std::unique_ptr m_tempDir; Archive *m_archive = nullptr; + QFuture m_archiveCheck; InfoLabel *m_label = nullptr; QPushButton *m_cancelButton = nullptr; QTextEdit *m_output = nullptr;