aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/python
diff options
context:
space:
mode:
authorDavid Schulz <david.schulz@qt.io>2023-11-27 14:15:29 +0100
committerDavid Schulz <david.schulz@qt.io>2023-12-13 11:26:16 +0000
commitfbe054116aff3ce5bd714a1982675c4ecc82d18a (patch)
treec9227e03b5cd0c8d22047df559f9f1645108e46f /src/plugins/python
parent12004190595cdf3dc567120d8310e5e8a4476385 (diff)
Python: Avoid polluting global or virtual environments with pylsp
Install the language server into the Qt Creator resource directory instead. Reuse the server for all interpreters with the same version. This also reduces the amount of editor toolbars asking the user to install a language server for a specific interpreter. Change-Id: I48ef4ad30fe0097ee8d2b855b0f278e98be5ce57 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Diffstat (limited to 'src/plugins/python')
-rw-r--r--src/plugins/python/pipsupport.cpp12
-rw-r--r--src/plugins/python/pipsupport.h2
-rw-r--r--src/plugins/python/pythonlanguageclient.cpp76
-rw-r--r--src/plugins/python/pythonlanguageclient.h3
-rw-r--r--src/plugins/python/pythonutils.cpp26
-rw-r--r--src/plugins/python/pythonutils.h2
6 files changed, 68 insertions, 53 deletions
diff --git a/src/plugins/python/pipsupport.cpp b/src/plugins/python/pipsupport.cpp
index a4d29a56303..dd213248961 100644
--- a/src/plugins/python/pipsupport.cpp
+++ b/src/plugins/python/pipsupport.cpp
@@ -55,6 +55,11 @@ void PipInstallTask::setPackages(const QList<PipPackage> &packages)
m_packages = packages;
}
+void PipInstallTask::setTargetPath(const Utils::FilePath &targetPath)
+{
+ m_targetPath = targetPath;
+}
+
void PipInstallTask::run()
{
if (m_packages.isEmpty() && m_requirementsFile.isEmpty()) {
@@ -75,9 +80,10 @@ void PipInstallTask::run()
}
}
- // add --user to global pythons, but skip it for venv pythons
- if (!QDir(m_python.parentDir().toString()).exists("activate"))
- arguments << "--user";
+ if (!m_targetPath.isEmpty())
+ arguments << "-t" << m_targetPath.toString();
+ else if (!QDir(m_python.parentDir().toString()).exists("activate"))
+ arguments << "--user"; // add --user to global pythons, but skip it for venv pythons
m_process.setCommand({m_python, arguments});
m_process.setTerminalMode(TerminalMode::Run);
diff --git a/src/plugins/python/pipsupport.h b/src/plugins/python/pipsupport.h
index 586a2f55e70..7b201ed5586 100644
--- a/src/plugins/python/pipsupport.h
+++ b/src/plugins/python/pipsupport.h
@@ -67,6 +67,7 @@ public:
void setWorkingDirectory(const Utils::FilePath &workingDirectory);
void addPackage(const PipPackage &package);
void setPackages(const QList<PipPackage> &packages);
+ void setTargetPath(const Utils::FilePath &targetPath);
void run();
signals:
@@ -83,6 +84,7 @@ private:
const Utils::FilePath m_python;
QList<PipPackage> m_packages;
Utils::FilePath m_requirementsFile;
+ Utils::FilePath m_targetPath;
Utils::Process m_process;
QFutureInterface<void> m_future;
QFutureWatcher<void> m_watcher;
diff --git a/src/plugins/python/pythonlanguageclient.cpp b/src/plugins/python/pythonlanguageclient.cpp
index b059401dd35..6383cca20bd 100644
--- a/src/plugins/python/pythonlanguageclient.cpp
+++ b/src/plugins/python/pythonlanguageclient.cpp
@@ -72,64 +72,33 @@ static QHash<FilePath, PyLSClient*> &pythonClients()
return clients;
}
-FilePath getPylsModulePath(CommandLine pylsCommand)
+static FilePath pyLspPath(const FilePath &python)
{
- static QMutex mutex; // protect the access to the cache
- QMutexLocker locker(&mutex);
- static QMap<FilePath, FilePath> cache;
- const FilePath &modulePath = cache.value(pylsCommand.executable());
- if (!modulePath.isEmpty())
- return modulePath;
-
- pylsCommand.addArg("-h");
-
- Process pythonProcess;
- Environment env = pythonProcess.environment();
- env.set("PYTHONVERBOSE", "x");
- pythonProcess.setEnvironment(env);
- pythonProcess.setCommand(pylsCommand);
- pythonProcess.runBlocking();
-
- static const QString pylsInitPattern = "(.*)"
- + QRegularExpression::escape(
- QDir::toNativeSeparators("/pylsp/__init__.py"))
- + '$';
- static const QRegularExpression regexCached(" matches " + pylsInitPattern,
- QRegularExpression::MultilineOption);
- static const QRegularExpression regexNotCached(" code object from " + pylsInitPattern,
- QRegularExpression::MultilineOption);
-
- const QString output = pythonProcess.allOutput();
- for (const auto &regex : {regexCached, regexNotCached}) {
- const QRegularExpressionMatch result = regex.match(output);
- if (result.hasMatch()) {
- const FilePath &modulePath = FilePath::fromUserInput(result.captured(1));
- cache[pylsCommand.executable()] = modulePath;
- return modulePath;
- }
- }
- return {};
+ if (python.needsDevice())
+ return {};
+ const QString version = pythonVersion(python);
+ if (version.isEmpty())
+ return {};
+ return Core::ICore::userResourcePath("pylsp") / FileUtils::fileSystemFriendlyName(version);
}
static PythonLanguageServerState checkPythonLanguageServer(const FilePath &python)
{
using namespace LanguageClient;
- const CommandLine pythonLShelpCommand(python, {"-m", "pylsp", "-h"});
- const FilePath &modulePath = getPylsModulePath(pythonLShelpCommand);
+ auto lspPath = pyLspPath(python);
+ if (lspPath.isEmpty())
+ return {PythonLanguageServerState::CanNotBeInstalled, FilePath()};
+
+ if (lspPath.pathAppended("bin").pathAppended("pylsp").withExecutableSuffix().exists())
+ return {PythonLanguageServerState::AlreadyInstalled, lspPath};
Process pythonProcess;
pythonProcess.setTimeoutS(2);
- pythonProcess.setCommand(pythonLShelpCommand);
- pythonProcess.runBlocking();
- if (pythonProcess.allOutput().contains("Python Language Server"))
- return {PythonLanguageServerState::AlreadyInstalled, modulePath};
-
pythonProcess.setCommand({python, {"-m", "pip", "-V"}});
pythonProcess.runBlocking();
if (pythonProcess.allOutput().startsWith("pip "))
- return {PythonLanguageServerState::CanBeInstalled, FilePath()};
- else
- return {PythonLanguageServerState::CanNotBeInstalled, FilePath()};
+ return {PythonLanguageServerState::CanBeInstalled, lspPath};
+ return {PythonLanguageServerState::CanNotBeInstalled, FilePath()};
}
@@ -150,6 +119,12 @@ protected:
env.appendOrSet("PYTHONPATH",
m_extraPythonPath.path().toString(),
OsSpecificAspects::pathListSeparator(env.osType()));
+ const FilePath lspPath = pyLspPath(m_cmd.executable());
+ if (!lspPath.isEmpty() && lspPath.exists()) {
+ env.appendOrSet("PYTHONPATH",
+ pyLspPath(m_cmd.executable()).toString(),
+ OsSpecificAspects::pathListSeparator(env.osType()));
+ }
setEnvironment(env);
}
StdIOClientInterface::startImpl();
@@ -293,7 +268,8 @@ PyLSConfigureAssistant *PyLSConfigureAssistant::instance()
}
void PyLSConfigureAssistant::installPythonLanguageServer(const FilePath &python,
- QPointer<TextEditor::TextDocument> document)
+ QPointer<TextEditor::TextDocument> document,
+ const FilePath &pylsPath)
{
document->infoBar()->removeInfo(installPylsInfoBarId);
@@ -317,6 +293,7 @@ void PyLSConfigureAssistant::installPythonLanguageServer(const FilePath &python,
install->deleteLater();
});
+ install->setTargetPath(pylsPath);
install->setPackages({PipPackage{"python-lsp-server[all]", "Python Language Server"}});
install->run();
}
@@ -376,8 +353,9 @@ void PyLSConfigureAssistant::handlePyLSState(const FilePath &python,
Utils::InfoBarEntry info(installPylsInfoBarId,
message,
Utils::InfoBarEntry::GlobalSuppression::Enabled);
- info.addCustomButton(Tr::tr("Install"),
- [=]() { installPythonLanguageServer(python, document); });
+ info.addCustomButton(Tr::tr("Install"), [=]() {
+ installPythonLanguageServer(python, document, state.pylsModulePath);
+ });
infoBar->addInfo(info);
m_infoBarEntries[python] << document;
} else if (state.state == PythonLanguageServerState::AlreadyInstalled) {
diff --git a/src/plugins/python/pythonlanguageclient.h b/src/plugins/python/pythonlanguageclient.h
index d3c88f046f2..fa0fb493e78 100644
--- a/src/plugins/python/pythonlanguageclient.h
+++ b/src/plugins/python/pythonlanguageclient.h
@@ -64,7 +64,8 @@ private:
TextEditor::TextDocument *document);
void resetEditorInfoBar(TextEditor::TextDocument *document);
void installPythonLanguageServer(const Utils::FilePath &python,
- QPointer<TextEditor::TextDocument> document);
+ QPointer<TextEditor::TextDocument> document,
+ const Utils::FilePath &pylsPath);
QHash<Utils::FilePath, QList<TextEditor::TextDocument *>> m_infoBarEntries;
QHash<TextEditor::TextDocument *, QPointer<QFutureWatcher<PythonLanguageServerState>>>
diff --git a/src/plugins/python/pythonutils.cpp b/src/plugins/python/pythonutils.cpp
index d1307a351d3..096520dd7ec 100644
--- a/src/plugins/python/pythonutils.cpp
+++ b/src/plugins/python/pythonutils.cpp
@@ -21,6 +21,8 @@
#include <utils/mimeutils.h>
#include <utils/process.h>
+#include <QReadLocker>
+
using namespace ProjectExplorer;
using namespace Utils;
@@ -208,4 +210,28 @@ bool pipIsUsable(const Utils::FilePath &python)
return process.result() == ProcessResult::FinishedWithSuccess;
}
+QString pythonVersion(const FilePath &python)
+{
+ static QReadWriteLock lock;
+ static QMap<FilePath, QString> versionCache;
+
+ {
+ QReadLocker locker(&lock);
+ auto it = versionCache.constFind(python);
+ if (it != versionCache.constEnd())
+ return *it;
+ }
+
+ Process p;
+ p.setCommand({python, {"--version"}});
+ p.runBlocking();
+ if (p.result() == Utils::ProcessResult::FinishedWithSuccess) {
+ const QString version = p.readAllStandardOutput().trimmed();
+ QWriteLocker locker(&lock);
+ versionCache.insert(python, version);
+ return version;
+ }
+ return QString();
+}
+
} // Python::Internal
diff --git a/src/plugins/python/pythonutils.h b/src/plugins/python/pythonutils.h
index b54a42d93af..fe81f6b8afb 100644
--- a/src/plugins/python/pythonutils.h
+++ b/src/plugins/python/pythonutils.h
@@ -24,4 +24,6 @@ bool isVenvPython(const Utils::FilePath &python);
bool venvIsUsable(const Utils::FilePath &python);
bool pipIsUsable(const Utils::FilePath &python);
+QString pythonVersion(const Utils::FilePath &python);
+
} // Python::Internal