ExtensionSystem: Move Installation logic into PluginSpecs

Change-Id: I5b6d284179bf62be89d6e5157fd7e14df5e65817
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Marcus Tillmanns
2024-07-25 14:46:14 +02:00
parent 2373c4c0a4
commit be2e5262c7
6 changed files with 123 additions and 63 deletions

View File

@@ -8,6 +8,7 @@
#include "pluginmanager.h" #include "pluginmanager.h"
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/appinfo.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/stringutils.h> #include <utils/stringutils.h>
@@ -1277,4 +1278,54 @@ void CppPluginSpec::kill()
d->plugin = nullptr; d->plugin = nullptr;
setState(PluginSpec::Deleted); setState(PluginSpec::Deleted);
} }
Utils::FilePath CppPluginSpec::installLocation(bool inUserFolder) const
{
return inUserFolder ? appInfo().userPluginsRoot : appInfo().plugins;
}
static QStringList libraryNameFilter()
{
if (HostOsInfo::isWindowsHost())
return {"*.dll"};
if (HostOsInfo::isLinuxHost())
return {"*.so"};
return {"*.dylib"};
}
static QList<PluginSpec *> createCppPluginsFromArchive(const FilePath &path)
{
QList<PluginSpec *> results;
// look for plugin
QDirIterator
it(path.path(),
libraryNameFilter(),
QDir::Files | QDir::NoSymLinks,
QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
expected_str<PluginSpec *> spec = readCppPluginSpec(FilePath::fromUserInput(it.filePath()));
if (spec)
results.push_back(spec.value());
}
return results;
}
QList<PluginFromArchiveFactory> &pluginSpecsFromArchiveFactories()
{
static QList<PluginFromArchiveFactory> factories = {&createCppPluginsFromArchive};
return factories;
}
QList<PluginSpec *> pluginSpecsFromArchive(const Utils::FilePath &path)
{
QList<PluginSpec *> results;
for (const PluginFromArchiveFactory &factory : pluginSpecsFromArchiveFactories()) {
results += factory(path);
}
return results;
}
} // namespace ExtensionSystem } // namespace ExtensionSystem

View File

