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 "pythonplugin.h"
|
||||
#include "pythontr.h"
|
||||
|
||||
#include <coreplugin/messagemanager.h>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/mimeutils.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
#include <utils/runextensions.h>
|
||||
|
||||
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<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
|
||||
|
||||
@@ -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<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
|
||||
{
|
||||
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)
|
||||
: RunConfiguration(target, id)
|
||||
, d(new PythonRunConfigurationPrivate(this))
|
||||
{
|
||||
auto interpreterAspect = addAspect<InterpreterAspect>();
|
||||
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<InterpreterAspect>()->currentInterpreter().command)
|
||||
checkForPySide(python);
|
||||
d->checkForPySide(python);
|
||||
});
|
||||
}
|
||||
|
||||
PythonRunConfiguration::~PythonRunConfiguration()
|
||||
{
|
||||
qDeleteAll(m_extraCompilers);
|
||||
delete d;
|
||||
}
|
||||
|
||||
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 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 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<InterpreterAspect>()->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<PySideUicExtraCompiler *> PythonRunConfiguration::extraCompilers() const
|
||||
{
|
||||
return m_extraCompilers;
|
||||
return d->m_extraCompilers;
|
||||
}
|
||||
|
||||
void PythonRunConfiguration::updateExtraCompilers()
|
||||
void PythonRunConfigurationPrivate::updateExtraCompilers()
|
||||
{
|
||||
QList<PySideUicExtraCompiler *> 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<PyLSClient *>(client))
|
||||
pylsClient->updateExtraCompilers(project(), m_extraCompilers);
|
||||
pylsClient->updateExtraCompilers(q->project(), m_extraCompilers);
|
||||
}
|
||||
qDeleteAll(oldCompilers);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
#include <projectexplorer/runconfiguration.h>
|
||||
#include <projectexplorer/runcontrol.h>
|
||||
|
||||
#include <QFutureWatcher>
|
||||
|
||||
namespace Python::Internal {
|
||||
|
||||
class PythonRunConfigurationPrivate;
|
||||
class PySideUicExtraCompiler;
|
||||
|
||||
class PythonRunConfiguration : public ProjectExplorer::RunConfiguration
|
||||
@@ -20,11 +23,7 @@ public:
|
||||
QList<PySideUicExtraCompiler *> extraCompilers() const;
|
||||
|
||||
private:
|
||||
void checkForPySide(const Utils::FilePath &python);
|
||||
void updateExtraCompilers();
|
||||
Utils::FilePath m_pySideUicPath;
|
||||
|
||||
QList<PySideUicExtraCompiler *> m_extraCompilers;
|
||||
PythonRunConfigurationPrivate *d = nullptr;
|
||||
};
|
||||
|
||||
class PythonRunConfigurationFactory : public ProjectExplorer::RunConfigurationFactory
|
||||
|
||||
Reference in New Issue
Block a user