forked from qt-creator/qt-creator
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:
@@ -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
|
||||
|
@@ -130,8 +130,6 @@ void CMakeDapEngine::setupEngine()
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
notifyEngineSetupOk();
|
||||
}
|
||||
|
||||
bool CMakeDapEngine::hasCapability(unsigned cap) const
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
|
@@ -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
|
||||
|
@@ -84,7 +84,7 @@ protected:
|
||||
void claimInitialBreakpoints();
|
||||
|
||||
void handleDapStarted();
|
||||
void handleDapLaunch();
|
||||
virtual void handleDapInitialize();
|
||||
void handleDapConfigurationDone();
|
||||
|
||||
void dapRemoveBreakpoint(const Breakpoint &bp);
|
||||
|
@@ -111,8 +111,6 @@ void GdbDapEngine::setupEngine()
|
||||
|
||||
connectDataGeneratorSignals();
|
||||
m_dapClient->dataProvider()->start();
|
||||
|
||||
notifyEngineSetupOk();
|
||||
}
|
||||
|
||||
} // namespace Debugger::Internal
|
||||
|
182
src/plugins/debugger/dap/pydapengine.cpp
Normal file
182
src/plugins/debugger/dap/pydapengine.cpp
Normal 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
|
21
src/plugins/debugger/dap/pydapengine.h
Normal file
21
src/plugins/debugger/dap/pydapengine.h
Normal 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
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -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");
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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";
|
||||
|
Reference in New Issue
Block a user