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