forked from qt-creator/qt-creator
Python: collect pip package info in another thread
Change-Id: I70a9066fddf812ce9bde5467913bb2bad98e2d0e Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
FilePath pySideProjectPath;
|
checkForPySide(python, "PySide6-Essentials");
|
||||||
FilePath pySideUicPath;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
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<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 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 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user