[utilities/kate] /: gdbplugin: support execution environment prefix and path mapping
Christoph Cullmann
null at kde.org
Mon Aug 25 17:27:31 BST 2025
Git commit e63b6269bba556983022fd793b2b7b4be7ea4599 by Christoph Cullmann, on behalf of Mark Nauwelaerts.
Committed on 25/08/2025 at 16:21.
Pushed by cullmann into branch 'master'.
gdbplugin: support execution environment prefix and path mapping
M +23 -0 addons/gdbplugin/configview.cpp
M +3 -0 addons/gdbplugin/configview.h
M +49 -13 addons/gdbplugin/dap/client.cpp
M +5 -2 addons/gdbplugin/dap/client.h
M +29 -29 addons/gdbplugin/dap/entities.cpp
M +17 -22 addons/gdbplugin/dap/entities.h
M +2 -1 addons/gdbplugin/dapbackend.cpp
M +2 -0 addons/gdbplugin/dapbackend.h
M +199 -5 doc/kate/plugins.docbook
https://invent.kde.org/utilities/kate/-/commit/e63b6269bba556983022fd793b2b7b4be7ea4599
diff --git a/addons/gdbplugin/configview.cpp b/addons/gdbplugin/configview.cpp
index d68b096963..e963ed7625 100644
--- a/addons/gdbplugin/configview.cpp
+++ b/addons/gdbplugin/configview.cpp
@@ -41,6 +41,7 @@
#include "plugin_kategdb.h"
#include "sessionconfig.h"
#include "target_json_keys.h"
+#include <exec_utils.h>
#include <json_utils.h>
#include <ktexteditor_utils.h>
@@ -431,6 +432,28 @@ DAPTargetConf ConfigView::currentDAPTarget(bool full, QString &errorMessage) con
auto &settings = cfg.dapSettings->settings;
settings = json::merge(settings, top);
+ // now we have all that, also consider execution prefix/environment settings
+ auto execConfig = Utils::ExecConfig::load(settings, projectConfig, {});
+ auto prefix = execConfig.prefix();
+ if (prefix.isArray()) {
+ // prepare map
+ cfg.dapSettings->pathMap = execConfig.init_mapping(view);
+ // adjust command-line
+ auto cmdline = settings.value(dap::settings::COMMAND).toArray();
+ auto newcmdline = prefix.toArray();
+ for (const auto &c : cmdline)
+ newcmdline += c;
+ settings[dap::settings::COMMAND] = newcmdline;
+ // also add environment for prefix runtime
+ auto M_ENV = QStringLiteral("environment");
+ auto env_v = settings[M_ENV];
+ auto env = env_v.isObject() ? env_v.toObject() : QJsonObject();
+ env[Utils::ExecConfig::ENV_KATE_EXEC_PLUGIN] = QStringLiteral("dap");
+ env[QStringLiteral("KATE_EXEC_SERVER")] = cfg.debugger;
+ env[QStringLiteral("KATE_EXEC_PROFILE")] = cfg.debuggerProfile;
+ settings[M_ENV] = env;
+ }
+
// var expansion in cmdline
auto cmdline = settings.value(dap::settings::COMMAND).toArray();
QStringList cmd;
diff --git a/addons/gdbplugin/configview.h b/addons/gdbplugin/configview.h
index 8fcc09ccb7..ae79ad8a9d 100644
--- a/addons/gdbplugin/configview.h
+++ b/addons/gdbplugin/configview.h
@@ -16,6 +16,8 @@
#include <optional>
+#include <exec_utils.h>
+
class QPushButton;
class QComboBox;
class QFrame;
@@ -55,6 +57,7 @@ struct DAPAdapterSettings {
// profile object = merged run/configuration
QJsonObject settings;
QStringList variables;
+ Utils::PathMappingPtr pathMap;
};
struct DAPTargetConf {
diff --git a/addons/gdbplugin/dap/client.cpp b/addons/gdbplugin/dap/client.cpp
index 211ec1339b..00b9cd5e73 100644
--- a/addons/gdbplugin/dap/client.cpp
+++ b/addons/gdbplugin/dap/client.cpp
@@ -6,6 +6,7 @@
#include "client.h"
#include <QJsonArray>
#include <QJsonDocument>
+#include <QFileInfo>
#include <limits>
#include "dap/bus_selector.h"
@@ -13,26 +14,61 @@
#include "messages.h"
+#include <exec_utils.h>
+
namespace dap
{
constexpr int MAX_HEADER_SIZE = 1 << 16;
-Client::Client(const settings::ProtocolSettings &protocolSettings, Bus *bus, QObject *parent)
+struct ClientMessageContext : public MessageContext {
+ Utils::PathMappingPtr pathMap;
+
+ ClientMessageContext(Utils::PathMappingPtr pm)
+ : pathMap(pm)
+ {
+ }
+
+ QString toRemote(const QUrl &url) override
+ {
+ if (!pathMap)
+ return url.toLocalFile();
+
+ auto result = Utils::mapPath(*pathMap, url, true).toLocalFile();
+ // if not able to map (e.g. src not present in remote env), then pass as-is
+ // e.g. gdb might be able to find some heuristic match in symbol info
+ return result.isEmpty() ? url.toLocalFile() : result;
+ }
+
+ virtual QUrl toLocal(const QString &path) override
+ {
+ auto url = QUrl::fromLocalFile(path);
+ // pass along relative paths; can not be reasonably mapped
+ // is a matter of search paths to resolve those
+ if (!pathMap || QFileInfo(path).isRelative())
+ return url;
+
+ return Utils::mapPath(*pathMap, url, false);
+ }
+};
+
+Client::Client(const settings::ProtocolSettings &protocolSettings, Bus *bus, Utils::PathMappingPtr pm, QObject *parent)
: QObject(parent)
, m_bus(bus)
, m_managedBus(false)
, m_protocol(protocolSettings)
, m_launchCommand(extractCommand(protocolSettings.launchRequest))
{
+ m_msgContext = std::make_unique<ClientMessageContext>(pm);
bind();
}
-Client::Client(const settings::ClientSettings &clientSettings, QObject *parent)
+Client::Client(const settings::ClientSettings &clientSettings, Utils::PathMappingPtr pm, QObject *parent)
: QObject(parent)
, m_managedBus(true)
, m_protocol(clientSettings.protocolSettings)
, m_launchCommand(extractCommand(clientSettings.protocolSettings.launchRequest))
{
+ m_msgContext = std::make_unique<ClientMessageContext>(pm);
m_bus = createBus(clientSettings.busSettings);
m_bus->setParent(this);
bind();
@@ -191,7 +227,7 @@ void Client::processEvent(const QJsonObject &msg)
const int exitCode = body[QStringLiteral("exitCode")].toInt(-1);
Q_EMIT debuggeeExited(exitCode);
} else if (DAP_OUTPUT == event) {
- Q_EMIT outputProduced(Output(body));
+ Q_EMIT outputProduced(Output(body, *m_msgContext));
} else if (QStringLiteral("process") == event) {
Q_EMIT debuggingProcess(ProcessInfo(body));
} else if (QStringLiteral("thread") == event) {
@@ -203,7 +239,7 @@ void Client::processEvent(const QJsonObject &msg)
} else if (QStringLiteral("continued") == event) {
Q_EMIT debuggeeContinued(ContinuedEvent(body));
} else if (DAP_BREAKPOINT == event) {
- Q_EMIT breakpointChanged(BreakpointEvent(body));
+ Q_EMIT breakpointChanged(BreakpointEvent(body, *m_msgContext));
} else {
qCWarning(DAPCLIENT, "unsupported event: %ls", qUtf16Printable(event));
}
@@ -242,7 +278,7 @@ void Client::processResponseStackTrace(const Response &response, const QJsonValu
{
const int threadId = request.toObject()[DAP_THREAD_ID].toInt();
if (response.success) {
- Q_EMIT stackTrace(threadId, StackTraceInfo(response.body.toObject()));
+ Q_EMIT stackTrace(threadId, StackTraceInfo(response.body.toObject(), *m_msgContext));
} else {
Q_EMIT stackTrace(threadId, StackTraceInfo());
}
@@ -252,7 +288,7 @@ void Client::processResponseScopes(const Response &response, const QJsonValue &r
{
const int frameId = request.toObject()[DAP_FRAME_ID].toInt();
if (response.success) {
- Q_EMIT scopes(frameId, Scope::parseList(response.body.toObject()[DAP_SCOPES].toArray()));
+ Q_EMIT scopes(frameId, Scope::parseList(response.body.toObject()[DAP_SCOPES].toArray(), *m_msgContext));
} else {
Q_EMIT scopes(frameId, QList<Scope>());
}
@@ -314,7 +350,7 @@ void Client::processResponseDisconnect(const Response &response, const QJsonValu
void Client::processResponseSource(const Response &response, const QJsonValue &request)
{
const auto req = request.toObject();
- const auto path = QUrl::fromLocalFile(req[DAP_SOURCE].toObject()[DAP_PATH].toString());
+ const auto path = m_msgContext->toLocal(req[DAP_SOURCE].toObject()[DAP_PATH].toString());
const auto reference = req[DAP_SOURCE_REFERENCE].toInt(0);
if (response.success) {
Q_EMIT sourceContent(path, reference, SourceContent(response.body.toObject()));
@@ -326,13 +362,13 @@ void Client::processResponseSource(const Response &response, const QJsonValue &r
void Client::processResponseSetBreakpoints(const Response &response, const QJsonValue &request)
{
- const auto source = Source(request.toObject()[DAP_SOURCE].toObject());
+ const auto source = Source(request.toObject()[DAP_SOURCE].toObject(), *m_msgContext);
if (response.success) {
const auto resp = response.body.toObject();
if (resp.contains(DAP_BREAKPOINTS)) {
QList<Breakpoint> breakpoints;
for (const auto &item : resp[DAP_BREAKPOINTS].toArray()) {
- breakpoints.append(Breakpoint(item.toObject()));
+ breakpoints.append(Breakpoint(item.toObject(), *m_msgContext));
}
Q_EMIT sourceBreakpoints(source.path, source.sourceReference.value_or(0), breakpoints);
} else {
@@ -360,7 +396,7 @@ void Client::processResponseEvaluate(const Response &response, const QJsonValue
void Client::processResponseGotoTargets(const Response &response, const QJsonValue &request)
{
const auto &req = request.toObject();
- const auto source = Source(req[DAP_SOURCE].toObject());
+ const auto source = Source(req[DAP_SOURCE].toObject(), *m_msgContext);
const int line = req[DAP_LINE].toInt();
if (response.success) {
Q_EMIT gotoTargets(source, line, GotoTarget::parseList(response.body.toObject()[QStringLiteral("targets")].toArray()));
@@ -621,7 +657,7 @@ void Client::requestSource(const Source &source)
if (reference == 0 && source.path.scheme() == Source::referenceScheme())
reference = source.path.path().toInt();
QJsonObject arguments{{DAP_SOURCE_REFERENCE, reference}};
- const QJsonObject sourceArg{{DAP_SOURCE_REFERENCE, reference}, {DAP_PATH, source.path.path()}};
+ const QJsonObject sourceArg{{DAP_SOURCE_REFERENCE, reference}, {DAP_PATH, m_msgContext->toRemote(source.path)}};
arguments[DAP_SOURCE] = sourceArg;
@@ -639,7 +675,7 @@ void Client::requestSetBreakpoints(const Source &source, const QList<SourceBreak
for (const auto &item : breakpoints) {
bpoints.append(item.toJson());
}
- QJsonObject arguments{{DAP_SOURCE, source.toJson()}, {DAP_BREAKPOINTS, bpoints}, {QStringLiteral("sourceModified"), sourceModified}};
+ QJsonObject arguments{{DAP_SOURCE, source.toJson(*m_msgContext)}, {DAP_BREAKPOINTS, bpoints}, {QStringLiteral("sourceModified"), sourceModified}};
this->write(makeRequest(QStringLiteral("setBreakpoints"), arguments, &Client::processResponseSetBreakpoints));
}
@@ -669,7 +705,7 @@ void Client::requestGotoTargets(const QUrl &path, const int line, const std::opt
void Client::requestGotoTargets(const Source &source, const int line, const std::optional<int> column)
{
- QJsonObject arguments{{DAP_SOURCE, source.toJson()}, {DAP_LINE, line}};
+ QJsonObject arguments{{DAP_SOURCE, source.toJson(*m_msgContext)}, {DAP_LINE, line}};
if (column) {
arguments[DAP_COLUMN] = *column;
}
diff --git a/addons/gdbplugin/dap/client.h b/addons/gdbplugin/dap/client.h
index c45d6ef175..8d3898977b 100644
--- a/addons/gdbplugin/dap/client.h
+++ b/addons/gdbplugin/dap/client.h
@@ -16,6 +16,8 @@
#include "entities.h"
#include "settings.h"
+#include <exec_utils.h>
+
namespace dap
{
class Client : public QObject
@@ -31,9 +33,9 @@ public:
};
Q_ENUM(State)
- Client(const settings::ProtocolSettings &protocolSettings, Bus *bus, QObject *parent = nullptr);
+ Client(const settings::ProtocolSettings &protocolSettings, Bus *bus, Utils::PathMappingPtr, QObject *parent = nullptr);
- Client(const settings::ClientSettings &clientSettings, QObject *parent = nullptr);
+ Client(const settings::ClientSettings &clientSettings, Utils::PathMappingPtr, QObject *parent = nullptr);
~Client() override;
@@ -213,6 +215,7 @@ private:
settings::ProtocolSettings m_protocol;
QString m_launchCommand;
+ std::unique_ptr<MessageContext> m_msgContext;
};
}
diff --git a/addons/gdbplugin/dap/entities.cpp b/addons/gdbplugin/dap/entities.cpp
index ddd15d10af..0cf4979149 100644
--- a/addons/gdbplugin/dap/entities.cpp
+++ b/addons/gdbplugin/dap/entities.cpp
@@ -38,13 +38,13 @@ static std::optional<QString> parseOptionalString(const QJsonValue &value)
return value.toString();
}
-template<typename T>
-static std::optional<T> parseOptionalObject(const QJsonValue &value)
+template<typename T, typename... Args>
+static std::optional<T> parseOptionalObject(const QJsonValue &value, Args &&...args)
{
if (value.isNull() || value.isUndefined() || !value.isObject()) {
return std::nullopt;
}
- return T(value.toObject());
+ return T(value.toObject(), std::forward<Args>(args)...);
}
template<typename T>
@@ -61,12 +61,12 @@ static std::optional<QHash<QString, T>> parseOptionalMap(const QJsonValue &value
return map;
}
-template<typename T>
-static QList<T> parseObjectList(const QJsonArray &array)
+template<typename T, typename... Args>
+static QList<T> parseObjectList(const QJsonArray &array, Args &&...args)
{
QList<T> out;
for (const auto &item : array) {
- out << T(item.toObject());
+ out << T(item.toObject(), std::forward<Args>(args)...);
}
return out;
}
@@ -93,12 +93,12 @@ static std::optional<QList<int>> parseOptionalIntList(const QJsonValue &value)
return values;
}
-template<typename T>
-static QJsonArray toJsonArray(const QList<T> &items)
+template<typename T, typename... Args>
+static QJsonArray toJsonArray(const QList<T> &items, Args &&...args)
{
QJsonArray out;
for (const auto &item : items) {
- out << item.toJson();
+ out << item.toJson(std::forward<Args>(args)...);
}
return out;
}
@@ -153,12 +153,12 @@ ProcessInfo::ProcessInfo(const QJsonObject &body)
{
}
-Output::Output(const QJsonObject &body)
+Output::Output(const QJsonObject &body, MessageContext &ctx)
: category(Category::Unknown)
, output(body[DAP_OUTPUT].toString())
, group(std::nullopt)
, variablesReference(parseOptionalInt(body[DAP_VARIABLES_REFERENCE]))
- , source(parseOptionalObject<Source>(DAP_SOURCE))
+ , source(parseOptionalObject<Source>(DAP_SOURCE, ctx))
, line(parseOptionalInt(body[DAP_LINE]))
, column(parseOptionalInt(body[DAP_COLUMN]))
, data(body[DAP_DATA])
@@ -223,9 +223,9 @@ QUrl Source::getUnifiedId(const QUrl &path, std::optional<int> sourceReference)
return path;
}
-Source::Source(const QJsonObject &body)
+Source::Source(const QJsonObject &body, MessageContext &ctx)
: name(body[DAP_NAME].toString())
- , path(QUrl::fromLocalFile(body[DAP_PATH].toString()))
+ , path(ctx.toLocal(body[DAP_PATH].toString()))
, sourceReference(parseOptionalInt(body[DAP_SOURCE_REFERENCE]))
, presentationHint(parseOptionalString(body[DAP_PRESENTATION_HINT]))
, origin(body[DAP_ORIGIN].toString())
@@ -235,7 +235,7 @@ Source::Source(const QJsonObject &body)
if (body.contains(DAP_SOURCES)) {
const auto values = body[DAP_SOURCES].toArray();
for (const auto &item : values) {
- sources << Source(item.toObject());
+ sources << Source(item.toObject(), ctx);
}
}
@@ -253,14 +253,14 @@ Source::Source(const QUrl &path)
{
}
-QJsonObject Source::toJson() const
+QJsonObject Source::toJson(MessageContext &ctx) const
{
QJsonObject out;
if (!name.isEmpty()) {
out[DAP_NAME] = name;
}
if (!path.isEmpty()) {
- out[DAP_PATH] = path.path();
+ out[DAP_PATH] = ctx.toRemote(path);
}
if (sourceReference) {
out[DAP_SOURCE_REFERENCE] = *sourceReference;
@@ -275,7 +275,7 @@ QJsonObject Source::toJson() const
out[DAP_ADAPTER_DATA] = adapterData;
}
if (!sources.isEmpty()) {
- out[DAP_SOURCES] = toJsonArray(sources);
+ out[DAP_SOURCES] = toJsonArray(sources, ctx);
}
if (!checksums.isEmpty()) {
out[DAP_CHECKSUMS] = toJsonArray(checksums);
@@ -343,10 +343,10 @@ QList<Thread> Thread::parseList(const QJsonArray &threads)
return parseObjectList<Thread>(threads);
}
-StackFrame::StackFrame(const QJsonObject &body)
+StackFrame::StackFrame(const QJsonObject &body, MessageContext &ctx)
: id(body[DAP_ID].toInt())
, name(body[DAP_NAME].toString())
- , source(parseOptionalObject<Source>(body[DAP_SOURCE]))
+ , source(parseOptionalObject<Source>(body[DAP_SOURCE], ctx))
, line(body[DAP_LINE].toInt())
, column(body[DAP_COLUMN].toInt())
, endLine(parseOptionalInt(body[QStringLiteral("endLine")]))
@@ -358,8 +358,8 @@ StackFrame::StackFrame(const QJsonObject &body)
{
}
-StackTraceInfo::StackTraceInfo(const QJsonObject &body)
- : stackFrames(parseObjectList<StackFrame>(body[QStringLiteral("stackFrames")].toArray()))
+StackTraceInfo::StackTraceInfo(const QJsonObject &body, MessageContext &ctx)
+ : stackFrames(parseObjectList<StackFrame>(body[QStringLiteral("stackFrames")].toArray(), ctx))
, totalFrames(parseOptionalInt(body[QStringLiteral("totalFrames")]))
{
}
@@ -385,14 +385,14 @@ ModuleEvent::ModuleEvent(const QJsonObject &body)
{
}
-Scope::Scope(const QJsonObject &body)
+Scope::Scope(const QJsonObject &body, MessageContext &ctx)
: name(body[DAP_NAME].toString())
, presentationHint(parseOptionalString(body[DAP_PRESENTATION_HINT]))
, variablesReference(body[DAP_VARIABLES_REFERENCE].toInt())
, namedVariables(parseOptionalInt(body[QStringLiteral("namedVariables")]))
, indexedVariables(parseOptionalInt(body[QStringLiteral("indexedVariables")]))
, expensive(parseOptionalBool(body[QStringLiteral("expensive")]))
- , source(parseOptionalObject<Source>(body[QStringLiteral("source")]))
+ , source(parseOptionalObject<Source>(body[QStringLiteral("source")], ctx))
, line(parseOptionalInt(body[QStringLiteral("line")]))
, column(parseOptionalInt(body[QStringLiteral("column")]))
, endLine(parseOptionalInt(body[QStringLiteral("endLine")]))
@@ -406,9 +406,9 @@ Scope::Scope(int variablesReference, QString name)
{
}
-QList<Scope> Scope::parseList(const QJsonArray &scopes)
+QList<Scope> Scope::parseList(const QJsonArray &scopes, MessageContext &ctx)
{
- return parseObjectList<Scope>(scopes);
+ return parseObjectList<Scope>(scopes, ctx);
}
Variable::Variable(const QJsonObject &body)
@@ -503,11 +503,11 @@ QJsonObject SourceBreakpoint::toJson() const
return out;
}
-Breakpoint::Breakpoint(const QJsonObject &body)
+Breakpoint::Breakpoint(const QJsonObject &body, MessageContext &ctx)
: id(parseOptionalInt(body[DAP_ID]))
, verified(body[QStringLiteral("verified")].toBool())
, message(parseOptionalString(body[QStringLiteral("message")]))
- , source(parseOptionalObject<Source>(body[DAP_SOURCE]))
+ , source(parseOptionalObject<Source>(body[DAP_SOURCE], ctx))
, line(parseOptionalInt(body[DAP_LINE]))
, column(parseOptionalInt(body[DAP_COLUMN]))
, endLine(parseOptionalInt(body[DAP_END_LINE]))
@@ -522,9 +522,9 @@ Breakpoint::Breakpoint(const int line)
{
}
-BreakpointEvent::BreakpointEvent(const QJsonObject &body)
+BreakpointEvent::BreakpointEvent(const QJsonObject &body, MessageContext &ctx)
: reason(body[DAP_REASON].toString())
- , breakpoint(Breakpoint(body[DAP_BREAKPOINT].toObject()))
+ , breakpoint(Breakpoint(body[DAP_BREAKPOINT].toObject(), ctx))
{
}
diff --git a/addons/gdbplugin/dap/entities.h b/addons/gdbplugin/dap/entities.h
index 4214ed8454..e71e38fe06 100644
--- a/addons/gdbplugin/dap/entities.h
+++ b/addons/gdbplugin/dap/entities.h
@@ -103,6 +103,12 @@ struct ProcessInfo {
ProcessInfo(const QJsonObject &body);
};
+struct MessageContext {
+ virtual ~MessageContext() = default;
+ virtual QString toRemote(const QUrl &) = 0;
+ virtual QUrl toLocal(const QString &) = 0;
+};
+
struct Checksum {
QString checksum;
QString algorithm;
@@ -128,10 +134,10 @@ struct Source {
static QUrl getUnifiedId(const QUrl &path, std::optional<int> sourceReference);
Source() = default;
- Source(const QJsonObject &body);
+ Source(const QJsonObject &body, MessageContext &);
Source(const QUrl &path);
- QJsonObject toJson() const;
+ QJsonObject toJson(MessageContext &ctx) const;
};
struct SourceContent {
@@ -212,7 +218,7 @@ struct Breakpoint {
std::optional<int> offset;
Breakpoint() = default;
- Breakpoint(const QJsonObject &body);
+ Breakpoint(const QJsonObject &body, MessageContext &);
Breakpoint(const int line);
};
@@ -220,14 +226,7 @@ class Output
{
Q_GADGET
public:
- enum class Category {
- Console,
- Important,
- Stdout,
- Stderr,
- Telemetry,
- Unknown
- };
+ enum class Category { Console, Important, Stdout, Stderr, Telemetry, Unknown };
Q_ENUM(Category)
@@ -247,7 +246,7 @@ public:
QJsonValue data;
Output() = default;
- Output(const QJsonObject &body);
+ Output(const QJsonObject &body, MessageContext &ctx);
Output(const QString &output, const Category &category);
bool isSpecialOutput() const;
@@ -361,7 +360,7 @@ struct BreakpointEvent {
Breakpoint breakpoint;
BreakpointEvent() = default;
- BreakpointEvent(const QJsonObject &body);
+ BreakpointEvent(const QJsonObject &body, MessageContext &);
};
struct Thread {
@@ -390,7 +389,7 @@ struct StackFrame {
std::optional<QString> presentationHint;
StackFrame() = default;
- StackFrame(const QJsonObject &body);
+ StackFrame(const QJsonObject &body, MessageContext &ctx);
};
struct StackTraceInfo {
@@ -398,7 +397,7 @@ struct StackTraceInfo {
std::optional<int> totalFrames;
StackTraceInfo() = default;
- StackTraceInfo(const QJsonObject &body);
+ StackTraceInfo(const QJsonObject &body, MessageContext &ctx);
};
struct Scope {
@@ -415,18 +414,14 @@ struct Scope {
std::optional<int> endColumn;
Scope() = default;
- Scope(const QJsonObject &body);
+ Scope(const QJsonObject &body, MessageContext &ctx);
Scope(int variablesReference, QString name);
- static QList<Scope> parseList(const QJsonArray &scopes);
+ static QList<Scope> parseList(const QJsonArray &scopes, MessageContext &ctx);
};
struct Variable {
- enum Type {
- Indexed = 1,
- Named = 2,
- Both = 3
- };
+ enum Type { Indexed = 1, Named = 2, Both = 3 };
QString name;
QString value;
diff --git a/addons/gdbplugin/dapbackend.cpp b/addons/gdbplugin/dapbackend.cpp
index a757cbde18..75bd9acd98 100644
--- a/addons/gdbplugin/dapbackend.cpp
+++ b/addons/gdbplugin/dapbackend.cpp
@@ -163,6 +163,7 @@ dap::settings::ClientSettings &DapBackend::target2dap(const DAPTargetConf &targe
Q_EMIT outputText(QString::fromLocal8Bit(QJsonDocument(out).toJson()) + QStringLiteral("\n"));
m_settings = dap::settings::ClientSettings(out);
+ m_pathMap = target.dapSettings->pathMap;
return *m_settings;
}
@@ -174,7 +175,7 @@ void DapBackend::start()
}
unsetClient();
- m_client = new dap::Client(*m_settings, this);
+ m_client = new dap::Client(*m_settings, m_pathMap, this);
Q_EMIT debuggerCapabilitiesChanged();
diff --git a/addons/gdbplugin/dapbackend.h b/addons/gdbplugin/dapbackend.h
index 617689b019..580372bd70 100644
--- a/addons/gdbplugin/dapbackend.h
+++ b/addons/gdbplugin/dapbackend.h
@@ -155,6 +155,8 @@ private:
dap::Client *m_client = nullptr;
std::optional<dap::settings::ClientSettings> m_settings;
+ Utils::PathMappingPtr m_pathMap;
+
State m_state;
Task m_task;
diff --git a/doc/kate/plugins.docbook b/doc/kate/plugins.docbook
index 5bee2bfbac..f59dfb7984 100644
--- a/doc/kate/plugins.docbook
+++ b/doc/kate/plugins.docbook
@@ -1595,8 +1595,12 @@ for opened documents on hover.</para></listitem>
<sect2 id="gdb-intro">
<title>Introduction</title>
-<para>&kate;'s &gdb; plugin provides a simple frontend to the popular &GNU;
-Project Debugger.</para>
+<para>&kate;'s &gdb; plugin provides a simple frontend to any debugger that supports the
+<ulink url="https://microsoft.github.io/debug-adapter-protocol/">Debugger Adapter Protocol</ulink>.
+In particular, that includes the GNU Project Debugger, aka GDB, as
+<ulink url="https://sourceware.org/gdb/current/onlinedocs/gdb.html/Debugger-Adapter-Protocol.html">outlined
+here</ulink>.
+</para>
<important>
<para>Previous experience with &gdb; is strongly recommended. For more
@@ -1608,9 +1612,6 @@ information on using &gdb;, visit <ulink url="https://www.gnu.org/software/gdb/"
<link linkend="config-dialog-plugins">the Plugins section of &kate;'s
configuration</link>.</para>
-<para>For the plugin to work properly, you must have a source file (of any type
-supported by &gdb;) and an executable.</para>
-
<tip>
<para>If you compile using &gcc;/<command>g++</command> you might want to use
the <command><parameter>-ggdb</parameter></command> command line argument.
@@ -1618,11 +1619,21 @@ the <command><parameter>-ggdb</parameter></command> command line argument.
</tip>
<para>After these preparations are made, open the source file in &kate;,
+select the "debugger profile",
enter the path to the executable in the <guilabel>Settings</guilabel> tab of the
<guilabel>Debug View</guilabel> tool view, and select
<menuchoice><guimenu>Debug</guimenu><guimenuitem>Start Debugging</guimenuitem></menuchoice>
from the menu to get started.</para>
+<para>
+The "debugger profile" selects the DAP server to use (e.g. GDB) and the way
+in which to launch this server. A typical case is to have the server launch
+a process as specified above, but it may also attach to a running process
+(in which case a PID will have to be specified rather than an executable).
+There may also be other modes which are specific to the language and DAP server.
+See also later for additional background and configuration details on this.
+</para>
+
</sect2>
<sect2 id="gdb-menus">
@@ -1888,6 +1899,189 @@ writing much of this section.</para>
</sect2>
+<sect2 id="gdb-configuration">
+<title>Configuration</title>
+
+<para>
+The plugin's configuration page defines the "debugger profiles" which can be
+selected. The default configuration (JSON) is shown there, and it can be "overlayed"
+by a user provided of similar form. An example excerpt is as follows:
+
+<screen>
+{
+ "dap": {
+ "debugpy": {
+ "url": "https://github.com/microsoft/debugpy",
+ "run": {
+ "command": ["python", "-m", "debugpy", "--listen", "${#run.port}", "--wait-for-client"],
+ "port": 0,
+ "supportsSourceRequest": false
+ },
+ "configurations": {
+ "launch": {
+ "commandArgs": ["${file}", "${args|list}"],
+ "request": {
+ "command": "attach",
+ "stopOnEntry": true,
+ "redirectOutput": true
+ }
+ },
+ "attach": {
+ "commandArgs": ["--pid", "${pid}"],
+ "request": {
+ "command": "attach",
+ "stopOnEntry": true,
+ "redirectOutput": true
+ }
+ },
+ },
+ "gdb": {
+ "url": "gdb",
+ "run": {
+ "command": [
+ "gdb",
+ "-i",
+ "dap"
+ ],
+ "redirectStderr": true,
+ "redirectStdout": true,
+ "supportsSourceRequest": true
+ },
+ "configurations": {
+ "launch (debug)": {
+ "request": {
+ "command": "launch",
+ "mode": "debug",
+ "program": "${file}",
+ "args": "${args|list}",
+ "cwd": "${workdir}"
+ }
+ }
+ }
+ }
+ }
+}
+</screen>
+
+Each of the entries in <literal>configurations</literal> is combined with
+the <literal>run</literal> data and forms a "profile". This specifies
+the DAP server to launch along with its arguments, where the latter are specific
+to the profile (<literal>commandArgs</literal>). The other parts specify
+the DAP protocol request (<literal>launch</literal> or <literal>attach</literal>),
+along with DAP specific extensions.
+</para>
+
+<para>
+Of course, the specified server should be installed (and typically also in
+<literal>PATH</literal> for proper execution).
+</para>
+
+<para>
+Various stages of override/merge are applied;
+user configuration (loaded from file) overrides (internal) default configuration,
+and the "dap" entry in <literal>.kateproject</literal> project configuration in turn
+overrides.
+</para>
+
+
+<sect3 id="gdb-exec">
+<title>Execution environment setup</title>
+
+<para>
+More background and specifics can be found in the
+<link linkend="lspclient-exec">LSP Client Execution environment</link> section below.
+But suffice it to say here it may be needed to run the debuggee process
+(and the DAP server) in a "special environment", whether merely defined by environment
+variables or some container (providing the required dependencies and circumstances
+for proper execution).
+</para>
+
+<para>
+Similar to the example in the referenced section, the following configuration
+may be provided in a <literal>.kateproject</literal>.
+
+<screen>
+{
+ // this may also be an array of objects
+ "exec": {
+ "hostname": "foobar"
+ // the command could also be an array of string
+ "prefix": "podman exec -i foobarcontainer",
+ "mapRemoteRoot": true,
+ "pathMappings": [
+ // either of the following forms are possible
+ [ "/dir/on/host", "/mounted/in/container" ]
+ { "localRoot": "/local/dir", "remoteRoot": "/remote/dir" }
+ ]
+ },
+ "dap": {
+ "debugpy": {
+ "run": {
+ // in this section, it applies to all configurations
+ // this will match/join with the above object
+ "exec": { "hostname": "foobar" },
+ // if server is connected to,
+ // optionally specify explicit port (which is suitable published/forwarded)
+ "port": 5678,
+ // the server may then also have to accept more than localhost
+ "host": "0.0.0.0"
+ }
+ }
+ }
+}
+</screen>
+
+The referenced section should be consulted for details, but in essence
+the <literal>prefix</literal> will be prepended before the DAP server
+command line specified elsewhere. The effect is that the server is run
+within the specified container and then in turn also the launched process.
+The <literal>pathMapping</literal> arranges for transformation of filepaths
+between editor's view and DAP server (container) view, e.g. when dealing with
+setting of breakpoints or handling of reported backtraces.
+Note that such mapping is optional and may or may not be useful. When dealing
+with C/C++ code that is compiled on "host", the symbol info references source
+files on host which do not exist at all in the other environment. However,
+in other scripted (e.g. python) circumstances, actual runtime files are
+referenced (on the other environment).
+</para>
+
+<para>
+The following must evidently be kept in mind;
+<itemizedlist>
+<listitem>
+<para>The DAP server must be present in the environment/container,
+which must be configured to support proper debugger operation
+(so, if needed, privileged, capabilities).
+</para>
+</listitem>
+<listitem>
+<para>Communication between editor and DAP must be possible. In case of
+a container, the latter should either use host networking, or provide a suitable
+mapped/published port along with corresponding config snippet as in above example
+(as an auto-selected one in case of port 0 would not make it through).
+</para>
+</listitem>
+<listitem>
+<para>
+A specified executable/PID should be in "container" perspective, as well as
+any (debuggee executable) arguments.
+</para>
+</listitem>
+</itemizedlist>
+
+Also, as in the LSP case, some environment variables are set;
+<literal>KATE_EXEC_PLUGIN</literal> is set to <literal>dap</literal>,
+<literal>KATE_EXEC_SERVER</literal> is set to the debugger/language type
+(e.g. <literal>python</literal>) and
+<literal>KATE_EXEC_PROFILE</literal> is set to the configuration entry
+(e.g. <literal>launch</literal>).
+
+</para>
+
+</sect3>
+
+</sect2>
+
</sect1>
<sect1 id="kate-application-plugin-projects">
More information about the kde-doc-english
mailing list