Python: collect pip package info in another thread

Change-Id: I70a9066fddf812ce9bde5467913bb2bad98e2d0e
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2023-01-25 12:29:08 +01:00
parent b05a34b81e
commit 471e67d1a6
4 changed files with 164 additions and 78 deletions

View File

@@ -3,6 +3,7 @@
#include "pipsupport.h" #include "pipsupport.h"
#include "pythonplugin.h"
#include "pythontr.h" #include "pythontr.h"
#include <coreplugin/messagemanager.h> #include <coreplugin/messagemanager.h>
@@ -15,6 +16,7 @@
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/mimeutils.h> #include <utils/mimeutils.h>
#include <utils/qtcprocess.h> #include <utils/qtcprocess.h>
#include <utils/runextensions.h>
using namespace Utils; using namespace Utils;
@@ -100,36 +102,6 @@ void PipInstallTask::handleError()
Core::MessageManager::writeSilently(stdErr); 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) void PipPackageInfo::parseField(const QString &field, const QStringList &data)
{ {
if (field.isEmpty()) if (field.isEmpty())
@@ -162,4 +134,55 @@ void PipPackageInfo::parseField(const QString &field, const QStringList &data)
} }
} }
Pip *Pip::instance(const FilePath &python)
{
static QMap<FilePath, Pip *> pips;
auto it = pips.find(python);
if (it == pips.end())
it = pips.insert(python, new Pip(python));
return it.value();
}
QFuture<PipPackageInfo> 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 } // Python::Internal

View File

