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: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Artem Sokolovskii
2023-08-25 15:40:18 +02:00
parent 270a9839e2
commit cecf577dc4
14 changed files with 259 additions and 26 deletions

View File

@@ -31,6 +31,7 @@ add_qtc_plugin(Debugger
dap/dapclient.cpp dap/dapclient.h dap/dapclient.cpp dap/dapclient.h
dap/dapengine.cpp dap/dapengine.h dap/dapengine.cpp dap/dapengine.h
dap/gdbdapengine.cpp dap/gdbdapengine.h dap/gdbdapengine.cpp dap/gdbdapengine.h
dap/pydapengine.cpp dap/pydapengine.h
debugger.qrc debugger.qrc
debugger_global.h debugger_global.h
debuggeractions.cpp debuggeractions.h debuggeractions.cpp debuggeractions.h

View File

@@ -130,8 +130,6 @@ void CMakeDapEngine::setupEngine()
return; return;
} }
}); });
notifyEngineSetupOk();
} }
bool CMakeDapEngine::hasCapability(unsigned cap) const bool CMakeDapEngine::hasCapability(unsigned cap) const

View File

@@ -67,6 +67,12 @@ void DapClient::sendLaunch(const Utils::FilePath &executable)
QJsonObject{{"noDebug", false}, {"program", executable.path()}, {"__restart", ""}}); QJsonObject{{"noDebug", false}, {"program", executable.path()}, {"__restart", ""}});
} }
void DapClient::sendAttach()
{
postRequest("attach",
QJsonObject{{"__restart", ""}});
}
void DapClient::sendConfigurationDone() void DapClient::sendConfigurationDone()
{ {
postRequest("configurationDone"); postRequest("configurationDone");
@@ -93,7 +99,6 @@ void DapClient::sendPause()
postRequest("pause"); postRequest("pause");
} }
void DapClient::sendStepIn(int threadId) void DapClient::sendStepIn(int threadId)
{ {
QTC_ASSERT(threadId != -1, return); QTC_ASSERT(threadId != -1, return);
@@ -190,7 +195,9 @@ void DapClient::emitSignals(const QJsonDocument &doc)
if (type == "response") { if (type == "response") {
DapResponseType type = DapResponseType::Unknown; DapResponseType type = DapResponseType::Unknown;
const QString command = ob.value("command").toString(); const QString command = ob.value("command").toString();
if (command == "configurationDone") { if (command == "initialize") {
type = DapResponseType::Initialize;
} else if (command == "configurationDone") {
type = DapResponseType::ConfigurationDone; type = DapResponseType::ConfigurationDone;
} else if (command == "continue") { } else if (command == "continue") {
type = DapResponseType::Continue; type = DapResponseType::Continue;
@@ -208,6 +215,8 @@ void DapClient::emitSignals(const QJsonDocument &doc)
type = DapResponseType::StepOver; type = DapResponseType::StepOver;
} else if (command == "threads") { } else if (command == "threads") {
type = DapResponseType::DapThreads; type = DapResponseType::DapThreads;
} else if (command == "pause") {
type = DapResponseType::Pause;
} }
emit responseReady(type, ob); emit responseReady(type, ob);
return; return;

View File

@@ -38,6 +38,7 @@ signals:
enum class DapResponseType enum class DapResponseType
{ {
Initialize,
ConfigurationDone, ConfigurationDone,
Continue, Continue,
StackTrace, StackTrace,
@@ -47,6 +48,7 @@ enum class DapResponseType
StepIn, StepIn,
StepOut, StepOut,
StepOver, StepOver,
Pause,
Unknown Unknown
}; };
@@ -76,6 +78,7 @@ public:
virtual void sendInitialize(); virtual void sendInitialize();
void sendLaunch(const Utils::FilePath &executable); void sendLaunch(const Utils::FilePath &executable);
void sendAttach();
void sendConfigurationDone(); void sendConfigurationDone();
void sendDisconnect(); void sendDisconnect();

View File

@@ -6,6 +6,7 @@
#include "cmakedapengine.h" #include "cmakedapengine.h"
#include "dapclient.h" #include "dapclient.h"
#include "gdbdapengine.h" #include "gdbdapengine.h"
#include "pydapengine.h"
#include <debugger/breakhandler.h> #include <debugger/breakhandler.h>
#include <debugger/debuggeractions.h> #include <debugger/debuggeractions.h>
@@ -111,6 +112,7 @@ void DapEngine::shutdownEngine()
void DapEngine::handleDapStarted() void DapEngine::handleDapStarted()
{ {
notifyEngineSetupOk();
QTC_ASSERT(state() == EngineRunRequested, qCDebug(dapEngineLog) << state()); QTC_ASSERT(state() == EngineRunRequested, qCDebug(dapEngineLog) << state());
m_dapClient->sendInitialize(); m_dapClient->sendInitialize();
@@ -128,7 +130,7 @@ void DapEngine::handleDapConfigurationDone()
} }
void DapEngine::handleDapLaunch() void DapEngine::handleDapInitialize()
{ {
QTC_ASSERT(state() == EngineRunRequested, qCDebug(dapEngineLog) << state()); 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(); const QString command = response.value("command").toString();
switch (type) { switch (type) {
case DapResponseType::Initialize:
qCDebug(dapEngineLog) << "initialize success";
handleDapInitialize();
break;
case DapResponseType::ConfigurationDone: case DapResponseType::ConfigurationDone:
showMessage("configurationDone", LogDebug); showMessage("configurationDone", LogDebug);
qCDebug(dapEngineLog) << "configurationDone success"; qCDebug(dapEngineLog) << "configurationDone success";
@@ -513,7 +519,6 @@ void DapEngine::handleResponse(DapResponseType type, const QJsonObject &response
case DapResponseType::DapThreads: case DapResponseType::DapThreads:
handleThreadsResponse(response); handleThreadsResponse(response);
break; break;
default: default:
showMessage("UNKNOWN RESPONSE:" + command); showMessage("UNKNOWN RESPONSE:" + command);
}; };
@@ -585,7 +590,7 @@ void DapEngine::handleEvent(DapEventType type, const QJsonObject &event)
switch (type) { switch (type) {
case DapEventType::Initialized: case DapEventType::Initialized:
qCDebug(dapEngineLog) << "initialize success"; qCDebug(dapEngineLog) << "initialize success";
handleDapLaunch(); claimInitialBreakpoints();
handleDapConfigurationDone(); handleDapConfigurationDone();
break; break;
case DapEventType::Stopped: case DapEventType::Stopped:
@@ -801,10 +806,14 @@ void DapEngine::connectDataGeneratorSignals()
DebuggerEngine *createDapEngine(Utils::Id runMode) 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; return new CMakeDapEngine;
if (runMode == ProjectExplorer::Constants::DAP_GDB_DEBUG_RUN_MODE)
return new GdbDapEngine; return new GdbDapEngine;
if (runMode == ProjectExplorer::Constants::DAP_PY_DEBUG_RUN_MODE)
return new PyDapEngine;
return nullptr;
} }
} // Debugger::Internal } // Debugger::Internal

View File

@@ -84,7 +84,7 @@ protected:
void claimInitialBreakpoints(); void claimInitialBreakpoints();
void handleDapStarted(); void handleDapStarted();
void handleDapLaunch(); virtual void handleDapInitialize();
void handleDapConfigurationDone(); void handleDapConfigurationDone();
void dapRemoveBreakpoint(const Breakpoint &bp); void dapRemoveBreakpoint(const Breakpoint &bp);

View File

@@ -111,8 +111,6 @@ void GdbDapEngine::setupEngine()
connectDataGeneratorSignals(); connectDataGeneratorSignals();
m_dapClient->dataProvider()->start(); m_dapClient->dataProvider()->start();
notifyEngineSetupOk();
} }
} // namespace Debugger::Internal } // namespace Debugger::Internal

View File

@@ -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 <coreplugin/messagemanager.h>
#include <debugger/debuggermainwindow.h>
#include <utils/temporarydirectory.h>
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/projecttree.h>
#include <QDebug>
#include <QLoggingCategory>
#include <QTcpSocket>
#include <QTimer>
#include <QVersionNumber>
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

View File

@@ -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

View File

@@ -727,10 +727,10 @@ void DebuggerEnginePrivate::setupViews()
m_breakWindow->setObjectName("Debugger.Dock.Break." + engineId); m_breakWindow->setObjectName("Debugger.Dock.Break." + engineId);
m_breakWindow->setWindowTitle(Tr::tr("&Breakpoints")); m_breakWindow->setWindowTitle(Tr::tr("&Breakpoints"));
if (!currentPerspective || currentPerspective->id() == Constants::PRESET_PERSPECTIVE_ID) if (currentPerspective && currentPerspective->id() != Constants::PRESET_PERSPECTIVE_ID)
m_perspective->useSubPerspectiveSwitcher(EngineManager::engineChooser());
else
m_perspective->useSubPerspectiveSwitcher(EngineManager::dapEngineChooser()); m_perspective->useSubPerspectiveSwitcher(EngineManager::dapEngineChooser());
else
m_perspective->useSubPerspectiveSwitcher(EngineManager::engineChooser());
m_perspective->addToolBarAction(&m_continueAction); m_perspective->addToolBarAction(&m_continueAction);
m_perspective->addToolBarAction(&m_interruptAction); m_perspective->addToolBarAction(&m_interruptAction);

View File

@@ -1242,11 +1242,15 @@ void DebuggerPluginPrivate::createDapDebuggerPerspective(QWidget *globalLogWindo
{ {
EngineManager::registerDefaultPerspective(Tr::tr("CMake Preset"), EngineManager::registerDefaultPerspective(Tr::tr("CMake Preset"),
"DAP", "DAP",
ProjectExplorer::Constants::CMAKE_DEBUG_RUN_MODE); Constants::DAP_PERSPECTIVE_ID);
EngineManager::registerDefaultPerspective(Tr::tr("GDB Preset"), EngineManager::registerDefaultPerspective(Tr::tr("GDB Preset"),
"DAP", "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 breakpointManagerView = createBreakpointManagerView("DAPDebugger.BreakWindow");
auto breakpointManagerWindow auto breakpointManagerWindow
@@ -1265,11 +1269,14 @@ void DebuggerPluginPrivate::createDapDebuggerPerspective(QWidget *globalLogWindo
connect(&m_startDapAction, &QAction::triggered, this, [] { connect(&m_startDapAction, &QAction::triggered, this, [] {
QComboBox *combo = qobject_cast<QComboBox *>(EngineManager::dapEngineChooser()); QComboBox *combo = qobject_cast<QComboBox *>(EngineManager::dapEngineChooser());
if (combo->currentText() == "CMake Preset") { if (combo->currentText() == "CMake Preset") {
ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::CMAKE_DEBUG_RUN_MODE, ProjectExplorerPlugin::runStartupProject(
true); 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 { } else {
ProjectExplorerPlugin::runStartupProject( ProjectExplorerPlugin::runStartupProject(
ProjectExplorer::Constants::DAP_GDB_DEBUG_RUN_MODE, true); ProjectExplorer::Constants::DAP_PY_DEBUG_RUN_MODE, false);
} }
}); });

View File

@@ -482,10 +482,12 @@ void DebuggerRunTool::start()
runControl()->setDisplayName(m_runParameters.displayName); runControl()->setDisplayName(m_runParameters.displayName);
if (!m_engine) { 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()); m_engine = createDapEngine(runControl()->runMode());
else if (runControl()->runMode() == ProjectExplorer::Constants::DAP_GDB_DEBUG_RUN_MODE) 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()) { else if (m_runParameters.isCppDebugging()) {
switch (m_runParameters.cppEngineType) { switch (m_runParameters.cppEngineType) {
case GdbEngineType: case GdbEngineType:
@@ -918,6 +920,7 @@ DebuggerRunTool::DebuggerRunTool(RunControl *runControl, AllowTerminal allowTerm
m_runParameters.interpreter = interpreter; m_runParameters.interpreter = interpreter;
if (auto args = runControl->aspect<ArgumentsAspect>()) if (auto args = runControl->aspect<ArgumentsAspect>())
m_runParameters.inferior.command.addArgs(args->arguments, CommandLine::Raw); m_runParameters.inferior.command.addArgs(args->arguments, CommandLine::Raw);
if (runControl->runMode() == ProjectExplorer::Constants::DEBUG_RUN_MODE)
m_engine = createPdbEngine(); m_engine = createPdbEngine();
} }
} }
@@ -1120,8 +1123,9 @@ DebuggerRunWorkerFactory::DebuggerRunWorkerFactory()
{ {
setProduct<DebuggerRunTool>(); setProduct<DebuggerRunTool>();
addSupportedRunMode(ProjectExplorer::Constants::DEBUG_RUN_MODE); 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_GDB_DEBUG_RUN_MODE);
addSupportedRunMode(ProjectExplorer::Constants::DAP_PY_DEBUG_RUN_MODE);
addSupportedDeviceType(ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE); addSupportedDeviceType(ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE);
addSupportedDeviceType("DockerDeviceType"); addSupportedDeviceType("DockerDeviceType");
} }

View File

@@ -2954,7 +2954,7 @@ void ProjectExplorerPlugin::runRunConfiguration(RunConfiguration *rc,
? BuildForRunConfigStatus::Building : BuildForRunConfigStatus::NotBuilding ? BuildForRunConfigStatus::Building : BuildForRunConfigStatus::NotBuilding
: BuildManager::potentiallyBuildForRunConfig(rc); : 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; buildStatus = BuildForRunConfigStatus::NotBuilding;
switch (buildStatus) { switch (buildStatus) {

View File

@@ -178,8 +178,9 @@ const char GENERATOR_ID_PREFIX[] = "PE.Wizard.Generator.";
const char NO_RUN_MODE[]="RunConfiguration.NoRunMode"; const char NO_RUN_MODE[]="RunConfiguration.NoRunMode";
const char NORMAL_RUN_MODE[]="RunConfiguration.NormalRunMode"; const char NORMAL_RUN_MODE[]="RunConfiguration.NormalRunMode";
const char DEBUG_RUN_MODE[]="RunConfiguration.DebugRunMode"; 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_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_RUN_MODE[]="RunConfiguration.QmlProfilerRunMode";
const char QML_PROFILER_RUNNER[]="RunConfiguration.QmlProfilerRunner"; const char QML_PROFILER_RUNNER[]="RunConfiguration.QmlProfilerRunner";
const char QML_PREVIEW_RUN_MODE[]="RunConfiguration.QmlPreviewRunMode"; const char QML_PREVIEW_RUN_MODE[]="RunConfiguration.QmlPreviewRunMode";