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 &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 &packages); + void setTargetPath(const Utils::FilePath &targetPath); void run(); signals: @@ -83,6 +84,7 @@ private: const Utils::FilePath m_python; QList m_packages; Utils::FilePath m_requirementsFile; + Utils::FilePath m_targetPath; Utils::Process m_process; QFutureInterface m_future; QFutureWatcher 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 &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 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 ®ex : {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 document) + QPointer 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 document); + QPointer document, + const Utils::FilePath &pylsPath); QHash> m_infoBarEntries; QHash>> 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 #include +#include + 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 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