diff --git a/src/plugins/python/CMakeLists.txt b/src/plugins/python/CMakeLists.txt index 42984eb91ee..53e1a960bcf 100644 --- a/src/plugins/python/CMakeLists.txt +++ b/src/plugins/python/CMakeLists.txt @@ -2,6 +2,7 @@ add_qtc_plugin(Python DEPENDS QmlJS PLUGIN_DEPENDS Core LanguageClient ProjectExplorer TextEditor SOURCES + pipsupport.cpp pipsupport.h python.qrc pythonconstants.h pythoneditor.cpp pythoneditor.h diff --git a/src/plugins/python/pipsupport.cpp b/src/plugins/python/pipsupport.cpp new file mode 100644 index 00000000000..d42598c18be --- /dev/null +++ b/src/plugins/python/pipsupport.cpp @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "pipsupport.h" + +#include "pythonproject.h" +#include "pythonrunconfiguration.h" +#include "pythonsettings.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include + +using namespace Utils; + +namespace Python { +namespace Internal { + +static constexpr char pipInstallTaskId[] = "Python::pipInstallTask"; + +PipInstallTask::PipInstallTask(const Utils::FilePath &python) + : m_python(python) +{ + m_watcher.setFuture(m_future.future()); +} + +void PipInstallTask::setPackage(const PipPackage &package) +{ + m_package = package; +} + +void PipInstallTask::run() +{ + if (m_package.packageName.isEmpty()) { + emit finished(false); + return; + } + const QString taskTitle = tr("Install %1").arg(m_package.displayName); + Core::ProgressManager::addTask(m_future.future(), taskTitle, pipInstallTaskId); + connect(&m_process, &QtcProcess::finished, this, &PipInstallTask::installFinished); + connect(&m_process, &QtcProcess::readyReadStandardError, this, &PipInstallTask::hanleError); + connect(&m_process, &QtcProcess::readyReadStandardOutput, this, &PipInstallTask::handleOutput); + + connect(&m_killTimer, &QTimer::timeout, this, &PipInstallTask::cancel); + connect(&m_watcher, &QFutureWatcher::canceled, this, &PipInstallTask::cancel); + + QString package = m_package.packageName; + if (!m_package.version.isEmpty()) + package += "==" + m_package.version; + QStringList arguments = {"-m", "pip", "install", package}; + + // add --user to global pythons, but skip it for venv pythons + if (!QDir(m_python.parentDir().toString()).exists("activate")) + arguments << "--user"; + + m_process.setCommand({m_python, arguments}); + m_process.start(); + + Core::MessageManager::writeDisrupting( + tr("Running \"%1\" to install %2.") + .arg(m_process.commandLine().toUserOutput(), m_package.displayName)); + + m_killTimer.setSingleShot(true); + m_killTimer.start(5 /*minutes*/ * 60 * 1000); +} + +void PipInstallTask::cancel() +{ + m_process.stopProcess(); + Core::MessageManager::writeFlashing( + tr("The %1 installation was canceled by %2.") + .arg(m_package.displayName, m_killTimer.isActive() ? tr("user") : tr("time out"))); +} + +void PipInstallTask::installFinished() +{ + m_future.reportFinished(); + const bool success = m_process.result() == ProcessResult::FinishedWithSuccess; + if (!success) { + Core::MessageManager::writeFlashing( + tr("Installing the %1 failed with exit code %2") + .arg(m_package.displayName, m_process.exitCode())); + } + emit finished(success); +} + +void PipInstallTask::handleOutput() +{ + const QString &stdOut = QString::fromLocal8Bit(m_process.readAllStandardOutput().trimmed()); + if (!stdOut.isEmpty()) + Core::MessageManager::writeSilently(stdOut); +} + +void PipInstallTask::hanleError() +{ + const QString &stdErr = QString::fromLocal8Bit(m_process.readAllStandardError().trimmed()); + if (!stdErr.isEmpty()) + Core::MessageManager::writeSilently(stdErr); +} + +} // namespace Internal +} // namespace Python diff --git a/src/plugins/python/pipsupport.h b/src/plugins/python/pipsupport.h new file mode 100644 index 00000000000..28038ba1bd0 --- /dev/null +++ b/src/plugins/python/pipsupport.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +#include + +namespace Python { +namespace Internal { + +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 PipInstallTask : public QObject +{ + Q_OBJECT +public: + explicit PipInstallTask(const Utils::FilePath &python); + void setPackage(const PipPackage &package); + void run(); + +signals: + void finished(bool success); + +private: + void cancel(); + void installFinished(); + void handleOutput(); + void hanleError(); + + const Utils::FilePath m_python; + PipPackage m_package; + Utils::QtcProcess m_process; + QFutureInterface m_future; + QFutureWatcher m_watcher; + QTimer m_killTimer; +}; + +} // namespace Internal +} // namespace Python diff --git a/src/plugins/python/python.qbs b/src/plugins/python/python.qbs index 14ec87b08fe..2003192a318 100644 --- a/src/plugins/python/python.qbs +++ b/src/plugins/python/python.qbs @@ -17,6 +17,8 @@ QtcPlugin { Group { name: "General" files: [ + "pipsupport.cpp", + "pipsupport.h", "python.qrc", "pythonconstants.h", "pythoneditor.cpp", diff --git a/src/plugins/python/pythonlanguageclient.cpp b/src/plugins/python/pythonlanguageclient.cpp index 7e7827faf6b..5525d9ae042 100644 --- a/src/plugins/python/pythonlanguageclient.cpp +++ b/src/plugins/python/pythonlanguageclient.cpp @@ -25,6 +25,7 @@ #include "pythonlanguageclient.h" +#include "pipsupport.h" #include "pythonconstants.h" #include "pythonplugin.h" #include "pythonproject.h" @@ -586,95 +587,6 @@ static Client *registerLanguageServer(const FilePath &python) return client; } -class PythonLSInstallHelper : public QObject -{ - Q_OBJECT -public: - PythonLSInstallHelper(const FilePath &python, QPointer document) - : m_python(python) - , m_document(document) - { - m_watcher.setFuture(m_future.future()); - } - - void run() - { - Core::ProgressManager::addTask(m_future.future(), "Install PyLS", installPylsTaskId); - connect(&m_process, &QtcProcess::finished, this, &PythonLSInstallHelper::installFinished); - connect(&m_process, - &QtcProcess::readyReadStandardError, - this, - &PythonLSInstallHelper::errorAvailable); - connect(&m_process, - &QtcProcess::readyReadStandardOutput, - this, - &PythonLSInstallHelper::outputAvailable); - - connect(&m_killTimer, &QTimer::timeout, this, &PythonLSInstallHelper::cancel); - connect(&m_watcher, &QFutureWatcher::canceled, this, &PythonLSInstallHelper::cancel); - - QStringList arguments = {"-m", "pip", "install", "python-lsp-server[all]"}; - - // add --user to global pythons, but skip it for venv pythons - if (!QDir(m_python.parentDir().toString()).exists("activate")) - arguments << "--user"; - - m_process.setCommand({m_python, arguments}); - m_process.start(); - - Core::MessageManager::writeDisrupting( - tr("Running \"%1\" to install Python language server.") - .arg(m_process.commandLine().toUserOutput())); - - m_killTimer.setSingleShot(true); - m_killTimer.start(5 /*minutes*/ * 60 * 1000); - } - -private: - void cancel() - { - m_process.stopProcess(); - Core::MessageManager::writeFlashing( - tr("The Python language server installation was canceled by %1.") - .arg(m_killTimer.isActive() ? tr("user") : tr("time out"))); - } - - void installFinished() - { - m_future.reportFinished(); - if (m_process.result() == ProcessResult::FinishedWithSuccess) { - if (Client *client = registerLanguageServer(m_python)) - LanguageClientManager::openDocumentWithClient(m_document, client); - } else { - Core::MessageManager::writeFlashing( - tr("Installing the Python language server failed with exit code %1") - .arg(m_process.exitCode())); - } - deleteLater(); - } - - void outputAvailable() - { - const QString &stdOut = QString::fromLocal8Bit(m_process.readAllStandardOutput().trimmed()); - if (!stdOut.isEmpty()) - Core::MessageManager::writeSilently(stdOut); - } - - void errorAvailable() - { - const QString &stdErr = QString::fromLocal8Bit(m_process.readAllStandardError().trimmed()); - if (!stdErr.isEmpty()) - Core::MessageManager::writeSilently(stdErr); - } - - QFutureInterface m_future; - QFutureWatcher m_watcher; - QtcProcess m_process; - QTimer m_killTimer; - const FilePath m_python; - QPointer m_document; -}; - void PyLSConfigureAssistant::installPythonLanguageServer(const FilePath &python, QPointer document) { @@ -685,7 +597,19 @@ void PyLSConfigureAssistant::installPythonLanguageServer(const FilePath &python, for (TextEditor::TextDocument *additionalDocument : m_infoBarEntries[python]) additionalDocument->infoBar()->removeInfo(installPylsInfoBarId); - auto install = new PythonLSInstallHelper(python, document); + auto install = new PipInstallTask(python); + + connect(install, &PipInstallTask::finished, this, [=](const bool success) { + if (success) { + if (Client *client = registerLanguageServer(python)) { + if (document) + LanguageClientManager::openDocumentWithClient(document, client); + } + } + install->deleteLater(); + }); + + install->setPackage(PipPackage{"python-lsp-server[all]", "Python Language Server"}); install->run(); } @@ -839,5 +763,3 @@ PyLSConfigureAssistant::PyLSConfigureAssistant(QObject *parent) } // namespace Internal } // namespace Python - -#include "pythonlanguageclient.moc"