forked from qt-creator/qt-creator
Python: detect virtual environments for documents and projects
After opening a document or project the directory hierarchy is looked up for a Scripts/(activate && python.exe) on windows or bin/(activate && python) on unix. This is the usual structure of python virtual environments. If such a folder is found add the python from that folder to the list of configured interpreters in the settings, set it as the current interpreter for the project and try to open the corresponding language server. Change-Id: I038c309ea2988f9370194330d250d1515beac0a0 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -260,8 +260,10 @@ PythonRunConfiguration::PythonRunConfiguration(Target *target, Core::Id id)
|
|||||||
connect(PythonSettings::instance(), &PythonSettings::interpretersChanged,
|
connect(PythonSettings::instance(), &PythonSettings::interpretersChanged,
|
||||||
interpreterAspect, &InterpreterAspect::updateInterpreters);
|
interpreterAspect, &InterpreterAspect::updateInterpreters);
|
||||||
|
|
||||||
interpreterAspect->updateInterpreters(PythonSettings::interpreters());
|
QList<Interpreter> interpreters = PythonSettings::detectPythonVenvs(project()->projectDirectory());
|
||||||
interpreterAspect->setDefaultInterpreter(PythonSettings::defaultInterpreter());
|
aspect<InterpreterAspect>()->updateInterpreters(PythonSettings::interpreters());
|
||||||
|
aspect<InterpreterAspect>()->setDefaultInterpreter(
|
||||||
|
interpreters.isEmpty() ? PythonSettings::defaultInterpreter() : interpreters.first());
|
||||||
|
|
||||||
auto scriptAspect = addAspect<MainScriptAspect>();
|
auto scriptAspect = addAspect<MainScriptAspect>();
|
||||||
scriptAspect->setSettingsKey("PythonEditor.RunConfiguation.Script");
|
scriptAspect->setSettingsKey("PythonEditor.RunConfiguation.Script");
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ public:
|
|||||||
InterpreterOptionsPage();
|
InterpreterOptionsPage();
|
||||||
|
|
||||||
void setInterpreter(const QList<Interpreter> &interpreters) { m_interpreters = interpreters; }
|
void setInterpreter(const QList<Interpreter> &interpreters) { m_interpreters = interpreters; }
|
||||||
|
void addInterpreter(const Interpreter &interpreter) { m_interpreters << interpreter; }
|
||||||
QList<Interpreter> interpreters() const { return m_interpreters; }
|
QList<Interpreter> interpreters() const { return m_interpreters; }
|
||||||
void setDefaultInterpreter(const QString &defaultId)
|
void setDefaultInterpreter(const QString &defaultId)
|
||||||
{ m_defaultInterpreterId = defaultId; }
|
{ m_defaultInterpreterId = defaultId; }
|
||||||
@@ -278,6 +279,7 @@ Interpreter::Interpreter(const FilePath &python, const QString &defaultName, boo
|
|||||||
{
|
{
|
||||||
SynchronousProcess pythonProcess;
|
SynchronousProcess pythonProcess;
|
||||||
pythonProcess.setProcessChannelMode(QProcess::MergedChannels);
|
pythonProcess.setProcessChannelMode(QProcess::MergedChannels);
|
||||||
|
pythonProcess.setTimeoutS(1);
|
||||||
SynchronousProcessResponse response = pythonProcess.runBlocking(
|
SynchronousProcessResponse response = pythonProcess.runBlocking(
|
||||||
CommandLine(python, {"--version"}));
|
CommandLine(python, {"--version"}));
|
||||||
if (response.result == SynchronousProcessResponse::Finished)
|
if (response.result == SynchronousProcessResponse::Finished)
|
||||||
@@ -467,9 +469,15 @@ void PythonSettings::setInterpreter(const QList<Interpreter> &interpreters, cons
|
|||||||
{
|
{
|
||||||
interpreterOptionsPage().setInterpreter(interpreters);
|
interpreterOptionsPage().setInterpreter(interpreters);
|
||||||
interpreterOptionsPage().setDefaultInterpreter(defaultId);
|
interpreterOptionsPage().setDefaultInterpreter(defaultId);
|
||||||
toSettings(Core::ICore::settings(), {interpreters, defaultId});
|
saveSettings();
|
||||||
if (QTC_GUARD(settingsInstance))
|
}
|
||||||
emit settingsInstance->interpretersChanged(interpreters, defaultId);
|
|
||||||
|
void PythonSettings::addInterpreter(const Interpreter &interpreter, bool isDefault)
|
||||||
|
{
|
||||||
|
interpreterOptionsPage().addInterpreter(interpreter);
|
||||||
|
if (isDefault)
|
||||||
|
interpreterOptionsPage().setDefaultInterpreter(interpreter.id);
|
||||||
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
PythonSettings *PythonSettings::instance()
|
PythonSettings *PythonSettings::instance()
|
||||||
@@ -478,6 +486,52 @@ PythonSettings *PythonSettings::instance()
|
|||||||
return settingsInstance;
|
return settingsInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<Interpreter> PythonSettings::detectPythonVenvs(const FilePath &path)
|
||||||
|
{
|
||||||
|
QList<Interpreter> result;
|
||||||
|
QDir dir = path.toFileInfo().isDir() ? QDir(path.toString()) : path.toFileInfo().dir();
|
||||||
|
if (dir.exists()) {
|
||||||
|
const QString venvPython = HostOsInfo::withExecutableSuffix("python");
|
||||||
|
const QString activatePath = HostOsInfo::isWindowsHost() ? QString{"Scripts"}
|
||||||
|
: QString{"bin"};
|
||||||
|
do {
|
||||||
|
for (const QString &directory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
|
||||||
|
if (dir.cd(directory)) {
|
||||||
|
if (dir.cd(activatePath)) {
|
||||||
|
if (dir.exists("activate") && dir.exists(venvPython)) {
|
||||||
|
FilePath python = FilePath::fromString(dir.absoluteFilePath(venvPython));
|
||||||
|
dir.cdUp();
|
||||||
|
const QString defaultName = QString("Python (%1 Virtual Environment)")
|
||||||
|
.arg(dir.dirName());
|
||||||
|
Interpreter interpreter
|
||||||
|
= Utils::findOrDefault(PythonSettings::interpreters(),
|
||||||
|
Utils::equal(&Interpreter::command, python));
|
||||||
|
if (interpreter.command.isEmpty()) {
|
||||||
|
interpreter = Interpreter(python, defaultName);
|
||||||
|
PythonSettings::addInterpreter(interpreter);
|
||||||
|
}
|
||||||
|
result << interpreter;
|
||||||
|
} else {
|
||||||
|
dir.cdUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dir.cdUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (dir.cdUp());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PythonSettings::saveSettings()
|
||||||
|
{
|
||||||
|
const QList<Interpreter> &interpreters = interpreterOptionsPage().interpreters();
|
||||||
|
const QString &defaultId = interpreterOptionsPage().defaultInterpreter().id;
|
||||||
|
toSettings(Core::ICore::settings(), {interpreters, defaultId});
|
||||||
|
if (QTC_GUARD(settingsInstance))
|
||||||
|
emit settingsInstance->interpretersChanged(interpreters, defaultId);
|
||||||
|
}
|
||||||
|
|
||||||
QList<Interpreter> PythonSettings::interpreters()
|
QList<Interpreter> PythonSettings::interpreters()
|
||||||
{
|
{
|
||||||
return interpreterOptionsPage().interpreters();
|
return interpreterOptionsPage().interpreters();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <utils/fileutils.h>
|
#include <utils/fileutils.h>
|
||||||
|
#include <utils/optional.h>
|
||||||
|
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
@@ -57,13 +58,18 @@ public:
|
|||||||
static QList<Interpreter> interpreters();
|
static QList<Interpreter> interpreters();
|
||||||
static Interpreter defaultInterpreter();
|
static Interpreter defaultInterpreter();
|
||||||
static void setInterpreter(const QList<Interpreter> &interpreters, const QString &defaultId);
|
static void setInterpreter(const QList<Interpreter> &interpreters, const QString &defaultId);
|
||||||
|
static void addInterpreter(const Interpreter &interpreter, bool isDefault = false);
|
||||||
static PythonSettings *instance();
|
static PythonSettings *instance();
|
||||||
|
|
||||||
|
static QList<Interpreter> detectPythonVenvs(const Utils::FilePath &path);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void interpretersChanged(const QList<Interpreter> &interpreters, const QString &defaultId);
|
void interpretersChanged(const QList<Interpreter> &interpreters, const QString &defaultId);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PythonSettings();
|
PythonSettings();
|
||||||
|
|
||||||
|
static void saveSettings();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
|
|||||||
@@ -175,6 +175,11 @@ static FilePath detectPython(const FilePath &documentPath)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check whether this file is inside a python virtual environment
|
||||||
|
QList<Interpreter> venvInterpreters = PythonSettings::detectPythonVenvs(documentPath);
|
||||||
|
if (!python.exists())
|
||||||
|
python = venvInterpreters.value(0).command;
|
||||||
|
|
||||||
if (!python.exists())
|
if (!python.exists())
|
||||||
python = PythonSettings::defaultInterpreter().command;
|
python = PythonSettings::defaultInterpreter().command;
|
||||||
|
|
||||||
@@ -248,7 +253,13 @@ public:
|
|||||||
? QString{"python-language-server[pyflakes]"}
|
? QString{"python-language-server[pyflakes]"}
|
||||||
: QString{"python-language-server[all]"};
|
: QString{"python-language-server[all]"};
|
||||||
|
|
||||||
m_process.start(m_python.toString(), {"-m", "pip", "install", "--user", pylsVersion});
|
QStringList arguments = {"-m", "pip", "install", pylsVersion};
|
||||||
|
|
||||||
|
// add --user to global pythons, but skip it for venv pythons
|
||||||
|
if (!QDir(m_python.parentDir().toString()).exists("activate"))
|
||||||
|
arguments << "--user";
|
||||||
|
|
||||||
|
m_process.start(m_python.toString(), arguments);
|
||||||
|
|
||||||
Core::MessageManager::write(tr("Running \"%1 %2\" to install Python language server")
|
Core::MessageManager::write(tr("Running \"%1 %2\" to install Python language server")
|
||||||
.arg(m_process.program(), m_process.arguments().join(' ')));
|
.arg(m_process.program(), m_process.arguments().join(' ')));
|
||||||
|
|||||||
Reference in New Issue
Block a user