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

View File

@@ -130,8 +130,6 @@ void CMakeDapEngine::setupEngine()
return;
}
});
notifyEngineSetupOk();
}
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", ""}});
}
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;

View File

@@ -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();

View File

@@ -6,6 +6,7 @@
#include "cmakedapengine.h"
#include "dapclient.h"
#include "gdbdapengine.h"
#include "pydapengine.h"
#include <debugger/breakhandler.h>
#include <debugger/debuggeractions.h>
@@ -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 nullptr;
}
} // Debugger::Internal

View File

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

View File

@@ -111,8 +111,6 @@ void GdbDapEngine::setupEngine()
connectDataGeneratorSignals();
m_dapClient->dataProvider()->start();
notifyEngineSetupOk();
}
} // 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->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);

View File

@@ -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<QComboBox *>(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);
}
});

View File

@@ -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,6 +920,7 @@ DebuggerRunTool::DebuggerRunTool(RunControl *runControl, AllowTerminal allowTerm
m_runParameters.interpreter = interpreter;
if (auto args = runControl->aspect<ArgumentsAspect>())
m_runParameters.inferior.command.addArgs(args->arguments, CommandLine::Raw);
if (runControl->runMode() == ProjectExplorer::Constants::DEBUG_RUN_MODE)
m_engine = createPdbEngine();
}
}
@@ -1120,8 +1123,9 @@ DebuggerRunWorkerFactory::DebuggerRunWorkerFactory()
{
setProduct<DebuggerRunTool>();
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");
}

View File

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

View File

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