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 <utils/algorithm.h>
#include <utils/appinfo.h>
#include <utils/hostosinfo.h>
#include <utils/qtcassert.h>
#include <utils/stringutils.h>
@@ -1277,4 +1278,54 @@ void CppPluginSpec::kill()
d->plugin = nullptr;
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

View File

@@ -147,6 +147,8 @@ public:
virtual void setEnabledBySettings(bool value);
virtual Utils::FilePath installLocation(bool inUserFolder) const = 0;
protected:
virtual void setEnabledByDefault(bool value);
virtual void setEnabledIndirectly(bool value);
@@ -174,6 +176,10 @@ private:
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(
const Utils::FilePath &filePath);
EXTENSIONSYSTEM_EXPORT Utils::expected_str<PluginSpec *> readCppPluginSpec(
@@ -201,6 +207,8 @@ public:
Utils::expected_str<void> readMetaData(const QJsonObject &pluginMetaData) override;
Utils::FilePath installLocation(bool inUserFolder) const override;
protected:
CppPluginSpec();

View File

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

View File

@@ -266,6 +266,21 @@ public:
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);
}

View File

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

View File

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