@@ -12,25 +12,6 @@
namespace Python::Internal { 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 class PipPackageInfo
{ {
public: public:
@@ -49,6 +30,37 @@ public:
void parseField(const QString &field, const QStringList &value); 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<PipPackageInfo> 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 class PipInstallTask : public QObject
{ {
Q_OBJECT Q_OBJECT

View File

@@ -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<PySideUicExtraCompiler *> m_extraCompilers;
QFutureWatcher<PipPackageInfo> m_watcher;
QMetaObject::Connection m_watcherConnection;
};
PythonRunConfiguration::PythonRunConfiguration(Target *target, Id id) PythonRunConfiguration::PythonRunConfiguration(Target *target, Id id)
: RunConfiguration(target, id) : RunConfiguration(target, id)
, d(new PythonRunConfigurationPrivate(this))
{ {
auto interpreterAspect = addAspect<InterpreterAspect>(); auto interpreterAspect = addAspect<InterpreterAspect>();
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter"); 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, &RunConfiguration::update);
connect(target, &Target::buildSystemUpdated, this, &PythonRunConfiguration::updateExtraCompilers); connect(target, &Target::buildSystemUpdated, this, [this]() { d->updateExtraCompilers(); });
currentInterpreterChanged(); currentInterpreterChanged();
setRunnableModifier([](Runnable &r) { setRunnableModifier([](Runnable &r) {
@@ -195,25 +222,53 @@ PythonRunConfiguration::PythonRunConfiguration(Target *target, Id id)
connect(PySideInstaller::instance(), &PySideInstaller::pySideInstalled, this, connect(PySideInstaller::instance(), &PySideInstaller::pySideInstalled, this,
[this](const FilePath &python) { [this](const FilePath &python) {
if (python == aspect<InterpreterAspect>()->currentInterpreter().command) if (python == aspect<InterpreterAspect>()->currentInterpreter().command)
checkForPySide(python); d->checkForPySide(python);
}); });
} }
PythonRunConfiguration::~PythonRunConfiguration() PythonRunConfiguration::~PythonRunConfiguration()
{ {
qDeleteAll(m_extraCompilers); delete d;
} }
struct PythonTools void PythonRunConfigurationPrivate::checkForPySide(const FilePath &python)
{ {
checkForPySide(python, "PySide6-Essentials");
}
void PythonRunConfigurationPrivate::checkForPySide(const FilePath &python,
const QString &pySidePackageName)
{
const PipPackage package(pySidePackageName);
QObject::disconnect(m_watcherConnection);
m_watcherConnection = QObject::connect(&m_watcher,
&QFutureWatcher<PipPackageInfo>::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 pySideProjectPath;
FilePath pySideUicPath; FilePath pySideUicPath;
}; };
void PythonRunConfiguration::checkForPySide(const FilePath &python)
{
BuildStepList *buildSteps = target()->activeBuildConfiguration()->buildSteps();
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 auto findPythonTools = [](const FilePaths &files,
const FilePath &location, const FilePath &location,
@@ -239,13 +294,10 @@ void PythonRunConfiguration::checkForPySide(const FilePath &python)
return {}; return {};
}; };
const PipPackage pySide6EssentialPackage("PySide6-Essentials"); PythonTools pythonTools = findPythonTools(pySideInfo.files, pySideInfo.location, python);
PipPackageInfo info = pySide6EssentialPackage.info(python); if (!pythonTools.pySideProjectPath.isExecutableFile() && requestedPackageName != "PySide6") {
PythonTools pythonTools = findPythonTools(info.files, info.location, python); checkForPySide(python, "PySide6");
if (!pythonTools.pySideProjectPath.isExecutableFile()) { return;
const PipPackage pySide6Package("PySide6");
info = pySide6Package.info(python);
pythonTools = findPythonTools(info.files, info.location, python);
} }
m_pySideUicPath = pythonTools.pySideUicPath; m_pySideUicPath = pythonTools.pySideUicPath;
@@ -259,7 +311,7 @@ void PythonRunConfiguration::checkForPySide(const FilePath &python)
void PythonRunConfiguration::currentInterpreterChanged() void PythonRunConfiguration::currentInterpreterChanged()
{ {
const FilePath python = aspect<InterpreterAspect>()->currentInterpreter().command; const FilePath python = aspect<InterpreterAspect>()->currentInterpreter().command;
checkForPySide(python); d->checkForPySide(python);
for (FilePath &file : project()->files(Project::AllFiles)) { for (FilePath &file : project()->files(Project::AllFiles)) {
if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) { if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) {
@@ -274,10 +326,10 @@ void PythonRunConfiguration::currentInterpreterChanged()
QList<PySideUicExtraCompiler *> PythonRunConfiguration::extraCompilers() const QList<PySideUicExtraCompiler *> PythonRunConfiguration::extraCompilers() const
{ {
return m_extraCompilers; return d->m_extraCompilers;
} }
void PythonRunConfiguration::updateExtraCompilers() void PythonRunConfigurationPrivate::updateExtraCompilers()
{ {
QList<PySideUicExtraCompiler *> oldCompilers = m_extraCompilers; QList<PySideUicExtraCompiler *> oldCompilers = m_extraCompilers;
m_extraCompilers.clear(); m_extraCompilers.clear();
@@ -288,21 +340,21 @@ void PythonRunConfiguration::updateExtraCompilers()
return fileNode->fileType() == ProjectExplorer::FileType::Form; return fileNode->fileType() == ProjectExplorer::FileType::Form;
return false; return false;
}; };
const FilePaths uiFiles = project()->files(uiMatcher); const FilePaths uiFiles = q->project()->files(uiMatcher);
for (const FilePath &uiFile : uiFiles) { for (const FilePath &uiFile : uiFiles) {
FilePath generated = uiFile.parentDir(); FilePath generated = uiFile.parentDir();
generated = generated.pathAppended("/ui_" + uiFile.baseName() + ".py"); generated = generated.pathAppended("/ui_" + uiFile.baseName() + ".py");
int index = Utils::indexOf(oldCompilers, [&](PySideUicExtraCompiler *oldCompiler) { int index = Utils::indexOf(oldCompilers, [&](PySideUicExtraCompiler *oldCompiler) {
return oldCompiler->pySideUicPath() == m_pySideUicPath return oldCompiler->pySideUicPath() == m_pySideUicPath
&& oldCompiler->project() == project() && oldCompiler->source() == uiFile && oldCompiler->project() == q->project() && oldCompiler->source() == uiFile
&& oldCompiler->targets() == FilePaths{generated}; && oldCompiler->targets() == FilePaths{generated};
}); });
if (index < 0) { if (index < 0) {
m_extraCompilers << new PySideUicExtraCompiler(m_pySideUicPath, m_extraCompilers << new PySideUicExtraCompiler(m_pySideUicPath,
project(), q->project(),
uiFile, uiFile,
{generated}, {generated},
this); q);
} else { } else {
m_extraCompilers << oldCompilers.takeAt(index); m_extraCompilers << oldCompilers.takeAt(index);
} }
@@ -310,7 +362,7 @@ void PythonRunConfiguration::updateExtraCompilers()
} }
for (LanguageClient::Client *client : LanguageClient::LanguageClientManager::clients()) { for (LanguageClient::Client *client : LanguageClient::LanguageClientManager::clients()) {
if (auto pylsClient = qobject_cast<PyLSClient *>(client)) if (auto pylsClient = qobject_cast<PyLSClient *>(client))
pylsClient->updateExtraCompilers(project(), m_extraCompilers); pylsClient->updateExtraCompilers(q->project(), m_extraCompilers);
} }
qDeleteAll(oldCompilers); qDeleteAll(oldCompilers);
} }

View File

@@ -6,8 +6,11 @@
#include <projectexplorer/runconfiguration.h> #include <projectexplorer/runconfiguration.h>
#include <projectexplorer/runcontrol.h> #include <projectexplorer/runcontrol.h>
#include <QFutureWatcher>
namespace Python::Internal { namespace Python::Internal {
class PythonRunConfigurationPrivate;
class PySideUicExtraCompiler; class PySideUicExtraCompiler;
class PythonRunConfiguration : public ProjectExplorer::RunConfiguration class PythonRunConfiguration : public ProjectExplorer::RunConfiguration
@@ -20,11 +23,7 @@ public:
QList<PySideUicExtraCompiler *> extraCompilers() const; QList<PySideUicExtraCompiler *> extraCompilers() const;
private: private:
void checkForPySide(const Utils::FilePath &python); PythonRunConfigurationPrivate *d = nullptr;
void updateExtraCompilers();
Utils::FilePath m_pySideUicPath;
QList<PySideUicExtraCompiler *> m_extraCompilers;
}; };
class PythonRunConfigurationFactory : public ProjectExplorer::RunConfigurationFactory class PythonRunConfigurationFactory : public ProjectExplorer::RunConfigurationFactory