// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "pipsupport.h" #include "pythontr.h" #include #include #include #include #include #include #include #include using namespace Utils; namespace Python::Internal { const char pipInstallTaskId[] = "Python::pipInstallTask"; PipInstallTask::PipInstallTask(const FilePath &python) : m_python(python) { connect(&m_process, &QtcProcess::done, this, &PipInstallTask::handleDone); connect(&m_process, &QtcProcess::readyReadStandardError, this, &PipInstallTask::handleError); connect(&m_process, &QtcProcess::readyReadStandardOutput, this, &PipInstallTask::handleOutput); connect(&m_killTimer, &QTimer::timeout, this, &PipInstallTask::cancel); connect(&m_watcher, &QFutureWatcher::canceled, this, &PipInstallTask::cancel); 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::tr("Install %1").arg(m_package.displayName); Core::ProgressManager::addTask(m_future.future(), taskTitle, pipInstallTaskId); 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::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.stop(); m_process.waitForFinished(); Core::MessageManager::writeFlashing( Tr::tr("The %1 installation was canceled by %2.") .arg(m_package.displayName, m_killTimer.isActive() ? Tr::tr("user") : Tr::tr("time out"))); } void PipInstallTask::handleDone() { m_future.reportFinished(); const bool success = m_process.result() == ProcessResult::FinishedWithSuccess; if (!success) { Core::MessageManager::writeFlashing(Tr::tr("Installing the %1 failed with exit code %2") .arg(m_package.displayName).arg(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::handleError() { const QString &stdErr = QString::fromLocal8Bit(m_process.readAllStandardError().trimmed()); if (!stdErr.isEmpty()) 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()) return; if (field == "Name") { name = data.value(0); } else if (field == "Version") { version = data.value(0); } else if (field == "Summary") { summary = data.value(0); } else if (field == "Home-page") { homePage = QUrl(data.value(0)); } else if (field == "Author") { author = data.value(0); } else if (field == "Author-email") { authorEmail = data.value(0); } else if (field == "License") { license = data.value(0); } else if (field == "Location") { location = FilePath::fromUserInput(data.value(0)).normalizedPathName(); } else if (field == "Requires") { requiresPackage = data.value(0).split(',', Qt::SkipEmptyParts); } else if (field == "Required-by") { requiredByPackage = data.value(0).split(',', Qt::SkipEmptyParts); } else if (field == "Files") { for (const QString &fileName : data) { if (!fileName.isEmpty()) files.append(FilePath::fromUserInput(fileName.trimmed())); } } } } // Python::Internal