Plugin install: Check if archive contains a usable plugin

Checks if there is a library file which is a Qt Creator plugin
that is compatible with the version of Qt Creator that is running.

Change-Id: Ic5284e3803c45b8e2ef0d30afccb1680fabf43f3
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Eike Ziller
2020-06-23 14:11:29 +02:00
parent 5b4b09481a
commit d67ab36c6f
4 changed files with 114 additions and 13 deletions

View File

@@ -1559,11 +1559,9 @@ void PluginManagerPrivate::readPluginPaths()
pluginCategories.insert(QString(), QVector<PluginSpec *>()); pluginCategories.insert(QString(), QVector<PluginSpec *>());
for (const QString &pluginFile : pluginFiles(pluginPaths)) { for (const QString &pluginFile : pluginFiles(pluginPaths)) {
auto *spec = new PluginSpec; PluginSpec *spec = PluginSpec::read(pluginFile);
if (!spec->d->read(pluginFile)) { // not a Qt Creator plugin if (!spec) // not a Qt Creator plugin
delete spec;
continue; continue;
}
// 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

View File

@@ -547,6 +547,16 @@ void PluginSpec::setEnabledBySettings(bool value)
d->setEnabledBySettings(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================== //==========PluginSpecPrivate==================
namespace { namespace {

View File

@@ -133,6 +133,8 @@ public:
void setEnabledBySettings(bool value); void setEnabledBySettings(bool value);
static PluginSpec *read(const QString &filePath);
private: private:
PluginSpec(); PluginSpec();

View File

@@ -25,13 +25,18 @@
#include "plugininstallwizard.h" #include "plugininstallwizard.h"
#include "coreplugin.h"
#include "icore.h" #include "icore.h"
#include <extensionsystem/pluginspec.h>
#include <utils/archive.h> #include <utils/archive.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/infolabel.h> #include <utils/infolabel.h>
#include <utils/pathchooser.h> #include <utils/pathchooser.h>
#include <utils/qtcassert.h>
#include <utils/runextensions.h>
#include <utils/temporarydirectory.h> #include <utils/temporarydirectory.h>
#include <utils/wizard.h> #include <utils/wizard.h>
#include <utils/wizardpage.h> #include <utils/wizardpage.h>
@@ -40,6 +45,7 @@
#include <QButtonGroup> #include <QButtonGroup>
#include <QDir> #include <QDir>
#include <QDirIterator>
#include <QFileInfo> #include <QFileInfo>
#include <QLabel> #include <QLabel>
#include <QMessageBox> #include <QMessageBox>
@@ -50,6 +56,7 @@
#include <memory> #include <memory>
using namespace ExtensionSystem;
using namespace Utils; using namespace Utils;
struct Data struct Data
@@ -59,6 +66,15 @@ struct Data
bool installIntoApplication; bool installIntoApplication;
}; };
static QStringList libraryNameFilter()
{
if (HostOsInfo().isWindowsHost())
return {"*.dll"};
if (HostOsInfo().isLinuxHost())
return {"*.so"};
return {"*.dylib"};
}
static bool hasLibSuffix(const FilePath &path) static bool hasLibSuffix(const FilePath &path)
{ {
return (HostOsInfo().isWindowsHost() && path.endsWith(".dll")) return (HostOsInfo().isWindowsHost() && path.endsWith(".dll"))
@@ -146,6 +162,12 @@ public:
class CheckArchivePage : public WizardPage class CheckArchivePage : public WizardPage
{ {
public: public:
struct ArchiveIssue
{
QString message;
InfoLabel::InfoType type;
};
CheckArchivePage(Data *data, QWidget *parent) CheckArchivePage(Data *data, QWidget *parent)
: WizardPage(parent) : WizardPage(parent)
, m_data(data) , m_data(data)
@@ -155,6 +177,8 @@ public:
setLayout(vlayout); setLayout(vlayout);
m_label = new InfoLabel; m_label = new InfoLabel;
m_label->setElideMode(Qt::ElideNone);
m_label->setWordWrap(true);
m_cancelButton = new QPushButton(PluginInstallWizard::tr("Cancel")); m_cancelButton = new QPushButton(PluginInstallWizard::tr("Cancel"));
m_output = new QTextEdit; m_output = new QTextEdit;
m_output->setReadOnly(true); m_output->setReadOnly(true);
@@ -187,18 +211,17 @@ public:
if (!m_archive) { if (!m_archive) {
m_label->setType(InfoLabel::Error); m_label->setType(InfoLabel::Error);
m_label->setText(PluginInstallWizard::tr("The file is not an archive.")); m_label->setText(PluginInstallWizard::tr("The file is not an archive."));
return; return;
} }
QObject::connect(m_archive, &Archive::outputReceived, this, [this](const QString &output) { QObject::connect(m_archive, &Archive::outputReceived, this, [this](const QString &output) {
m_output->append(output); m_output->append(output);
}); });
QObject::connect(m_archive, &Archive::finished, this, [this](bool success) { QObject::connect(m_archive, &Archive::finished, this, [this](bool success) {
m_cancelButton->setVisible(false); m_archive = nullptr; // we don't own it
m_isComplete = success; m_cancelButton->disconnect();
if (success) { if (!success) { // unarchiving failed
m_label->setType(InfoLabel::Ok); m_cancelButton->setVisible(false);
m_label->setText(PluginInstallWizard::tr("Archive is ok."));
} else {
if (m_canceled) { if (m_canceled) {
m_label->setType(InfoLabel::Information); m_label->setType(InfoLabel::Information);
m_label->setText(PluginInstallWizard::tr("Canceled.")); m_label->setText(PluginInstallWizard::tr("Canceled."));
@@ -207,9 +230,31 @@ public:
m_label->setText( m_label->setText(
PluginInstallWizard::tr("There was an error while unarchiving.")); PluginInstallWizard::tr("There was an error while unarchiving."));
} }
} else { // unarchiving was successful, run a check
m_archiveCheck = Utils::runAsync(
[this](QFutureInterface<ArchiveIssue> &fi) { return checkContents(fi); });
Utils::onFinished(m_archiveCheck, this, [this](const QFuture<ArchiveIssue> &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] { QObject::connect(m_cancelButton, &QPushButton::clicked, m_archive, [this] {
m_canceled = true; m_canceled = true;
@@ -217,15 +262,60 @@ public:
}); });
} }
// Async. Result is set if any issue was found.
void checkContents(QFutureInterface<ArchiveIssue> &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<PluginDependency> 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() void cleanupPage()
{ {
// back button pressed // back button pressed
m_cancelButton->disconnect();
if (m_archive) { if (m_archive) {
m_cancelButton->disconnect();
m_archive->disconnect(); m_archive->disconnect();
m_archive->cancel(); m_archive->cancel();
m_archive = nullptr; // we don't own it m_archive = nullptr; // we don't own it
} }
if (m_archiveCheck.isRunning()) {
m_archiveCheck.cancel();
m_archiveCheck.waitForFinished();
}
m_tempDir.reset(); m_tempDir.reset();
} }
@@ -233,6 +323,7 @@ public:
std::unique_ptr<TemporaryDirectory> m_tempDir; std::unique_ptr<TemporaryDirectory> m_tempDir;
Archive *m_archive = nullptr; Archive *m_archive = nullptr;
QFuture<ArchiveIssue> m_archiveCheck;
InfoLabel *m_label = nullptr; InfoLabel *m_label = nullptr;
QPushButton *m_cancelButton = nullptr; QPushButton *m_cancelButton = nullptr;
QTextEdit *m_output = nullptr; QTextEdit *m_output = nullptr;