diff options
author | David Schulz <[email protected]> | 2025-06-19 14:58:36 +0200 |
---|---|---|
committer | David Schulz <[email protected]> | 2025-06-30 10:26:46 +0000 |
commit | 975eaa91eae8f8fc5789a3db82fad36749dd8a4e (patch) | |
tree | 85646ec8601424edc3389d5eb31fbf8c6d78dc26 | |
parent | c1f917ee490b6e504c31e23da4b95efcf734cae6 (diff) |
Fixes: QTCREATORBUG-33057
Change-Id: I41fca7728c10fbf512b383cf04c4b3dacdb536ac
Reviewed-by: Christian Stenger <[email protected]>
-rw-r--r-- | src/plugins/projectexplorer/runcontrol.cpp | 15 | ||||
-rw-r--r-- | src/plugins/projectexplorer/runcontrol.h | 4 | ||||
-rw-r--r-- | src/plugins/python/pyside.cpp | 31 | ||||
-rw-r--r-- | src/plugins/python/pyside.h | 6 | ||||
-rw-r--r-- | src/plugins/python/pythonrunconfiguration.cpp | 139 |
5 files changed, 139 insertions, 56 deletions
diff --git a/src/plugins/projectexplorer/runcontrol.cpp b/src/plugins/projectexplorer/runcontrol.cpp index 275360b3885..c006a79b418 100644 --- a/src/plugins/projectexplorer/runcontrol.cpp +++ b/src/plugins/projectexplorer/runcontrol.cpp @@ -973,7 +973,7 @@ void RunControlPrivate::showError(const QString &msg) void RunControl::setupFormatter(OutputFormatter *formatter) const { - QList<Utils::OutputLineParser *> parsers = createOutputParsers(target()); + QList<Utils::OutputLineParser *> parsers = createOutputParsers(buildConfiguration()); if (const auto customParsersAspect = aspectData<CustomParsersAspect>()) { for (const Id id : std::as_const(customParsersAspect->parsers)) { if (auto parser = createCustomParserFromId(id)) @@ -1581,13 +1581,13 @@ void RunWorker::addStopDependency(RunWorker *dependency) // Output parser factories -static QList<std::function<OutputLineParser *(Target *)>> g_outputParserFactories; +static QList<std::function<OutputLineParser *(BuildConfiguration *)>> g_outputParserFactories; -QList<OutputLineParser *> createOutputParsers(Target *target) +QList<OutputLineParser *> createOutputParsers(BuildConfiguration *bc) { QList<OutputLineParser *> formatters; for (auto factory : std::as_const(g_outputParserFactories)) { - if (OutputLineParser *parser = factory(target)) + if (OutputLineParser *parser = factory(bc)) formatters << parser; } return formatters; @@ -1595,6 +1595,13 @@ QList<OutputLineParser *> createOutputParsers(Target *target) void addOutputParserFactory(const std::function<Utils::OutputLineParser *(Target *)> &factory) { + g_outputParserFactories.append( + [factory](BuildConfiguration *bc) { return factory(bc ? bc->target() : nullptr); }); +} + +void addOutputParserFactory( + const std::function<Utils::OutputLineParser *(BuildConfiguration *)> &factory) +{ g_outputParserFactories.append(factory); } diff --git a/src/plugins/projectexplorer/runcontrol.h b/src/plugins/projectexplorer/runcontrol.h index fef9204dadc..04e3a9749b6 100644 --- a/src/plugins/projectexplorer/runcontrol.h +++ b/src/plugins/projectexplorer/runcontrol.h @@ -264,8 +264,10 @@ public: PROJECTEXPLORER_EXPORT void addOutputParserFactory(const std::function<Utils::OutputLineParser *(Target *)> &); +PROJECTEXPLORER_EXPORT +void addOutputParserFactory(const std::function<Utils::OutputLineParser *(BuildConfiguration *)> &); -PROJECTEXPLORER_EXPORT QList<Utils::OutputLineParser *> createOutputParsers(Target *target); +PROJECTEXPLORER_EXPORT QList<Utils::OutputLineParser *> createOutputParsers(BuildConfiguration *bc); class PROJECTEXPLORER_EXPORT RunInterface : public QObject { diff --git a/src/plugins/python/pyside.cpp b/src/plugins/python/pyside.cpp index b30b764664c..d338623d14d 100644 --- a/src/plugins/python/pyside.cpp +++ b/src/plugins/python/pyside.cpp @@ -22,6 +22,7 @@ #include <utils/algorithm.h> #include <utils/async.h> +#include <utils/checkablemessagebox.h> #include <utils/infobar.h> #include <utils/mimeconstants.h> #include <utils/qtcassert.h> @@ -29,6 +30,7 @@ #include <QBoxLayout> #include <QComboBox> +#include <QDesktopServices> #include <QDialogButtonBox> #include <QRegularExpression> #include <QTextCursor> @@ -84,13 +86,27 @@ QString PySideInstaller::usedPySide(const QString &text, const QString &mimeType return {}; } +void PySideInstaller::installPySide(const QUrl &url) +{ + FilePath python = FilePath::fromUserInput(QUrl::fromPercentEncoding(url.path().toLatin1())); + QTC_ASSERT(python.isExecutableFile(), return); + installPySide(python, "PySide6"); +} + PySideInstaller::PySideInstaller() { + QDesktopServices::setUrlHandler("pysideinstall", this, "installPySide"); + connect(Core::EditorManager::instance(), &Core::EditorManager::documentOpened, this, &PySideInstaller::handleDocumentOpened); } -void PySideInstaller::installPyside(const FilePath &python, const QString &pySide) +PySideInstaller::~PySideInstaller() +{ + QDesktopServices::unsetUrlHandler("pysideinstall"); +} + +void PySideInstaller::installPySide(const FilePath &python, const QString &pySide, bool quiet) { QMap<QVersionNumber, Utils::FilePath> availablePySides; @@ -132,6 +148,17 @@ void PySideInstaller::installPyside(const FilePath &python, const QString &pySid emit pySideInstalled(python, pySide); }); if (availablePySides.isEmpty()) { + if (!quiet) { + QMessageBox::StandardButton selected = CheckableMessageBox::question( + Tr::tr("Missing PySide6 installation"), + Tr::tr("Install PySide6 via pip for %1?").arg(python.shortNativePath()), + {}, + QMessageBox::Yes | QMessageBox::No, + QMessageBox::Yes, + QMessageBox::Yes); + if (selected == QMessageBox::No) + return; + } install->setPackages({PipPackage(pySide)}); } else { QDialog dialog; @@ -198,7 +225,7 @@ void PySideInstaller::handlePySideMissing(const FilePath &python, const QString message = Tr::tr("%1 installation missing for %2 (%3)") .arg(pySide, pythonName(python), python.toUserOutput()); InfoBarEntry info(installPySideInfoBarId, message, InfoBarEntry::GlobalSuppression::Enabled); - auto installCallback = [this, python, pySide] { installPyside(python, pySide); }; + auto installCallback = [this, python, pySide] { installPySide(python, pySide, true); }; const QString installTooltip = Tr::tr("Install %1 for %2 using pip package installer.") .arg(pySide, python.toUserOutput()); info.addCustomButton( diff --git a/src/plugins/python/pyside.h b/src/plugins/python/pyside.h index b2e1a65343a..f6f8f25f1e3 100644 --- a/src/plugins/python/pyside.h +++ b/src/plugins/python/pyside.h @@ -30,14 +30,18 @@ class PySideInstaller : public QObject public: void checkPySideInstallation(const Utils::FilePath &python, TextEditor::TextDocument *document); static PySideInstaller &instance(); + void installPySide(const Utils::FilePath &python, const QString &pySide, bool quiet = false); + +public slots: + void installPySide(const QUrl &url); signals: void pySideInstalled(const Utils::FilePath &python, const QString &pySide); private: PySideInstaller(); + ~PySideInstaller(); - void installPyside(const Utils::FilePath &python, const QString &pySide); void handlePySideMissing(const Utils::FilePath &python, const QString &pySide, TextEditor::TextDocument *document); diff --git a/src/plugins/python/pythonrunconfiguration.cpp b/src/plugins/python/pythonrunconfiguration.cpp index d1dbac54de2..3b7f8742f6e 100644 --- a/src/plugins/python/pythonrunconfiguration.cpp +++ b/src/plugins/python/pythonrunconfiguration.cpp @@ -3,6 +3,8 @@ #include "pythonrunconfiguration.h" +#include "pyside.h" +#include "pythonbuildconfiguration.h" #include "pythonconstants.h" #include "pythonproject.h" #include "pythontr.h" @@ -23,6 +25,8 @@ #include <utils/fileutils.h> #include <utils/outputformatter.h> +#include <QUrl> + using namespace ProjectExplorer; using namespace Utils; @@ -34,10 +38,18 @@ static const QRegularExpression &tracebackFilePattern() return s_filePattern; } +static const QRegularExpression &moduleNotFoundPattern() +{ + static const QRegularExpression s_functionPattern( + "^ModuleNotFoundError: No module named '([_a-zA-Z][_a-zA-Z0-9]*)'$"); + return s_functionPattern; +} + class PythonOutputLineParser : public OutputLineParser { public: - PythonOutputLineParser() + PythonOutputLineParser(const FilePath &python) + : m_python(python) { TaskHub::clearTasks(PythonErrorTaskCategory); } @@ -45,62 +57,87 @@ public: private: Result handleLine(const QString &text, OutputFormat format) final { - if (!m_inTraceBack) { - m_inTraceBack = format == StdErrFormat - && text.startsWith("Traceback (most recent call last):"); - if (m_inTraceBack) - return Status::InProgress; - return Status::NotHandled; - } - const Id category(PythonErrorTaskCategory); - const QRegularExpressionMatch match = tracebackFilePattern().match(text); - if (match.hasMatch()) { - const LinkSpec link(match.capturedStart(2), match.capturedLength(2), match.captured(2)); - const auto fileName = FilePath::fromString(match.captured(3)); - const int lineNumber = match.captured(4).toInt(); - m_tasks.append({Task::Warning, QString(), fileName, lineNumber, category}); - return {Status::InProgress, {link}}; - } - Status status = Status::InProgress; - if (text.startsWith(' ')) { - // Neither traceback start, nor file, nor error message line. - // Not sure if that can actually happen. - if (m_tasks.isEmpty()) { - m_tasks.append({Task::Warning, text.trimmed(), {}, -1, category}); + if (m_inTraceBack) { + const QRegularExpressionMatch match = tracebackFilePattern().match(text); + if (match.hasMatch()) { + const LinkSpec link(match.capturedStart(2), match.capturedLength(2), match.captured(2)); + const auto fileName = FilePath::fromUserInput(match.captured(3)); + const int lineNumber = match.captured(4).toInt(); + m_tasks.append({Task::Warning, QString(), fileName, lineNumber, category}); + return {Status::InProgress, {link}}; + } + + if (text.startsWith(' ')) { + // Neither traceback start, nor file, nor error message line. + // Not sure if that can actually happen. + if (m_tasks.isEmpty()) { + m_tasks.append({Task::Warning, text.trimmed(), {}, -1, category}); + } else { + Task &task = m_tasks.back(); + if (!task.summary().isEmpty()) + task.addToSummary(QChar(' ')); + task.addToSummary(text.trimmed()); + } } else { - Task &task = m_tasks.back(); - QString t = text.trimmed(); - if (!task.summary().isEmpty()) - t.prepend(' '); - task.addToSummary(t); + // The actual exception. This ends the traceback. + Task exception{Task::Error, text, {}, -1, category}; + const QString detail = Tr::tr("Install %1 (requires pip)"); + const QString pySide6Text = Tr::tr("PySide6"); + const QString link = QString("pysideinstall:") + + QUrl::toPercentEncoding(m_python.toFSPathString()); + exception.addToDetails(detail.arg(pySide6Text)); + QTextCharFormat format; + format.setAnchor(true); + format.setAnchorHref(link); + const int offset = exception.summary().length() + detail.indexOf("%1") + 1; + exception.setFormats( + {QTextLayout::FormatRange{offset, int(pySide6Text.length()), format}}); + TaskHub::addTask(exception); + for (auto rit = m_tasks.crbegin(), rend = m_tasks.crend(); rit != rend; ++rit) + TaskHub::addTask(*rit); + m_inTraceBack = false; + const QRegularExpressionMatch match = moduleNotFoundPattern().match(text); + if (match.hasMatch()) { + const QString moduleName = match.captured(1); + if (moduleName == "PySide6") { + const LinkSpec + link(match.capturedStart(1), match.capturedLength(1), moduleName); + return {Status::Done, {link}}; + } + } + return Status::Done; } - } else { - // The actual exception. This ends the traceback. - TaskHub::addTask({Task::Error, text, {}, -1, category}); - for (auto rit = m_tasks.crbegin(), rend = m_tasks.crend(); rit != rend; ++rit) - TaskHub::addTask(*rit); - m_tasks.clear(); - m_inTraceBack = false; - status = Status::Done; + return Status::InProgress; + } + + if (format == StdErrFormat) { + m_inTraceBack = text.startsWith("Traceback (most recent call last):"); + if (m_inTraceBack) + return Status::InProgress; } - return status; + + return Status::NotHandled; } bool handleLink(const QString &href) final { - const QRegularExpressionMatch match = tracebackFilePattern().match(href); - if (!match.hasMatch()) - return false; - const QString fileName = match.captured(3); - const int lineNumber = match.captured(4).toInt(); - Core::EditorManager::openEditorAt({FilePath::fromString(fileName), lineNumber}); - return true; + if (const QRegularExpressionMatch match = tracebackFilePattern().match(href); + match.hasMatch()) { + const QString fileName = match.captured(3); + const int lineNumber = match.captured(4).toInt(); + Core::EditorManager::openEditorAt({FilePath::fromString(fileName), lineNumber}); + return true; + } + if (href == "PySide6") + PySideInstaller::instance().installPySide(m_python, href); + return false; } QList<Task> m_tasks; bool m_inTraceBack = false; + const FilePath m_python; }; // RunConfiguration @@ -195,12 +232,18 @@ void setupPythonDebugWorker() void setupPythonOutputParser() { - addOutputParserFactory([](Target *t) -> OutputLineParser * { - if (!t) + addOutputParserFactory([](BuildConfiguration *bc) -> OutputLineParser * { + auto *pythonBuildConfig = qobject_cast<PythonBuildConfiguration *>(bc); + if (!pythonBuildConfig) return nullptr; + + Target *t = pythonBuildConfig->target(); + QTC_ASSERT(t, return nullptr); + if (t->project()->mimeType() == Constants::C_PY_PROJECT_MIME_TYPE - || t->project()->mimeType() == Constants::C_PY_PROJECT_MIME_TYPE_TOML) - return new PythonOutputLineParser; + || t->project()->mimeType() == Constants::C_PY_PROJECT_MIME_TYPE_TOML) { + return new PythonOutputLineParser(pythonBuildConfig->python()); + } return nullptr; }); } |