@@ -147,6 +147,8 @@ public:
virtual void setEnabledBySettings(bool value); virtual void setEnabledBySettings(bool value);
virtual Utils::FilePath installLocation(bool inUserFolder) const = 0;
protected: protected:
virtual void setEnabledByDefault(bool value); virtual void setEnabledByDefault(bool value);
virtual void setEnabledIndirectly(bool value); virtual void setEnabledIndirectly(bool value);
@@ -174,6 +176,10 @@ private:
std::unique_ptr<Internal::PluginSpecPrivate> d; std::unique_ptr<Internal::PluginSpecPrivate> d;
}; };
using PluginFromArchiveFactory = std::function<QList<PluginSpec *>(const Utils::FilePath &path)>;
EXTENSIONSYSTEM_EXPORT QList<PluginFromArchiveFactory> &pluginSpecsFromArchiveFactories();
EXTENSIONSYSTEM_EXPORT QList<PluginSpec *> pluginSpecsFromArchive(const Utils::FilePath &path);
EXTENSIONSYSTEM_EXPORT Utils::expected_str<PluginSpec *> readCppPluginSpec( EXTENSIONSYSTEM_EXPORT Utils::expected_str<PluginSpec *> readCppPluginSpec(
const Utils::FilePath &filePath); const Utils::FilePath &filePath);
EXTENSIONSYSTEM_EXPORT Utils::expected_str<PluginSpec *> readCppPluginSpec( EXTENSIONSYSTEM_EXPORT Utils::expected_str<PluginSpec *> readCppPluginSpec(
@@ -201,6 +207,8 @@ public:
Utils::expected_str<void> readMetaData(const QJsonObject &pluginMetaData) override; Utils::expected_str<void> readMetaData(const QJsonObject &pluginMetaData) override;
Utils::FilePath installLocation(bool inUserFolder) const override;
protected: protected:
CppPluginSpec(); CppPluginSpec();

View File

@@ -46,17 +46,9 @@ struct Data
FilePath sourcePath; FilePath sourcePath;
FilePath extractedPath; FilePath extractedPath;
bool installIntoApplication = false; bool installIntoApplication = false;
std::unique_ptr<PluginSpec> pluginSpec = nullptr;
}; };
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"))
@@ -64,12 +56,6 @@ static bool hasLibSuffix(const FilePath &path)
|| (HostOsInfo::isMacHost() && path.endsWith(".dylib")); || (HostOsInfo::isMacHost() && path.endsWith(".dylib"));
} }
static FilePath pluginInstallPath(bool installIntoApplication)
{
return FilePath::fromString(installIntoApplication ? Core::ICore::pluginPath()
: Core::ICore::userPluginPath());
}
namespace Core { namespace Core {
class SourcePage : public WizardPage class SourcePage : public WizardPage
@@ -135,45 +121,31 @@ public:
Data *m_data = nullptr; Data *m_data = nullptr;
}; };
struct ArchiveIssue using CheckResult = expected_str<PluginSpec *>;
{
QString message;
InfoLabel::InfoType type;
};
// Async. Result is set if any issue was found. // Async. Result is set if any issue was found.
void checkContents(QPromise<ArchiveIssue> &promise, const FilePath &tempDir) void checkContents(QPromise<CheckResult> &promise, const FilePath &tempDir)
{ {
PluginSpec *coreplugin = PluginManager::specForPlugin(Internal::CorePlugin::instance()); QList<PluginSpec *> plugins = pluginSpecsFromArchive(tempDir);
if (plugins.isEmpty()) {
promise.addResult(Utils::make_unexpected(Tr::tr("No plugins found.")));
return;
}
if (plugins.size() > 1) {
promise.addResult(Utils::make_unexpected(Tr::tr("More than one plugin found.")));
qDeleteAll(plugins);
return;
}
// look for plugin if (!plugins.front()->resolveDependencies(PluginManager::plugins())) {
QDirIterator it(tempDir.path(), libraryNameFilter(), QDir::Files | QDir::NoSymLinks, promise.addResult(Utils::make_unexpected(
QDirIterator::Subdirectories); Tr::tr("Plugin failed to resolve dependencies:") + " "
while (it.hasNext()) { + plugins.front()->errorString()));
if (promise.isCanceled()) qDeleteAll(plugins);
return; return;
it.next();
expected_str<PluginSpec *> spec = readCppPluginSpec(FilePath::fromUserInput(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())
return;
if ((*spec)->provides(coreplugin, *found))
return;
promise.addResult(
ArchiveIssue{Tr::tr("Plugin requires an incompatible version of %1 (%2).")
.arg(QGuiApplication::applicationDisplayName(), found->version),
InfoLabel::Error});
return; // successful / no error
} }
}
promise.addResult( promise.addResult(plugins.front());
ArchiveIssue{Tr::tr("Did not find %1 plugin.").arg(QGuiApplication::applicationDisplayName()),
InfoLabel::Error});
} }
class CheckArchivePage : public WizardPage class CheckArchivePage : public WizardPage
@@ -236,30 +208,33 @@ public:
m_label->setText(Tr::tr("There was an error while unarchiving.")); m_label->setText(Tr::tr("There was an error while unarchiving."));
}; };
const auto onCheckerSetup = [this](Async<ArchiveIssue> &async) { const auto onCheckerSetup = [this](Async<CheckResult> &async) {
if (!m_tempDir) if (!m_tempDir)
return SetupResult::StopWithError; return SetupResult::StopWithError;
async.setConcurrentCallData(checkContents, m_tempDir->path()); async.setConcurrentCallData(checkContents, m_tempDir->path());
return SetupResult::Continue; return SetupResult::Continue;
}; };
const auto onCheckerDone = [this](const Async<ArchiveIssue> &async) { const auto onCheckerDone = [this](const Async<CheckResult> &async) {
m_isComplete = !async.isResultAvailable(); expected_str<PluginSpec *> result = async.result();
if (m_isComplete) { if (!result) {
m_label->setType(InfoLabel::Error);
m_label->setText(result.error());
} else {
m_label->setType(InfoLabel::Ok); m_label->setType(InfoLabel::Ok);
m_label->setText(Tr::tr("Archive is OK.")); m_label->setText(Tr::tr("Archive is OK."));
} else { m_data->pluginSpec.reset(*result);
const ArchiveIssue issue = async.result(); m_isComplete = true;
m_label->setType(issue.type);
m_label->setText(issue.message);
} }
emit completeChanged(); emit completeChanged();
}; };
const Group root { // clang-format off
const Group root{
UnarchiverTask(onUnarchiverSetup, onUnarchiverError, CallDoneIf::Error), UnarchiverTask(onUnarchiverSetup, onUnarchiverError, CallDoneIf::Error),
AsyncTask<ArchiveIssue>(onCheckerSetup, onCheckerDone, CallDoneIf::Success) AsyncTask<CheckResult>(onCheckerSetup, onCheckerDone, CallDoneIf::Success)
}; };
// clang-format on
m_cancelButton->setVisible(true); m_cancelButton->setVisible(true);
m_taskTreeRunner.start(root, {}, [this](DoneWith) { m_cancelButton->setVisible(false); }); m_taskTreeRunner.start(root, {}, [this](DoneWith) { m_cancelButton->setVisible(false); });
} }
@@ -346,8 +321,9 @@ public:
{ {
m_summaryLabel->setText( m_summaryLabel->setText(
Tr::tr("\"%1\" will be installed into \"%2\".") Tr::tr("\"%1\" will be installed into \"%2\".")
.arg(m_data->sourcePath.toUserOutput(), .arg(m_data->sourcePath.toUserOutput())
pluginInstallPath(m_data->installIntoApplication).toUserOutput())); .arg(m_data->pluginSpec->installLocation(!m_data->installIntoApplication)
.toUserOutput()));
} }
private: private:
@@ -420,7 +396,7 @@ bool executePluginInstallWizard(const FilePath &archive)
wizard.addPage(summaryPage); wizard.addPage(summaryPage);
if (wizard.exec()) { if (wizard.exec()) {
const FilePath installPath = pluginInstallPath(data.installIntoApplication); const FilePath installPath = data.pluginSpec->installLocation(!data.installIntoApplication);
if (hasLibSuffix(data.sourcePath)) { if (hasLibSuffix(data.sourcePath)) {
return copyPluginFile(data.sourcePath, installPath); return copyPluginFile(data.sourcePath, installPath);
} else { } else {

View File

@@ -266,6 +266,21 @@ public:
Core::JsExpander::registerGlobalObject("Lua", [] { return new LuaJsExtension(); }); Core::JsExpander::registerGlobalObject("Lua", [] { return new LuaJsExtension(); });
pluginSpecsFromArchiveFactories().push_back([](const FilePath &path) {
QList<PluginSpec *> plugins;
auto dirs = path.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot);
for (const auto &dir : dirs) {
const auto specFilePath = dir / (dir.fileName() + ".lua");
if (specFilePath.exists()) {
Utils::expected_str<PluginSpec *> spec = loadPlugin(specFilePath);
QTC_CHECK_EXPECTED(spec);
if (spec)
plugins.push_back(*spec);
}
}
return plugins;
});
m_pane = new LuaPane(this); m_pane = new LuaPane(this);
} }

View File

@@ -170,4 +170,12 @@ bool LuaPluginSpec::printToOutputPane() const
return d->printToOutputPane; return d->printToOutputPane;
} }
Utils::FilePath LuaPluginSpec::installLocation(bool inUserFolder) const
{
if (inUserFolder)
return appInfo().userLuaPlugins;
return appInfo().luaPlugins;
}
} // namespace Lua } // namespace Lua

View File

@@ -55,6 +55,8 @@ public:
ExtensionSystem::IPlugin::ShutdownFlag stop() override; ExtensionSystem::IPlugin::ShutdownFlag stop() override;
void kill() override; void kill() override;
Utils::FilePath installLocation(bool inUserFolder) const override;
public: public:
bool printToOutputPane() const; bool printToOutputPane() const;
}; };