From 471e67d1a6b82e331eea137c1768520b47111289 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Wed, 25 Jan 2023 12:29:08 +0100 Subject: [PATCH] Python: collect pip package info in another thread Change-Id: I70a9066fddf812ce9bde5467913bb2bad98e2d0e Reviewed-by: Christian Stenger --- src/plugins/python/pipsupport.cpp | 83 +++++++++------ src/plugins/python/pipsupport.h | 50 +++++---- src/plugins/python/pythonrunconfiguration.cpp | 100 +++++++++++++----- src/plugins/python/pythonrunconfiguration.h | 9 +- 4 files changed, 164 insertions(+), 78 deletions(-) diff --git a/src/plugins/python/pipsupport.cpp b/src/plugins/python/pipsupport.cpp index 8c98bd423da..848184b462f 100644 --- a/src/plugins/python/pipsupport.cpp +++ b/src/plugins/python/pipsupport.cpp @@ -3,6 +3,7 @@ #include "pipsupport.h" +#include "pythonplugin.h" #include "pythontr.h" #include @@ -15,6 +16,7 @@ #include #include #include +#include using namespace Utils; @@ -100,36 +102,6 @@ void PipInstallTask::handleError() Core::MessageManager::writeSilently(stdErr); } -PipPackageInfo PipPackage::info(const FilePath &python) const -{ - PipPackageInfo result; - - QtcProcess pip; - pip.setCommand(CommandLine(python, {"-m", "pip", "show", "-f", packageName})); - pip.runBlocking(); - QString fieldName; - QStringList data; - const QString pipOutput = pip.allOutput(); - for (const QString &line : pipOutput.split('\n')) { - if (line.isEmpty()) - continue; - if (line.front().isSpace()) { - data.append(line.trimmed()); - } else { - result.parseField(fieldName, data); - if (auto colonPos = line.indexOf(':'); colonPos >= 0) { - fieldName = line.left(colonPos); - data = QStringList(line.mid(colonPos + 1).trimmed()); - } else { - fieldName.clear(); - data.clear(); - } - } - } - result.parseField(fieldName, data); - return result; -} - void PipPackageInfo::parseField(const QString &field, const QStringList &data) { if (field.isEmpty()) @@ -162,4 +134,55 @@ void PipPackageInfo::parseField(const QString &field, const QStringList &data) } } +Pip *Pip::instance(const FilePath &python) +{ + static QMap pips; + auto it = pips.find(python); + if (it == pips.end()) + it = pips.insert(python, new Pip(python)); + return it.value(); +} + +QFuture Pip::info(const PipPackage &package) +{ + return Utils::runAsync(&Pip::infoImpl, this, package); +} + +PipPackageInfo Pip::infoImpl(const PipPackage &package) +{ + PipPackageInfo result; + + QtcProcess pip; + pip.setCommand(CommandLine(m_python, {"-m", "pip", "show", "-f", package.packageName})); + m_lock.lock(); + pip.runBlocking(); + m_lock.unlock(); + QString fieldName; + QStringList data; + const QString pipOutput = pip.allOutput(); + for (const QString &line : pipOutput.split('\n')) { + if (line.isEmpty()) + continue; + if (line.front().isSpace()) { + data.append(line.trimmed()); + } else { + result.parseField(fieldName, data); + if (auto colonPos = line.indexOf(':'); colonPos >= 0) { + fieldName = line.left(colonPos); + data = QStringList(line.mid(colonPos + 1).trimmed()); + } else { + fieldName.clear(); + data.clear(); + } + } + } + result.parseField(fieldName, data); + return result; +} + +Pip::Pip(const Utils::FilePath &python) + : QObject(PythonPlugin::instance()) + , m_python(python) +{} + } // Python::Internal diff --git a/src/plugins/python/pipsupport.h b/src/plugins/python/pipsupport.h index a26360392d9..e5f53768ce9 100644 --- a/src/plugins/python/pipsupport.h +++ b/src/plugins/python/pipsupport.h @@ -12,25 +12,6 @@ namespace Python::Internal { -class PipPackageInfo; - -class PipPackage -{ -public: - explicit PipPackage(const QString &packageName = {}, - const QString &displayName = {}, - const QString &version = {}) - : packageName(packageName) - , displayName(displayName.isEmpty() ? packageName : displayName) - , version(version) - {} - QString packageName; - QString displayName; - QString version; - - PipPackageInfo info(const Utils::FilePath &python) const; -}; - class PipPackageInfo { public: @@ -49,6 +30,37 @@ public: void parseField(const QString &field, const QStringList &value); }; +class PipPackage +{ +public: + explicit PipPackage(const QString &packageName = {}, + const QString &displayName = {}, + const QString &version = {}) + : packageName(packageName) + , displayName(displayName.isEmpty() ? packageName : displayName) + , version(version) + {} + QString packageName; + QString displayName; + QString version; +}; + +class Pip : public QObject +{ +public: + static Pip *instance(const Utils::FilePath &python); + + QFuture info(const PipPackage &package); + +private: + Pip(const Utils::FilePath &python); + + PipPackageInfo infoImpl(const PipPackage &package); + + QMutex m_lock; + Utils::FilePath m_python; +}; + class PipInstallTask : public QObject { Q_OBJECT diff --git a/src/plugins/python/pythonrunconfiguration.cpp b/src/plugins/python/pythonrunconfiguration.cpp index 480f3f153f4..2163e2b7ed6 100644 --- a/src/plugins/python/pythonrunconfiguration.cpp +++ b/src/plugins/python/pythonrunconfiguration.cpp @@ -117,8 +117,35 @@ private: //////////////////////////////////////////////////////////////// +class PythonRunConfigurationPrivate +{ +public: + PythonRunConfigurationPrivate(PythonRunConfiguration *rc) + : q(rc) + {} + ~PythonRunConfigurationPrivate() + { + qDeleteAll(m_extraCompilers); + } + + void checkForPySide(const Utils::FilePath &python); + void checkForPySide(const Utils::FilePath &python, const QString &pySidePackageName); + void handlePySidePackageInfo(const PipPackageInfo &pySideInfo, + const Utils::FilePath &python, + const QString &requestedPackageName); + void updateExtraCompilers(); + Utils::FilePath m_pySideUicPath; + + PythonRunConfiguration *q; + QList m_extraCompilers; + QFutureWatcher m_watcher; + QMetaObject::Connection m_watcherConnection; + +}; + PythonRunConfiguration::PythonRunConfiguration(Target *target, Id id) : RunConfiguration(target, id) + , d(new PythonRunConfigurationPrivate(this)) { auto interpreterAspect = addAspect(); interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter"); @@ -185,7 +212,7 @@ PythonRunConfiguration::PythonRunConfiguration(Target *target, Id id) }); connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update); - connect(target, &Target::buildSystemUpdated, this, &PythonRunConfiguration::updateExtraCompilers); + connect(target, &Target::buildSystemUpdated, this, [this]() { d->updateExtraCompilers(); }); currentInterpreterChanged(); setRunnableModifier([](Runnable &r) { @@ -195,25 +222,53 @@ PythonRunConfiguration::PythonRunConfiguration(Target *target, Id id) connect(PySideInstaller::instance(), &PySideInstaller::pySideInstalled, this, [this](const FilePath &python) { if (python == aspect()->currentInterpreter().command) - checkForPySide(python); + d->checkForPySide(python); }); } PythonRunConfiguration::~PythonRunConfiguration() { - qDeleteAll(m_extraCompilers); + delete d; } -struct PythonTools +void PythonRunConfigurationPrivate::checkForPySide(const FilePath &python) { - FilePath pySideProjectPath; - FilePath pySideUicPath; -}; + checkForPySide(python, "PySide6-Essentials"); +} -void PythonRunConfiguration::checkForPySide(const FilePath &python) +void PythonRunConfigurationPrivate::checkForPySide(const FilePath &python, + const QString &pySidePackageName) { - BuildStepList *buildSteps = target()->activeBuildConfiguration()->buildSteps(); + const PipPackage package(pySidePackageName); + QObject::disconnect(m_watcherConnection); + m_watcherConnection = QObject::connect(&m_watcher, + &QFutureWatcher::finished, + q, + [=]() { + handlePySidePackageInfo(m_watcher.result(), + python, + pySidePackageName); + }); + m_watcher.setFuture(Pip::instance(python)->info(package)); +} +void PythonRunConfigurationPrivate::handlePySidePackageInfo(const PipPackageInfo &pySideInfo, + const Utils::FilePath &python, + const QString &requestedPackageName) +{ + struct PythonTools + { + FilePath pySideProjectPath; + FilePath pySideUicPath; + }; + + BuildStepList *buildSteps = nullptr; + if (Target *target = q->target()) { + if (auto buildConfiguration = target->activeBuildConfiguration()) + buildSteps = buildConfiguration->buildSteps(); + } + if (!buildSteps) + return; const auto findPythonTools = [](const FilePaths &files, const FilePath &location, @@ -239,13 +294,10 @@ void PythonRunConfiguration::checkForPySide(const FilePath &python) return {}; }; - const PipPackage pySide6EssentialPackage("PySide6-Essentials"); - PipPackageInfo info = pySide6EssentialPackage.info(python); - PythonTools pythonTools = findPythonTools(info.files, info.location, python); - if (!pythonTools.pySideProjectPath.isExecutableFile()) { - const PipPackage pySide6Package("PySide6"); - info = pySide6Package.info(python); - pythonTools = findPythonTools(info.files, info.location, python); + PythonTools pythonTools = findPythonTools(pySideInfo.files, pySideInfo.location, python); + if (!pythonTools.pySideProjectPath.isExecutableFile() && requestedPackageName != "PySide6") { + checkForPySide(python, "PySide6"); + return; } m_pySideUicPath = pythonTools.pySideUicPath; @@ -259,7 +311,7 @@ void PythonRunConfiguration::checkForPySide(const FilePath &python) void PythonRunConfiguration::currentInterpreterChanged() { const FilePath python = aspect()->currentInterpreter().command; - checkForPySide(python); + d->checkForPySide(python); for (FilePath &file : project()->files(Project::AllFiles)) { if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) { @@ -274,10 +326,10 @@ void PythonRunConfiguration::currentInterpreterChanged() QList PythonRunConfiguration::extraCompilers() const { - return m_extraCompilers; + return d->m_extraCompilers; } -void PythonRunConfiguration::updateExtraCompilers() +void PythonRunConfigurationPrivate::updateExtraCompilers() { QList oldCompilers = m_extraCompilers; m_extraCompilers.clear(); @@ -288,21 +340,21 @@ void PythonRunConfiguration::updateExtraCompilers() return fileNode->fileType() == ProjectExplorer::FileType::Form; return false; }; - const FilePaths uiFiles = project()->files(uiMatcher); + const FilePaths uiFiles = q->project()->files(uiMatcher); for (const FilePath &uiFile : uiFiles) { FilePath generated = uiFile.parentDir(); generated = generated.pathAppended("/ui_" + uiFile.baseName() + ".py"); int index = Utils::indexOf(oldCompilers, [&](PySideUicExtraCompiler *oldCompiler) { return oldCompiler->pySideUicPath() == m_pySideUicPath - && oldCompiler->project() == project() && oldCompiler->source() == uiFile + && oldCompiler->project() == q->project() && oldCompiler->source() == uiFile && oldCompiler->targets() == FilePaths{generated}; }); if (index < 0) { m_extraCompilers << new PySideUicExtraCompiler(m_pySideUicPath, - project(), + q->project(), uiFile, {generated}, - this); + q); } else { m_extraCompilers << oldCompilers.takeAt(index); } @@ -310,7 +362,7 @@ void PythonRunConfiguration::updateExtraCompilers() } for (LanguageClient::Client *client : LanguageClient::LanguageClientManager::clients()) { if (auto pylsClient = qobject_cast(client)) - pylsClient->updateExtraCompilers(project(), m_extraCompilers); + pylsClient->updateExtraCompilers(q->project(), m_extraCompilers); } qDeleteAll(oldCompilers); } diff --git a/src/plugins/python/pythonrunconfiguration.h b/src/plugins/python/pythonrunconfiguration.h index 0def9af0a2c..605e3c42aae 100644 --- a/src/plugins/python/pythonrunconfiguration.h +++ b/src/plugins/python/pythonrunconfiguration.h @@ -6,8 +6,11 @@ #include #include +#include + namespace Python::Internal { +class PythonRunConfigurationPrivate; class PySideUicExtraCompiler; class PythonRunConfiguration : public ProjectExplorer::RunConfiguration @@ -20,11 +23,7 @@ public: QList extraCompilers() const; private: - void checkForPySide(const Utils::FilePath &python); - void updateExtraCompilers(); - Utils::FilePath m_pySideUicPath; - - QList m_extraCompilers; + PythonRunConfigurationPrivate *d = nullptr; }; class PythonRunConfigurationFactory : public ProjectExplorer::RunConfigurationFactory