From cecf577dc497ff75d9b6681b25cb72cdaa821d71 Mon Sep 17 00:00:00 2001 From: Artem Sokolovskii Date: Fri, 25 Aug 2023 15:40:18 +0200 Subject: [PATCH] DAP: Add Python debugging Added Python support to the DAP engine in Qt Creator. Note: Locals aren't displayed for python. It will be fixed in the following commit. Change-Id: I6d3b41fecc98b92951ed0522e9201401293034d7 Reviewed-by: Reviewed-by: hjk --- src/plugins/debugger/CMakeLists.txt | 1 + src/plugins/debugger/dap/cmakedapengine.cpp | 2 - src/plugins/debugger/dap/dapclient.cpp | 13 +- src/plugins/debugger/dap/dapclient.h | 3 + src/plugins/debugger/dap/dapengine.cpp | 19 +- src/plugins/debugger/dap/dapengine.h | 2 +- src/plugins/debugger/dap/gdbdapengine.cpp | 2 - src/plugins/debugger/dap/pydapengine.cpp | 182 ++++++++++++++++++ src/plugins/debugger/dap/pydapengine.h | 21 ++ src/plugins/debugger/debuggerengine.cpp | 6 +- src/plugins/debugger/debuggerplugin.cpp | 17 +- src/plugins/debugger/debuggerruncontrol.cpp | 12 +- .../projectexplorer/projectexplorer.cpp | 2 +- .../projectexplorerconstants.h | 3 +- 14 files changed, 259 insertions(+), 26 deletions(-) create mode 100644 src/plugins/debugger/dap/pydapengine.cpp create mode 100644 src/plugins/debugger/dap/pydapengine.h diff --git a/src/plugins/debugger/CMakeLists.txt b/src/plugins/debugger/CMakeLists.txt index cc632969e03..21920f8fef9 100644 --- a/src/plugins/debugger/CMakeLists.txt +++ b/src/plugins/debugger/CMakeLists.txt @@ -31,6 +31,7 @@ add_qtc_plugin(Debugger dap/dapclient.cpp dap/dapclient.h dap/dapengine.cpp dap/dapengine.h dap/gdbdapengine.cpp dap/gdbdapengine.h + dap/pydapengine.cpp dap/pydapengine.h debugger.qrc debugger_global.h debuggeractions.cpp debuggeractions.h diff --git a/src/plugins/debugger/dap/cmakedapengine.cpp b/src/plugins/debugger/dap/cmakedapengine.cpp index 4a4e0fe59ef..fb9a459dc75 100644 --- a/src/plugins/debugger/dap/cmakedapengine.cpp +++ b/src/plugins/debugger/dap/cmakedapengine.cpp @@ -130,8 +130,6 @@ void CMakeDapEngine::setupEngine() return; } }); - - notifyEngineSetupOk(); } bool CMakeDapEngine::hasCapability(unsigned cap) const diff --git a/src/plugins/debugger/dap/dapclient.cpp b/src/plugins/debugger/dap/dapclient.cpp index 4ad74b45586..1a70283e9e0 100644 --- a/src/plugins/debugger/dap/dapclient.cpp +++ b/src/plugins/debugger/dap/dapclient.cpp @@ -67,6 +67,12 @@ void DapClient::sendLaunch(const Utils::FilePath &executable) QJsonObject{{"noDebug", false}, {"program", executable.path()}, {"__restart", ""}}); } +void DapClient::sendAttach() +{ + postRequest("attach", + QJsonObject{{"__restart", ""}}); +} + void DapClient::sendConfigurationDone() { postRequest("configurationDone"); @@ -93,7 +99,6 @@ void DapClient::sendPause() postRequest("pause"); } - void DapClient::sendStepIn(int threadId) { QTC_ASSERT(threadId != -1, return); @@ -190,7 +195,9 @@ void DapClient::emitSignals(const QJsonDocument &doc) if (type == "response") { DapResponseType type = DapResponseType::Unknown; const QString command = ob.value("command").toString(); - if (command == "configurationDone") { + if (command == "initialize") { + type = DapResponseType::Initialize; + } else if (command == "configurationDone") { type = DapResponseType::ConfigurationDone; } else if (command == "continue") { type = DapResponseType::Continue; @@ -208,6 +215,8 @@ void DapClient::emitSignals(const QJsonDocument &doc) type = DapResponseType::StepOver; } else if (command == "threads") { type = DapResponseType::DapThreads; + } else if (command == "pause") { + type = DapResponseType::Pause; } emit responseReady(type, ob); return; diff --git a/src/plugins/debugger/dap/dapclient.h b/src/plugins/debugger/dap/dapclient.h index 3570bd65ca0..92528fc59df 100644 --- a/src/plugins/debugger/dap/dapclient.h +++ b/src/plugins/debugger/dap/dapclient.h @@ -38,6 +38,7 @@ signals: enum class DapResponseType { + Initialize, ConfigurationDone, Continue, StackTrace, @@ -47,6 +48,7 @@ enum class DapResponseType StepIn, StepOut, StepOver, + Pause, Unknown }; @@ -76,6 +78,7 @@ public: virtual void sendInitialize(); void sendLaunch(const Utils::FilePath &executable); + void sendAttach(); void sendConfigurationDone(); void sendDisconnect(); diff --git a/src/plugins/debugger/dap/dapengine.cpp b/src/plugins/debugger/dap/dapengine.cpp index 180b3bdf1a7..7209aecf60c 100644 --- a/src/plugins/debugger/dap/dapengine.cpp +++ b/src/plugins/debugger/dap/dapengine.cpp @@ -6,6 +6,7 @@ #include "cmakedapengine.h" #include "dapclient.h" #include "gdbdapengine.h" +#include "pydapengine.h" #include #include @@ -111,6 +112,7 @@ void DapEngine::shutdownEngine() void DapEngine::handleDapStarted() { + notifyEngineSetupOk(); QTC_ASSERT(state() == EngineRunRequested, qCDebug(dapEngineLog) << state()); m_dapClient->sendInitialize(); @@ -128,7 +130,7 @@ void DapEngine::handleDapConfigurationDone() } -void DapEngine::handleDapLaunch() +void DapEngine::handleDapInitialize() { QTC_ASSERT(state() == EngineRunRequested, qCDebug(dapEngineLog) << state()); @@ -479,6 +481,10 @@ void DapEngine::handleResponse(DapResponseType type, const QJsonObject &response const QString command = response.value("command").toString(); switch (type) { + case DapResponseType::Initialize: + qCDebug(dapEngineLog) << "initialize success"; + handleDapInitialize(); + break; case DapResponseType::ConfigurationDone: showMessage("configurationDone", LogDebug); qCDebug(dapEngineLog) << "configurationDone success"; @@ -513,7 +519,6 @@ void DapEngine::handleResponse(DapResponseType type, const QJsonObject &response case DapResponseType::DapThreads: handleThreadsResponse(response); break; - default: showMessage("UNKNOWN RESPONSE:" + command); }; @@ -585,7 +590,7 @@ void DapEngine::handleEvent(DapEventType type, const QJsonObject &event) switch (type) { case DapEventType::Initialized: qCDebug(dapEngineLog) << "initialize success"; - handleDapLaunch(); + claimInitialBreakpoints(); handleDapConfigurationDone(); break; case DapEventType::Stopped: @@ -801,10 +806,14 @@ void DapEngine::connectDataGeneratorSignals() DebuggerEngine *createDapEngine(Utils::Id runMode) { - if (runMode == ProjectExplorer::Constants::CMAKE_DEBUG_RUN_MODE) + if (runMode == ProjectExplorer::Constants::DAP_CMAKE_DEBUG_RUN_MODE) return new CMakeDapEngine; + if (runMode == ProjectExplorer::Constants::DAP_GDB_DEBUG_RUN_MODE) + return new GdbDapEngine; + if (runMode == ProjectExplorer::Constants::DAP_PY_DEBUG_RUN_MODE) + return new PyDapEngine; - return new GdbDapEngine; + return nullptr; } } // Debugger::Internal diff --git a/src/plugins/debugger/dap/dapengine.h b/src/plugins/debugger/dap/dapengine.h index b8ad63aab0a..8463f508f6b 100644 --- a/src/plugins/debugger/dap/dapengine.h +++ b/src/plugins/debugger/dap/dapengine.h @@ -84,7 +84,7 @@ protected: void claimInitialBreakpoints(); void handleDapStarted(); - void handleDapLaunch(); + virtual void handleDapInitialize(); void handleDapConfigurationDone(); void dapRemoveBreakpoint(const Breakpoint &bp); diff --git a/src/plugins/debugger/dap/gdbdapengine.cpp b/src/plugins/debugger/dap/gdbdapengine.cpp index 213d38f0d82..fa4ae14bd13 100644 --- a/src/plugins/debugger/dap/gdbdapengine.cpp +++ b/src/plugins/debugger/dap/gdbdapengine.cpp @@ -111,8 +111,6 @@ void GdbDapEngine::setupEngine() connectDataGeneratorSignals(); m_dapClient->dataProvider()->start(); - - notifyEngineSetupOk(); } } // namespace Debugger::Internal diff --git a/src/plugins/debugger/dap/pydapengine.cpp b/src/plugins/debugger/dap/pydapengine.cpp new file mode 100644 index 00000000000..bf5791ab5ae --- /dev/null +++ b/src/plugins/debugger/dap/pydapengine.cpp @@ -0,0 +1,182 @@ +// Copyright (C) 2016 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 "pydapengine.h" + +#include "dapclient.h" + +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace Core; +using namespace Utils; + +static Q_LOGGING_CATEGORY(dapEngineLog, "qtc.dbg.dapengine", QtWarningMsg) + +namespace Debugger::Internal { + +class TcpSocketDataProvider : public IDataProvider +{ +public: + TcpSocketDataProvider(const DebuggerRunParameters &rp, + const CommandLine &cmd, + const QString &hostName, + const quint16 port, + QObject *parent = nullptr) + : IDataProvider(parent) + , m_runParameters(rp) + , m_cmd(cmd) + , m_hostName(hostName) + , m_port(port) + { + connect(&m_socket, &QTcpSocket::connected, this, &IDataProvider::started); + connect(&m_socket, &QTcpSocket::disconnected, this, &IDataProvider::done); + connect(&m_socket, &QTcpSocket::readyRead, this, &IDataProvider::readyReadStandardOutput); + connect(&m_socket, + &QTcpSocket::errorOccurred, + this, + &IDataProvider::readyReadStandardError); + } + + ~TcpSocketDataProvider() { m_socket.disconnect(); } + + void start() override + { + m_proc.setEnvironment(m_runParameters.debugger.environment); + m_proc.setCommand(m_cmd); + m_proc.start(); + + m_timer = new QTimer(this); + m_timer->setInterval(100); + + connect(m_timer, &QTimer::timeout, this, [this]() { + m_socket.connectToHost(m_hostName, m_port); + m_socket.waitForConnected(); + if (m_socket.state() == QTcpSocket::ConnectedState) + m_timer->stop(); + + if (m_attempts >= m_maxAttempts) + kill(); + + m_attempts++; + }); + + m_timer->start(); + } + + bool isRunning() const override { return m_socket.isOpen(); } + void writeRaw(const QByteArray &data) override { m_socket.write(data); } + void kill() override + { + m_timer->stop(); + + if (m_proc.state() == QProcess::Running) + m_proc.kill(); + + if (m_socket.isOpen()) + m_socket.disconnect(); + + m_socket.abort(); + emit done(); + } + QByteArray readAllStandardOutput() override { return m_socket.readAll(); } + QString readAllStandardError() override { return QString(); } + int exitCode() const override { return 0; } + QString executable() const override { return m_hostName + ":" + QString::number(m_port); } + + QProcess::ExitStatus exitStatus() const override { return QProcess::NormalExit; } + QProcess::ProcessError error() const override { return QProcess::UnknownError; } + Utils::ProcessResult result() const override { return ProcessResult::FinishedWithSuccess; } + QString exitMessage() const override { return QString(); }; + +private: + Utils::Process m_proc; + const DebuggerRunParameters m_runParameters; + const CommandLine m_cmd; + + QTcpSocket m_socket; + const QString m_hostName; + const quint16 m_port; + + QTimer *m_timer; + const int m_maxAttempts = 10; + int m_attempts = 0; +}; + +class PythonDapClient : public DapClient +{ +public: + PythonDapClient(IDataProvider *provider, QObject *parent = nullptr) + : DapClient(provider, parent) + {} + + void sendInitialize() + { + postRequest("initialize", + QJsonObject{{"clientID", "QtCreator"}, + {"clientName", "QtCreator"}, + {"adapterID", "python"}, + {"pathFormat", "path"}}); + } +}; + +PyDapEngine::PyDapEngine() + : DapEngine() +{ + setObjectName("PythonDapEngine"); + setDebuggerName("PythonDAP"); + setDebuggerType("DAP"); +} + +void PyDapEngine::handleDapInitialize() +{ + QTC_ASSERT(state() == EngineRunRequested, qCDebug(dapEngineLog) << state()); + + m_dapClient->sendAttach(); + + qCDebug(dapEngineLog) << "handleDapAttach"; +} + +void PyDapEngine::setupEngine() +{ + QTC_ASSERT(state() == EngineSetupRequested, qDebug() << state()); + + Utils::FilePath interpreter = runParameters().interpreter; + + const FilePath scriptFile = runParameters().mainScript; + if (!scriptFile.isReadableFile()) { + MessageManager::writeDisrupting( + "Python Error" + QString("Cannot open script file %1").arg(scriptFile.toUserOutput())); + notifyEngineSetupFailed(); + } + + CommandLine cmd{interpreter, + {"-Xfrozen_modules=off", + "-m", "debugpy", + "--listen", "127.0.0.1:5679", + "--wait-for-client", + scriptFile.path(), + runParameters().inferior.workingDirectory.path()}}; + + IDataProvider *dataProvider + = new TcpSocketDataProvider(runParameters(), cmd, "127.0.0.1", 5679, this); + m_dapClient = new PythonDapClient(dataProvider, this); + + connectDataGeneratorSignals(); + m_dapClient->dataProvider()->start(); +} + +} // namespace Debugger::Internal diff --git a/src/plugins/debugger/dap/pydapengine.h b/src/plugins/debugger/dap/pydapengine.h new file mode 100644 index 00000000000..d81bd4e8359 --- /dev/null +++ b/src/plugins/debugger/dap/pydapengine.h @@ -0,0 +1,21 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "dapengine.h" + +namespace Debugger::Internal { + +class PyDapEngine : public DapEngine +{ +public: + PyDapEngine(); + +private: + void handleDapInitialize() override; + void setupEngine() override; + Utils::Process m_proc; +}; + +} // Debugger::Internal diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp index ea8ed9b214a..a4751628ec1 100644 --- a/src/plugins/debugger/debuggerengine.cpp +++ b/src/plugins/debugger/debuggerengine.cpp @@ -727,10 +727,10 @@ void DebuggerEnginePrivate::setupViews() m_breakWindow->setObjectName("Debugger.Dock.Break." + engineId); m_breakWindow->setWindowTitle(Tr::tr("&Breakpoints")); - if (!currentPerspective || currentPerspective->id() == Constants::PRESET_PERSPECTIVE_ID) - m_perspective->useSubPerspectiveSwitcher(EngineManager::engineChooser()); - else + if (currentPerspective && currentPerspective->id() != Constants::PRESET_PERSPECTIVE_ID) m_perspective->useSubPerspectiveSwitcher(EngineManager::dapEngineChooser()); + else + m_perspective->useSubPerspectiveSwitcher(EngineManager::engineChooser()); m_perspective->addToolBarAction(&m_continueAction); m_perspective->addToolBarAction(&m_interruptAction); diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index f155365bd7e..04c78ca205c 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -1242,11 +1242,15 @@ void DebuggerPluginPrivate::createDapDebuggerPerspective(QWidget *globalLogWindo { EngineManager::registerDefaultPerspective(Tr::tr("CMake Preset"), "DAP", - ProjectExplorer::Constants::CMAKE_DEBUG_RUN_MODE); + Constants::DAP_PERSPECTIVE_ID); EngineManager::registerDefaultPerspective(Tr::tr("GDB Preset"), "DAP", - ProjectExplorer::Constants::DAP_GDB_DEBUG_RUN_MODE); + Constants::DAP_PERSPECTIVE_ID); + + EngineManager::registerDefaultPerspective(Tr::tr("Python Preset"), + "DAP", + Constants::DAP_PERSPECTIVE_ID); auto breakpointManagerView = createBreakpointManagerView("DAPDebugger.BreakWindow"); auto breakpointManagerWindow @@ -1265,11 +1269,14 @@ void DebuggerPluginPrivate::createDapDebuggerPerspective(QWidget *globalLogWindo connect(&m_startDapAction, &QAction::triggered, this, [] { QComboBox *combo = qobject_cast(EngineManager::dapEngineChooser()); if (combo->currentText() == "CMake Preset") { - ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::CMAKE_DEBUG_RUN_MODE, - true); + ProjectExplorerPlugin::runStartupProject( + ProjectExplorer::Constants::DAP_CMAKE_DEBUG_RUN_MODE, false); + } else if (combo->currentText() == "GDB Preset") { + ProjectExplorerPlugin::runStartupProject( + ProjectExplorer::Constants::DAP_GDB_DEBUG_RUN_MODE, false); } else { ProjectExplorerPlugin::runStartupProject( - ProjectExplorer::Constants::DAP_GDB_DEBUG_RUN_MODE, true); + ProjectExplorer::Constants::DAP_PY_DEBUG_RUN_MODE, false); } }); diff --git a/src/plugins/debugger/debuggerruncontrol.cpp b/src/plugins/debugger/debuggerruncontrol.cpp index 9110ae1fdba..788eacb4bfa 100644 --- a/src/plugins/debugger/debuggerruncontrol.cpp +++ b/src/plugins/debugger/debuggerruncontrol.cpp @@ -482,10 +482,12 @@ void DebuggerRunTool::start() runControl()->setDisplayName(m_runParameters.displayName); if (!m_engine) { - if (runControl()->runMode() == ProjectExplorer::Constants::CMAKE_DEBUG_RUN_MODE) + if (runControl()->runMode() == ProjectExplorer::Constants::DAP_CMAKE_DEBUG_RUN_MODE) m_engine = createDapEngine(runControl()->runMode()); else if (runControl()->runMode() == ProjectExplorer::Constants::DAP_GDB_DEBUG_RUN_MODE) - m_engine = createDapEngine(); + m_engine = createDapEngine(runControl()->runMode()); + else if (runControl()->runMode() == ProjectExplorer::Constants::DAP_PY_DEBUG_RUN_MODE) + m_engine = createDapEngine(runControl()->runMode()); else if (m_runParameters.isCppDebugging()) { switch (m_runParameters.cppEngineType) { case GdbEngineType: @@ -918,7 +920,8 @@ DebuggerRunTool::DebuggerRunTool(RunControl *runControl, AllowTerminal allowTerm m_runParameters.interpreter = interpreter; if (auto args = runControl->aspect()) m_runParameters.inferior.command.addArgs(args->arguments, CommandLine::Raw); - m_engine = createPdbEngine(); + if (runControl->runMode() == ProjectExplorer::Constants::DEBUG_RUN_MODE) + m_engine = createPdbEngine(); } } } @@ -1120,8 +1123,9 @@ DebuggerRunWorkerFactory::DebuggerRunWorkerFactory() { setProduct(); addSupportedRunMode(ProjectExplorer::Constants::DEBUG_RUN_MODE); - addSupportedRunMode(ProjectExplorer::Constants::CMAKE_DEBUG_RUN_MODE); + addSupportedRunMode(ProjectExplorer::Constants::DAP_CMAKE_DEBUG_RUN_MODE); addSupportedRunMode(ProjectExplorer::Constants::DAP_GDB_DEBUG_RUN_MODE); + addSupportedRunMode(ProjectExplorer::Constants::DAP_PY_DEBUG_RUN_MODE); addSupportedDeviceType(ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE); addSupportedDeviceType("DockerDeviceType"); } diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp index d4c622af291..bda6b4b8f65 100644 --- a/src/plugins/projectexplorer/projectexplorer.cpp +++ b/src/plugins/projectexplorer/projectexplorer.cpp @@ -2954,7 +2954,7 @@ void ProjectExplorerPlugin::runRunConfiguration(RunConfiguration *rc, ? BuildForRunConfigStatus::Building : BuildForRunConfigStatus::NotBuilding : BuildManager::potentiallyBuildForRunConfig(rc); - if (dd->m_runMode == Constants::CMAKE_DEBUG_RUN_MODE) + if (dd->m_runMode == Constants::DAP_CMAKE_DEBUG_RUN_MODE) buildStatus = BuildForRunConfigStatus::NotBuilding; switch (buildStatus) { diff --git a/src/plugins/projectexplorer/projectexplorerconstants.h b/src/plugins/projectexplorer/projectexplorerconstants.h index 1537d32e7e2..6677081f4ca 100644 --- a/src/plugins/projectexplorer/projectexplorerconstants.h +++ b/src/plugins/projectexplorer/projectexplorerconstants.h @@ -178,8 +178,9 @@ const char GENERATOR_ID_PREFIX[] = "PE.Wizard.Generator."; const char NO_RUN_MODE[]="RunConfiguration.NoRunMode"; const char NORMAL_RUN_MODE[]="RunConfiguration.NormalRunMode"; const char DEBUG_RUN_MODE[]="RunConfiguration.DebugRunMode"; -const char CMAKE_DEBUG_RUN_MODE[]="RunConfiguration.CmakeDebugRunMode"; +const char DAP_CMAKE_DEBUG_RUN_MODE[]="RunConfiguration.CmakeDebugRunMode"; const char DAP_GDB_DEBUG_RUN_MODE[]="RunConfiguration.DapGdbDebugRunMode"; +const char DAP_PY_DEBUG_RUN_MODE[]="RunConfiguration.DapPyDebugRunMode"; const char QML_PROFILER_RUN_MODE[]="RunConfiguration.QmlProfilerRunMode"; const char QML_PROFILER_RUNNER[]="RunConfiguration.QmlProfilerRunner"; const char QML_PREVIEW_RUN_MODE[]="RunConfiguration.QmlPreviewRunMode";