summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGlen Chung <kuchung@microsoft.com>2023-03-16 00:50:08 (GMT)
committerBrad King <brad.king@kitware.com>2023-05-30 13:46:12 (GMT)
commita9a592f96e6498da302f8e968be1db0ad3c32123 (patch)
tree0d75f16ee2eae99b1a3f063e575b3f5f8f2ee931
parentb0d1ddb7234950374977b83f8dbded806c15b356 (diff)
downloadCMake-a9a592f96e6498da302f8e968be1db0ad3c32123.zip
CMake-a9a592f96e6498da302f8e968be1db0ad3c32123.tar.gz
CMake-a9a592f96e6498da302f8e968be1db0ad3c32123.tar.bz2
cmake: Add debugger
- Depends on cppdap and jsoncpp. - Add --debugger argument to enable the Debugger. - Add --debugger-pipe argument for DAP traffics over named pipes. - Support breakpoints by filenames and line numbers. - Support exception breakpoints. - Call stack shows filenames and line numbers. - Show Cache Variables. - Show the state of currently defined targets, tests and directories with their properties. - Add cmakeVersion to DAP initialize response. - Include unit tests. Co-authored-by: Ben McMorran <bemcmorr@microsoft.com>
-rw-r--r--CMakeLists.txt14
-rw-r--r--Help/manual/cmake.1.rst53
-rw-r--r--Help/release/dev/cmake-debugger.rst5
-rw-r--r--Source/CMakeLists.txt32
-rw-r--r--Source/Modules/CMakeBuildUtilities.cmake2
-rw-r--r--Source/cmConfigure.cmake.h.in1
-rw-r--r--Source/cmDebuggerAdapter.cxx462
-rw-r--r--Source/cmDebuggerAdapter.h93
-rw-r--r--Source/cmDebuggerBreakpointManager.cxx200
-rw-r--r--Source/cmDebuggerBreakpointManager.h61
-rw-r--r--Source/cmDebuggerExceptionManager.cxx129
-rw-r--r--Source/cmDebuggerExceptionManager.h70
-rw-r--r--Source/cmDebuggerPipeConnection.cxx293
-rw-r--r--Source/cmDebuggerPipeConnection.h139
-rw-r--r--Source/cmDebuggerProtocol.cxx80
-rw-r--r--Source/cmDebuggerProtocol.h191
-rw-r--r--Source/cmDebuggerSourceBreakpoint.cxx14
-rw-r--r--Source/cmDebuggerSourceBreakpoint.h26
-rw-r--r--Source/cmDebuggerStackFrame.cxx28
-rw-r--r--Source/cmDebuggerStackFrame.h33
-rw-r--r--Source/cmDebuggerThread.cxx150
-rw-r--r--Source/cmDebuggerThread.h59
-rw-r--r--Source/cmDebuggerThreadManager.cxx47
-rw-r--r--Source/cmDebuggerThreadManager.h38
-rw-r--r--Source/cmDebuggerVariables.cxx133
-rw-r--r--Source/cmDebuggerVariables.h124
-rw-r--r--Source/cmDebuggerVariablesHelper.cxx644
-rw-r--r--Source/cmDebuggerVariablesHelper.h106
-rw-r--r--Source/cmDebuggerVariablesManager.cxx38
-rw-r--r--Source/cmDebuggerVariablesManager.h40
-rw-r--r--Source/cmMakefile.cxx95
-rw-r--r--Source/cmMessageCommand.cxx11
-rw-r--r--Source/cmMessenger.cxx10
-rw-r--r--Source/cmMessenger.h17
-rw-r--r--Source/cmake.cxx115
-rw-r--r--Source/cmake.h31
-rw-r--r--Source/cmakemain.cxx6
-rw-r--r--Tests/CMakeLib/CMakeLists.txt25
-rw-r--r--Tests/CMakeLib/DebuggerSample/CMakeLists.txt9
-rw-r--r--Tests/CMakeLib/DebuggerSample/script.cmake1
-rw-r--r--Tests/CMakeLib/testCommon.h30
-rw-r--r--Tests/CMakeLib/testDebugger.h99
-rw-r--r--Tests/CMakeLib/testDebuggerAdapter.cxx173
-rw-r--r--Tests/CMakeLib/testDebuggerAdapterPipe.cxx184
-rw-r--r--Tests/CMakeLib/testDebuggerBreakpointManager.cxx172
-rw-r--r--Tests/CMakeLib/testDebuggerNamedPipe.cxx218
-rw-r--r--Tests/CMakeLib/testDebuggerVariables.cxx185
-rw-r--r--Tests/CMakeLib/testDebuggerVariablesHelper.cxx587
-rw-r--r--Tests/CMakeLib/testDebuggerVariablesManager.cxx50
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-result.txt1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-stderr.txt2
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog.cmake1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-result.txt1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-stderr.txt2
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerArgMissingPipe.cmake1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerCapabilityInspect-check.cmake5
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupported-result.txt1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupported-stderr.txt2
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupported.cmake1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-result.txt1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-stderr.txt2
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog.cmake1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-result.txt1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-stderr.txt2
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe.cmake1
-rw-r--r--Tests/RunCMake/CommandLine/E_capabilities-stdout.txt2
-rw-r--r--Tests/RunCMake/CommandLine/RunCMakeTest.cmake11
-rw-r--r--Utilities/IWYU/mapping.imp2
-rwxr-xr-xbootstrap11
69 files changed, 5364 insertions, 10 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9ec6267..d559c08 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -131,21 +131,21 @@ if(CMake_BUILD_LTO)
endif()
endif()
-# Check whether to build cppdap.
+# Check whether to build support for the debugger mode.
if(NOT CMake_TEST_EXTERNAL_CMAKE)
- if(NOT DEFINED CMake_ENABLE_CPPDAP)
- # cppdap does not compile everywhere.
+ if(NOT DEFINED CMake_ENABLE_DEBUGGER)
+ # The debugger uses cppdap, which does not compile everywhere.
if(CMAKE_SYSTEM_NAME MATCHES "Windows|Darwin|Linux|BSD|DragonFly|CYGWIN|MSYS"
AND NOT (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.16)
AND NOT (CMAKE_CXX_COMPILER_ID STREQUAL "XLClang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16.1)
)
- set(CMake_ENABLE_CPPDAP 1)
+ set(CMake_ENABLE_DEBUGGER 1)
else()
- set(CMake_ENABLE_CPPDAP 0)
+ set(CMake_ENABLE_DEBUGGER 0)
endif()
endif()
else()
- set(CMake_ENABLE_CPPDAP 0)
+ set(CMake_ENABLE_DEBUGGER 0)
endif()
#-----------------------------------------------------------------------
@@ -186,7 +186,7 @@ macro(CMAKE_HANDLE_SYSTEM_LIBRARIES)
# Optionally use system utility libraries.
option(CMAKE_USE_SYSTEM_LIBARCHIVE "Use system-installed libarchive" "${CMAKE_USE_SYSTEM_LIBRARY_LIBARCHIVE}")
- if(CMake_ENABLE_CPPDAP)
+ if(CMake_ENABLE_DEBUGGER)
option(CMAKE_USE_SYSTEM_CPPDAP "Use system-installed cppdap" "${CMAKE_USE_SYSTEM_LIBRARY_CPPDAP}")
endif()
option(CMAKE_USE_SYSTEM_CURL "Use system-installed curl" "${CMAKE_USE_SYSTEM_LIBRARY_CURL}")
diff --git a/Help/manual/cmake.1.rst b/Help/manual/cmake.1.rst
index 1ea7626..b5848f7 100644
--- a/Help/manual/cmake.1.rst
+++ b/Help/manual/cmake.1.rst
@@ -517,6 +517,53 @@ Options
If ``<type>`` is omitted, ``configure`` is assumed. The current working
directory must contain CMake preset files.
+.. option:: --debugger
+
+ Enables interactive debugging of the CMake language. CMake exposes a debugging
+ interface on the pipe named by :option:`--debugger-pipe <cmake --debugger-pipe>`
+ that conforms to the `Debug Adapter Protocol`_ specification with the following
+ modifications.
+
+ The ``initialize`` response includes an additional field named ``cmakeVersion``
+ which specifies the version of CMake being debugged.
+
+ .. code-block:: json
+ :caption: Debugger initialize response
+
+ {
+ "cmakeVersion": {
+ "major": 3,
+ "minor": 27,
+ "patch": 0,
+ "full": "3.27.0"
+ }
+ }
+
+ The members are:
+
+ ``major``
+ An integer specifying the major version number.
+
+ ``minor``
+ An integer specifying the minor version number.
+
+ ``patch``
+ An integer specifying the patch version number.
+
+ ``full``
+ A string specifying the full CMake version.
+
+.. _`Debug Adapter Protocol`: https://microsoft.github.io/debug-adapter-protocol/
+
+.. option:: --debugger-pipe <pipe name>, --debugger-pipe=<pipe name>
+
+ Name of the pipe (on Windows) or domain socket (on Unix) to use for
+ debugger communication.
+
+.. option:: --debugger-dap-log <log path>, --debugger-dap-log=<log path>
+
+ Logs all debugger communication to the specified file.
+
.. _`Build Tool Mode`:
Build a Project
@@ -809,6 +856,12 @@ Available commands are:
``true`` if TLS support is enabled and ``false`` otherwise.
+ ``debugger``
+ .. versionadded:: 3.27
+
+ ``true`` if the :option:`--debugger <cmake --debugger>` mode
+ is supported and ``false`` otherwise.
+
.. option:: cat [--] <files>...
.. versionadded:: 3.18
diff --git a/Help/release/dev/cmake-debugger.rst b/Help/release/dev/cmake-debugger.rst
new file mode 100644
index 0000000..bfc4f6c
--- /dev/null
+++ b/Help/release/dev/cmake-debugger.rst
@@ -0,0 +1,5 @@
+cmake-debugger
+--------------
+
+* :manual:`cmake(1)` now supports interactive debugging of the CMake language.
+ See the :option:`--debugger <cmake --debugger>` option.
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 2354f3d..bcaf890 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -762,6 +762,38 @@ target_link_libraries(
ZLIB::ZLIB
)
+if(CMake_ENABLE_DEBUGGER)
+ target_sources(
+ CMakeLib
+ PRIVATE
+ cmDebuggerAdapter.cxx
+ cmDebuggerAdapter.h
+ cmDebuggerBreakpointManager.cxx
+ cmDebuggerBreakpointManager.h
+ cmDebuggerExceptionManager.cxx
+ cmDebuggerExceptionManager.h
+ cmDebuggerPipeConnection.cxx
+ cmDebuggerPipeConnection.h
+ cmDebuggerProtocol.cxx
+ cmDebuggerProtocol.h
+ cmDebuggerSourceBreakpoint.cxx
+ cmDebuggerSourceBreakpoint.h
+ cmDebuggerStackFrame.cxx
+ cmDebuggerStackFrame.h
+ cmDebuggerThread.cxx
+ cmDebuggerThread.h
+ cmDebuggerThreadManager.cxx
+ cmDebuggerThreadManager.h
+ cmDebuggerVariables.cxx
+ cmDebuggerVariables.h
+ cmDebuggerVariablesHelper.cxx
+ cmDebuggerVariablesHelper.h
+ cmDebuggerVariablesManager.cxx
+ cmDebuggerVariablesManager.h
+ )
+ target_link_libraries(CMakeLib PUBLIC cppdap::cppdap)
+endif()
+
# Check if we can build the Mach-O parser.
if(CMake_USE_MACH_PARSER)
target_sources(
diff --git a/Source/Modules/CMakeBuildUtilities.cmake b/Source/Modules/CMakeBuildUtilities.cmake
index 7d1e7da..c891fe9 100644
--- a/Source/Modules/CMakeBuildUtilities.cmake
+++ b/Source/Modules/CMakeBuildUtilities.cmake
@@ -379,7 +379,7 @@ endif()
#---------------------------------------------------------------------
# Build cppdap library.
-if(CMake_ENABLE_CPPDAP)
+if(CMake_ENABLE_DEBUGGER)
if(CMAKE_USE_SYSTEM_CPPDAP)
find_package(cppdap CONFIG)
if(NOT cppdap_FOUND)
diff --git a/Source/cmConfigure.cmake.h.in b/Source/cmConfigure.cmake.h.in
index 3f19a11..de74716 100644
--- a/Source/cmConfigure.cmake.h.in
+++ b/Source/cmConfigure.cmake.h.in
@@ -20,6 +20,7 @@
#cmakedefine HAVE_ENVIRON_NOT_REQUIRE_PROTOTYPE
#cmakedefine HAVE_UNSETENV
+#cmakedefine CMake_ENABLE_DEBUGGER
#cmakedefine CMake_USE_MACH_PARSER
#cmakedefine CMake_USE_XCOFF_PARSER
#cmakedefine CMAKE_USE_WMAKE
diff --git a/Source/cmDebuggerAdapter.cxx b/Source/cmDebuggerAdapter.cxx
new file mode 100644
index 0000000..d03f79d
--- /dev/null
+++ b/Source/cmDebuggerAdapter.cxx
@@ -0,0 +1,462 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmDebuggerAdapter.h"
+
+#include <algorithm>
+#include <climits>
+#include <condition_variable>
+#include <cstdint>
+#include <functional>
+#include <iostream>
+#include <stdexcept>
+#include <utility>
+
+#include <cm/memory>
+#include <cm/optional>
+
+#include <cm3p/cppdap/io.h> // IWYU pragma: keep
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/session.h>
+
+#include "cmDebuggerBreakpointManager.h"
+#include "cmDebuggerExceptionManager.h"
+#include "cmDebuggerProtocol.h"
+#include "cmDebuggerSourceBreakpoint.h" // IWYU pragma: keep
+#include "cmDebuggerStackFrame.h"
+#include "cmDebuggerThread.h"
+#include "cmDebuggerThreadManager.h"
+#include "cmListFileCache.h"
+#include "cmMakefile.h"
+#include "cmValue.h"
+#include "cmVersionConfig.h"
+#include <cmcppdap/include/dap/optional.h>
+#include <cmcppdap/include/dap/types.h>
+
+namespace cmDebugger {
+
+// Event provides a basic wait and signal synchronization primitive.
+class SyncEvent
+{
+public:
+ // Wait() blocks until the event is fired.
+ void Wait()
+ {
+ std::unique_lock<std::mutex> lock(Mutex);
+ Cv.wait(lock, [&] { return Fired; });
+ }
+
+ // Fire() sets signals the event, and unblocks any calls to Wait().
+ void Fire()
+ {
+ std::unique_lock<std::mutex> lock(Mutex);
+ Fired = true;
+ Cv.notify_all();
+ }
+
+private:
+ std::mutex Mutex;
+ std::condition_variable Cv;
+ bool Fired = false;
+};
+
+class Semaphore
+{
+public:
+ Semaphore(int count_ = 0)
+ : Count(count_)
+ {
+ }
+
+ inline void Notify()
+ {
+ std::unique_lock<std::mutex> lock(Mutex);
+ Count++;
+ // notify the waiting thread
+ Cv.notify_one();
+ }
+
+ inline void Wait()
+ {
+ std::unique_lock<std::mutex> lock(Mutex);
+ while (Count == 0) {
+ // wait on the mutex until notify is called
+ Cv.wait(lock);
+ }
+ Count--;
+ }
+
+private:
+ std::mutex Mutex;
+ std::condition_variable Cv;
+ int Count;
+};
+
+cmDebuggerAdapter::cmDebuggerAdapter(
+ std::shared_ptr<cmDebuggerConnection> connection,
+ std::string const& dapLogPath)
+ : cmDebuggerAdapter(std::move(connection),
+ dapLogPath.empty()
+ ? cm::nullopt
+ : cm::optional<std::shared_ptr<dap::Writer>>(
+ dap::file(dapLogPath.c_str())))
+{
+}
+
+cmDebuggerAdapter::cmDebuggerAdapter(
+ std::shared_ptr<cmDebuggerConnection> connection,
+ cm::optional<std::shared_ptr<dap::Writer>> logger)
+ : Connection(std::move(connection))
+ , SessionActive(true)
+ , DisconnectEvent(cm::make_unique<SyncEvent>())
+ , ConfigurationDoneEvent(cm::make_unique<SyncEvent>())
+ , ContinueSem(cm::make_unique<Semaphore>())
+ , ThreadManager(cm::make_unique<cmDebuggerThreadManager>())
+{
+ if (logger.has_value()) {
+ SessionLog = std::move(logger.value());
+ }
+ ClearStepRequests();
+
+ Session = dap::Session::create();
+ BreakpointManager =
+ cm::make_unique<cmDebuggerBreakpointManager>(Session.get());
+ ExceptionManager =
+ cm::make_unique<cmDebuggerExceptionManager>(Session.get());
+
+ // Handle errors reported by the Session. These errors include protocol
+ // parsing errors and receiving messages with no handler.
+ Session->onError([this](const char* msg) {
+ if (SessionLog) {
+ dap::writef(SessionLog, "dap::Session error: %s\n", msg);
+ }
+
+ std::cout << "[CMake Debugger] DAP session error: " << msg << std::endl;
+
+ BreakpointManager->ClearAll();
+ ExceptionManager->ClearAll();
+ ClearStepRequests();
+ ContinueSem->Notify();
+ DisconnectEvent->Fire();
+ SessionActive.store(false);
+ });
+
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
+ Session->registerHandler([this](const dap::CMakeInitializeRequest& req) {
+ SupportsVariableType = req.supportsVariableType.value(false);
+ dap::CMakeInitializeResponse response;
+ response.supportsConfigurationDoneRequest = true;
+ response.cmakeVersion.major = CMake_VERSION_MAJOR;
+ response.cmakeVersion.minor = CMake_VERSION_MINOR;
+ response.cmakeVersion.patch = CMake_VERSION_PATCH;
+ response.cmakeVersion.full = CMake_VERSION;
+ ExceptionManager->HandleInitializeRequest(response);
+ return response;
+ });
+
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Events_Initialized
+ Session->registerSentHandler(
+ [&](const dap::ResponseOrError<dap::CMakeInitializeResponse>&) {
+ Session->send(dap::InitializedEvent());
+ });
+
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Threads
+ Session->registerHandler([this](const dap::ThreadsRequest& req) {
+ (void)req;
+ std::unique_lock<std::mutex> lock(Mutex);
+ dap::ThreadsResponse response;
+ dap::Thread thread;
+ thread.id = DefaultThread->GetId();
+ thread.name = DefaultThread->GetName();
+ response.threads.push_back(thread);
+ return response;
+ });
+
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StackTrace
+ Session->registerHandler([this](const dap::StackTraceRequest& request)
+ -> dap::ResponseOrError<dap::StackTraceResponse> {
+ std::unique_lock<std::mutex> lock(Mutex);
+
+ cm::optional<dap::StackTraceResponse> response =
+ ThreadManager->GetThreadStackTraceResponse(request.threadId);
+ if (response.has_value()) {
+ return response.value();
+ }
+
+ return dap::Error("Unknown threadId '%d'", int(request.threadId));
+ });
+
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Scopes
+ Session->registerHandler([this](const dap::ScopesRequest& request)
+ -> dap::ResponseOrError<dap::ScopesResponse> {
+ std::unique_lock<std::mutex> lock(Mutex);
+ return DefaultThread->GetScopesResponse(request.frameId,
+ SupportsVariableType);
+ });
+
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Variables
+ Session->registerHandler([this](const dap::VariablesRequest& request)
+ -> dap::ResponseOrError<dap::VariablesResponse> {
+ return DefaultThread->GetVariablesResponse(request);
+ });
+
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Pause
+ Session->registerHandler([this](const dap::PauseRequest& req) {
+ (void)req;
+ PauseRequest.store(true);
+ return dap::PauseResponse();
+ });
+
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Continue
+ Session->registerHandler([this](const dap::ContinueRequest& req) {
+ (void)req;
+ ContinueSem->Notify();
+ return dap::ContinueResponse();
+ });
+
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Next
+ Session->registerHandler([this](const dap::NextRequest& req) {
+ (void)req;
+ NextStepFrom.store(DefaultThread->GetStackFrameSize());
+ ContinueSem->Notify();
+ return dap::NextResponse();
+ });
+
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StepIn
+ Session->registerHandler([this](const dap::StepInRequest& req) {
+ (void)req;
+ // This would stop after stepped in, single line stepped or stepped out.
+ StepInRequest.store(true);
+ ContinueSem->Notify();
+ return dap::StepInResponse();
+ });
+
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StepOut
+ Session->registerHandler([this](const dap::StepOutRequest& req) {
+ (void)req;
+ StepOutDepth.store(DefaultThread->GetStackFrameSize() - 1);
+ ContinueSem->Notify();
+ return dap::StepOutResponse();
+ });
+
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Launch
+ Session->registerHandler([](const dap::LaunchRequest& req) {
+ (void)req;
+ return dap::LaunchResponse();
+ });
+
+ // Handler for disconnect requests
+ Session->registerHandler([this](const dap::DisconnectRequest& request) {
+ (void)request;
+ BreakpointManager->ClearAll();
+ ExceptionManager->ClearAll();
+ ClearStepRequests();
+ ContinueSem->Notify();
+ DisconnectEvent->Fire();
+ SessionActive.store(false);
+ return dap::DisconnectResponse();
+ });
+
+ Session->registerHandler([this](const dap::EvaluateRequest& request) {
+ dap::EvaluateResponse response;
+ if (request.frameId.has_value()) {
+ std::shared_ptr<cmDebuggerStackFrame> frame =
+ DefaultThread->GetStackFrame(request.frameId.value());
+
+ auto var = frame->GetMakefile()->GetDefinition(request.expression);
+ if (var) {
+ response.type = "string";
+ response.result = var;
+ return response;
+ }
+ }
+
+ return response;
+ });
+
+ // The ConfigurationDone request is made by the client once all configuration
+ // requests have been made.
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_ConfigurationDone
+ Session->registerHandler([this](const dap::ConfigurationDoneRequest& req) {
+ (void)req;
+ ConfigurationDoneEvent->Fire();
+ return dap::ConfigurationDoneResponse();
+ });
+
+ std::string errorMessage;
+ if (!Connection->StartListening(errorMessage)) {
+ throw std::runtime_error(errorMessage);
+ }
+
+ // Connect to the client. Write a well-known message to stdout so that
+ // clients know it is safe to attempt to connect.
+ std::cout << "Waiting for debugger client to connect..." << std::endl;
+ Connection->WaitForConnection();
+ std::cout << "Debugger client connected." << std::endl;
+
+ if (SessionLog) {
+ Session->connect(spy(Connection->GetReader(), SessionLog),
+ spy(Connection->GetWriter(), SessionLog));
+ } else {
+ Session->connect(Connection->GetReader(), Connection->GetWriter());
+ }
+
+ // Start the processing thread.
+ SessionThread = std::thread([this] {
+ while (SessionActive.load()) {
+ if (auto payload = Session->getPayload()) {
+ payload();
+ }
+ }
+ });
+
+ ConfigurationDoneEvent->Wait();
+
+ DefaultThread = ThreadManager->StartThread("CMake script");
+ dap::ThreadEvent threadEvent;
+ threadEvent.reason = "started";
+ threadEvent.threadId = DefaultThread->GetId();
+ Session->send(threadEvent);
+}
+
+cmDebuggerAdapter::~cmDebuggerAdapter()
+{
+ if (SessionThread.joinable()) {
+ SessionThread.join();
+ }
+
+ Session.reset(nullptr);
+
+ if (SessionLog) {
+ SessionLog->close();
+ }
+}
+
+void cmDebuggerAdapter::ReportExitCode(int exitCode)
+{
+ ThreadManager->EndThread(DefaultThread);
+ dap::ThreadEvent threadEvent;
+ threadEvent.reason = "exited";
+ threadEvent.threadId = DefaultThread->GetId();
+ DefaultThread.reset();
+
+ dap::ExitedEvent exitEvent;
+ exitEvent.exitCode = exitCode;
+
+ dap::TerminatedEvent terminatedEvent;
+
+ if (SessionActive.load()) {
+ Session->send(threadEvent);
+ Session->send(exitEvent);
+ Session->send(terminatedEvent);
+ }
+
+ // Wait until disconnected or error.
+ DisconnectEvent->Wait();
+}
+
+void cmDebuggerAdapter::OnFileParsedSuccessfully(
+ std::string const& sourcePath,
+ std::vector<cmListFileFunction> const& functions)
+{
+ BreakpointManager->SourceFileLoaded(sourcePath, functions);
+}
+
+void cmDebuggerAdapter::OnBeginFunctionCall(cmMakefile* mf,
+ std::string const& sourcePath,
+ cmListFileFunction const& lff)
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ DefaultThread->PushStackFrame(mf, sourcePath, lff);
+
+ if (lff.Line() == 0) {
+ // File just loaded, continue to first valid function call.
+ return;
+ }
+
+ auto hits = BreakpointManager->GetBreakpoints(sourcePath, lff.Line());
+ lock.unlock();
+
+ bool waitSem = false;
+ dap::StoppedEvent stoppedEvent;
+ stoppedEvent.allThreadsStopped = true;
+ stoppedEvent.threadId = DefaultThread->GetId();
+ if (!hits.empty()) {
+ ClearStepRequests();
+ waitSem = true;
+
+ dap::array<dap::integer> hitBreakpoints;
+ hitBreakpoints.resize(hits.size());
+ std::transform(hits.begin(), hits.end(), hitBreakpoints.begin(),
+ [&](const int64_t& id) { return dap::integer(id); });
+ stoppedEvent.reason = "breakpoint";
+ stoppedEvent.hitBreakpointIds = hitBreakpoints;
+ }
+
+ if (long(DefaultThread->GetStackFrameSize()) <= NextStepFrom.load() ||
+ StepInRequest.load() ||
+ long(DefaultThread->GetStackFrameSize()) <= StepOutDepth.load()) {
+ ClearStepRequests();
+ waitSem = true;
+
+ stoppedEvent.reason = "step";
+ }
+
+ if (PauseRequest.load()) {
+ ClearStepRequests();
+ waitSem = true;
+
+ stoppedEvent.reason = "pause";
+ }
+
+ if (waitSem) {
+ Session->send(stoppedEvent);
+ ContinueSem->Wait();
+ }
+}
+
+void cmDebuggerAdapter::OnEndFunctionCall()
+{
+ DefaultThread->PopStackFrame();
+}
+
+static std::shared_ptr<cmListFileFunction> listFileFunction;
+
+void cmDebuggerAdapter::OnBeginFileParse(cmMakefile* mf,
+ std::string const& sourcePath)
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+
+ listFileFunction = std::make_shared<cmListFileFunction>(
+ sourcePath, 0, 0, std::vector<cmListFileArgument>());
+ DefaultThread->PushStackFrame(mf, sourcePath, *listFileFunction);
+}
+
+void cmDebuggerAdapter::OnEndFileParse()
+{
+ DefaultThread->PopStackFrame();
+ listFileFunction = nullptr;
+}
+
+void cmDebuggerAdapter::OnMessageOutput(MessageType t, std::string const& text)
+{
+ cm::optional<dap::StoppedEvent> stoppedEvent =
+ ExceptionManager->RaiseExceptionIfAny(t, text);
+ if (stoppedEvent.has_value()) {
+ stoppedEvent->threadId = DefaultThread->GetId();
+ Session->send(*stoppedEvent);
+ ContinueSem->Wait();
+ }
+}
+
+void cmDebuggerAdapter::ClearStepRequests()
+{
+ NextStepFrom.store(INT_MIN);
+ StepInRequest.store(false);
+ StepOutDepth.store(INT_MIN);
+ PauseRequest.store(false);
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerAdapter.h b/Source/cmDebuggerAdapter.h
new file mode 100644
index 0000000..f261d88
--- /dev/null
+++ b/Source/cmDebuggerAdapter.h
@@ -0,0 +1,93 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <atomic>
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <cm/optional>
+
+#include <cm3p/cppdap/io.h> // IWYU pragma: keep
+
+#include "cmMessageType.h"
+
+class cmListFileFunction;
+class cmMakefile;
+
+namespace cmDebugger {
+class Semaphore;
+class SyncEvent;
+class cmDebuggerBreakpointManager;
+class cmDebuggerExceptionManager;
+class cmDebuggerThread;
+class cmDebuggerThreadManager;
+}
+
+namespace dap {
+class Session;
+}
+
+namespace cmDebugger {
+
+class cmDebuggerConnection
+{
+public:
+ virtual ~cmDebuggerConnection() = default;
+ virtual bool StartListening(std::string& errorMessage) = 0;
+ virtual void WaitForConnection() = 0;
+ virtual std::shared_ptr<dap::Reader> GetReader() = 0;
+ virtual std::shared_ptr<dap::Writer> GetWriter() = 0;
+};
+
+class cmDebuggerAdapter
+{
+public:
+ cmDebuggerAdapter(std::shared_ptr<cmDebuggerConnection> connection,
+ std::string const& dapLogPath);
+ cmDebuggerAdapter(std::shared_ptr<cmDebuggerConnection> connection,
+ cm::optional<std::shared_ptr<dap::Writer>> logger);
+ ~cmDebuggerAdapter();
+
+ void ReportExitCode(int exitCode);
+
+ void OnFileParsedSuccessfully(
+ std::string const& sourcePath,
+ std::vector<cmListFileFunction> const& functions);
+ void OnBeginFunctionCall(cmMakefile* mf, std::string const& sourcePath,
+ cmListFileFunction const& lff);
+ void OnEndFunctionCall();
+ void OnBeginFileParse(cmMakefile* mf, std::string const& sourcePath);
+ void OnEndFileParse();
+
+ void OnMessageOutput(MessageType t, std::string const& text);
+
+private:
+ void ClearStepRequests();
+ std::shared_ptr<cmDebuggerConnection> Connection;
+ std::unique_ptr<dap::Session> Session;
+ std::shared_ptr<dap::Writer> SessionLog;
+ std::thread SessionThread;
+ std::atomic<bool> SessionActive;
+ std::mutex Mutex;
+ std::unique_ptr<SyncEvent> DisconnectEvent;
+ std::unique_ptr<SyncEvent> ConfigurationDoneEvent;
+ std::unique_ptr<Semaphore> ContinueSem;
+ std::atomic<int64_t> NextStepFrom;
+ std::atomic<bool> StepInRequest;
+ std::atomic<int64_t> StepOutDepth;
+ std::atomic<bool> PauseRequest;
+ std::unique_ptr<cmDebuggerThreadManager> ThreadManager;
+ std::shared_ptr<cmDebuggerThread> DefaultThread;
+ std::unique_ptr<cmDebuggerBreakpointManager> BreakpointManager;
+ std::unique_ptr<cmDebuggerExceptionManager> ExceptionManager;
+ bool SupportsVariableType;
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerBreakpointManager.cxx b/Source/cmDebuggerBreakpointManager.cxx
new file mode 100644
index 0000000..152f0f5
--- /dev/null
+++ b/Source/cmDebuggerBreakpointManager.cxx
@@ -0,0 +1,200 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmDebuggerBreakpointManager.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/session.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerSourceBreakpoint.h"
+#include "cmListFileCache.h"
+#include "cmSystemTools.h"
+
+namespace cmDebugger {
+
+cmDebuggerBreakpointManager::cmDebuggerBreakpointManager(
+ dap::Session* dapSession)
+ : DapSession(dapSession)
+{
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetBreakpoints
+ DapSession->registerHandler([&](const dap::SetBreakpointsRequest& request) {
+ return HandleSetBreakpointsRequest(request);
+ });
+}
+
+int64_t cmDebuggerBreakpointManager::FindFunctionStartLine(
+ std::string const& sourcePath, int64_t line)
+{
+ auto location =
+ find_if(ListFileFunctionLines[sourcePath].begin(),
+ ListFileFunctionLines[sourcePath].end(),
+ [=](cmDebuggerFunctionLocation const& loc) {
+ return loc.StartLine <= line && loc.EndLine >= line;
+ });
+
+ if (location != ListFileFunctionLines[sourcePath].end()) {
+ return location->StartLine;
+ }
+
+ return 0;
+}
+
+int64_t cmDebuggerBreakpointManager::CalibrateBreakpointLine(
+ std::string const& sourcePath, int64_t line)
+{
+ auto location = find_if(ListFileFunctionLines[sourcePath].begin(),
+ ListFileFunctionLines[sourcePath].end(),
+ [=](cmDebuggerFunctionLocation const& loc) {
+ return loc.StartLine >= line;
+ });
+
+ if (location != ListFileFunctionLines[sourcePath].end()) {
+ return location->StartLine;
+ }
+
+ if (!ListFileFunctionLines[sourcePath].empty() &&
+ ListFileFunctionLines[sourcePath].back().EndLine <= line) {
+ // return last function start line for any breakpoints after.
+ return ListFileFunctionLines[sourcePath].back().StartLine;
+ }
+
+ return 0;
+}
+
+dap::SetBreakpointsResponse
+cmDebuggerBreakpointManager::HandleSetBreakpointsRequest(
+ dap::SetBreakpointsRequest const& request)
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+
+ dap::SetBreakpointsResponse response;
+
+ auto sourcePath =
+ cmSystemTools::GetActualCaseForPath(request.source.path.value());
+ const dap::array<dap::SourceBreakpoint> defaultValue{};
+ const auto& breakpoints = request.breakpoints.value(defaultValue);
+ if (ListFileFunctionLines.find(sourcePath) != ListFileFunctionLines.end()) {
+ // The file has loaded, we can validate breakpoints.
+ if (Breakpoints.find(sourcePath) != Breakpoints.end()) {
+ Breakpoints[sourcePath].clear();
+ }
+ response.breakpoints.resize(breakpoints.size());
+ for (size_t i = 0; i < breakpoints.size(); i++) {
+ int64_t correctedLine =
+ CalibrateBreakpointLine(sourcePath, breakpoints[i].line);
+ if (correctedLine > 0) {
+ Breakpoints[sourcePath].emplace_back(NextBreakpointId++,
+ correctedLine);
+ response.breakpoints[i].id = Breakpoints[sourcePath].back().GetId();
+ response.breakpoints[i].line =
+ Breakpoints[sourcePath].back().GetLine();
+ response.breakpoints[i].verified = true;
+ } else {
+ response.breakpoints[i].verified = false;
+ response.breakpoints[i].line = breakpoints[i].line;
+ }
+ dap::Source dapSrc;
+ dapSrc.path = sourcePath;
+ response.breakpoints[i].source = dapSrc;
+ }
+ } else {
+ // The file has not loaded, validate breakpoints later.
+ ListFilePendingValidations.emplace(sourcePath);
+
+ response.breakpoints.resize(breakpoints.size());
+ for (size_t i = 0; i < breakpoints.size(); i++) {
+ Breakpoints[sourcePath].emplace_back(NextBreakpointId++,
+ breakpoints[i].line);
+ response.breakpoints[i].id = Breakpoints[sourcePath].back().GetId();
+ response.breakpoints[i].line = Breakpoints[sourcePath].back().GetLine();
+ response.breakpoints[i].verified = false;
+ dap::Source dapSrc;
+ dapSrc.path = sourcePath;
+ response.breakpoints[i].source = dapSrc;
+ }
+ }
+
+ return response;
+}
+
+void cmDebuggerBreakpointManager::SourceFileLoaded(
+ std::string const& sourcePath,
+ std::vector<cmListFileFunction> const& functions)
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ if (ListFileFunctionLines.find(sourcePath) != ListFileFunctionLines.end()) {
+ // this is not expected.
+ return;
+ }
+
+ for (cmListFileFunction const& func : functions) {
+ ListFileFunctionLines[sourcePath].emplace_back(
+ cmDebuggerFunctionLocation{ func.Line(), func.LineEnd() });
+ }
+
+ if (ListFilePendingValidations.find(sourcePath) ==
+ ListFilePendingValidations.end()) {
+ return;
+ }
+
+ ListFilePendingValidations.erase(sourcePath);
+
+ for (size_t i = 0; i < Breakpoints[sourcePath].size(); i++) {
+ dap::BreakpointEvent breakpointEvent;
+ breakpointEvent.breakpoint.id = Breakpoints[sourcePath][i].GetId();
+ breakpointEvent.breakpoint.line = Breakpoints[sourcePath][i].GetLine();
+ auto source = dap::Source();
+ source.path = sourcePath;
+ breakpointEvent.breakpoint.source = source;
+ int64_t correctedLine = CalibrateBreakpointLine(
+ sourcePath, Breakpoints[sourcePath][i].GetLine());
+ if (correctedLine != Breakpoints[sourcePath][i].GetLine()) {
+ Breakpoints[sourcePath][i].ChangeLine(correctedLine);
+ }
+ breakpointEvent.reason = "changed";
+ breakpointEvent.breakpoint.verified = (correctedLine > 0);
+ if (breakpointEvent.breakpoint.verified) {
+ breakpointEvent.breakpoint.line = correctedLine;
+ } else {
+ Breakpoints[sourcePath][i].Invalid();
+ }
+
+ DapSession->send(breakpointEvent);
+ }
+}
+
+std::vector<int64_t> cmDebuggerBreakpointManager::GetBreakpoints(
+ std::string const& sourcePath, int64_t line)
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ const auto& all = Breakpoints[sourcePath];
+ std::vector<int64_t> breakpoints;
+ if (all.empty()) {
+ return breakpoints;
+ }
+
+ auto it = all.begin();
+
+ while ((it = std::find_if(
+ it, all.end(), [&](const cmDebuggerSourceBreakpoint& breakpoint) {
+ return (breakpoint.GetIsValid() && breakpoint.GetLine() == line);
+ })) != all.end()) {
+ breakpoints.emplace_back(it->GetId());
+ ++it;
+ }
+
+ return breakpoints;
+}
+
+void cmDebuggerBreakpointManager::ClearAll()
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ Breakpoints.clear();
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerBreakpointManager.h b/Source/cmDebuggerBreakpointManager.h
new file mode 100644
index 0000000..a4e5df5
--- /dev/null
+++ b/Source/cmDebuggerBreakpointManager.h
@@ -0,0 +1,61 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstdint>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+
+class cmListFileFunction;
+
+namespace cmDebugger {
+class cmDebuggerSourceBreakpoint;
+}
+
+namespace dap {
+class Session;
+}
+
+namespace cmDebugger {
+
+struct cmDebuggerFunctionLocation
+{
+ int64_t StartLine;
+ int64_t EndLine;
+};
+
+/** The breakpoint manager. */
+class cmDebuggerBreakpointManager
+{
+ dap::Session* DapSession;
+ std::mutex Mutex;
+ std::unordered_map<std::string, std::vector<cmDebuggerSourceBreakpoint>>
+ Breakpoints;
+ std::unordered_map<std::string,
+ std::vector<struct cmDebuggerFunctionLocation>>
+ ListFileFunctionLines;
+ std::unordered_set<std::string> ListFilePendingValidations;
+ int64_t NextBreakpointId = 0;
+
+ dap::SetBreakpointsResponse HandleSetBreakpointsRequest(
+ dap::SetBreakpointsRequest const& request);
+ int64_t FindFunctionStartLine(std::string const& sourcePath, int64_t line);
+ int64_t CalibrateBreakpointLine(std::string const& sourcePath, int64_t line);
+
+public:
+ cmDebuggerBreakpointManager(dap::Session* dapSession);
+ void SourceFileLoaded(std::string const& sourcePath,
+ std::vector<cmListFileFunction> const& functions);
+ std::vector<int64_t> GetBreakpoints(std::string const& sourcePath,
+ int64_t line);
+ void ClearAll();
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerExceptionManager.cxx b/Source/cmDebuggerExceptionManager.cxx
new file mode 100644
index 0000000..a27426c
--- /dev/null
+++ b/Source/cmDebuggerExceptionManager.cxx
@@ -0,0 +1,129 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmDebuggerExceptionManager.h"
+
+#include <utility>
+#include <vector>
+
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/session.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerProtocol.h"
+#include "cmMessageType.h"
+
+namespace cmDebugger {
+
+cmDebuggerExceptionManager::cmDebuggerExceptionManager(
+ dap::Session* dapSession)
+ : DapSession(dapSession)
+{
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetExceptionBreakpoints
+ DapSession->registerHandler(
+ [&](const dap::SetExceptionBreakpointsRequest& request) {
+ return HandleSetExceptionBreakpointsRequest(request);
+ });
+
+ // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_ExceptionInfo
+ DapSession->registerHandler([&](const dap::ExceptionInfoRequest& request) {
+ (void)request;
+ return HandleExceptionInfoRequest();
+ });
+
+ ExceptionMap[MessageType::AUTHOR_WARNING] =
+ cmDebuggerExceptionFilter{ "AUTHOR_WARNING", "Warning (dev)" };
+ ExceptionMap[MessageType::AUTHOR_ERROR] =
+ cmDebuggerExceptionFilter{ "AUTHOR_ERROR", "Error (dev)" };
+ ExceptionMap[MessageType::FATAL_ERROR] =
+ cmDebuggerExceptionFilter{ "FATAL_ERROR", "Fatal error" };
+ ExceptionMap[MessageType::INTERNAL_ERROR] =
+ cmDebuggerExceptionFilter{ "INTERNAL_ERROR", "Internal error" };
+ ExceptionMap[MessageType::MESSAGE] =
+ cmDebuggerExceptionFilter{ "MESSAGE", "Other messages" };
+ ExceptionMap[MessageType::WARNING] =
+ cmDebuggerExceptionFilter{ "WARNING", "Warning" };
+ ExceptionMap[MessageType::LOG] =
+ cmDebuggerExceptionFilter{ "LOG", "Debug log" };
+ ExceptionMap[MessageType::DEPRECATION_ERROR] =
+ cmDebuggerExceptionFilter{ "DEPRECATION_ERROR", "Deprecation error" };
+ ExceptionMap[MessageType::DEPRECATION_WARNING] =
+ cmDebuggerExceptionFilter{ "DEPRECATION_WARNING", "Deprecation warning" };
+ RaiseExceptions["AUTHOR_ERROR"] = true;
+ RaiseExceptions["FATAL_ERROR"] = true;
+ RaiseExceptions["INTERNAL_ERROR"] = true;
+ RaiseExceptions["DEPRECATION_ERROR"] = true;
+}
+
+dap::SetExceptionBreakpointsResponse
+cmDebuggerExceptionManager::HandleSetExceptionBreakpointsRequest(
+ dap::SetExceptionBreakpointsRequest const& request)
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ dap::SetExceptionBreakpointsResponse response;
+ RaiseExceptions.clear();
+ for (const auto& filter : request.filters) {
+ RaiseExceptions[filter] = true;
+ }
+
+ return response;
+}
+
+dap::ExceptionInfoResponse
+cmDebuggerExceptionManager::HandleExceptionInfoRequest()
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+
+ dap::ExceptionInfoResponse response;
+ if (TheException.has_value()) {
+ response.exceptionId = TheException->Id;
+ response.breakMode = "always";
+ response.description = TheException->Description;
+ TheException = {};
+ }
+ return response;
+}
+
+void cmDebuggerExceptionManager::HandleInitializeRequest(
+ dap::CMakeInitializeResponse& response)
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ response.supportsExceptionInfoRequest = true;
+
+ dap::array<dap::ExceptionBreakpointsFilter> exceptionBreakpointFilters;
+ for (auto& pair : ExceptionMap) {
+ dap::ExceptionBreakpointsFilter filter;
+ filter.filter = pair.second.Filter;
+ filter.label = pair.second.Label;
+ filter.def = RaiseExceptions[filter.filter];
+ exceptionBreakpointFilters.emplace_back(filter);
+ }
+
+ response.exceptionBreakpointFilters = exceptionBreakpointFilters;
+}
+
+cm::optional<dap::StoppedEvent>
+cmDebuggerExceptionManager::RaiseExceptionIfAny(MessageType t,
+ std::string const& text)
+{
+ cm::optional<dap::StoppedEvent> maybeStoppedEvent;
+ std::unique_lock<std::mutex> lock(Mutex);
+ if (RaiseExceptions[ExceptionMap[t].Filter]) {
+ dap::StoppedEvent stoppedEvent;
+ stoppedEvent.allThreadsStopped = true;
+ stoppedEvent.reason = "exception";
+ stoppedEvent.description = "Pause on exception";
+ stoppedEvent.text = text;
+ TheException = cmDebuggerException{ ExceptionMap[t].Filter, text };
+ maybeStoppedEvent = std::move(stoppedEvent);
+ }
+
+ return maybeStoppedEvent;
+}
+
+void cmDebuggerExceptionManager::ClearAll()
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ RaiseExceptions.clear();
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerExceptionManager.h b/Source/cmDebuggerExceptionManager.h
new file mode 100644
index 0000000..b819128
--- /dev/null
+++ b/Source/cmDebuggerExceptionManager.h
@@ -0,0 +1,70 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstddef>
+#include <functional>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+
+#include <cm/optional>
+
+#include <cm3p/cppdap/protocol.h>
+
+#include "cmMessageType.h"
+
+namespace dap {
+class Session;
+struct CMakeInitializeResponse;
+}
+
+namespace cmDebugger {
+
+struct cmDebuggerException
+{
+ std::string Id;
+ std::string Description;
+};
+
+struct cmDebuggerExceptionFilter
+{
+ std::string Filter;
+ std::string Label;
+};
+
+/** The exception manager. */
+class cmDebuggerExceptionManager
+{
+ // Some older C++ standard libraries cannot hash an enum class by default.
+ struct MessageTypeHash
+ {
+ std::size_t operator()(MessageType t) const
+ {
+ return std::hash<int>{}(static_cast<int>(t));
+ }
+ };
+
+ dap::Session* DapSession;
+ std::mutex Mutex;
+ std::unordered_map<std::string, bool> RaiseExceptions;
+ std::unordered_map<MessageType, cmDebuggerExceptionFilter, MessageTypeHash>
+ ExceptionMap;
+ cm::optional<cmDebuggerException> TheException;
+
+ dap::SetExceptionBreakpointsResponse HandleSetExceptionBreakpointsRequest(
+ dap::SetExceptionBreakpointsRequest const& request);
+
+ dap::ExceptionInfoResponse HandleExceptionInfoRequest();
+
+public:
+ cmDebuggerExceptionManager(dap::Session* dapSession);
+ void HandleInitializeRequest(dap::CMakeInitializeResponse& response);
+ cm::optional<dap::StoppedEvent> RaiseExceptionIfAny(MessageType t,
+ std::string const& text);
+ void ClearAll();
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerPipeConnection.cxx b/Source/cmDebuggerPipeConnection.cxx
new file mode 100644
index 0000000..1b54346
--- /dev/null
+++ b/Source/cmDebuggerPipeConnection.cxx
@@ -0,0 +1,293 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmDebuggerPipeConnection.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+#include <stdexcept>
+#include <utility>
+
+namespace cmDebugger {
+
+struct write_req_t
+{
+ uv_write_t req;
+ uv_buf_t buf;
+};
+
+cmDebuggerPipeBase::cmDebuggerPipeBase(std::string name)
+ : PipeName(std::move(name))
+{
+ Loop.init();
+ LoopExit.init(
+ *Loop, [](uv_async_t* handle) { uv_stop((uv_loop_t*)handle->data); },
+ Loop);
+ WriteEvent.init(
+ *Loop,
+ [](uv_async_t* handle) {
+ auto* conn = static_cast<cmDebuggerPipeBase*>(handle->data);
+ conn->WriteInternal();
+ },
+ this);
+ PipeClose.init(
+ *Loop,
+ [](uv_async_t* handle) {
+ auto* conn = static_cast<cmDebuggerPipeBase*>(handle->data);
+ if (conn->Pipe.get()) {
+ conn->Pipe->data = nullptr;
+ conn->Pipe.reset();
+ }
+ },
+ this);
+}
+
+void cmDebuggerPipeBase::WaitForConnection()
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ Connected.wait(lock, [this] { return isOpen() || FailedToOpen; });
+ if (FailedToOpen) {
+ throw std::runtime_error("Failed to open debugger connection.");
+ }
+}
+
+void cmDebuggerPipeBase::close()
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+
+ CloseConnection();
+ PipeClose.send();
+ lock.unlock();
+ ReadReady.notify_all();
+}
+
+size_t cmDebuggerPipeBase::read(void* buffer, size_t n)
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ ReadReady.wait(lock, [this] { return !isOpen() || !ReadBuffer.empty(); });
+
+ if (!isOpen() && ReadBuffer.empty()) {
+ return 0;
+ }
+
+ auto size = std::min(n, ReadBuffer.size());
+ memcpy(buffer, ReadBuffer.data(), size);
+ ReadBuffer.erase(0, size);
+ return size;
+}
+
+bool cmDebuggerPipeBase::write(const void* buffer, size_t n)
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ WriteBuffer.append(static_cast<const char*>(buffer), n);
+ lock.unlock();
+ WriteEvent.send();
+
+ lock.lock();
+ WriteComplete.wait(lock, [this] { return WriteBuffer.empty(); });
+ return true;
+}
+
+void cmDebuggerPipeBase::StopLoop()
+{
+ LoopExit.send();
+
+ if (LoopThread.joinable()) {
+ LoopThread.join();
+ }
+}
+
+void cmDebuggerPipeBase::BufferData(const std::string& data)
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ ReadBuffer += data;
+ lock.unlock();
+ ReadReady.notify_all();
+}
+
+void cmDebuggerPipeBase::WriteInternal()
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ auto n = WriteBuffer.length();
+ assert(this->Pipe.get());
+ write_req_t* req = new write_req_t;
+ req->req.data = &WriteComplete;
+ char* rawBuffer = new char[n];
+ req->buf = uv_buf_init(rawBuffer, static_cast<unsigned int>(n));
+ memcpy(req->buf.base, WriteBuffer.data(), n);
+ WriteBuffer.clear();
+ lock.unlock();
+
+ uv_write(
+ reinterpret_cast<uv_write_t*>(req), this->Pipe, &req->buf, 1,
+ [](uv_write_t* cb_req, int status) {
+ (void)status; // We need to free memory even if the write failed.
+ write_req_t* wr = reinterpret_cast<write_req_t*>(cb_req);
+ reinterpret_cast<std::condition_variable*>(wr->req.data)->notify_all();
+ delete[] (wr->buf.base);
+ delete wr;
+ });
+
+#ifdef __clang_analyzer__
+ // Tell clang-analyzer that 'rawBuffer' does not leak.
+ // We pass ownership to the closure.
+ delete[] rawBuffer;
+#endif
+}
+
+cmDebuggerPipeConnection::cmDebuggerPipeConnection(std::string name)
+ : cmDebuggerPipeBase(std::move(name))
+{
+ ServerPipeClose.init(
+ *Loop,
+ [](uv_async_t* handle) {
+ auto* conn = static_cast<cmDebuggerPipeConnection*>(handle->data);
+ if (conn->ServerPipe.get()) {
+ conn->ServerPipe->data = nullptr;
+ conn->ServerPipe.reset();
+ }
+ },
+ this);
+}
+
+cmDebuggerPipeConnection::~cmDebuggerPipeConnection()
+{
+ StopLoop();
+}
+
+bool cmDebuggerPipeConnection::StartListening(std::string& errorMessage)
+{
+ this->ServerPipe.init(*Loop, 0,
+ static_cast<cmDebuggerPipeConnection*>(this));
+
+ int r;
+ if ((r = uv_pipe_bind(this->ServerPipe, this->PipeName.c_str())) != 0) {
+ errorMessage =
+ "Internal Error with " + this->PipeName + ": " + uv_err_name(r);
+ return false;
+ }
+
+ r = uv_listen(this->ServerPipe, 1, [](uv_stream_t* stream, int status) {
+ if (status >= 0) {
+ auto* conn = static_cast<cmDebuggerPipeConnection*>(stream->data);
+ if (conn) {
+ conn->Connect(stream);
+ }
+ }
+ });
+
+ if (r != 0) {
+ errorMessage =
+ "Internal Error listening on " + this->PipeName + ": " + uv_err_name(r);
+ return false;
+ }
+
+ // Start the libuv event loop thread so that a client can connect.
+ LoopThread = std::thread([this] { uv_run(Loop, UV_RUN_DEFAULT); });
+
+ StartedListening.set_value();
+
+ return true;
+}
+
+std::shared_ptr<dap::Reader> cmDebuggerPipeConnection::GetReader()
+{
+ return std::static_pointer_cast<dap::Reader>(shared_from_this());
+}
+
+std::shared_ptr<dap::Writer> cmDebuggerPipeConnection::GetWriter()
+{
+ return std::static_pointer_cast<dap::Writer>(shared_from_this());
+}
+
+bool cmDebuggerPipeConnection::isOpen()
+{
+ return this->Pipe.get() != nullptr;
+}
+
+void cmDebuggerPipeConnection::CloseConnection()
+{
+ ServerPipeClose.send();
+}
+
+void cmDebuggerPipeConnection::Connect(uv_stream_t* server)
+{
+ if (this->Pipe.get()) {
+ // Accept and close all pipes but the first:
+ cm::uv_pipe_ptr rejectPipe;
+
+ rejectPipe.init(*Loop, 0);
+ uv_accept(server, rejectPipe);
+
+ return;
+ }
+
+ cm::uv_pipe_ptr ClientPipe;
+ ClientPipe.init(*Loop, 0, static_cast<cmDebuggerPipeConnection*>(this));
+
+ if (uv_accept(server, ClientPipe) != 0) {
+ return;
+ }
+
+ StartReading<cmDebuggerPipeConnection>(ClientPipe);
+
+ std::unique_lock<std::mutex> lock(Mutex);
+ Pipe = std::move(ClientPipe);
+ lock.unlock();
+ Connected.notify_all();
+}
+
+cmDebuggerPipeClient::~cmDebuggerPipeClient()
+{
+ StopLoop();
+}
+
+void cmDebuggerPipeClient::Start()
+{
+ this->Pipe.init(*Loop, 0, static_cast<cmDebuggerPipeClient*>(this));
+
+ uv_connect_t* connect = new uv_connect_t;
+ connect->data = this;
+ uv_pipe_connect(
+ connect, Pipe, PipeName.c_str(), [](uv_connect_t* cb_connect, int status) {
+ auto* conn = static_cast<cmDebuggerPipeClient*>(cb_connect->data);
+ if (status >= 0) {
+ conn->Connect();
+ } else {
+ conn->FailConnection();
+ }
+ delete cb_connect;
+ });
+
+ // Start the libuv event loop so that the pipe can connect.
+ LoopThread = std::thread([this] { uv_run(Loop, UV_RUN_DEFAULT); });
+}
+
+bool cmDebuggerPipeClient::isOpen()
+{
+ return IsConnected;
+}
+
+void cmDebuggerPipeClient::CloseConnection()
+{
+ IsConnected = false;
+}
+
+void cmDebuggerPipeClient::Connect()
+{
+ StartReading<cmDebuggerPipeClient>(Pipe);
+ std::unique_lock<std::mutex> lock(Mutex);
+ IsConnected = true;
+ lock.unlock();
+ Connected.notify_all();
+}
+
+void cmDebuggerPipeClient::FailConnection()
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ FailedToOpen = true;
+ lock.unlock();
+ Connected.notify_all();
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerPipeConnection.h b/Source/cmDebuggerPipeConnection.h
new file mode 100644
index 0000000..0991ff7
--- /dev/null
+++ b/Source/cmDebuggerPipeConnection.h
@@ -0,0 +1,139 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <condition_variable>
+#include <cstddef>
+#include <future>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+
+#include <cm3p/cppdap/io.h>
+#include <cm3p/uv.h>
+
+#include "cmDebuggerAdapter.h"
+#include "cmUVHandlePtr.h"
+
+namespace cmDebugger {
+
+class cmDebuggerPipeBase : public dap::ReaderWriter
+{
+public:
+ cmDebuggerPipeBase(std::string name);
+
+ void WaitForConnection();
+
+ // dap::ReaderWriter implementation
+
+ void close() final;
+ size_t read(void* buffer, size_t n) final;
+ bool write(const void* buffer, size_t n) final;
+
+protected:
+ virtual void CloseConnection(){};
+ template <typename T>
+ void StartReading(uv_stream_t* stream)
+ {
+ uv_read_start(
+ stream,
+ // alloc_cb
+ [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
+ (void)handle;
+ char* rawBuffer = new char[suggested_size];
+ *buf =
+ uv_buf_init(rawBuffer, static_cast<unsigned int>(suggested_size));
+ },
+ // read_cb
+ [](uv_stream_t* readStream, ssize_t nread, const uv_buf_t* buf) {
+ auto conn = static_cast<T*>(readStream->data);
+ if (conn) {
+ if (nread >= 0) {
+ conn->BufferData(std::string(buf->base, buf->base + nread));
+ } else {
+ conn->close();
+ }
+ }
+ delete[] (buf->base);
+ });
+ }
+ void StopLoop();
+
+ const std::string PipeName;
+ std::thread LoopThread;
+ cm::uv_loop_ptr Loop;
+ cm::uv_pipe_ptr Pipe;
+ std::mutex Mutex;
+ std::condition_variable Connected;
+ bool FailedToOpen = false;
+
+private:
+ void BufferData(const std::string& data);
+ void WriteInternal();
+
+ cm::uv_async_ptr LoopExit;
+ cm::uv_async_ptr WriteEvent;
+ cm::uv_async_ptr PipeClose;
+ std::string WriteBuffer;
+ std::string ReadBuffer;
+ std::condition_variable ReadReady;
+ std::condition_variable WriteComplete;
+};
+
+class cmDebuggerPipeConnection
+ : public cmDebuggerPipeBase
+ , public cmDebuggerConnection
+ , public std::enable_shared_from_this<cmDebuggerPipeConnection>
+{
+public:
+ cmDebuggerPipeConnection(std::string name);
+ ~cmDebuggerPipeConnection() override;
+
+ void WaitForConnection() override
+ {
+ cmDebuggerPipeBase::WaitForConnection();
+ }
+
+ bool StartListening(std::string& errorMessage) override;
+ std::shared_ptr<dap::Reader> GetReader() override;
+ std::shared_ptr<dap::Writer> GetWriter() override;
+
+ // dap::ReaderWriter implementation
+
+ bool isOpen() override;
+
+ // Used for unit test synchronization
+ std::promise<void> StartedListening;
+
+private:
+ void CloseConnection() override;
+ void Connect(uv_stream_t* server);
+
+ cm::uv_pipe_ptr ServerPipe;
+ cm::uv_async_ptr ServerPipeClose;
+};
+
+class cmDebuggerPipeClient : public cmDebuggerPipeBase
+{
+public:
+ using cmDebuggerPipeBase::cmDebuggerPipeBase;
+ ~cmDebuggerPipeClient() override;
+
+ void Start();
+
+ // dap::ReaderWriter implementation
+
+ bool isOpen() override;
+
+private:
+ void CloseConnection() override;
+ void Connect();
+ void FailConnection();
+
+ bool IsConnected = false;
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerProtocol.cxx b/Source/cmDebuggerProtocol.cxx
new file mode 100644
index 0000000..505de35
--- /dev/null
+++ b/Source/cmDebuggerProtocol.cxx
@@ -0,0 +1,80 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cmDebuggerProtocol.h"
+
+#include <string>
+
+namespace dap {
+DAP_IMPLEMENT_STRUCT_TYPEINFO(CMakeVersion, "", DAP_FIELD(major, "major"),
+ DAP_FIELD(minor, "minor"),
+ DAP_FIELD(patch, "patch"),
+ DAP_FIELD(full, "full"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(
+ CMakeInitializeResponse, "",
+ DAP_FIELD(additionalModuleColumns, "additionalModuleColumns"),
+ DAP_FIELD(completionTriggerCharacters, "completionTriggerCharacters"),
+ DAP_FIELD(exceptionBreakpointFilters, "exceptionBreakpointFilters"),
+ DAP_FIELD(supportSuspendDebuggee, "supportSuspendDebuggee"),
+ DAP_FIELD(supportTerminateDebuggee, "supportTerminateDebuggee"),
+ DAP_FIELD(supportedChecksumAlgorithms, "supportedChecksumAlgorithms"),
+ DAP_FIELD(supportsBreakpointLocationsRequest,
+ "supportsBreakpointLocationsRequest"),
+ DAP_FIELD(supportsCancelRequest, "supportsCancelRequest"),
+ DAP_FIELD(supportsClipboardContext, "supportsClipboardContext"),
+ DAP_FIELD(supportsCompletionsRequest, "supportsCompletionsRequest"),
+ DAP_FIELD(supportsConditionalBreakpoints, "supportsConditionalBreakpoints"),
+ DAP_FIELD(supportsConfigurationDoneRequest,
+ "supportsConfigurationDoneRequest"),
+ DAP_FIELD(supportsDataBreakpoints, "supportsDataBreakpoints"),
+ DAP_FIELD(supportsDelayedStackTraceLoading,
+ "supportsDelayedStackTraceLoading"),
+ DAP_FIELD(supportsDisassembleRequest, "supportsDisassembleRequest"),
+ DAP_FIELD(supportsEvaluateForHovers, "supportsEvaluateForHovers"),
+ DAP_FIELD(supportsExceptionFilterOptions, "supportsExceptionFilterOptions"),
+ DAP_FIELD(supportsExceptionInfoRequest, "supportsExceptionInfoRequest"),
+ DAP_FIELD(supportsExceptionOptions, "supportsExceptionOptions"),
+ DAP_FIELD(supportsFunctionBreakpoints, "supportsFunctionBreakpoints"),
+ DAP_FIELD(supportsGotoTargetsRequest, "supportsGotoTargetsRequest"),
+ DAP_FIELD(supportsHitConditionalBreakpoints,
+ "supportsHitConditionalBreakpoints"),
+ DAP_FIELD(supportsInstructionBreakpoints, "supportsInstructionBreakpoints"),
+ DAP_FIELD(supportsLoadedSourcesRequest, "supportsLoadedSourcesRequest"),
+ DAP_FIELD(supportsLogPoints, "supportsLogPoints"),
+ DAP_FIELD(supportsModulesRequest, "supportsModulesRequest"),
+ DAP_FIELD(supportsReadMemoryRequest, "supportsReadMemoryRequest"),
+ DAP_FIELD(supportsRestartFrame, "supportsRestartFrame"),
+ DAP_FIELD(supportsRestartRequest, "supportsRestartRequest"),
+ DAP_FIELD(supportsSetExpression, "supportsSetExpression"),
+ DAP_FIELD(supportsSetVariable, "supportsSetVariable"),
+ DAP_FIELD(supportsSingleThreadExecutionRequests,
+ "supportsSingleThreadExecutionRequests"),
+ DAP_FIELD(supportsStepBack, "supportsStepBack"),
+ DAP_FIELD(supportsStepInTargetsRequest, "supportsStepInTargetsRequest"),
+ DAP_FIELD(supportsSteppingGranularity, "supportsSteppingGranularity"),
+ DAP_FIELD(supportsTerminateRequest, "supportsTerminateRequest"),
+ DAP_FIELD(supportsTerminateThreadsRequest,
+ "supportsTerminateThreadsRequest"),
+ DAP_FIELD(supportsValueFormattingOptions, "supportsValueFormattingOptions"),
+ DAP_FIELD(supportsWriteMemoryRequest, "supportsWriteMemoryRequest"),
+ DAP_FIELD(cmakeVersion, "cmakeVersion"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(
+ CMakeInitializeRequest, "initialize", DAP_FIELD(adapterID, "adapterID"),
+ DAP_FIELD(clientID, "clientID"), DAP_FIELD(clientName, "clientName"),
+ DAP_FIELD(columnsStartAt1, "columnsStartAt1"),
+ DAP_FIELD(linesStartAt1, "linesStartAt1"), DAP_FIELD(locale, "locale"),
+ DAP_FIELD(pathFormat, "pathFormat"),
+ DAP_FIELD(supportsArgsCanBeInterpretedByShell,
+ "supportsArgsCanBeInterpretedByShell"),
+ DAP_FIELD(supportsInvalidatedEvent, "supportsInvalidatedEvent"),
+ DAP_FIELD(supportsMemoryEvent, "supportsMemoryEvent"),
+ DAP_FIELD(supportsMemoryReferences, "supportsMemoryReferences"),
+ DAP_FIELD(supportsProgressReporting, "supportsProgressReporting"),
+ DAP_FIELD(supportsRunInTerminalRequest, "supportsRunInTerminalRequest"),
+ DAP_FIELD(supportsStartDebuggingRequest, "supportsStartDebuggingRequest"),
+ DAP_FIELD(supportsVariablePaging, "supportsVariablePaging"),
+ DAP_FIELD(supportsVariableType, "supportsVariableType"));
+
+} // namespace dap
diff --git a/Source/cmDebuggerProtocol.h b/Source/cmDebuggerProtocol.h
new file mode 100644
index 0000000..4334aed
--- /dev/null
+++ b/Source/cmDebuggerProtocol.h
@@ -0,0 +1,191 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+
+#include <cmcppdap/include/dap/optional.h>
+#include <cmcppdap/include/dap/typeof.h>
+#include <cmcppdap/include/dap/types.h>
+
+namespace dap {
+
+// Represents the cmake version.
+struct CMakeVersion : public InitializeResponse
+{
+ // The major version number.
+ integer major;
+ // The minor version number.
+ integer minor;
+ // The patch number.
+ integer patch;
+ // The full version string.
+ string full;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(CMakeVersion);
+
+// Response to `initialize` request.
+struct CMakeInitializeResponse : public Response
+{
+ // The set of additional module information exposed by the debug adapter.
+ optional<array<ColumnDescriptor>> additionalModuleColumns;
+ // The set of characters that should trigger completion in a REPL. If not
+ // specified, the UI should assume the `.` character.
+ optional<array<string>> completionTriggerCharacters;
+ // Available exception filter options for the `setExceptionBreakpoints`
+ // request.
+ optional<array<ExceptionBreakpointsFilter>> exceptionBreakpointFilters;
+ // The debug adapter supports the `suspendDebuggee` attribute on the
+ // `disconnect` request.
+ optional<boolean> supportSuspendDebuggee;
+ // The debug adapter supports the `terminateDebuggee` attribute on the
+ // `disconnect` request.
+ optional<boolean> supportTerminateDebuggee;
+ // Checksum algorithms supported by the debug adapter.
+ optional<array<ChecksumAlgorithm>> supportedChecksumAlgorithms;
+ // The debug adapter supports the `breakpointLocations` request.
+ optional<boolean> supportsBreakpointLocationsRequest;
+ // The debug adapter supports the `cancel` request.
+ optional<boolean> supportsCancelRequest;
+ // The debug adapter supports the `clipboard` context value in the `evaluate`
+ // request.
+ optional<boolean> supportsClipboardContext;
+ // The debug adapter supports the `completions` request.
+ optional<boolean> supportsCompletionsRequest;
+ // The debug adapter supports conditional breakpoints.
+ optional<boolean> supportsConditionalBreakpoints;
+ // The debug adapter supports the `configurationDone` request.
+ optional<boolean> supportsConfigurationDoneRequest;
+ // The debug adapter supports data breakpoints.
+ optional<boolean> supportsDataBreakpoints;
+ // The debug adapter supports the delayed loading of parts of the stack,
+ // which requires that both the `startFrame` and `levels` arguments and the
+ // `totalFrames` result of the `stackTrace` request are supported.
+ optional<boolean> supportsDelayedStackTraceLoading;
+ // The debug adapter supports the `disassemble` request.
+ optional<boolean> supportsDisassembleRequest;
+ // The debug adapter supports a (side effect free) `evaluate` request for
+ // data hovers.
+ optional<boolean> supportsEvaluateForHovers;
+ // The debug adapter supports `filterOptions` as an argument on the
+ // `setExceptionBreakpoints` request.
+ optional<boolean> supportsExceptionFilterOptions;
+ // The debug adapter supports the `exceptionInfo` request.
+ optional<boolean> supportsExceptionInfoRequest;
+ // The debug adapter supports `exceptionOptions` on the
+ // `setExceptionBreakpoints` request.
+ optional<boolean> supportsExceptionOptions;
+ // The debug adapter supports function breakpoints.
+ optional<boolean> supportsFunctionBreakpoints;
+ // The debug adapter supports the `gotoTargets` request.
+ optional<boolean> supportsGotoTargetsRequest;
+ // The debug adapter supports breakpoints that break execution after a
+ // specified number of hits.
+ optional<boolean> supportsHitConditionalBreakpoints;
+ // The debug adapter supports adding breakpoints based on instruction
+ // references.
+ optional<boolean> supportsInstructionBreakpoints;
+ // The debug adapter supports the `loadedSources` request.
+ optional<boolean> supportsLoadedSourcesRequest;
+ // The debug adapter supports log points by interpreting the `logMessage`
+ // attribute of the `SourceBreakpoint`.
+ optional<boolean> supportsLogPoints;
+ // The debug adapter supports the `modules` request.
+ optional<boolean> supportsModulesRequest;
+ // The debug adapter supports the `readMemory` request.
+ optional<boolean> supportsReadMemoryRequest;
+ // The debug adapter supports restarting a frame.
+ optional<boolean> supportsRestartFrame;
+ // The debug adapter supports the `restart` request. In this case a client
+ // should not implement `restart` by terminating and relaunching the adapter
+ // but by calling the `restart` request.
+ optional<boolean> supportsRestartRequest;
+ // The debug adapter supports the `setExpression` request.
+ optional<boolean> supportsSetExpression;
+ // The debug adapter supports setting a variable to a value.
+ optional<boolean> supportsSetVariable;
+ // The debug adapter supports the `singleThread` property on the execution
+ // requests (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`,
+ // `stepBack`).
+ optional<boolean> supportsSingleThreadExecutionRequests;
+ // The debug adapter supports stepping back via the `stepBack` and
+ // `reverseContinue` requests.
+ optional<boolean> supportsStepBack;
+ // The debug adapter supports the `stepInTargets` request.
+ optional<boolean> supportsStepInTargetsRequest;
+ // The debug adapter supports stepping granularities (argument `granularity`)
+ // for the stepping requests.
+ optional<boolean> supportsSteppingGranularity;
+ // The debug adapter supports the `terminate` request.
+ optional<boolean> supportsTerminateRequest;
+ // The debug adapter supports the `terminateThreads` request.
+ optional<boolean> supportsTerminateThreadsRequest;
+ // The debug adapter supports a `format` attribute on the `stackTrace`,
+ // `variables`, and `evaluate` requests.
+ optional<boolean> supportsValueFormattingOptions;
+ // The debug adapter supports the `writeMemory` request.
+ optional<boolean> supportsWriteMemoryRequest;
+ // The CMake version.
+ CMakeVersion cmakeVersion;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(CMakeInitializeResponse);
+
+// The `initialize` request is sent as the first request from the client to the
+// debug adapter in order to configure it with client capabilities and to
+// retrieve capabilities from the debug adapter. Until the debug adapter has
+// responded with an `initialize` response, the client must not send any
+// additional requests or events to the debug adapter. In addition the debug
+// adapter is not allowed to send any requests or events to the client until it
+// has responded with an `initialize` response. The `initialize` request may
+// only be sent once.
+struct CMakeInitializeRequest : public Request
+{
+ using Response = CMakeInitializeResponse;
+ // The ID of the debug adapter.
+ string adapterID;
+ // The ID of the client using this adapter.
+ optional<string> clientID;
+ // The human-readable name of the client using this adapter.
+ optional<string> clientName;
+ // If true all column numbers are 1-based (default).
+ optional<boolean> columnsStartAt1;
+ // If true all line numbers are 1-based (default).
+ optional<boolean> linesStartAt1;
+ // The ISO-639 locale of the client using this adapter, e.g. en-US or de-CH.
+ optional<string> locale;
+ // Determines in what format paths are specified. The default is `path`,
+ // which is the native format.
+ //
+ // May be one of the following enumeration values:
+ // 'path', 'uri'
+ optional<string> pathFormat;
+ // Client supports the `argsCanBeInterpretedByShell` attribute on the
+ // `runInTerminal` request.
+ optional<boolean> supportsArgsCanBeInterpretedByShell;
+ // Client supports the `invalidated` event.
+ optional<boolean> supportsInvalidatedEvent;
+ // Client supports the `memory` event.
+ optional<boolean> supportsMemoryEvent;
+ // Client supports memory references.
+ optional<boolean> supportsMemoryReferences;
+ // Client supports progress reporting.
+ optional<boolean> supportsProgressReporting;
+ // Client supports the `runInTerminal` request.
+ optional<boolean> supportsRunInTerminalRequest;
+ // Client supports the `startDebugging` request.
+ optional<boolean> supportsStartDebuggingRequest;
+ // Client supports the paging of variables.
+ optional<boolean> supportsVariablePaging;
+ // Client supports the `type` attribute for variables.
+ optional<boolean> supportsVariableType;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(CMakeInitializeRequest);
+
+} // namespace dap
diff --git a/Source/cmDebuggerSourceBreakpoint.cxx b/Source/cmDebuggerSourceBreakpoint.cxx
new file mode 100644
index 0000000..d4665e6
--- /dev/null
+++ b/Source/cmDebuggerSourceBreakpoint.cxx
@@ -0,0 +1,14 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmDebuggerSourceBreakpoint.h"
+
+namespace cmDebugger {
+
+cmDebuggerSourceBreakpoint::cmDebuggerSourceBreakpoint(int64_t id,
+ int64_t line)
+ : Id(id)
+ , Line(line)
+{
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerSourceBreakpoint.h b/Source/cmDebuggerSourceBreakpoint.h
new file mode 100644
index 0000000..f6d6cac
--- /dev/null
+++ b/Source/cmDebuggerSourceBreakpoint.h
@@ -0,0 +1,26 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstdint>
+
+namespace cmDebugger {
+
+class cmDebuggerSourceBreakpoint
+{
+ int64_t Id;
+ int64_t Line;
+ bool IsValid = true;
+
+public:
+ cmDebuggerSourceBreakpoint(int64_t id, int64_t line);
+ int64_t GetId() const noexcept { return this->Id; }
+ int64_t GetLine() const noexcept { return this->Line; }
+ void ChangeLine(int64_t line) noexcept { this->Line = line; }
+ bool GetIsValid() const noexcept { return this->IsValid; }
+ void Invalid() noexcept { this->IsValid = false; }
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerStackFrame.cxx b/Source/cmDebuggerStackFrame.cxx
new file mode 100644
index 0000000..789b0a5
--- /dev/null
+++ b/Source/cmDebuggerStackFrame.cxx
@@ -0,0 +1,28 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmDebuggerStackFrame.h"
+
+#include <utility>
+
+#include "cmListFileCache.h"
+
+namespace cmDebugger {
+
+std::atomic<int64_t> cmDebuggerStackFrame::NextId(1);
+
+cmDebuggerStackFrame::cmDebuggerStackFrame(cmMakefile* mf,
+ std::string sourcePath,
+ cmListFileFunction const& lff)
+ : Id(NextId.fetch_add(1))
+ , FileName(std::move(sourcePath))
+ , Function(lff)
+ , Makefile(mf)
+{
+}
+
+int64_t cmDebuggerStackFrame::GetLine() const noexcept
+{
+ return this->Function.Line();
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerStackFrame.h b/Source/cmDebuggerStackFrame.h
new file mode 100644
index 0000000..dc3b2ab
--- /dev/null
+++ b/Source/cmDebuggerStackFrame.h
@@ -0,0 +1,33 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <atomic>
+#include <cstdint>
+#include <string>
+
+class cmListFileFunction;
+class cmMakefile;
+
+namespace cmDebugger {
+
+class cmDebuggerStackFrame
+{
+ static std::atomic<std::int64_t> NextId;
+ std::int64_t Id;
+ std::string FileName;
+ cmListFileFunction const& Function;
+ cmMakefile* Makefile;
+
+public:
+ cmDebuggerStackFrame(cmMakefile* mf, std::string sourcePath,
+ cmListFileFunction const& lff);
+ int64_t GetId() const noexcept { return this->Id; }
+ std::string const& GetFileName() const noexcept { return this->FileName; }
+ int64_t GetLine() const noexcept;
+ cmMakefile* GetMakefile() const noexcept { return this->Makefile; }
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerThread.cxx b/Source/cmDebuggerThread.cxx
new file mode 100644
index 0000000..fd52f5a
--- /dev/null
+++ b/Source/cmDebuggerThread.cxx
@@ -0,0 +1,150 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cmDebuggerThread.h"
+
+#include <cstdint>
+#include <utility>
+
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerStackFrame.h"
+#include "cmDebuggerVariables.h"
+#include "cmDebuggerVariablesHelper.h"
+#include "cmDebuggerVariablesManager.h"
+
+namespace cmDebugger {
+
+cmDebuggerThread::cmDebuggerThread(int64_t id, std::string name)
+ : Id(id)
+ , Name(std::move(name))
+ , VariablesManager(std::make_shared<cmDebuggerVariablesManager>())
+{
+}
+
+void cmDebuggerThread::PushStackFrame(cmMakefile* mf,
+ std::string const& sourcePath,
+ cmListFileFunction const& lff)
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ Frames.emplace_back(
+ std::make_shared<cmDebuggerStackFrame>(mf, sourcePath, lff));
+ FrameMap.insert({ Frames.back()->GetId(), Frames.back() });
+}
+
+void cmDebuggerThread::PopStackFrame()
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ FrameMap.erase(Frames.back()->GetId());
+ FrameScopes.erase(Frames.back()->GetId());
+ FrameVariables.erase(Frames.back()->GetId());
+ Frames.pop_back();
+}
+
+std::shared_ptr<cmDebuggerStackFrame> cmDebuggerThread::GetTopStackFrame()
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ if (!Frames.empty()) {
+ return Frames.back();
+ }
+
+ return {};
+}
+
+std::shared_ptr<cmDebuggerStackFrame> cmDebuggerThread::GetStackFrame(
+ int64_t frameId)
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ auto it = FrameMap.find(frameId);
+
+ if (it == FrameMap.end()) {
+ return {};
+ }
+
+ return it->second;
+}
+
+dap::ScopesResponse cmDebuggerThread::GetScopesResponse(
+ int64_t frameId, bool supportsVariableType)
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ auto it = FrameScopes.find(frameId);
+
+ if (it != FrameScopes.end()) {
+ dap::ScopesResponse response;
+ response.scopes = it->second;
+ return response;
+ }
+
+ auto it2 = FrameMap.find(frameId);
+ if (it2 == FrameMap.end()) {
+ return dap::ScopesResponse();
+ }
+
+ std::shared_ptr<cmDebuggerStackFrame> frame = it2->second;
+ std::shared_ptr<cmDebuggerVariables> localVariables =
+ cmDebuggerVariablesHelper::Create(VariablesManager, "Locals",
+ supportsVariableType, frame);
+
+ FrameVariables[frameId].emplace_back(localVariables);
+
+ dap::Scope scope;
+ scope.name = localVariables->GetName();
+ scope.presentationHint = "locals";
+ scope.variablesReference = localVariables->GetId();
+
+ dap::Source source;
+ source.name = frame->GetFileName();
+ source.path = source.name;
+ scope.source = source;
+
+ FrameScopes[frameId].push_back(scope);
+
+ dap::ScopesResponse response;
+ response.scopes.push_back(scope);
+ return response;
+}
+
+dap::VariablesResponse cmDebuggerThread::GetVariablesResponse(
+ dap::VariablesRequest const& request)
+{
+ std::unique_lock<std::mutex> lock(Mutex);
+ dap::VariablesResponse response;
+ response.variables = VariablesManager->HandleVariablesRequest(request);
+ return response;
+}
+
+dap::StackTraceResponse GetStackTraceResponse(
+ std::shared_ptr<cmDebuggerThread> const& thread)
+{
+ dap::StackTraceResponse response;
+ std::unique_lock<std::mutex> lock(thread->Mutex);
+ for (int i = static_cast<int>(thread->Frames.size()) - 1; i >= 0; --i) {
+ dap::Source source;
+ source.name = thread->Frames[i]->GetFileName();
+ source.path = source.name;
+
+#ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Warray-bounds"
+#endif
+ dap::StackFrame stackFrame;
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+ stackFrame.line = thread->Frames[i]->GetLine();
+ stackFrame.column = 1;
+ stackFrame.name = thread->Frames[i]->GetFileName() + " Line " +
+ std::to_string(stackFrame.line);
+ stackFrame.id = thread->Frames[i]->GetId();
+ stackFrame.source = source;
+
+ response.stackFrames.push_back(stackFrame);
+ }
+
+ response.totalFrames = response.stackFrames.size();
+ return response;
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerThread.h b/Source/cmDebuggerThread.h
new file mode 100644
index 0000000..65ee2cf
--- /dev/null
+++ b/Source/cmDebuggerThread.h
@@ -0,0 +1,59 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+
+class cmListFileFunction;
+class cmMakefile;
+
+namespace cmDebugger {
+class cmDebuggerStackFrame;
+class cmDebuggerVariables;
+class cmDebuggerVariablesManager;
+}
+
+namespace cmDebugger {
+
+class cmDebuggerThread
+{
+ int64_t Id;
+ std::string Name;
+ std::vector<std::shared_ptr<cmDebuggerStackFrame>> Frames;
+ std::unordered_map<int64_t, std::shared_ptr<cmDebuggerStackFrame>> FrameMap;
+ std::mutex Mutex;
+ std::unordered_map<int64_t, std::vector<dap::Scope>> FrameScopes;
+ std::unordered_map<int64_t,
+ std::vector<std::shared_ptr<cmDebuggerVariables>>>
+ FrameVariables;
+ std::shared_ptr<cmDebuggerVariablesManager> VariablesManager;
+
+public:
+ cmDebuggerThread(int64_t id, std::string name);
+ int64_t GetId() const { return this->Id; }
+ const std::string& GetName() const { return this->Name; }
+ void PushStackFrame(cmMakefile* mf, std::string const& sourcePath,
+ cmListFileFunction const& lff);
+ void PopStackFrame();
+ std::shared_ptr<cmDebuggerStackFrame> GetTopStackFrame();
+ std::shared_ptr<cmDebuggerStackFrame> GetStackFrame(int64_t frameId);
+ size_t GetStackFrameSize() const { return this->Frames.size(); }
+ dap::ScopesResponse GetScopesResponse(int64_t frameId,
+ bool supportsVariableType);
+ dap::VariablesResponse GetVariablesResponse(
+ dap::VariablesRequest const& request);
+ friend dap::StackTraceResponse GetStackTraceResponse(
+ std::shared_ptr<cmDebuggerThread> const& thread);
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerThreadManager.cxx b/Source/cmDebuggerThreadManager.cxx
new file mode 100644
index 0000000..0eb443b
--- /dev/null
+++ b/Source/cmDebuggerThreadManager.cxx
@@ -0,0 +1,47 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cmDebuggerThreadManager.h"
+
+#include <algorithm>
+
+#include <cm3p/cppdap/protocol.h>
+
+#include "cmDebuggerThread.h"
+
+namespace cmDebugger {
+
+std::atomic<int64_t> cmDebuggerThreadManager::NextThreadId(1);
+
+std::shared_ptr<cmDebuggerThread> cmDebuggerThreadManager::StartThread(
+ std::string const& name)
+{
+ std::shared_ptr<cmDebuggerThread> thread =
+ std::make_shared<cmDebuggerThread>(
+ cmDebuggerThreadManager::NextThreadId.fetch_add(1), name);
+ Threads.emplace_back(thread);
+ return thread;
+}
+
+void cmDebuggerThreadManager::EndThread(
+ std::shared_ptr<cmDebuggerThread> const& thread)
+{
+ Threads.remove(thread);
+}
+
+cm::optional<dap::StackTraceResponse>
+cmDebuggerThreadManager::GetThreadStackTraceResponse(int64_t id)
+{
+ auto it = find_if(Threads.begin(), Threads.end(),
+ [&](const std::shared_ptr<cmDebuggerThread>& t) {
+ return t->GetId() == id;
+ });
+
+ if (it == Threads.end()) {
+ return {};
+ }
+
+ return GetStackTraceResponse(*it);
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerThreadManager.h b/Source/cmDebuggerThreadManager.h
new file mode 100644
index 0000000..934cf85
--- /dev/null
+++ b/Source/cmDebuggerThreadManager.h
@@ -0,0 +1,38 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <atomic>
+#include <cstdint>
+#include <list>
+#include <memory>
+#include <string>
+
+#include <cm/optional>
+
+namespace cmDebugger {
+class cmDebuggerThread;
+}
+
+namespace dap {
+struct StackTraceResponse;
+}
+
+namespace cmDebugger {
+
+class cmDebuggerThreadManager
+{
+ static std::atomic<std::int64_t> NextThreadId;
+ std::list<std::shared_ptr<cmDebuggerThread>> Threads;
+
+public:
+ cmDebuggerThreadManager() = default;
+ std::shared_ptr<cmDebuggerThread> StartThread(std::string const& name);
+ void EndThread(std::shared_ptr<cmDebuggerThread> const& thread);
+ cm::optional<dap::StackTraceResponse> GetThreadStackTraceResponse(
+ std::int64_t id);
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerVariables.cxx b/Source/cmDebuggerVariables.cxx
new file mode 100644
index 0000000..40fe41f
--- /dev/null
+++ b/Source/cmDebuggerVariables.cxx
@@ -0,0 +1,133 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cmDebuggerVariables.h"
+
+#include <algorithm>
+#include <vector>
+
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerVariablesManager.h"
+
+namespace cmDebugger {
+
+namespace {
+const dap::VariablePresentationHint PrivatePropertyHint = { {},
+ "property",
+ {},
+ "private" };
+const dap::VariablePresentationHint PrivateDataHint = { {},
+ "data",
+ {},
+ "private" };
+}
+
+std::atomic<int64_t> cmDebuggerVariables::NextId(1);
+
+cmDebuggerVariables::cmDebuggerVariables(
+ std::shared_ptr<cmDebuggerVariablesManager> variablesManager,
+ std::string name, bool supportsVariableType)
+ : Id(NextId.fetch_add(1))
+ , Name(std::move(name))
+ , SupportsVariableType(supportsVariableType)
+ , VariablesManager(std::move(variablesManager))
+{
+ VariablesManager->RegisterHandler(
+ Id, [this](dap::VariablesRequest const& request) {
+ (void)request;
+ return this->HandleVariablesRequest();
+ });
+}
+
+cmDebuggerVariables::cmDebuggerVariables(
+ std::shared_ptr<cmDebuggerVariablesManager> variablesManager,
+ std::string name, bool supportsVariableType,
+ std::function<std::vector<cmDebuggerVariableEntry>()> getKeyValuesFunction)
+ : Id(NextId.fetch_add(1))
+ , Name(std::move(name))
+ , GetKeyValuesFunction(std::move(getKeyValuesFunction))
+ , SupportsVariableType(supportsVariableType)
+ , VariablesManager(std::move(variablesManager))
+{
+ VariablesManager->RegisterHandler(
+ Id, [this](dap::VariablesRequest const& request) {
+ (void)request;
+ return this->HandleVariablesRequest();
+ });
+}
+
+void cmDebuggerVariables::AddSubVariables(
+ std::shared_ptr<cmDebuggerVariables> const& variables)
+{
+ if (variables != nullptr) {
+ SubVariables.emplace_back(variables);
+ }
+}
+
+dap::array<dap::Variable> cmDebuggerVariables::HandleVariablesRequest()
+{
+ dap::array<dap::Variable> variables;
+
+ if (GetKeyValuesFunction != nullptr) {
+ auto values = GetKeyValuesFunction();
+ for (auto const& entry : values) {
+ if (IgnoreEmptyStringEntries && entry.Type == "string" &&
+ entry.Value.empty()) {
+ continue;
+ }
+ variables.push_back(dap::Variable{ {},
+ {},
+ {},
+ entry.Name,
+ {},
+ PrivateDataHint,
+ entry.Type,
+ entry.Value,
+ 0 });
+ }
+ }
+
+ EnumerateSubVariablesIfAny(variables);
+
+ if (EnableSorting) {
+ std::sort(variables.begin(), variables.end(),
+ [](dap::Variable const& a, dap::Variable const& b) {
+ return a.name < b.name;
+ });
+ }
+ return variables;
+}
+
+void cmDebuggerVariables::EnumerateSubVariablesIfAny(
+ dap::array<dap::Variable>& toBeReturned) const
+{
+ dap::array<dap::Variable> ret;
+ for (auto const& variables : SubVariables) {
+ toBeReturned.emplace_back(
+ dap::Variable{ {},
+ {},
+ {},
+ variables->GetName(),
+ {},
+ PrivatePropertyHint,
+ SupportsVariableType ? "collection" : nullptr,
+ variables->GetValue(),
+ variables->GetId() });
+ }
+}
+
+void cmDebuggerVariables::ClearSubVariables()
+{
+ SubVariables.clear();
+}
+
+cmDebuggerVariables::~cmDebuggerVariables()
+{
+ ClearSubVariables();
+ VariablesManager->UnregisterHandler(Id);
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerVariables.h b/Source/cmDebuggerVariables.h
new file mode 100644
index 0000000..eaaf2a8
--- /dev/null
+++ b/Source/cmDebuggerVariables.h
@@ -0,0 +1,124 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <atomic>
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <cm3p/cppdap/types.h> // IWYU pragma: keep
+
+namespace cmDebugger {
+class cmDebuggerVariablesManager;
+}
+
+namespace dap {
+struct Variable;
+}
+
+namespace cmDebugger {
+
+struct cmDebuggerVariableEntry
+{
+ cmDebuggerVariableEntry()
+ : cmDebuggerVariableEntry("", "", "")
+ {
+ }
+ cmDebuggerVariableEntry(std::string name, std::string value,
+ std::string type)
+ : Name(std::move(name))
+ , Value(std::move(value))
+ , Type(std::move(type))
+ {
+ }
+ cmDebuggerVariableEntry(std::string name, std::string value)
+ : Name(std::move(name))
+ , Value(std::move(value))
+ , Type("string")
+ {
+ }
+ cmDebuggerVariableEntry(std::string name, const char* value)
+ : Name(std::move(name))
+ , Value(value == nullptr ? "" : value)
+ , Type("string")
+ {
+ }
+ cmDebuggerVariableEntry(std::string name, bool value)
+ : Name(std::move(name))
+ , Value(value ? "TRUE" : "FALSE")
+ , Type("bool")
+ {
+ }
+ cmDebuggerVariableEntry(std::string name, int64_t value)
+ : Name(std::move(name))
+ , Value(std::to_string(value))
+ , Type("int")
+ {
+ }
+ cmDebuggerVariableEntry(std::string name, int value)
+ : Name(std::move(name))
+ , Value(std::to_string(value))
+ , Type("int")
+ {
+ }
+ std::string const Name;
+ std::string const Value;
+ std::string const Type;
+};
+
+class cmDebuggerVariables
+{
+ static std::atomic<int64_t> NextId;
+ int64_t Id;
+ std::string Name;
+ std::string Value;
+
+ std::function<std::vector<cmDebuggerVariableEntry>()> GetKeyValuesFunction;
+ std::vector<std::shared_ptr<cmDebuggerVariables>> SubVariables;
+ bool IgnoreEmptyStringEntries = false;
+ bool EnableSorting = true;
+
+ virtual dap::array<dap::Variable> HandleVariablesRequest();
+ friend class cmDebuggerVariablesManager;
+
+protected:
+ const bool SupportsVariableType;
+ std::shared_ptr<cmDebuggerVariablesManager> VariablesManager;
+ void EnumerateSubVariablesIfAny(
+ dap::array<dap::Variable>& toBeReturned) const;
+ void ClearSubVariables();
+
+public:
+ cmDebuggerVariables(
+ std::shared_ptr<cmDebuggerVariablesManager> variablesManager,
+ std::string name, bool supportsVariableType);
+ cmDebuggerVariables(
+ std::shared_ptr<cmDebuggerVariablesManager> variablesManager,
+ std::string name, bool supportsVariableType,
+ std::function<std::vector<cmDebuggerVariableEntry>()> getKeyValuesFunc);
+ inline int64_t GetId() const noexcept { return this->Id; }
+ inline std::string GetName() const noexcept { return this->Name; }
+ inline std::string GetValue() const noexcept { return this->Value; }
+ inline void SetValue(std::string const& value) noexcept
+ {
+ this->Value = value;
+ }
+ void AddSubVariables(std::shared_ptr<cmDebuggerVariables> const& variables);
+ inline void SetIgnoreEmptyStringEntries(bool value) noexcept
+ {
+ this->IgnoreEmptyStringEntries = value;
+ }
+ inline void SetEnableSorting(bool value) noexcept
+ {
+ this->EnableSorting = value;
+ }
+ virtual ~cmDebuggerVariables();
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerVariablesHelper.cxx b/Source/cmDebuggerVariablesHelper.cxx
new file mode 100644
index 0000000..42ce5e7
--- /dev/null
+++ b/Source/cmDebuggerVariablesHelper.cxx
@@ -0,0 +1,644 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cmDebuggerVariablesHelper.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <functional>
+#include <iomanip>
+#include <map>
+#include <sstream>
+
+#include "cm_codecvt.hxx"
+
+#include "cmDebuggerStackFrame.h"
+#include "cmDebuggerVariables.h"
+#include "cmFileSet.h"
+#include "cmGlobalGenerator.h"
+#include "cmList.h"
+#include "cmListFileCache.h"
+#include "cmMakefile.h"
+#include "cmPropertyMap.h"
+#include "cmState.h"
+#include "cmStateSnapshot.h"
+#include "cmTarget.h"
+#include "cmTest.h"
+#include "cmValue.h"
+#include "cmake.h"
+
+namespace cmDebugger {
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::Create(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ cmPolicies::PolicyMap const& policyMap)
+{
+ static std::map<cmPolicies::PolicyStatus, std::string> policyStatusString = {
+ { cmPolicies::PolicyStatus::OLD, "OLD" },
+ { cmPolicies::PolicyStatus::WARN, "WARN" },
+ { cmPolicies::PolicyStatus::NEW, "NEW" },
+ { cmPolicies::PolicyStatus::REQUIRED_IF_USED, "REQUIRED_IF_USED" },
+ { cmPolicies::PolicyStatus::REQUIRED_ALWAYS, "REQUIRED_ALWAYS" }
+ };
+
+ return std::make_shared<cmDebuggerVariables>(
+ variablesManager, name, supportsVariableType, [=]() {
+ std::vector<cmDebuggerVariableEntry> ret;
+ ret.reserve(cmPolicies::CMPCOUNT);
+ for (int i = 0; i < cmPolicies::CMPCOUNT; ++i) {
+ if (policyMap.IsDefined(static_cast<cmPolicies::PolicyID>(i))) {
+ auto status = policyMap.Get(static_cast<cmPolicies::PolicyID>(i));
+ std::ostringstream ss;
+ ss << "CMP" << std::setfill('0') << std::setw(4) << i;
+ ret.emplace_back(ss.str(), policyStatusString[status]);
+ }
+ }
+ return ret;
+ });
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::vector<std::pair<std::string, std::string>> const& list)
+{
+ if (list.empty()) {
+ return {};
+ }
+
+ auto listVariables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, name, supportsVariableType, [=]() {
+ std::vector<cmDebuggerVariableEntry> ret;
+ ret.reserve(list.size());
+ for (auto const& kv : list) {
+ ret.emplace_back(kv.first, kv.second);
+ }
+ return ret;
+ });
+
+ listVariables->SetValue(std::to_string(list.size()));
+ return listVariables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ cmBTStringRange const& entries)
+{
+ if (entries.empty()) {
+ return {};
+ }
+
+ auto sourceEntries = std::make_shared<cmDebuggerVariables>(
+ variablesManager, name, supportsVariableType);
+
+ for (auto const& entry : entries) {
+ auto arrayVariables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, entry.Value, supportsVariableType, [=]() {
+ cmList items{ entry.Value };
+ std::vector<cmDebuggerVariableEntry> ret;
+ ret.reserve(items.size());
+ int i = 0;
+ for (std::string const& item : items) {
+ ret.emplace_back("[" + std::to_string(i++) + "]", item);
+ }
+ return ret;
+ });
+ arrayVariables->SetEnableSorting(false);
+ sourceEntries->AddSubVariables(arrayVariables);
+ }
+
+ sourceEntries->SetValue(std::to_string(entries.size()));
+ return sourceEntries;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::set<std::string> const& values)
+{
+ if (values.empty()) {
+ return {};
+ }
+
+ auto arrayVariables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, name, supportsVariableType, [=]() {
+ std::vector<cmDebuggerVariableEntry> ret;
+ ret.reserve(values.size());
+ int i = 0;
+ for (std::string const& value : values) {
+ ret.emplace_back("[" + std::to_string(i++) + "]", value);
+ }
+ return ret;
+ });
+ arrayVariables->SetValue(std::to_string(values.size()));
+ arrayVariables->SetEnableSorting(false);
+ return arrayVariables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::vector<std::string> const& values)
+{
+ if (values.empty()) {
+ return {};
+ }
+
+ auto arrayVariables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, name, supportsVariableType, [=]() {
+ std::vector<cmDebuggerVariableEntry> ret;
+ ret.reserve(values.size());
+ int i = 0;
+ for (std::string const& value : values) {
+ ret.emplace_back("[" + std::to_string(i++) + "]", value);
+ }
+ return ret;
+ });
+
+ arrayVariables->SetValue(std::to_string(values.size()));
+ arrayVariables->SetEnableSorting(false);
+ return arrayVariables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::vector<BT<std::string>> const& list)
+{
+ if (list.empty()) {
+ return {};
+ }
+
+ auto variables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, name, supportsVariableType, [=]() {
+ std::vector<cmDebuggerVariableEntry> ret;
+ ret.reserve(list.size());
+ int i = 0;
+ for (auto const& item : list) {
+ ret.emplace_back("[" + std::to_string(i++) + "]", item.Value);
+ }
+
+ return ret;
+ });
+
+ variables->SetValue(std::to_string(list.size()));
+ variables->SetEnableSorting(false);
+ return variables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType, cmFileSet* fileSet)
+{
+ if (fileSet == nullptr) {
+ return {};
+ }
+
+ static auto visibilityString = [](cmFileSetVisibility visibility) {
+ switch (visibility) {
+ case cmFileSetVisibility::Private:
+ return "Private";
+ case cmFileSetVisibility::Public:
+ return "Public";
+ case cmFileSetVisibility::Interface:
+ return "Interface";
+ default:
+ return "Unknown";
+ }
+ };
+
+ auto variables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, name, supportsVariableType, [=]() {
+ std::vector<cmDebuggerVariableEntry> ret{
+ { "Name", fileSet->GetName() },
+ { "Type", fileSet->GetType() },
+ { "Visibility", visibilityString(fileSet->GetVisibility()) },
+ };
+
+ return ret;
+ });
+
+ variables->AddSubVariables(CreateIfAny(variablesManager, "Directories",
+ supportsVariableType,
+ fileSet->GetDirectoryEntries()));
+ variables->AddSubVariables(CreateIfAny(variablesManager, "Files",
+ supportsVariableType,
+ fileSet->GetFileEntries()));
+ return variables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::vector<cmFileSet*> const& fileSets)
+{
+ if (fileSets.empty()) {
+ return {};
+ }
+
+ auto fileSetsVariables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, name, supportsVariableType);
+
+ for (auto const& fileSet : fileSets) {
+ fileSetsVariables->AddSubVariables(CreateIfAny(
+ variablesManager, fileSet->GetName(), supportsVariableType, fileSet));
+ }
+
+ return fileSetsVariables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::vector<cmTarget*> const& targets)
+{
+ if (targets.empty()) {
+ return {};
+ }
+
+ auto targetsVariables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, name, supportsVariableType);
+
+ for (auto const& target : targets) {
+ auto targetVariables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, target->GetName(), supportsVariableType, [=]() {
+ std::vector<cmDebuggerVariableEntry> ret = {
+ { "InstallPath", target->GetInstallPath() },
+ { "IsAIX", target->IsAIX() },
+ { "IsAndroidGuiExecutable", target->IsAndroidGuiExecutable() },
+ { "IsAppBundleOnApple", target->IsAppBundleOnApple() },
+ { "IsDLLPlatform", target->IsDLLPlatform() },
+ { "IsExecutableWithExports", target->IsExecutableWithExports() },
+ { "IsFrameworkOnApple", target->IsFrameworkOnApple() },
+ { "IsImported", target->IsImported() },
+ { "IsImportedGloballyVisible", target->IsImportedGloballyVisible() },
+ { "IsPerConfig", target->IsPerConfig() },
+ { "Name", target->GetName() },
+ { "RuntimeInstallPath", target->GetRuntimeInstallPath() },
+ { "Type", cmState::GetTargetTypeName(target->GetType()) },
+ };
+
+ return ret;
+ });
+ targetVariables->SetValue(cmState::GetTargetTypeName(target->GetType()));
+
+ targetVariables->AddSubVariables(Create(variablesManager, "PolicyMap",
+ supportsVariableType,
+ target->GetPolicyMap()));
+ targetVariables->AddSubVariables(
+ CreateIfAny(variablesManager, "Properties", supportsVariableType,
+ target->GetProperties().GetList()));
+
+ targetVariables->AddSubVariables(
+ CreateIfAny(variablesManager, "IncludeDirectories", supportsVariableType,
+ target->GetIncludeDirectoriesEntries()));
+ targetVariables->AddSubVariables(CreateIfAny(variablesManager, "Sources",
+ supportsVariableType,
+ target->GetSourceEntries()));
+ targetVariables->AddSubVariables(
+ CreateIfAny(variablesManager, "CompileDefinitions", supportsVariableType,
+ target->GetCompileDefinitionsEntries()));
+ targetVariables->AddSubVariables(
+ CreateIfAny(variablesManager, "CompileFeatures", supportsVariableType,
+ target->GetCompileFeaturesEntries()));
+ targetVariables->AddSubVariables(
+ CreateIfAny(variablesManager, "CompileOptions", supportsVariableType,
+ target->GetCompileOptionsEntries()));
+ targetVariables->AddSubVariables(CreateIfAny(
+ variablesManager, "CxxModuleHeaderSets", supportsVariableType,
+ target->GetCxxModuleHeaderSetsEntries()));
+ targetVariables->AddSubVariables(
+ CreateIfAny(variablesManager, "CxxModuleSets", supportsVariableType,
+ target->GetCxxModuleSetsEntries()));
+ targetVariables->AddSubVariables(
+ CreateIfAny(variablesManager, "HeaderSets", supportsVariableType,
+ target->GetHeaderSetsEntries()));
+ targetVariables->AddSubVariables(CreateIfAny(
+ variablesManager, "InterfaceCxxModuleHeaderSets", supportsVariableType,
+ target->GetInterfaceCxxModuleHeaderSetsEntries()));
+ targetVariables->AddSubVariables(CreateIfAny(
+ variablesManager, "InterfaceHeaderSets", supportsVariableType,
+ target->GetInterfaceHeaderSetsEntries()));
+ targetVariables->AddSubVariables(
+ CreateIfAny(variablesManager, "LinkDirectories", supportsVariableType,
+ target->GetLinkDirectoriesEntries()));
+ targetVariables->AddSubVariables(CreateIfAny(
+ variablesManager, "LinkImplementations", supportsVariableType,
+ target->GetLinkImplementationEntries()));
+ targetVariables->AddSubVariables(CreateIfAny(
+ variablesManager, "LinkInterfaceDirects", supportsVariableType,
+ target->GetLinkInterfaceDirectEntries()));
+ targetVariables->AddSubVariables(CreateIfAny(
+ variablesManager, "LinkInterfaceDirectExcludes", supportsVariableType,
+ target->GetLinkInterfaceDirectExcludeEntries()));
+ targetVariables->AddSubVariables(
+ CreateIfAny(variablesManager, "LinkInterfaces", supportsVariableType,
+ target->GetLinkInterfaceEntries()));
+ targetVariables->AddSubVariables(
+ CreateIfAny(variablesManager, "LinkOptions", supportsVariableType,
+ target->GetLinkOptionsEntries()));
+ targetVariables->AddSubVariables(CreateIfAny(
+ variablesManager, "SystemIncludeDirectories", supportsVariableType,
+ target->GetSystemIncludeDirectories()));
+ targetVariables->AddSubVariables(CreateIfAny(variablesManager, "Makefile",
+ supportsVariableType,
+ target->GetMakefile()));
+ targetVariables->AddSubVariables(
+ CreateIfAny(variablesManager, "GlobalGenerator", supportsVariableType,
+ target->GetGlobalGenerator()));
+
+ std::vector<cmFileSet*> allFileSets;
+ auto allFileSetNames = target->GetAllFileSetNames();
+ allFileSets.reserve(allFileSetNames.size());
+ for (auto const& fileSetName : allFileSetNames) {
+ allFileSets.emplace_back(target->GetFileSet(fileSetName));
+ }
+ targetVariables->AddSubVariables(CreateIfAny(
+ variablesManager, "AllFileSets", supportsVariableType, allFileSets));
+
+ std::vector<cmFileSet*> allInterfaceFileSets;
+ auto allInterfaceFileSetNames = target->GetAllInterfaceFileSets();
+ allInterfaceFileSets.reserve(allInterfaceFileSetNames.size());
+ for (auto const& interfaceFileSetName : allInterfaceFileSetNames) {
+ allInterfaceFileSets.emplace_back(
+ target->GetFileSet(interfaceFileSetName));
+ }
+ targetVariables->AddSubVariables(
+ CreateIfAny(variablesManager, "AllInterfaceFileSets",
+ supportsVariableType, allInterfaceFileSets));
+
+ targetVariables->SetIgnoreEmptyStringEntries(true);
+ targetsVariables->AddSubVariables(targetVariables);
+ }
+
+ targetsVariables->SetValue(std::to_string(targets.size()));
+ return targetsVariables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::Create(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::shared_ptr<cmDebuggerStackFrame> const& frame)
+{
+ auto variables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, name, supportsVariableType, [=]() {
+ return std::vector<cmDebuggerVariableEntry>{ { "CurrentLine",
+ frame->GetLine() } };
+ });
+
+ auto closureKeys = frame->GetMakefile()->GetStateSnapshot().ClosureKeys();
+ auto locals = std::make_shared<cmDebuggerVariables>(
+ variablesManager, "Locals", supportsVariableType, [=]() {
+ std::vector<cmDebuggerVariableEntry> ret;
+ ret.reserve(closureKeys.size());
+ for (auto const& key : closureKeys) {
+ ret.emplace_back(
+ key, frame->GetMakefile()->GetStateSnapshot().GetDefinition(key));
+ }
+ return ret;
+ });
+ locals->SetValue(std::to_string(closureKeys.size()));
+ variables->AddSubVariables(locals);
+
+ std::function<bool(std::string const&)> isDirectory =
+ [](std::string const& key) {
+ size_t pos1 = key.rfind("_DIR");
+ size_t pos2 = key.rfind("_DIRECTORY");
+ return !((pos1 == std::string::npos || pos1 != key.size() - 4) &&
+ (pos2 == std::string::npos || pos2 != key.size() - 10));
+ };
+ auto directorySize =
+ std::count_if(closureKeys.begin(), closureKeys.end(), isDirectory);
+ auto directories = std::make_shared<cmDebuggerVariables>(
+ variablesManager, "Directories", supportsVariableType, [=]() {
+ std::vector<cmDebuggerVariableEntry> ret;
+ ret.reserve(directorySize);
+ for (auto const& key : closureKeys) {
+ if (isDirectory(key)) {
+ ret.emplace_back(
+ key, frame->GetMakefile()->GetStateSnapshot().GetDefinition(key));
+ }
+ }
+ return ret;
+ });
+ directories->SetValue(std::to_string(directorySize));
+ variables->AddSubVariables(directories);
+
+ auto cacheVariables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, "CacheVariables", supportsVariableType);
+ auto* state = frame->GetMakefile()->GetCMakeInstance()->GetState();
+ auto keys = state->GetCacheEntryKeys();
+ for (auto const& key : keys) {
+ auto entry = std::make_shared<cmDebuggerVariables>(
+ variablesManager,
+ key + ":" +
+ cmState::CacheEntryTypeToString(state->GetCacheEntryType(key)),
+ supportsVariableType, [=]() {
+ std::vector<cmDebuggerVariableEntry> ret;
+ auto properties = state->GetCacheEntryPropertyList(key);
+ ret.reserve(properties.size() + 2);
+ for (auto const& propertyName : properties) {
+ ret.emplace_back(propertyName,
+ state->GetCacheEntryProperty(key, propertyName));
+ }
+
+ ret.emplace_back(
+ "TYPE",
+ cmState::CacheEntryTypeToString(state->GetCacheEntryType(key)));
+ ret.emplace_back("VALUE", state->GetCacheEntryValue(key));
+ return ret;
+ });
+
+ entry->SetValue(state->GetCacheEntryValue(key));
+ cacheVariables->AddSubVariables(entry);
+ }
+
+ cacheVariables->SetValue(std::to_string(keys.size()));
+ variables->AddSubVariables(cacheVariables);
+
+ auto targetVariables =
+ CreateIfAny(variablesManager, "Targets", supportsVariableType,
+ frame->GetMakefile()->GetOrderedTargets());
+
+ variables->AddSubVariables(targetVariables);
+ std::vector<cmTest*> tests;
+ frame->GetMakefile()->GetTests(
+ frame->GetMakefile()->GetDefaultConfiguration(), tests);
+ variables->AddSubVariables(
+ CreateIfAny(variablesManager, "Tests", supportsVariableType, tests));
+
+ return variables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType, cmTest* test)
+{
+ if (test == nullptr) {
+ return {};
+ }
+
+ auto variables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, name, supportsVariableType, [=]() {
+ std::vector<cmDebuggerVariableEntry> ret{
+ { "CommandExpandLists", test->GetCommandExpandLists() },
+ { "Name", test->GetName() },
+ { "OldStyle", test->GetOldStyle() },
+ };
+
+ return ret;
+ });
+
+ variables->AddSubVariables(CreateIfAny(
+ variablesManager, "Command", supportsVariableType, test->GetCommand()));
+
+ variables->AddSubVariables(CreateIfAny(variablesManager, "Properties",
+ supportsVariableType,
+ test->GetProperties().GetList()));
+ return variables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::vector<cmTest*> const& tests)
+{
+ if (tests.empty()) {
+ return {};
+ }
+
+ auto variables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, name, supportsVariableType);
+
+ for (auto const& test : tests) {
+ variables->AddSubVariables(CreateIfAny(variablesManager, test->GetName(),
+ supportsVariableType, test));
+ }
+ variables->SetValue(std::to_string(tests.size()));
+ return variables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType, cmMakefile* mf)
+{
+ if (mf == nullptr) {
+ return {};
+ }
+
+ auto AppleSDKTypeString = [&](cmMakefile::AppleSDK sdk) {
+ switch (sdk) {
+ case cmMakefile::AppleSDK::MacOS:
+ return "MacOS";
+ case cmMakefile::AppleSDK::IPhoneOS:
+ return "IPhoneOS";
+ case cmMakefile::AppleSDK::IPhoneSimulator:
+ return "IPhoneSimulator";
+ case cmMakefile::AppleSDK::AppleTVOS:
+ return "AppleTVOS";
+ case cmMakefile::AppleSDK::AppleTVSimulator:
+ return "AppleTVSimulator";
+ default:
+ return "Unknown";
+ }
+ };
+
+ auto variables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, name, supportsVariableType, [=]() {
+ std::vector<cmDebuggerVariableEntry> ret = {
+ { "DefineFlags", mf->GetDefineFlags() },
+ { "DirectoryId", mf->GetDirectoryId().String },
+ { "IsRootMakefile", mf->IsRootMakefile() },
+ { "HomeDirectory", mf->GetHomeDirectory() },
+ { "HomeOutputDirectory", mf->GetHomeOutputDirectory() },
+ { "CurrentSourceDirectory", mf->GetCurrentSourceDirectory() },
+ { "CurrentBinaryDirectory", mf->GetCurrentBinaryDirectory() },
+ { "PlatformIs32Bit", mf->PlatformIs32Bit() },
+ { "PlatformIs64Bit", mf->PlatformIs64Bit() },
+ { "PlatformIsx32", mf->PlatformIsx32() },
+ { "AppleSDKType", AppleSDKTypeString(mf->GetAppleSDKType()) },
+ { "PlatformIsAppleEmbedded", mf->PlatformIsAppleEmbedded() }
+ };
+
+ return ret;
+ });
+
+ variables->AddSubVariables(CreateIfAny(
+ variablesManager, "ListFiles", supportsVariableType, mf->GetListFiles()));
+ variables->AddSubVariables(CreateIfAny(variablesManager, "OutputFiles",
+ supportsVariableType,
+ mf->GetOutputFiles()));
+
+ variables->SetIgnoreEmptyStringEntries(true);
+ variables->SetValue(mf->GetDirectoryId().String);
+ return variables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType, cmGlobalGenerator* gen)
+{
+ if (gen == nullptr) {
+ return {};
+ }
+
+ auto makeFileEncodingString = [](codecvt::Encoding encoding) {
+ switch (encoding) {
+ case codecvt::Encoding::None:
+ return "None";
+ case codecvt::Encoding::UTF8:
+ return "UTF8";
+ case codecvt::Encoding::UTF8_WITH_BOM:
+ return "UTF8_WITH_BOM";
+ case codecvt::Encoding::ANSI:
+ return "ANSI";
+ case codecvt::Encoding::ConsoleOutput:
+ return "ConsoleOutput";
+ default:
+ return "Unknown";
+ }
+ };
+
+ auto variables = std::make_shared<cmDebuggerVariables>(
+ variablesManager, name, supportsVariableType, [=]() {
+ std::vector<cmDebuggerVariableEntry> ret = {
+ { "AllTargetName", gen->GetAllTargetName() },
+ { "CleanTargetName", gen->GetCleanTargetName() },
+ { "EditCacheCommand", gen->GetEditCacheCommand() },
+ { "EditCacheTargetName", gen->GetEditCacheTargetName() },
+ { "ExtraGeneratorName", gen->GetExtraGeneratorName() },
+ { "ForceUnixPaths", gen->GetForceUnixPaths() },
+ { "InstallLocalTargetName", gen->GetInstallLocalTargetName() },
+ { "InstallStripTargetName", gen->GetInstallStripTargetName() },
+ { "InstallTargetName", gen->GetInstallTargetName() },
+ { "IsMultiConfig", gen->IsMultiConfig() },
+ { "Name", gen->GetName() },
+ { "MakefileEncoding",
+ makeFileEncodingString(gen->GetMakefileEncoding()) },
+ { "PackageSourceTargetName", gen->GetPackageSourceTargetName() },
+ { "PackageTargetName", gen->GetPackageTargetName() },
+ { "PreinstallTargetName", gen->GetPreinstallTargetName() },
+ { "NeedSymbolicMark", gen->GetNeedSymbolicMark() },
+ { "RebuildCacheTargetName", gen->GetRebuildCacheTargetName() },
+ { "TestTargetName", gen->GetTestTargetName() },
+ { "UseLinkScript", gen->GetUseLinkScript() },
+ };
+
+ return ret;
+ });
+
+ if (gen->GetInstallComponents() != nullptr) {
+ variables->AddSubVariables(
+ CreateIfAny(variablesManager, "InstallComponents", supportsVariableType,
+ *gen->GetInstallComponents()));
+ }
+
+ variables->SetIgnoreEmptyStringEntries(true);
+ variables->SetValue(gen->GetName());
+
+ return variables;
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerVariablesHelper.h b/Source/cmDebuggerVariablesHelper.h
new file mode 100644
index 0000000..9b11eaf
--- /dev/null
+++ b/Source/cmDebuggerVariablesHelper.h
@@ -0,0 +1,106 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "cmAlgorithms.h"
+#include "cmPolicies.h"
+
+class cmFileSet;
+class cmGlobalGenerator;
+class cmMakefile;
+class cmTarget;
+class cmTest;
+
+namespace cmDebugger {
+class cmDebuggerStackFrame;
+class cmDebuggerVariables;
+class cmDebuggerVariablesManager;
+}
+
+template <typename T>
+class BT;
+
+namespace cmDebugger {
+
+class cmDebuggerVariablesHelper
+{
+ cmDebuggerVariablesHelper() = default;
+
+public:
+ static std::shared_ptr<cmDebuggerVariables> Create(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ cmPolicies::PolicyMap const& policyMap);
+
+ static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::vector<std::pair<std::string, std::string>> const& list);
+
+ static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ cmBTStringRange const& entries);
+
+ static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::set<std::string> const& values);
+
+ static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::vector<std::string> const& values);
+
+ static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::vector<BT<std::string>> const& list);
+
+ static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType, cmFileSet* fileSet);
+
+ static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::vector<cmFileSet*> const& fileSets);
+
+ static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType, cmTest* test);
+
+ static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::vector<cmTest*> const& tests);
+
+ static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::vector<cmTarget*> const& targets);
+
+ static std::shared_ptr<cmDebuggerVariables> Create(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ std::shared_ptr<cmDebuggerStackFrame> const& frame);
+
+ static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType, cmMakefile* mf);
+
+ static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+ std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+ std::string const& name, bool supportsVariableType,
+ cmGlobalGenerator* gen);
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerVariablesManager.cxx b/Source/cmDebuggerVariablesManager.cxx
new file mode 100644
index 0000000..9b9b476
--- /dev/null
+++ b/Source/cmDebuggerVariablesManager.cxx
@@ -0,0 +1,38 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cmDebuggerVariablesManager.h"
+
+#include <utility>
+
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/types.h>
+
+namespace cmDebugger {
+
+void cmDebuggerVariablesManager::RegisterHandler(
+ int64_t id,
+ std::function<dap::array<dap::Variable>(dap::VariablesRequest const&)>
+ handler)
+{
+ VariablesHandlers[id] = std::move(handler);
+}
+
+void cmDebuggerVariablesManager::UnregisterHandler(int64_t id)
+{
+ VariablesHandlers.erase(id);
+}
+
+dap::array<dap::Variable> cmDebuggerVariablesManager::HandleVariablesRequest(
+ dap::VariablesRequest const& request)
+{
+ auto it = VariablesHandlers.find(request.variablesReference);
+
+ if (it != VariablesHandlers.end()) {
+ return it->second(request);
+ }
+
+ return dap::array<dap::Variable>();
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerVariablesManager.h b/Source/cmDebuggerVariablesManager.h
new file mode 100644
index 0000000..c219164
--- /dev/null
+++ b/Source/cmDebuggerVariablesManager.h
@@ -0,0 +1,40 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstdint>
+#include <functional>
+#include <unordered_map>
+#include <vector>
+
+#include <cm3p/cppdap/types.h> // IWYU pragma: keep
+
+namespace dap {
+struct Variable;
+struct VariablesRequest;
+}
+
+namespace cmDebugger {
+
+class cmDebuggerVariablesManager
+{
+ std::unordered_map<
+ int64_t,
+ std::function<dap::array<dap::Variable>(dap::VariablesRequest const&)>>
+ VariablesHandlers;
+ void RegisterHandler(
+ int64_t id,
+ std::function<dap::array<dap::Variable>(dap::VariablesRequest const&)>
+ handler);
+ void UnregisterHandler(int64_t id);
+ friend class cmDebuggerVariables;
+
+public:
+ cmDebuggerVariablesManager() = default;
+ dap::array<dap::Variable> HandleVariablesRequest(
+ dap::VariablesRequest const& request);
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index 585924d..92ba2d4 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -68,6 +68,10 @@
# include "cmVariableWatch.h"
#endif
+#ifdef CMake_ENABLE_DEBUGGER
+# include "cmDebuggerAdapter.h"
+#endif
+
#ifndef __has_feature
# define __has_feature(x) 0
#endif
@@ -424,6 +428,13 @@ public:
return argsValue;
});
#endif
+#ifdef CMake_ENABLE_DEBUGGER
+ if (this->Makefile->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+ this->Makefile->GetCMakeInstance()
+ ->GetDebugAdapter()
+ ->OnBeginFunctionCall(mf, lfc.FilePath, lff);
+ }
+#endif
}
~cmMakefileCall()
@@ -434,6 +445,13 @@ public:
this->Makefile->ExecutionStatusStack.pop_back();
--this->Makefile->RecursionDepth;
this->Makefile->Backtrace = this->Makefile->Backtrace.Pop();
+#ifdef CMake_ENABLE_DEBUGGER
+ if (this->Makefile->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+ this->Makefile->GetCMakeInstance()
+ ->GetDebugAdapter()
+ ->OnEndFunctionCall();
+ }
+#endif
}
cmMakefileCall(const cmMakefileCall&) = delete;
@@ -663,12 +681,33 @@ bool cmMakefile::ReadDependentFile(const std::string& filename,
IncludeScope incScope(this, filenametoread, noPolicyScope);
+#ifdef CMake_ENABLE_DEBUGGER
+ if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+ this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse(
+ this, filenametoread);
+ }
+#endif
+
cmListFile listFile;
if (!listFile.ParseFile(filenametoread.c_str(), this->GetMessenger(),
this->Backtrace)) {
+#ifdef CMake_ENABLE_DEBUGGER
+ if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+ this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse();
+ }
+#endif
+
return false;
}
+#ifdef CMake_ENABLE_DEBUGGER
+ if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+ this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse();
+ this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully(
+ filenametoread, listFile.Functions);
+ }
+#endif
+
this->RunListFile(listFile, filenametoread);
if (cmSystemTools::GetFatalErrorOccurred()) {
incScope.Quiet();
@@ -764,12 +803,33 @@ bool cmMakefile::ReadListFile(const std::string& filename)
ListFileScope scope(this, filenametoread);
+#ifdef CMake_ENABLE_DEBUGGER
+ if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+ this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse(
+ this, filenametoread);
+ }
+#endif
+
cmListFile listFile;
if (!listFile.ParseFile(filenametoread.c_str(), this->GetMessenger(),
this->Backtrace)) {
+#ifdef CMake_ENABLE_DEBUGGER
+ if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+ this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse();
+ }
+#endif
+
return false;
}
+#ifdef CMake_ENABLE_DEBUGGER
+ if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+ this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse();
+ this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully(
+ filenametoread, listFile.Functions);
+ }
+#endif
+
this->RunListFile(listFile, filenametoread);
if (cmSystemTools::GetFatalErrorOccurred()) {
scope.Quiet();
@@ -791,6 +851,13 @@ bool cmMakefile::ReadListFileAsString(const std::string& content,
return false;
}
+#ifdef CMake_ENABLE_DEBUGGER
+ if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+ this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully(
+ filenametoread, listFile.Functions);
+ }
+#endif
+
this->RunListFile(listFile, filenametoread);
if (cmSystemTools::GetFatalErrorOccurred()) {
scope.Quiet();
@@ -1658,11 +1725,33 @@ void cmMakefile::Configure()
assert(cmSystemTools::FileExists(currentStart, true));
this->AddDefinition("CMAKE_PARENT_LIST_FILE", currentStart);
+#ifdef CMake_ENABLE_DEBUGGER
+ if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+ this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse(
+ this, currentStart);
+ }
+#endif
+
cmListFile listFile;
if (!listFile.ParseFile(currentStart.c_str(), this->GetMessenger(),
this->Backtrace)) {
+#ifdef CMake_ENABLE_DEBUGGER
+ if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+ this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse();
+ }
+#endif
+
return;
}
+
+#ifdef CMake_ENABLE_DEBUGGER
+ if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+ this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse();
+ this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully(
+ currentStart, listFile.Functions);
+ }
+#endif
+
if (this->IsRootMakefile()) {
bool hasVersion = false;
// search for the right policy command
@@ -3769,6 +3858,12 @@ void cmMakefile::DisplayStatus(const std::string& message, float s) const
return;
}
cm->UpdateProgress(message, s);
+
+#ifdef CMake_ENABLE_DEBUGGER
+ if (cm->GetDebugAdapter() != nullptr) {
+ cm->GetDebugAdapter()->OnMessageOutput(MessageType::MESSAGE, message);
+ }
+#endif
}
std::string cmMakefile::GetModulesFile(const std::string& filename,
diff --git a/Source/cmMessageCommand.cxx b/Source/cmMessageCommand.cxx
index baf40f8..68b3a5d 100644
--- a/Source/cmMessageCommand.cxx
+++ b/Source/cmMessageCommand.cxx
@@ -3,6 +3,7 @@
#include "cmMessageCommand.h"
#include <cassert>
+#include <memory>
#include <utility>
#include <cm/string_view>
@@ -19,6 +20,10 @@
#include "cmSystemTools.h"
#include "cmake.h"
+#ifdef CMake_ENABLE_DEBUGGER
+# include "cmDebuggerAdapter.h"
+#endif
+
namespace {
enum class CheckingType
@@ -202,6 +207,12 @@ bool cmMessageCommand(std::vector<std::string> const& args,
case Message::LogLevel::LOG_NOTICE:
cmSystemTools::Message(IndentText(message, mf));
+#ifdef CMake_ENABLE_DEBUGGER
+ if (mf.GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+ mf.GetCMakeInstance()->GetDebugAdapter()->OnMessageOutput(type,
+ message);
+ }
+#endif
break;
case Message::LogLevel::LOG_STATUS:
diff --git a/Source/cmMessenger.cxx b/Source/cmMessenger.cxx
index 7de8936..4e975d1 100644
--- a/Source/cmMessenger.cxx
+++ b/Source/cmMessenger.cxx
@@ -16,6 +16,10 @@
#include "cmsys/Terminal.h"
+#ifdef CMake_ENABLE_DEBUGGER
+# include "cmDebuggerAdapter.h"
+#endif
+
MessageType cmMessenger::ConvertMessageType(MessageType t) const
{
if (t == MessageType::AUTHOR_WARNING || t == MessageType::AUTHOR_ERROR) {
@@ -207,6 +211,12 @@ void cmMessenger::DisplayMessage(MessageType t, const std::string& text,
PrintCallStack(msg, backtrace, this->TopSource);
displayMessage(t, msg);
+
+#ifdef CMake_ENABLE_DEBUGGER
+ if (DebuggerAdapter != nullptr) {
+ DebuggerAdapter->OnMessageOutput(t, msg.str());
+ }
+#endif
}
void cmMessenger::PrintBacktraceTitle(std::ostream& out,
diff --git a/Source/cmMessenger.h b/Source/cmMessenger.h
index 451add0..bdefb00 100644
--- a/Source/cmMessenger.h
+++ b/Source/cmMessenger.h
@@ -5,6 +5,7 @@
#include "cmConfigure.h" // IWYU pragma: keep
#include <iosfwd>
+#include <memory>
#include <string>
#include <cm/optional>
@@ -12,6 +13,12 @@
#include "cmListFileCache.h"
#include "cmMessageType.h"
+#ifdef CMake_ENABLE_DEBUGGER
+namespace cmDebugger {
+class cmDebuggerAdapter;
+}
+#endif
+
class cmMessenger
{
public:
@@ -55,6 +62,13 @@ public:
// Print the top of a backtrace.
void PrintBacktraceTitle(std::ostream& out,
cmListFileBacktrace const& bt) const;
+#ifdef CMake_ENABLE_DEBUGGER
+ void SetDebuggerAdapter(
+ std::shared_ptr<cmDebugger::cmDebuggerAdapter> const& debuggerAdapter)
+ {
+ DebuggerAdapter = debuggerAdapter;
+ }
+#endif
private:
bool IsMessageTypeVisible(MessageType t) const;
@@ -66,4 +80,7 @@ private:
bool SuppressDeprecatedWarnings = false;
bool DevWarningsAsErrors = false;
bool DeprecatedWarningsAsErrors = false;
+#ifdef CMake_ENABLE_DEBUGGER
+ std::shared_ptr<cmDebugger::cmDebuggerAdapter> DebuggerAdapter;
+#endif
};
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index c5b467d..0a1e7ab 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -38,6 +38,10 @@
#include "cmCMakePresetsGraph.h"
#include "cmCommandLineArgument.h"
#include "cmCommands.h"
+#ifdef CMake_ENABLE_DEBUGGER
+# include "cmDebuggerAdapter.h"
+# include "cmDebuggerPipeConnection.h"
+#endif
#include "cmDocumentation.h"
#include "cmDocumentationEntry.h"
#include "cmDuration.h"
@@ -411,6 +415,11 @@ Json::Value cmake::ReportCapabilitiesJson() const
obj["fileApi"] = cmFileAPI::ReportCapabilities();
obj["serverMode"] = false;
obj["tls"] = static_cast<bool>(curlVersion->features & CURL_VERSION_SSL);
+# ifdef CMake_ENABLE_DEBUGGER
+ obj["debugger"] = true;
+# else
+ obj["debugger"] = false;
+# endif
return obj;
}
@@ -617,6 +626,13 @@ bool cmake::SetCacheArgs(const std::vector<std::string>& args)
};
auto ScriptLambda = [&](std::string const& path, cmake* state) -> bool {
+#ifdef CMake_ENABLE_DEBUGGER
+ // Script mode doesn't hit the usual code path in cmake::Run() that starts
+ // the debugger, so start it manually here instead.
+ if (!this->StartDebuggerIfEnabled()) {
+ return false;
+ }
+#endif
// Register fake project commands that hint misuse in script mode.
GetProjectCommandsInScriptMode(state->GetState());
// Documented behavior of CMAKE{,_CURRENT}_{SOURCE,BINARY}_DIR is to be
@@ -1233,7 +1249,52 @@ void cmake::SetArgs(const std::vector<std::string>& args)
"CMAKE_COMPILE_WARNING_AS_ERROR variable.\n";
state->SetIgnoreWarningAsError(true);
return true;
- } }
+ } },
+ CommandArgument{ "--debugger", CommandArgument::Values::Zero,
+ [](std::string const&, cmake* state) -> bool {
+#ifdef CMake_ENABLE_DEBUGGER
+ std::cout << "Running with debugger on.\n";
+ state->SetDebuggerOn(true);
+ return true;
+#else
+ static_cast<void>(state);
+ cmSystemTools::Error(
+ "CMake was not built with support for --debugger");
+ return false;
+#endif
+ } },
+ CommandArgument{ "--debugger-pipe",
+ "No path specified for --debugger-pipe",
+ CommandArgument::Values::One,
+ [](std::string const& value, cmake* state) -> bool {
+#ifdef CMake_ENABLE_DEBUGGER
+ state->DebuggerPipe = value;
+ return true;
+#else
+ static_cast<void>(value);
+ static_cast<void>(state);
+ cmSystemTools::Error("CMake was not built with support "
+ "for --debugger-pipe");
+ return false;
+#endif
+ } },
+ CommandArgument{
+ "--debugger-dap-log", "No file specified for --debugger-dap-log",
+ CommandArgument::Values::One,
+ [](std::string const& value, cmake* state) -> bool {
+#ifdef CMake_ENABLE_DEBUGGER
+ std::string path = cmSystemTools::CollapseFullPath(value);
+ cmSystemTools::ConvertToUnixSlashes(path);
+ state->DebuggerDapLogFile = path;
+ return true;
+#else
+ static_cast<void>(value);
+ static_cast<void>(state);
+ cmSystemTools::Error(
+ "CMake was not built with support for --debugger-dap-log");
+ return false;
+#endif
+ } },
};
#if defined(CMAKE_HAVE_VS_GENERATORS)
@@ -2618,6 +2679,52 @@ void cmake::PreLoadCMakeFiles()
}
}
+#ifdef CMake_ENABLE_DEBUGGER
+
+bool cmake::StartDebuggerIfEnabled()
+{
+ if (!this->GetDebuggerOn()) {
+ return true;
+ }
+
+ if (DebugAdapter == nullptr) {
+ if (this->GetDebuggerPipe().empty()) {
+ std::cerr
+ << "Error: --debugger-pipe must be set when debugging is enabled.\n";
+ return false;
+ }
+
+ try {
+ DebugAdapter = std::make_shared<cmDebugger::cmDebuggerAdapter>(
+ std::make_shared<cmDebugger::cmDebuggerPipeConnection>(
+ this->GetDebuggerPipe()),
+ this->GetDebuggerDapLogFile());
+ } catch (const std::runtime_error& error) {
+ std::cerr << "Error: Failed to create debugger adapter.\n";
+ std::cerr << error.what() << "\n";
+ return false;
+ }
+ Messenger->SetDebuggerAdapter(DebugAdapter);
+ }
+
+ return true;
+}
+
+void cmake::StopDebuggerIfNeeded(int exitCode)
+{
+ if (!this->GetDebuggerOn()) {
+ return;
+ }
+
+ // The debug adapter may have failed to start (e.g. invalid pipe path).
+ if (DebugAdapter != nullptr) {
+ DebugAdapter->ReportExitCode(exitCode);
+ DebugAdapter.reset();
+ }
+}
+
+#endif
+
// handle a command line invocation
int cmake::Run(const std::vector<std::string>& args, bool noconfigure)
{
@@ -2707,6 +2814,12 @@ int cmake::Run(const std::vector<std::string>& args, bool noconfigure)
return 0;
}
+#ifdef CMake_ENABLE_DEBUGGER
+ if (!this->StartDebuggerIfEnabled()) {
+ return -1;
+ }
+#endif
+
int ret = this->Configure();
if (ret) {
#if defined(CMAKE_HAVE_VS_GENERATORS)
diff --git a/Source/cmake.h b/Source/cmake.h
index 955ec4f..d394a3e 100644
--- a/Source/cmake.h
+++ b/Source/cmake.h
@@ -37,6 +37,13 @@
#endif
class cmConfigureLog;
+
+#ifdef CMake_ENABLE_DEBUGGER
+namespace cmDebugger {
+class cmDebuggerAdapter;
+}
+#endif
+
class cmExternalMakefileProjectGeneratorFactory;
class cmFileAPI;
class cmFileTimeCache;
@@ -662,6 +669,23 @@ public:
}
#endif
+#ifdef CMake_ENABLE_DEBUGGER
+ bool GetDebuggerOn() const { return this->DebuggerOn; }
+ std::string GetDebuggerPipe() const { return this->DebuggerPipe; }
+ std::string GetDebuggerDapLogFile() const
+ {
+ return this->DebuggerDapLogFile;
+ }
+ void SetDebuggerOn(bool b) { this->DebuggerOn = b; }
+ bool StartDebuggerIfEnabled();
+ void StopDebuggerIfNeeded(int exitCode);
+ std::shared_ptr<cmDebugger::cmDebuggerAdapter> GetDebugAdapter()
+ const noexcept
+ {
+ return this->DebugAdapter;
+ }
+#endif
+
protected:
void RunCheckForUnusedVariables();
int HandleDeleteCacheVariables(const std::string& var);
@@ -802,6 +826,13 @@ private:
std::unique_ptr<cmMakefileProfilingData> ProfilingOutput;
#endif
+#ifdef CMake_ENABLE_DEBUGGER
+ std::shared_ptr<cmDebugger::cmDebuggerAdapter> DebugAdapter;
+ bool DebuggerOn = false;
+ std::string DebuggerPipe;
+ std::string DebuggerDapLogFile;
+#endif
+
public:
static cmDocumentationEntry CMAKE_STANDARD_OPTIONS_TABLE[18];
};
diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx
index ad27443..ced83dc 100644
--- a/Source/cmakemain.cxx
+++ b/Source/cmakemain.cxx
@@ -392,8 +392,14 @@ int do_cmake(int ac, char const* const* av)
// Always return a non-negative value. Windows tools do not always
// interpret negative return values as errors.
if (res != 0) {
+#ifdef CMake_ENABLE_DEBUGGER
+ cm.StopDebuggerIfNeeded(1);
+#endif
return 1;
}
+#ifdef CMake_ENABLE_DEBUGGER
+ cm.StopDebuggerIfNeeded(0);
+#endif
return 0;
}
diff --git a/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt
index 944b328..5c14de2 100644
--- a/Tests/CMakeLib/CMakeLists.txt
+++ b/Tests/CMakeLib/CMakeLists.txt
@@ -32,6 +32,16 @@ set(CMakeLib_TESTS
testCMExtEnumSet.cxx
testList.cxx
)
+if(CMake_ENABLE_DEBUGGER)
+ list(APPEND CMakeLib_TESTS
+ testDebuggerAdapter.cxx
+ testDebuggerAdapterPipe.cxx
+ testDebuggerBreakpointManager.cxx
+ testDebuggerVariables.cxx
+ testDebuggerVariablesHelper.cxx
+ testDebuggerVariablesManager.cxx
+ )
+endif()
if (CMake_TEST_FILESYSTEM_PATH OR NOT CMake_HAVE_CXX_FILESYSTEM)
list(APPEND CMakeLib_TESTS testCMFilesystemPath.cxx)
endif()
@@ -78,3 +88,18 @@ add_subdirectory(PseudoMemcheck)
add_executable(testAffinity testAffinity.cxx)
target_link_libraries(testAffinity CMakeLib)
+
+if(CMake_ENABLE_DEBUGGER)
+ add_executable(testDebuggerNamedPipe testDebuggerNamedPipe.cxx)
+ target_link_libraries(testDebuggerNamedPipe PRIVATE CMakeLib)
+ set(testDebuggerNamedPipe_Project_ARGS
+ "$<TARGET_FILE:cmake>" ${CMAKE_CURRENT_SOURCE_DIR}/DebuggerSample ${CMAKE_CURRENT_BINARY_DIR}/DebuggerSample
+ )
+ set(testDebuggerNamedPipe_Script_ARGS
+ "$<TARGET_FILE:cmake>" ${CMAKE_CURRENT_SOURCE_DIR}/DebuggerSample/script.cmake
+ )
+ foreach(case Project Script)
+ add_test(NAME CMakeLib.testDebuggerNamedPipe-${case} COMMAND testDebuggerNamedPipe ${testDebuggerNamedPipe_${case}_ARGS})
+ set_property(TEST CMakeLib.testDebuggerNamedPipe-${case} PROPERTY TIMEOUT 300)
+ endforeach()
+endif()
diff --git a/Tests/CMakeLib/DebuggerSample/CMakeLists.txt b/Tests/CMakeLib/DebuggerSample/CMakeLists.txt
new file mode 100644
index 0000000..8f8603a
--- /dev/null
+++ b/Tests/CMakeLib/DebuggerSample/CMakeLists.txt
@@ -0,0 +1,9 @@
+cmake_minimum_required(VERSION 3.26)
+project(DebuggerSample NONE)
+message("Hello CMake Debugger")
+
+# There are concerns that because the debugger uses libuv for pipe
+# communication, libuv may register a SIGCHILD handler that interferes with
+# the existing handler used by kwsys process management. Test this case with a
+# simple external process.
+execute_process(COMMAND "${CMAKE_COMMAND}" -E echo test)
diff --git a/Tests/CMakeLib/DebuggerSample/script.cmake b/Tests/CMakeLib/DebuggerSample/script.cmake
new file mode 100644
index 0000000..4c0c00a
--- /dev/null
+++ b/Tests/CMakeLib/DebuggerSample/script.cmake
@@ -0,0 +1 @@
+message(STATUS "This is an example script")
diff --git a/Tests/CMakeLib/testCommon.h b/Tests/CMakeLib/testCommon.h
new file mode 100644
index 0000000..bd2d54e
--- /dev/null
+++ b/Tests/CMakeLib/testCommon.h
@@ -0,0 +1,30 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include <functional>
+#include <iostream>
+#include <vector>
+
+#define ASSERT_TRUE(x) \
+ do { \
+ if (!(x)) { \
+ std::cout << "ASSERT_TRUE(" #x ") failed on line " << __LINE__ << "\n"; \
+ return false; \
+ } \
+ } while (false)
+
+inline int runTests(std::vector<std::function<bool()>> const& tests)
+{
+ for (auto const& test : tests) {
+ if (!test()) {
+ return 1;
+ }
+ std::cout << ".";
+ }
+
+ std::cout << " Passed" << std::endl;
+ return 0;
+}
+
+#define BOOL_STRING(b) ((b) ? "TRUE" : "FALSE")
diff --git a/Tests/CMakeLib/testDebugger.h b/Tests/CMakeLib/testDebugger.h
new file mode 100644
index 0000000..8ba21f6
--- /dev/null
+++ b/Tests/CMakeLib/testDebugger.h
@@ -0,0 +1,99 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include "cmDebuggerAdapter.h"
+#include "cmDebuggerProtocol.h"
+#include "cmListFileCache.h"
+#include "cmMessenger.h"
+#include <cmcppdap/include/dap/io.h>
+#include <cmcppdap/include/dap/session.h>
+#include <cmcppdap/include/dap/types.h>
+
+#include "testCommon.h"
+
+#define ASSERT_VARIABLE(x, expectedName, expectedValue, expectedType) \
+ do { \
+ ASSERT_TRUE(x.name == expectedName); \
+ ASSERT_TRUE(x.value == expectedValue); \
+ ASSERT_TRUE(x.type.value() == expectedType); \
+ ASSERT_TRUE(x.evaluateName.has_value() == false); \
+ if (std::string(expectedType) == "collection") { \
+ ASSERT_TRUE(x.variablesReference != 0); \
+ } \
+ } while (false)
+
+#define ASSERT_VARIABLE_REFERENCE(x, expectedName, expectedValue, \
+ expectedType, expectedReference) \
+ do { \
+ ASSERT_VARIABLE(x, expectedName, expectedValue, expectedType); \
+ ASSERT_TRUE(x.variablesReference == (expectedReference)); \
+ } while (false)
+
+#define ASSERT_VARIABLE_REFERENCE_NOT_ZERO(x, expectedName, expectedValue, \
+ expectedType) \
+ do { \
+ ASSERT_VARIABLE(x, expectedName, expectedValue, expectedType); \
+ ASSERT_TRUE(x.variablesReference != 0); \
+ } while (false)
+
+#define ASSERT_BREAKPOINT(x, expectedId, expectedLine, sourcePath, \
+ isVerified) \
+ do { \
+ ASSERT_TRUE(x.id.has_value()); \
+ ASSERT_TRUE(x.id.value() == expectedId); \
+ ASSERT_TRUE(x.line.has_value()); \
+ ASSERT_TRUE(x.line.value() == expectedLine); \
+ ASSERT_TRUE(x.source.has_value()); \
+ ASSERT_TRUE(x.source.value().path.has_value()); \
+ ASSERT_TRUE(x.source.value().path.value() == sourcePath); \
+ ASSERT_TRUE(x.verified == isVerified); \
+ } while (false)
+
+class DebuggerTestHelper
+{
+ std::shared_ptr<dap::ReaderWriter> Client2Debugger = dap::pipe();
+ std::shared_ptr<dap::ReaderWriter> Debugger2Client = dap::pipe();
+
+public:
+ std::unique_ptr<dap::Session> Client = dap::Session::create();
+ std::unique_ptr<dap::Session> Debugger = dap::Session::create();
+ void bind()
+ {
+ auto client2server = dap::pipe();
+ auto server2client = dap::pipe();
+ Client->bind(server2client, client2server);
+ Debugger->bind(client2server, server2client);
+ }
+ std::vector<cmListFileFunction> CreateListFileFunctions(const char* str,
+ const char* filename)
+ {
+ cmMessenger messenger;
+ cmListFileBacktrace backtrace;
+ cmListFile listfile;
+ listfile.ParseString(str, filename, &messenger, backtrace);
+ return listfile.Functions;
+ }
+};
+
+class ScopedThread
+{
+public:
+ template <class... Args>
+ explicit ScopedThread(Args&&... args)
+ : Thread(std::forward<Args>(args)...)
+ {
+ }
+
+ ~ScopedThread()
+ {
+ if (Thread.joinable())
+ Thread.join();
+ }
+
+private:
+ std::thread Thread;
+};
diff --git a/Tests/CMakeLib/testDebuggerAdapter.cxx b/Tests/CMakeLib/testDebuggerAdapter.cxx
new file mode 100644
index 0000000..394986b
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerAdapter.cxx
@@ -0,0 +1,173 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include <chrono>
+#include <cstdio>
+#include <functional>
+#include <future>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <cm3p/cppdap/future.h>
+#include <cm3p/cppdap/io.h>
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/session.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerAdapter.h"
+#include "cmDebuggerProtocol.h"
+#include "cmVersionConfig.h"
+
+#include "testCommon.h"
+#include "testDebugger.h"
+
+class DebuggerLocalConnection : public cmDebugger::cmDebuggerConnection
+{
+public:
+ DebuggerLocalConnection()
+ : ClientToDebugger(dap::pipe())
+ , DebuggerToClient(dap::pipe())
+ {
+ }
+
+ bool StartListening(std::string& errorMessage) override
+ {
+ errorMessage = "";
+ return true;
+ }
+ void WaitForConnection() override {}
+
+ std::shared_ptr<dap::Reader> GetReader() override
+ {
+ return ClientToDebugger;
+ };
+
+ std::shared_ptr<dap::Writer> GetWriter() override
+ {
+ return DebuggerToClient;
+ }
+
+ std::shared_ptr<dap::ReaderWriter> ClientToDebugger;
+ std::shared_ptr<dap::ReaderWriter> DebuggerToClient;
+};
+
+bool testBasicProtocol()
+{
+ std::promise<bool> debuggerAdapterInitializedPromise;
+ std::future<bool> debuggerAdapterInitializedFuture =
+ debuggerAdapterInitializedPromise.get_future();
+
+ std::promise<bool> initializedEventReceivedPromise;
+ std::future<bool> initializedEventReceivedFuture =
+ initializedEventReceivedPromise.get_future();
+
+ std::promise<bool> exitedEventReceivedPromise;
+ std::future<bool> exitedEventReceivedFuture =
+ exitedEventReceivedPromise.get_future();
+
+ std::promise<bool> terminatedEventReceivedPromise;
+ std::future<bool> terminatedEventReceivedFuture =
+ terminatedEventReceivedPromise.get_future();
+
+ std::promise<bool> threadStartedPromise;
+ std::future<bool> threadStartedFuture = threadStartedPromise.get_future();
+
+ std::promise<bool> threadExitedPromise;
+ std::future<bool> threadExitedFuture = threadExitedPromise.get_future();
+
+ std::promise<bool> disconnectResponseReceivedPromise;
+ std::future<bool> disconnectResponseReceivedFuture =
+ disconnectResponseReceivedPromise.get_future();
+
+ auto futureTimeout = std::chrono::seconds(60);
+
+ auto connection = std::make_shared<DebuggerLocalConnection>();
+ std::unique_ptr<dap::Session> client = dap::Session::create();
+ client->registerHandler([&](const dap::InitializedEvent& e) {
+ (void)e;
+ initializedEventReceivedPromise.set_value(true);
+ });
+ client->registerHandler([&](const dap::ExitedEvent& e) {
+ (void)e;
+ exitedEventReceivedPromise.set_value(true);
+ });
+ client->registerHandler([&](const dap::TerminatedEvent& e) {
+ (void)e;
+ terminatedEventReceivedPromise.set_value(true);
+ });
+ client->registerHandler([&](const dap::ThreadEvent& e) {
+ if (e.reason == "started") {
+ threadStartedPromise.set_value(true);
+ } else if (e.reason == "exited") {
+ threadExitedPromise.set_value(true);
+ }
+ });
+
+ client->bind(connection->DebuggerToClient, connection->ClientToDebugger);
+
+ ScopedThread debuggerThread([&]() -> int {
+ std::shared_ptr<cmDebugger::cmDebuggerAdapter> debuggerAdapter =
+ std::make_shared<cmDebugger::cmDebuggerAdapter>(
+ connection, dap::file(stdout, false));
+
+ debuggerAdapterInitializedPromise.set_value(true);
+ debuggerAdapter->ReportExitCode(0);
+
+ // Ensure the disconnectResponse has been received before
+ // destructing debuggerAdapter.
+ ASSERT_TRUE(disconnectResponseReceivedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+ return 0;
+ });
+
+ dap::CMakeInitializeRequest initializeRequest;
+ auto initializeResponse = client->send(initializeRequest).get();
+ ASSERT_TRUE(initializeResponse.response.cmakeVersion.full == CMake_VERSION);
+ ASSERT_TRUE(initializeResponse.response.cmakeVersion.major ==
+ CMake_VERSION_MAJOR);
+ ASSERT_TRUE(initializeResponse.response.cmakeVersion.minor ==
+ CMake_VERSION_MINOR);
+ ASSERT_TRUE(initializeResponse.response.cmakeVersion.patch ==
+ CMake_VERSION_PATCH);
+ ASSERT_TRUE(initializeResponse.response.supportsExceptionInfoRequest);
+ ASSERT_TRUE(
+ initializeResponse.response.exceptionBreakpointFilters.has_value());
+
+ dap::LaunchRequest launchRequest;
+ auto launchResponse = client->send(launchRequest).get();
+ ASSERT_TRUE(!launchResponse.error);
+
+ dap::ConfigurationDoneRequest configurationDoneRequest;
+ auto configurationDoneResponse =
+ client->send(configurationDoneRequest).get();
+ ASSERT_TRUE(!configurationDoneResponse.error);
+
+ ASSERT_TRUE(debuggerAdapterInitializedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+ ASSERT_TRUE(initializedEventReceivedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+ ASSERT_TRUE(threadStartedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+ ASSERT_TRUE(threadExitedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+ ASSERT_TRUE(exitedEventReceivedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+ ASSERT_TRUE(terminatedEventReceivedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+
+ dap::DisconnectRequest disconnectRequest;
+ auto disconnectResponse = client->send(disconnectRequest).get();
+ disconnectResponseReceivedPromise.set_value(true);
+ ASSERT_TRUE(!disconnectResponse.error);
+
+ return true;
+}
+
+int testDebuggerAdapter(int, char*[])
+{
+ return runTests(std::vector<std::function<bool()>>{
+ testBasicProtocol,
+ });
+}
diff --git a/Tests/CMakeLib/testDebuggerAdapterPipe.cxx b/Tests/CMakeLib/testDebuggerAdapterPipe.cxx
new file mode 100644
index 0000000..643661d
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerAdapterPipe.cxx
@@ -0,0 +1,184 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include <chrono>
+#include <cstdio>
+#include <functional>
+#include <future>
+#include <iostream>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <cm3p/cppdap/future.h>
+#include <cm3p/cppdap/io.h>
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/session.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerAdapter.h"
+#include "cmDebuggerPipeConnection.h"
+#include "cmDebuggerProtocol.h"
+#include "cmVersionConfig.h"
+
+#ifdef _WIN32
+# include "cmCryptoHash.h"
+# include "cmSystemTools.h"
+#endif
+
+#include "testCommon.h"
+#include "testDebugger.h"
+
+bool testProtocolWithPipes()
+{
+ std::promise<void> debuggerConnectionCreatedPromise;
+ std::future<void> debuggerConnectionCreatedFuture =
+ debuggerConnectionCreatedPromise.get_future();
+
+ std::future<void> startedListeningFuture;
+
+ std::promise<bool> debuggerAdapterInitializedPromise;
+ std::future<bool> debuggerAdapterInitializedFuture =
+ debuggerAdapterInitializedPromise.get_future();
+
+ std::promise<bool> initializedEventReceivedPromise;
+ std::future<bool> initializedEventReceivedFuture =
+ initializedEventReceivedPromise.get_future();
+
+ std::promise<bool> exitedEventReceivedPromise;
+ std::future<bool> exitedEventReceivedFuture =
+ exitedEventReceivedPromise.get_future();
+
+ std::promise<bool> terminatedEventReceivedPromise;
+ std::future<bool> terminatedEventReceivedFuture =
+ terminatedEventReceivedPromise.get_future();
+
+ std::promise<bool> threadStartedPromise;
+ std::future<bool> threadStartedFuture = threadStartedPromise.get_future();
+
+ std::promise<bool> threadExitedPromise;
+ std::future<bool> threadExitedFuture = threadExitedPromise.get_future();
+
+ std::promise<bool> disconnectResponseReceivedPromise;
+ std::future<bool> disconnectResponseReceivedFuture =
+ disconnectResponseReceivedPromise.get_future();
+
+ auto futureTimeout = std::chrono::seconds(60);
+
+#ifdef _WIN32
+ std::string namedPipe = R"(\\.\pipe\LOCAL\CMakeDebuggerPipe2_)" +
+ cmCryptoHash(cmCryptoHash::AlgoSHA256)
+ .HashString(cmSystemTools::GetCurrentWorkingDirectory());
+#else
+ std::string namedPipe = "CMakeDebuggerPipe2";
+#endif
+
+ std::unique_ptr<dap::Session> client = dap::Session::create();
+ client->registerHandler([&](const dap::InitializedEvent& e) {
+ (void)e;
+ initializedEventReceivedPromise.set_value(true);
+ });
+ client->registerHandler([&](const dap::ExitedEvent& e) {
+ (void)e;
+ exitedEventReceivedPromise.set_value(true);
+ });
+ client->registerHandler([&](const dap::TerminatedEvent& e) {
+ (void)e;
+ terminatedEventReceivedPromise.set_value(true);
+ });
+ client->registerHandler([&](const dap::ThreadEvent& e) {
+ if (e.reason == "started") {
+ threadStartedPromise.set_value(true);
+ } else if (e.reason == "exited") {
+ threadExitedPromise.set_value(true);
+ }
+ });
+
+ ScopedThread debuggerThread([&]() -> int {
+ try {
+ auto connection =
+ std::make_shared<cmDebugger::cmDebuggerPipeConnection>(namedPipe);
+ startedListeningFuture = connection->StartedListening.get_future();
+ debuggerConnectionCreatedPromise.set_value();
+ std::shared_ptr<cmDebugger::cmDebuggerAdapter> debuggerAdapter =
+ std::make_shared<cmDebugger::cmDebuggerAdapter>(
+ connection, dap::file(stdout, false));
+
+ debuggerAdapterInitializedPromise.set_value(true);
+ debuggerAdapter->ReportExitCode(0);
+
+ // Ensure the disconnectResponse has been received before
+ // destructing debuggerAdapter.
+ ASSERT_TRUE(disconnectResponseReceivedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+ return 0;
+ } catch (const std::runtime_error& error) {
+ std::cerr << "Error: Failed to create debugger adapter.\n";
+ std::cerr << error.what() << "\n";
+ return -1;
+ }
+ });
+
+ ASSERT_TRUE(debuggerConnectionCreatedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+ ASSERT_TRUE(startedListeningFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+
+ auto client2Debugger =
+ std::make_shared<cmDebugger::cmDebuggerPipeClient>(namedPipe);
+ client2Debugger->Start();
+ client2Debugger->WaitForConnection();
+ client->bind(client2Debugger, client2Debugger);
+
+ dap::CMakeInitializeRequest initializeRequest;
+ auto response = client->send(initializeRequest);
+ auto initializeResponse = response.get();
+ ASSERT_TRUE(!initializeResponse.error);
+ ASSERT_TRUE(initializeResponse.response.cmakeVersion.full == CMake_VERSION);
+ ASSERT_TRUE(initializeResponse.response.cmakeVersion.major ==
+ CMake_VERSION_MAJOR);
+ ASSERT_TRUE(initializeResponse.response.cmakeVersion.minor ==
+ CMake_VERSION_MINOR);
+ ASSERT_TRUE(initializeResponse.response.cmakeVersion.patch ==
+ CMake_VERSION_PATCH);
+ ASSERT_TRUE(initializeResponse.response.supportsExceptionInfoRequest);
+ ASSERT_TRUE(
+ initializeResponse.response.exceptionBreakpointFilters.has_value());
+ dap::LaunchRequest launchRequest;
+ auto launchResponse = client->send(launchRequest).get();
+ ASSERT_TRUE(!launchResponse.error);
+
+ dap::ConfigurationDoneRequest configurationDoneRequest;
+ auto configurationDoneResponse =
+ client->send(configurationDoneRequest).get();
+ ASSERT_TRUE(!configurationDoneResponse.error);
+
+ ASSERT_TRUE(debuggerAdapterInitializedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+ ASSERT_TRUE(initializedEventReceivedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+ ASSERT_TRUE(terminatedEventReceivedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+ ASSERT_TRUE(threadStartedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+ ASSERT_TRUE(threadExitedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+ ASSERT_TRUE(exitedEventReceivedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+
+ dap::DisconnectRequest disconnectRequest;
+ auto disconnectResponse = client->send(disconnectRequest).get();
+ disconnectResponseReceivedPromise.set_value(true);
+ ASSERT_TRUE(!disconnectResponse.error);
+
+ return true;
+}
+
+int testDebuggerAdapterPipe(int, char*[])
+{
+ return runTests(std::vector<std::function<bool()>>{
+ testProtocolWithPipes,
+ });
+}
diff --git a/Tests/CMakeLib/testDebuggerBreakpointManager.cxx b/Tests/CMakeLib/testDebuggerBreakpointManager.cxx
new file mode 100644
index 0000000..83734ea
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerBreakpointManager.cxx
@@ -0,0 +1,172 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include <atomic>
+#include <chrono>
+#include <functional>
+#include <future>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <cm3p/cppdap/future.h>
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/session.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerBreakpointManager.h"
+#include "cmDebuggerSourceBreakpoint.h" // IWYU pragma: keep
+#include "cmListFileCache.h"
+
+#include "testCommon.h"
+#include "testDebugger.h"
+
+static bool testHandleBreakpointRequestBeforeFileIsLoaded()
+{
+ // Arrange
+ DebuggerTestHelper helper;
+ cmDebugger::cmDebuggerBreakpointManager breakpointManager(
+ helper.Debugger.get());
+ helper.bind();
+ dap::SetBreakpointsRequest setBreakpointRequest;
+ std::string sourcePath = "C:/CMakeLists.txt";
+ setBreakpointRequest.source.path = sourcePath;
+ dap::array<dap::SourceBreakpoint> sourceBreakpoints(3);
+ sourceBreakpoints[0].line = 1;
+ sourceBreakpoints[1].line = 2;
+ sourceBreakpoints[2].line = 3;
+ setBreakpointRequest.breakpoints = sourceBreakpoints;
+
+ // Act
+ auto got = helper.Client->send(setBreakpointRequest).get();
+
+ // Assert
+ auto& response = got.response;
+ ASSERT_TRUE(!got.error);
+ ASSERT_TRUE(response.breakpoints.size() == sourceBreakpoints.size());
+ ASSERT_BREAKPOINT(response.breakpoints[0], 0, sourceBreakpoints[0].line,
+ sourcePath, false);
+ ASSERT_BREAKPOINT(response.breakpoints[1], 1, sourceBreakpoints[1].line,
+ sourcePath, false);
+ ASSERT_BREAKPOINT(response.breakpoints[2], 2, sourceBreakpoints[2].line,
+ sourcePath, false);
+ return true;
+}
+
+static bool testHandleBreakpointRequestAfterFileIsLoaded()
+{
+ // Arrange
+ DebuggerTestHelper helper;
+ std::atomic<bool> notExpectBreakpointEvents(true);
+ helper.Client->registerHandler([&](const dap::BreakpointEvent&) {
+ notExpectBreakpointEvents.store(false);
+ });
+
+ cmDebugger::cmDebuggerBreakpointManager breakpointManager(
+ helper.Debugger.get());
+ helper.bind();
+ std::string sourcePath = "C:/CMakeLists.txt";
+ std::vector<cmListFileFunction> functions = helper.CreateListFileFunctions(
+ "# Comment1\nset(var1 foo)\n# Comment2\nset(var2\nbar)\n",
+ sourcePath.c_str());
+
+ breakpointManager.SourceFileLoaded(sourcePath, functions);
+ dap::SetBreakpointsRequest setBreakpointRequest;
+ setBreakpointRequest.source.path = sourcePath;
+ dap::array<dap::SourceBreakpoint> sourceBreakpoints(5);
+ sourceBreakpoints[0].line = 1;
+ sourceBreakpoints[1].line = 2;
+ sourceBreakpoints[2].line = 3;
+ sourceBreakpoints[3].line = 4;
+ sourceBreakpoints[4].line = 5;
+ setBreakpointRequest.breakpoints = sourceBreakpoints;
+
+ // Act
+ auto got = helper.Client->send(setBreakpointRequest).get();
+
+ // Assert
+ auto& response = got.response;
+ ASSERT_TRUE(!got.error);
+ ASSERT_TRUE(response.breakpoints.size() == sourceBreakpoints.size());
+ // Line 1 is a comment. Move it to next valid function, which is line 2.
+ ASSERT_BREAKPOINT(response.breakpoints[0], 0, 2, sourcePath, true);
+ ASSERT_BREAKPOINT(response.breakpoints[1], 1, sourceBreakpoints[1].line,
+ sourcePath, true);
+ // Line 3 is a comment. Move it to next valid function, which is line 4.
+ ASSERT_BREAKPOINT(response.breakpoints[2], 2, 4, sourcePath, true);
+ ASSERT_BREAKPOINT(response.breakpoints[3], 3, sourceBreakpoints[3].line,
+ sourcePath, true);
+ // Line 5 is the 2nd part of line 4 function. No valid function after line 5,
+ // show the breakpoint at line 4.
+ ASSERT_BREAKPOINT(response.breakpoints[4], 4, sourceBreakpoints[3].line,
+ sourcePath, true);
+
+ ASSERT_TRUE(notExpectBreakpointEvents.load());
+
+ return true;
+}
+
+static bool testSourceFileLoadedAfterHandleBreakpointRequest()
+{
+ // Arrange
+ DebuggerTestHelper helper;
+ std::vector<dap::BreakpointEvent> breakpointEvents;
+ std::atomic<int> remainingBreakpointEvents(5);
+ std::promise<void> allBreakpointEventsReceivedPromise;
+ std::future<void> allBreakpointEventsReceivedFuture =
+ allBreakpointEventsReceivedPromise.get_future();
+ helper.Client->registerHandler([&](const dap::BreakpointEvent& event) {
+ breakpointEvents.emplace_back(event);
+ if (--remainingBreakpointEvents == 0) {
+ allBreakpointEventsReceivedPromise.set_value();
+ }
+ });
+ cmDebugger::cmDebuggerBreakpointManager breakpointManager(
+ helper.Debugger.get());
+ helper.bind();
+ dap::SetBreakpointsRequest setBreakpointRequest;
+ std::string sourcePath = "C:/CMakeLists.txt";
+ setBreakpointRequest.source.path = sourcePath;
+ dap::array<dap::SourceBreakpoint> sourceBreakpoints(5);
+ sourceBreakpoints[0].line = 1;
+ sourceBreakpoints[1].line = 2;
+ sourceBreakpoints[2].line = 3;
+ sourceBreakpoints[3].line = 4;
+ sourceBreakpoints[4].line = 5;
+ setBreakpointRequest.breakpoints = sourceBreakpoints;
+ std::vector<cmListFileFunction> functions = helper.CreateListFileFunctions(
+ "# Comment1\nset(var1 foo)\n# Comment2\nset(var2\nbar)\n",
+ sourcePath.c_str());
+ auto got = helper.Client->send(setBreakpointRequest).get();
+
+ // Act
+ breakpointManager.SourceFileLoaded(sourcePath, functions);
+ ASSERT_TRUE(allBreakpointEventsReceivedFuture.wait_for(
+ std::chrono::seconds(10)) == std::future_status::ready);
+
+ // Assert
+ ASSERT_TRUE(breakpointEvents.size() > 0);
+ // Line 1 is a comment. Move it to next valid function, which is line 2.
+ ASSERT_BREAKPOINT(breakpointEvents[0].breakpoint, 0, 2, sourcePath, true);
+ ASSERT_BREAKPOINT(breakpointEvents[1].breakpoint, 1,
+ sourceBreakpoints[1].line, sourcePath, true);
+ // Line 3 is a comment. Move it to next valid function, which is line 4.
+ ASSERT_BREAKPOINT(breakpointEvents[2].breakpoint, 2, 4, sourcePath, true);
+ ASSERT_BREAKPOINT(breakpointEvents[3].breakpoint, 3,
+ sourceBreakpoints[3].line, sourcePath, true);
+ // Line 5 is the 2nd part of line 4 function. No valid function after line 5,
+ // show the breakpoint at line 4.
+ ASSERT_BREAKPOINT(breakpointEvents[4].breakpoint, 4,
+ sourceBreakpoints[3].line, sourcePath, true);
+ return true;
+}
+
+int testDebuggerBreakpointManager(int, char*[])
+{
+ return runTests(std::vector<std::function<bool()>>{
+ testHandleBreakpointRequestBeforeFileIsLoaded,
+ testHandleBreakpointRequestAfterFileIsLoaded,
+ testSourceFileLoadedAfterHandleBreakpointRequest,
+ });
+}
diff --git a/Tests/CMakeLib/testDebuggerNamedPipe.cxx b/Tests/CMakeLib/testDebuggerNamedPipe.cxx
new file mode 100644
index 0000000..d2b0728
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerNamedPipe.cxx
@@ -0,0 +1,218 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include <chrono>
+#include <cstdio>
+#include <exception>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <cm3p/cppdap/io.h>
+
+#include "cmsys/RegularExpression.hxx"
+
+#include "cmDebuggerPipeConnection.h"
+#include "cmSystemTools.h"
+
+#ifdef _WIN32
+# include "cmCryptoHash.h"
+#endif
+
+static void sendCommands(std::shared_ptr<dap::ReaderWriter> const& debugger,
+ int delayMs,
+ std::vector<std::string> const& initCommands)
+{
+ for (const auto& command : initCommands) {
+ std::string contentLength = "Content-Length:";
+ contentLength += std::to_string(command.size()) + "\r\n\r\n";
+ debugger->write(contentLength.c_str(), contentLength.size());
+ if (!debugger->write(command.c_str(), command.size())) {
+ std::cout << "debugger write error" << std::endl;
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(delayMs));
+ }
+}
+
+/** \brief Test CMake debugger named pipe.
+ *
+ * Test CMake debugger named pipe by
+ * 1. Create a named pipe for DAP traffic between the client and the debugger.
+ * 2. Create a client thread to wait for the debugger connection.
+ * - Once the debugger is connected, send the minimum required commands to
+ * get debugger going.
+ * - Wait for the CMake to complete the cache generation
+ * - Send the disconnect command.
+ * - Read and store the debugger's responses for validation.
+ * 3. Run the CMake command with debugger on and wait for it to complete.
+ * 4. Validate the response to ensure we are getting the expected responses.
+ *
+ */
+int runTest(int argc, char* argv[])
+{
+ if (argc < 3) {
+ std::cout << "Usage:\n";
+ std::cout << "\t(project mode) TestDebuggerNamedPipe <CMakePath> "
+ "<SourceFolder> <OutputFolder>\n";
+ std::cout << "\t(script mode) TestDebuggerNamedPipe <CMakePath> "
+ "<ScriptPath>\n";
+ return 1;
+ }
+
+ bool scriptMode = argc == 3;
+
+#ifdef _WIN32
+ std::string namedPipe = R"(\\.\pipe\LOCAL\CMakeDebuggerPipe_)" +
+ cmCryptoHash(cmCryptoHash::AlgoSHA256)
+ .HashString(scriptMode ? argv[2] : argv[3]);
+#else
+ std::string namedPipe =
+ std::string("CMakeDebuggerPipe") + (scriptMode ? "Script" : "Project");
+#endif
+
+ std::vector<std::string> cmakeCommand;
+ cmakeCommand.emplace_back(argv[1]);
+ cmakeCommand.emplace_back("--debugger");
+ cmakeCommand.emplace_back("--debugger-pipe");
+ cmakeCommand.emplace_back(namedPipe);
+
+ if (scriptMode) {
+ cmakeCommand.emplace_back("-P");
+ cmakeCommand.emplace_back(argv[2]);
+ } else {
+ cmakeCommand.emplace_back("-S");
+ cmakeCommand.emplace_back(argv[2]);
+ cmakeCommand.emplace_back("-B");
+ cmakeCommand.emplace_back(argv[3]);
+ }
+
+ // Capture debugger response stream.
+ std::stringstream debuggerResponseStream;
+
+ // Start the debugger client process.
+ std::thread clientThread([&]() {
+ // Poll until the pipe server is running. Clients can also look for a magic
+ // string in the CMake output, but this is easier for the test case.
+ std::shared_ptr<cmDebugger::cmDebuggerPipeClient> client;
+ int attempt = 0;
+ do {
+ attempt++;
+ try {
+ client = std::make_shared<cmDebugger::cmDebuggerPipeClient>(namedPipe);
+ client->Start();
+ client->WaitForConnection();
+ std::cout << "cmDebuggerPipeClient connected.\n";
+ break;
+ } catch (std::runtime_error&) {
+ std::cout << "Failed attempt " << attempt
+ << " to connect to pipe server. Retrying.\n";
+ client.reset();
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
+ }
+ } while (attempt < 50); // 10 seconds
+
+ if (attempt >= 50) {
+ return -1;
+ }
+
+ // Send init commands to get debugger going.
+ sendCommands(
+ client, 400,
+ { "{\"arguments\":{\"adapterID\":\"\"},\"command\":\"initialize\","
+ "\"seq\":"
+ "1,\"type\":\"request\"}",
+ "{\"arguments\":{},\"command\":\"launch\",\"seq\":2,\"type\":"
+ "\"request\"}",
+ "{\"arguments\":{},\"command\":\"configurationDone\",\"seq\":3,"
+ "\"type\":"
+ "\"request\"}" });
+
+ // Look for "exitCode" as a sign that configuration has completed and
+ // it's now safe to disconnect.
+ for (;;) {
+ char buffer[1];
+ size_t result = client->read(buffer, 1);
+ if (result != 1) {
+ std::cout << "debugger read error: " << result << std::endl;
+ break;
+ }
+ debuggerResponseStream << buffer[0];
+ if (debuggerResponseStream.str().find("exitCode") != std::string::npos) {
+ break;
+ }
+ }
+
+ // Send disconnect command.
+ sendCommands(
+ client, 200,
+ { "{\"arguments\":{},\"command\":\"disconnect\",\"seq\":4,\"type\":"
+ "\"request\"}" });
+
+ // Read any remaining debugger responses.
+ for (;;) {
+ char buffer[1];
+ size_t result = client->read(buffer, 1);
+ if (result != 1) {
+ std::cout << "debugger read error: " << result << std::endl;
+ break;
+ }
+ debuggerResponseStream << buffer[0];
+ }
+
+ client->close();
+
+ return 0;
+ });
+
+ if (!cmSystemTools::RunSingleCommand(cmakeCommand, nullptr, nullptr, nullptr,
+ nullptr, cmSystemTools::OUTPUT_MERGE)) {
+ std::cout << "Error running command" << std::endl;
+ return -1;
+ }
+
+ clientThread.join();
+
+ auto debuggerResponse = debuggerResponseStream.str();
+
+ std::vector<std::string> expectedResponses = {
+ R"("event" : "initialized".*"type" : "event")",
+ R"("command" : "launch".*"success" : true.*"type" : "response")",
+ R"("command" : "configurationDone".*"success" : true.*"type" : "response")",
+ R"("reason" : "started".*"threadId" : 1.*"event" : "thread".*"type" : "event")",
+ R"("reason" : "exited".*"threadId" : 1.*"event" : "thread".*"type" : "event")",
+ R"("exitCode" : 0.*"event" : "exited".*"type" : "event")",
+ R"("command" : "disconnect".*"success" : true.*"type" : "response")"
+ };
+
+ for (auto& regexString : expectedResponses) {
+ cmsys::RegularExpression regex(regexString);
+ if (!regex.find(debuggerResponse)) {
+ std::cout << "Expected response not found: " << regexString << std::endl;
+ std::cout << debuggerResponse << std::endl;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char* argv[])
+{
+ try {
+ return runTest(argc, argv);
+ } catch (const std::exception& ex) {
+ std::cout << "An exception occurred: " << ex.what() << std::endl;
+ return -1;
+ } catch (const std::string& ex) {
+ std::cout << "An exception occurred: " << ex << std::endl;
+ return -1;
+ } catch (...) {
+ std::cout << "An unknown exception occurred" << std::endl;
+ return -1;
+ }
+}
diff --git a/Tests/CMakeLib/testDebuggerVariables.cxx b/Tests/CMakeLib/testDebuggerVariables.cxx
new file mode 100644
index 0000000..6c19baa
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerVariables.cxx
@@ -0,0 +1,185 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerVariables.h"
+#include "cmDebuggerVariablesManager.h"
+
+#include "testCommon.h"
+#include "testDebugger.h"
+
+static dap::VariablesRequest CreateVariablesRequest(int64_t reference)
+{
+ dap::VariablesRequest variableRequest;
+ variableRequest.variablesReference = reference;
+ return variableRequest;
+}
+
+static bool testUniqueIds()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ std::unordered_set<int64_t> variableIds;
+ bool noDuplicateIds = true;
+ for (int i = 0; i < 10000 && noDuplicateIds; ++i) {
+ auto variable =
+ cmDebugger::cmDebuggerVariables(variablesManager, "Locals", true, []() {
+ return std::vector<cmDebugger::cmDebuggerVariableEntry>();
+ });
+
+ if (variableIds.find(variable.GetId()) != variableIds.end()) {
+ noDuplicateIds = false;
+ }
+ variableIds.insert(variable.GetId());
+ }
+
+ ASSERT_TRUE(noDuplicateIds);
+
+ return true;
+}
+
+static bool testConstructors()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ auto parent = std::make_shared<cmDebugger::cmDebuggerVariables>(
+ variablesManager, "Parent", true, [=]() {
+ return std::vector<cmDebugger::cmDebuggerVariableEntry>{
+ { "ParentKey", "ParentValue", "ParentType" }
+ };
+ });
+
+ auto children1 = std::make_shared<cmDebugger::cmDebuggerVariables>(
+ variablesManager, "Children1", true, [=]() {
+ return std::vector<cmDebugger::cmDebuggerVariableEntry>{
+ { "ChildKey1", "ChildValue1", "ChildType1" },
+ { "ChildKey2", "ChildValue2", "ChildType2" }
+ };
+ });
+
+ parent->AddSubVariables(children1);
+
+ auto children2 = std::make_shared<cmDebugger::cmDebuggerVariables>(
+ variablesManager, "Children2", true);
+
+ auto grandChildren21 = std::make_shared<cmDebugger::cmDebuggerVariables>(
+ variablesManager, "GrandChildren21", true);
+ grandChildren21->SetValue("GrandChildren21 Value");
+ children2->AddSubVariables(grandChildren21);
+ parent->AddSubVariables(children2);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(parent->GetId()));
+ ASSERT_TRUE(variables.size() == 3);
+ ASSERT_VARIABLE_REFERENCE(variables[0], "Children1", "", "collection",
+ children1->GetId());
+ ASSERT_VARIABLE_REFERENCE(variables[1], "Children2", "", "collection",
+ children2->GetId());
+ ASSERT_VARIABLE(variables[2], "ParentKey", "ParentValue", "ParentType");
+
+ variables = variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(children1->GetId()));
+ ASSERT_TRUE(variables.size() == 2);
+ ASSERT_VARIABLE(variables[0], "ChildKey1", "ChildValue1", "ChildType1");
+ ASSERT_VARIABLE(variables[1], "ChildKey2", "ChildValue2", "ChildType2");
+
+ variables = variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(children2->GetId()));
+ ASSERT_TRUE(variables.size() == 1);
+ ASSERT_VARIABLE_REFERENCE(variables[0], "GrandChildren21",
+ "GrandChildren21 Value", "collection",
+ grandChildren21->GetId());
+
+ return true;
+}
+
+static bool testIgnoreEmptyStringEntries()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ auto vars = std::make_shared<cmDebugger::cmDebuggerVariables>(
+ variablesManager, "Variables", true, []() {
+ return std::vector<cmDebugger::cmDebuggerVariableEntry>{
+ { "IntValue1", 5 }, { "StringValue1", "" },
+ { "StringValue2", "foo" }, { "StringValue3", "" },
+ { "StringValue4", "bar" }, { "StringValue5", "" },
+ { "IntValue2", int64_t(99) }, { "BooleanTrue", true },
+ { "BooleanFalse", false },
+ };
+ });
+
+ vars->SetIgnoreEmptyStringEntries(true);
+ vars->SetEnableSorting(false);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+ ASSERT_TRUE(variables.size() == 6);
+ ASSERT_VARIABLE(variables[0], "IntValue1", "5", "int");
+ ASSERT_VARIABLE(variables[1], "StringValue2", "foo", "string");
+ ASSERT_VARIABLE(variables[2], "StringValue4", "bar", "string");
+ ASSERT_VARIABLE(variables[3], "IntValue2", "99", "int");
+ ASSERT_VARIABLE(variables[4], "BooleanTrue", "TRUE", "bool");
+ ASSERT_VARIABLE(variables[5], "BooleanFalse", "FALSE", "bool");
+
+ return true;
+}
+
+static bool testSortTheResult()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ auto vars = std::make_shared<cmDebugger::cmDebuggerVariables>(
+ variablesManager, "Variables", true, []() {
+ return std::vector<cmDebugger::cmDebuggerVariableEntry>{
+ { "4", "4" }, { "2", "2" }, { "1", "1" }, { "3", "3" }, { "5", "5" },
+ };
+ });
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+ ASSERT_TRUE(variables.size() == 5);
+ ASSERT_VARIABLE(variables[0], "1", "1", "string");
+ ASSERT_VARIABLE(variables[1], "2", "2", "string");
+ ASSERT_VARIABLE(variables[2], "3", "3", "string");
+ ASSERT_VARIABLE(variables[3], "4", "4", "string");
+ ASSERT_VARIABLE(variables[4], "5", "5", "string");
+
+ vars->SetEnableSorting(false);
+
+ variables = variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+ ASSERT_TRUE(variables.size() == 5);
+ ASSERT_VARIABLE(variables[0], "4", "4", "string");
+ ASSERT_VARIABLE(variables[1], "2", "2", "string");
+ ASSERT_VARIABLE(variables[2], "1", "1", "string");
+ ASSERT_VARIABLE(variables[3], "3", "3", "string");
+ ASSERT_VARIABLE(variables[4], "5", "5", "string");
+
+ return true;
+}
+
+int testDebuggerVariables(int, char*[])
+{
+ return runTests(std::vector<std::function<bool()>>{
+ testUniqueIds,
+ testConstructors,
+ testIgnoreEmptyStringEntries,
+ testSortTheResult,
+ });
+}
diff --git a/Tests/CMakeLib/testDebuggerVariablesHelper.cxx b/Tests/CMakeLib/testDebuggerVariablesHelper.cxx
new file mode 100644
index 0000000..e0bbdf0
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerVariablesHelper.cxx
@@ -0,0 +1,587 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include <functional>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/types.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "cmDebuggerStackFrame.h"
+#include "cmDebuggerVariables.h"
+#include "cmDebuggerVariablesHelper.h"
+#include "cmDebuggerVariablesManager.h"
+#include "cmFileSet.h"
+#include "cmGlobalGenerator.h"
+#include "cmListFileCache.h"
+#include "cmMakefile.h"
+#include "cmPolicies.h"
+#include "cmPropertyMap.h"
+#include "cmState.h"
+#include "cmStateDirectory.h"
+#include "cmStateSnapshot.h"
+#include "cmStateTypes.h"
+#include "cmTarget.h"
+#include "cmTest.h"
+#include "cmake.h"
+
+#include "testCommon.h"
+#include "testDebugger.h"
+
+static dap::VariablesRequest CreateVariablesRequest(int64_t reference)
+{
+ dap::VariablesRequest variableRequest;
+ variableRequest.variablesReference = reference;
+ return variableRequest;
+}
+
+struct Dummies
+{
+ std::shared_ptr<cmake> CMake;
+ std::shared_ptr<cmMakefile> Makefile;
+ std::shared_ptr<cmGlobalGenerator> GlobalGenerator;
+};
+
+static Dummies CreateDummies(
+ std::string targetName,
+ std::string currentSourceDirectory = "c:/CurrentSourceDirectory",
+ std::string currentBinaryDirectory = "c:/CurrentBinaryDirectory")
+{
+ Dummies dummies;
+ dummies.CMake =
+ std::make_shared<cmake>(cmake::RoleProject, cmState::Project);
+ cmState* state = dummies.CMake->GetState();
+ dummies.GlobalGenerator =
+ std::make_shared<cmGlobalGenerator>(dummies.CMake.get());
+ cmStateSnapshot snapshot = state->CreateBaseSnapshot();
+ snapshot.GetDirectory().SetCurrentSource(currentSourceDirectory);
+ snapshot.GetDirectory().SetCurrentBinary(currentBinaryDirectory);
+ dummies.Makefile =
+ std::make_shared<cmMakefile>(dummies.GlobalGenerator.get(), snapshot);
+ dummies.Makefile->CreateNewTarget(targetName, cmStateEnums::EXECUTABLE);
+ return dummies;
+}
+
+static bool testCreateFromPolicyMap()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ cmPolicies::PolicyMap policyMap;
+ policyMap.Set(cmPolicies::CMP0000, cmPolicies::NEW);
+ policyMap.Set(cmPolicies::CMP0003, cmPolicies::WARN);
+ policyMap.Set(cmPolicies::CMP0005, cmPolicies::OLD);
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::Create(
+ variablesManager, "Locals", true, policyMap);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+ ASSERT_TRUE(variables.size() == 3);
+ ASSERT_VARIABLE(variables[0], "CMP0000", "NEW", "string");
+ ASSERT_VARIABLE(variables[1], "CMP0003", "WARN", "string");
+ ASSERT_VARIABLE(variables[2], "CMP0005", "OLD", "string");
+
+ return true;
+}
+
+static bool testCreateFromPairVector()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ std::vector<std::pair<std::string, std::string>> pairs = {
+ { "Foo1", "Bar1" }, { "Foo2", "Bar2" }
+ };
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, pairs);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(vars->GetValue() == std::to_string(pairs.size()));
+ ASSERT_TRUE(variables.size() == 2);
+ ASSERT_VARIABLE(variables[0], "Foo1", "Bar1", "string");
+ ASSERT_VARIABLE(variables[1], "Foo2", "Bar2", "string");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true,
+ std::vector<std::pair<std::string, std::string>>());
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromSet()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ std::set<std::string> set = { "Foo", "Bar" };
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, set);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(vars->GetValue() == std::to_string(set.size()));
+ ASSERT_TRUE(variables.size() == 2);
+ ASSERT_VARIABLE(variables[0], "[0]", "Bar", "string");
+ ASSERT_VARIABLE(variables[1], "[1]", "Foo", "string");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, std::set<std::string>());
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromStringVector()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ std::vector<std::string> list = { "Foo", "Bar" };
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, list);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(vars->GetValue() == std::to_string(list.size()));
+ ASSERT_TRUE(variables.size() == 2);
+ ASSERT_VARIABLE(variables[0], "[0]", "Foo", "string");
+ ASSERT_VARIABLE(variables[1], "[1]", "Bar", "string");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, std::vector<std::string>());
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromTarget()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ auto dummies = CreateDummies("Foo");
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, dummies.Makefile->GetOrderedTargets());
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(variables.size() == 1);
+ ASSERT_VARIABLE(variables[0], "Foo", "EXECUTABLE", "collection");
+
+ variables = variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(variables[0].variablesReference));
+
+ ASSERT_TRUE(variables.size() == 15);
+ ASSERT_VARIABLE(variables[0], "GlobalGenerator", "Generic", "collection");
+ ASSERT_VARIABLE(variables[1], "IsAIX", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[2], "IsAndroidGuiExecutable", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[3], "IsAppBundleOnApple", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[4], "IsDLLPlatform", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[5], "IsExecutableWithExports", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[6], "IsFrameworkOnApple", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[7], "IsImported", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[8], "IsImportedGloballyVisible", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[9], "IsPerConfig", "TRUE", "bool");
+ ASSERT_VARIABLE(variables[10], "Makefile",
+ dummies.Makefile->GetDirectoryId().String, "collection");
+ ASSERT_VARIABLE(variables[11], "Name", "Foo", "string");
+ ASSERT_VARIABLE(variables[12], "PolicyMap", "", "collection");
+ ASSERT_VARIABLE(variables[13], "Properties",
+ std::to_string(dummies.Makefile->GetOrderedTargets()[0]
+ ->GetProperties()
+ .GetList()
+ .size()),
+ "collection");
+ ASSERT_VARIABLE(variables[14], "Type", "EXECUTABLE", "string");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, std::vector<cmTarget*>());
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromGlobalGenerator()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ auto dummies = CreateDummies("Foo");
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, dummies.GlobalGenerator.get());
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(variables.size() == 10);
+ ASSERT_VARIABLE(variables[0], "AllTargetName", "ALL_BUILD", "string");
+ ASSERT_VARIABLE(variables[1], "ForceUnixPaths", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[2], "InstallTargetName", "INSTALL", "string");
+ ASSERT_VARIABLE(variables[3], "IsMultiConfig", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[4], "MakefileEncoding", "None", "string");
+ ASSERT_VARIABLE(variables[5], "Name", "Generic", "string");
+ ASSERT_VARIABLE(variables[6], "NeedSymbolicMark", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[7], "PackageTargetName", "PACKAGE", "string");
+ ASSERT_VARIABLE(variables[8], "TestTargetName", "RUN_TESTS", "string");
+ ASSERT_VARIABLE(variables[9], "UseLinkScript", "FALSE", "bool");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true,
+ static_cast<cmGlobalGenerator*>(nullptr));
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromTests()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ auto dummies = CreateDummies("Foo");
+ cmTest test1 = cmTest(dummies.Makefile.get());
+ test1.SetName("Test1");
+ test1.SetOldStyle(false);
+ test1.SetCommandExpandLists(true);
+ test1.SetCommand(std::vector<std::string>{ "Foo1", "arg1" });
+ test1.SetProperty("Prop1", "Prop1");
+ cmTest test2 = cmTest(dummies.Makefile.get());
+ test2.SetName("Test2");
+ test2.SetOldStyle(false);
+ test2.SetCommandExpandLists(false);
+ test2.SetCommand(std::vector<std::string>{ "Bar1", "arg1", "arg2" });
+ test2.SetProperty("Prop2", "Prop2");
+ test2.SetProperty("Prop3", "Prop3");
+
+ std::vector<cmTest*> tests = { &test1, &test2 };
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, tests);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(vars->GetValue() == std::to_string(tests.size()));
+ ASSERT_TRUE(variables.size() == 2);
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[0], test1.GetName(), "",
+ "collection");
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[1], test2.GetName(), "",
+ "collection");
+
+ dap::array<dap::Variable> testVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(variables[0].variablesReference));
+ ASSERT_TRUE(testVariables.size() == 5);
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(testVariables[0], "Command",
+ std::to_string(test1.GetCommand().size()),
+ "collection");
+ ASSERT_VARIABLE(testVariables[1], "CommandExpandLists",
+ BOOL_STRING(test1.GetCommandExpandLists()), "bool");
+ ASSERT_VARIABLE(testVariables[2], "Name", test1.GetName(), "string");
+ ASSERT_VARIABLE(testVariables[3], "OldStyle",
+ BOOL_STRING(test1.GetOldStyle()), "bool");
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(testVariables[4], "Properties", "1",
+ "collection");
+
+ dap::array<dap::Variable> commandVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(testVariables[0].variablesReference));
+ ASSERT_TRUE(commandVariables.size() == test1.GetCommand().size());
+ for (size_t i = 0; i < commandVariables.size(); ++i) {
+ ASSERT_VARIABLE(commandVariables[i], "[" + std::to_string(i) + "]",
+ test1.GetCommand()[i], "string");
+ }
+
+ dap::array<dap::Variable> propertiesVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(testVariables[4].variablesReference));
+ ASSERT_TRUE(propertiesVariables.size() == 1);
+ ASSERT_VARIABLE(propertiesVariables[0], "Prop1", "Prop1", "string");
+
+ testVariables = variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(variables[1].variablesReference));
+ ASSERT_TRUE(testVariables.size() == 5);
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(testVariables[0], "Command",
+ std::to_string(test2.GetCommand().size()),
+ "collection");
+ ASSERT_VARIABLE(testVariables[1], "CommandExpandLists",
+ BOOL_STRING(test2.GetCommandExpandLists()), "bool");
+ ASSERT_VARIABLE(testVariables[2], "Name", test2.GetName(), "string");
+ ASSERT_VARIABLE(testVariables[3], "OldStyle",
+ BOOL_STRING(test2.GetOldStyle()), "bool");
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(testVariables[4], "Properties", "2",
+ "collection");
+
+ commandVariables = variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(testVariables[0].variablesReference));
+ ASSERT_TRUE(commandVariables.size() == test2.GetCommand().size());
+ for (size_t i = 0; i < commandVariables.size(); ++i) {
+ ASSERT_VARIABLE(commandVariables[i], "[" + std::to_string(i) + "]",
+ test2.GetCommand()[i], "string");
+ }
+
+ propertiesVariables = variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(testVariables[4].variablesReference));
+ ASSERT_TRUE(propertiesVariables.size() == 2);
+ ASSERT_VARIABLE(propertiesVariables[0], "Prop2", "Prop2", "string");
+ ASSERT_VARIABLE(propertiesVariables[1], "Prop3", "Prop3", "string");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, std::vector<cmTest*>());
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromMakefile()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ auto dummies = CreateDummies("Foo");
+ auto snapshot = dummies.Makefile->GetStateSnapshot();
+ auto state = dummies.Makefile->GetState();
+ state->SetSourceDirectory("c:/HomeDirectory");
+ state->SetBinaryDirectory("c:/HomeOutputDirectory");
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, dummies.Makefile.get());
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(variables.size() == 12);
+ ASSERT_VARIABLE(variables[0], "AppleSDKType", "MacOS", "string");
+ ASSERT_VARIABLE(variables[1], "CurrentBinaryDirectory",
+ snapshot.GetDirectory().GetCurrentBinary(), "string");
+ ASSERT_VARIABLE(variables[2], "CurrentSourceDirectory",
+ snapshot.GetDirectory().GetCurrentSource(), "string");
+ ASSERT_VARIABLE(variables[3], "DefineFlags", " ", "string");
+ ASSERT_VARIABLE(variables[4], "DirectoryId",
+ dummies.Makefile->GetDirectoryId().String, "string");
+ ASSERT_VARIABLE(variables[5], "HomeDirectory", state->GetSourceDirectory(),
+ "string");
+ ASSERT_VARIABLE(variables[6], "HomeOutputDirectory",
+ state->GetBinaryDirectory(), "string");
+ ASSERT_VARIABLE(variables[7], "IsRootMakefile", "TRUE", "bool");
+ ASSERT_VARIABLE(variables[8], "PlatformIs32Bit", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[9], "PlatformIs64Bit", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[10], "PlatformIsAppleEmbedded", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[11], "PlatformIsx32", "FALSE", "bool");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, static_cast<cmMakefile*>(nullptr));
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromStackFrame()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+ auto dummies = CreateDummies("Foo");
+
+ cmListFileFunction lff = cmListFileFunction("set", 99, 99, {});
+ auto frame = std::make_shared<cmDebugger::cmDebuggerStackFrame>(
+ dummies.Makefile.get(), "c:/CMakeLists.txt", lff);
+
+ dummies.CMake->AddCacheEntry("CMAKE_BUILD_TYPE", "Debug", "Build Type",
+ cmStateEnums::CacheEntryType::STRING);
+
+ auto locals = cmDebugger::cmDebuggerVariablesHelper::Create(
+ variablesManager, "Locals", true, frame);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(locals->GetId()));
+
+ ASSERT_TRUE(variables.size() == 5);
+ ASSERT_VARIABLE(variables[0], "CacheVariables", "1", "collection");
+ ASSERT_VARIABLE(variables[1], "CurrentLine", std::to_string(lff.Line()),
+ "int");
+ ASSERT_VARIABLE(variables[2], "Directories", "2", "collection");
+ ASSERT_VARIABLE(variables[3], "Locals", "2", "collection");
+ ASSERT_VARIABLE(variables[4], "Targets", "1", "collection");
+
+ dap::array<dap::Variable> cacheVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(variables[0].variablesReference));
+ ASSERT_TRUE(cacheVariables.size() == 1);
+ ASSERT_VARIABLE(cacheVariables[0], "CMAKE_BUILD_TYPE:STRING", "Debug",
+ "collection");
+
+ dap::array<dap::Variable> directoriesVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(variables[2].variablesReference));
+ ASSERT_TRUE(directoriesVariables.size() == 2);
+ ASSERT_VARIABLE(
+ directoriesVariables[0], "CMAKE_CURRENT_BINARY_DIR",
+ dummies.Makefile->GetStateSnapshot().GetDirectory().GetCurrentBinary(),
+ "string");
+ ASSERT_VARIABLE(
+ directoriesVariables[1], "CMAKE_CURRENT_SOURCE_DIR",
+ dummies.Makefile->GetStateSnapshot().GetDirectory().GetCurrentSource(),
+ "string");
+
+ dap::array<dap::Variable> propertiesVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(cacheVariables[0].variablesReference));
+ ASSERT_TRUE(propertiesVariables.size() == 3);
+ ASSERT_VARIABLE(propertiesVariables[0], "HELPSTRING", "Build Type",
+ "string");
+ ASSERT_VARIABLE(propertiesVariables[1], "TYPE", "STRING", "string");
+ ASSERT_VARIABLE(propertiesVariables[2], "VALUE", "Debug", "string");
+
+ return true;
+}
+
+static bool testCreateFromBTStringVector()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ std::vector<BT<std::string>> list(2);
+ list[0].Value = "Foo";
+ list[1].Value = "Bar";
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, list);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(vars->GetValue() == std::to_string(list.size()));
+ ASSERT_TRUE(variables.size() == 2);
+ ASSERT_VARIABLE(variables[0], "[0]", "Foo", "string");
+ ASSERT_VARIABLE(variables[1], "[1]", "Bar", "string");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, std::vector<std::string>());
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromFileSet()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ cmake cm(cmake::RoleScript, cmState::Unknown);
+ cmFileSet fileSet(cm, "Foo", "HEADERS", cmFileSetVisibility::Public);
+ BT<std::string> directory;
+ directory.Value = "c:/";
+ fileSet.AddDirectoryEntry(directory);
+ BT<std::string> file;
+ file.Value = "c:/foo.cxx";
+ fileSet.AddFileEntry(file);
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, &fileSet);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(variables.size() == 5);
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[0], "Directories", "1",
+ "collection");
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[1], "Files", "1", "collection");
+ ASSERT_VARIABLE(variables[2], "Name", "Foo", "string");
+ ASSERT_VARIABLE(variables[3], "Type", "HEADERS", "string");
+ ASSERT_VARIABLE(variables[4], "Visibility", "Public", "string");
+
+ dap::array<dap::Variable> directoriesVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(variables[0].variablesReference));
+ ASSERT_TRUE(directoriesVariables.size() == 1);
+ ASSERT_VARIABLE(directoriesVariables[0], "[0]", directory.Value, "string");
+
+ dap::array<dap::Variable> filesVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(variables[1].variablesReference));
+ ASSERT_TRUE(filesVariables.size() == 1);
+ ASSERT_VARIABLE(filesVariables[0], "[0]", file.Value, "string");
+
+ return true;
+}
+
+static bool testCreateFromFileSets()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ cmake cm(cmake::RoleScript, cmState::Unknown);
+ cmFileSet fileSet(cm, "Foo", "HEADERS", cmFileSetVisibility::Public);
+ BT<std::string> directory;
+ directory.Value = "c:/";
+ fileSet.AddDirectoryEntry(directory);
+ BT<std::string> file;
+ file.Value = "c:/foo.cxx";
+ fileSet.AddFileEntry(file);
+
+ auto fileSets = std::vector<cmFileSet*>{ &fileSet };
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, fileSets);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(variables.size() == 1);
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[0], "Foo", "", "collection");
+
+ return true;
+}
+
+int testDebuggerVariablesHelper(int, char*[])
+{
+ return runTests(std::vector<std::function<bool()>>{
+ testCreateFromPolicyMap,
+ testCreateFromPairVector,
+ testCreateFromSet,
+ testCreateFromStringVector,
+ testCreateFromTarget,
+ testCreateFromGlobalGenerator,
+ testCreateFromMakefile,
+ testCreateFromStackFrame,
+ testCreateFromTests,
+ testCreateFromBTStringVector,
+ testCreateFromFileSet,
+ testCreateFromFileSets,
+ });
+}
diff --git a/Tests/CMakeLib/testDebuggerVariablesManager.cxx b/Tests/CMakeLib/testDebuggerVariablesManager.cxx
new file mode 100644
index 0000000..3013b9f
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerVariablesManager.cxx
@@ -0,0 +1,50 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/types.h>
+#include <stdint.h>
+
+#include "cmDebuggerVariables.h"
+#include "cmDebuggerVariablesManager.h"
+
+#include "testCommon.h"
+
+static bool testVariablesRegistration()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ int64_t line = 5;
+ auto local = std::make_shared<cmDebugger::cmDebuggerVariables>(
+ variablesManager, "Local", true, [=]() {
+ return std::vector<cmDebugger::cmDebuggerVariableEntry>{ { "CurrentLine",
+ line } };
+ });
+
+ dap::VariablesRequest variableRequest;
+ variableRequest.variablesReference = local->GetId();
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(variableRequest);
+
+ ASSERT_TRUE(variables.size() == 1);
+
+ local.reset();
+
+ variables = variablesManager->HandleVariablesRequest(variableRequest);
+ ASSERT_TRUE(variables.size() == 0);
+
+ return true;
+}
+
+int testDebuggerVariablesManager(int, char*[])
+{
+ return runTests(std::vector<std::function<bool()>>{
+ testVariablesRegistration,
+ });
+}
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-result.txt b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-stderr.txt
new file mode 100644
index 0000000..6269c19
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: No file specified for --debugger-dap-log
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog.cmake b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-result.txt b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-stderr.txt
new file mode 100644
index 0000000..947cb00
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: No path specified for --debugger-pipe
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe.cmake b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CommandLine/DebuggerCapabilityInspect-check.cmake b/Tests/RunCMake/CommandLine/DebuggerCapabilityInspect-check.cmake
new file mode 100644
index 0000000..75769f2
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerCapabilityInspect-check.cmake
@@ -0,0 +1,5 @@
+if(actual_stdout MATCHES [["debugger" *: *true]])
+ set_property(DIRECTORY PROPERTY CMake_ENABLE_DEBUGGER 1)
+else()
+ set_property(DIRECTORY PROPERTY CMake_ENABLE_DEBUGGER 0)
+endif()
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupported-result.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupported-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupported-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupported-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupported-stderr.txt
new file mode 100644
index 0000000..5845bb3
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupported-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: CMake was not built with support for --debugger
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupported.cmake b/Tests/RunCMake/CommandLine/DebuggerNotSupported.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupported.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-result.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-stderr.txt
new file mode 100644
index 0000000..84c2200
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: CMake was not built with support for --debugger-dap-log
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog.cmake b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-result.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-stderr.txt
new file mode 100644
index 0000000..5684f4c
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: CMake was not built with support for --debugger-pipe
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe.cmake b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt b/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt
index e2f63cd..c01f414 100644
--- a/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt
+++ b/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt
@@ -1 +1 @@
-^{"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":6}]},{"kind":"configureLog","version":\[{"major":1,"minor":0}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":0}]},{"kind":"toolchains","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"tls":(true|false),"version":{.*}}$
+^{"debugger":(true|false),"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":6}]},{"kind":"configureLog","version":\[{"major":1,"minor":0}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":0}]},{"kind":"toolchains","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"tls":(true|false),"version":{.*}}$
diff --git a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
index 205949b..45b4c0e 100644
--- a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
@@ -125,6 +125,17 @@ run_cmake_command(cache-bad-entry
run_cmake_command(cache-empty-entry
${CMAKE_COMMAND} --build ${RunCMake_SOURCE_DIR}/cache-empty-entry/)
+run_cmake_command(DebuggerCapabilityInspect ${CMAKE_COMMAND} -E capabilities)
+get_property(CMake_ENABLE_DEBUGGER DIRECTORY PROPERTY CMake_ENABLE_DEBUGGER)
+if(CMake_ENABLE_DEBUGGER)
+ run_cmake_with_options(DebuggerArgMissingPipe --debugger-pipe)
+ run_cmake_with_options(DebuggerArgMissingDapLog --debugger-dap-log)
+else()
+ run_cmake_with_options(DebuggerNotSupported --debugger)
+ run_cmake_with_options(DebuggerNotSupportedPipe --debugger-pipe pipe)
+ run_cmake_with_options(DebuggerNotSupportedDapLog --debugger-dap-log dap-log)
+endif()
+
function(run_ExplicitDirs)
set(RunCMake_TEST_NO_CLEAN 1)
set(RunCMake_TEST_NO_SOURCE_DIR 1)
diff --git a/Utilities/IWYU/mapping.imp b/Utilities/IWYU/mapping.imp
index 366c517..6c12ada 100644
--- a/Utilities/IWYU/mapping.imp
+++ b/Utilities/IWYU/mapping.imp
@@ -22,6 +22,7 @@
# HACK: check whether this can be removed with next iwyu release.
{ include: [ "<bits/cxxabi_forced.h>", private, "<ctime>", public ] },
+ { include: [ "<bits/exception.h>", private, "<exception>", public ] },
{ include: [ "<bits/shared_ptr.h>", private, "<memory>", public ] },
{ include: [ "<bits/std_function.h>", private, "<functional>", public ] },
{ include: [ "<bits/refwrap.h>", private, "<functional>", public ] },
@@ -101,6 +102,7 @@
{ symbol: [ "__gnu_cxx::__enable_if<true, bool>::__type", private, "\"cmConfigure.h\"", public ] },
{ symbol: [ "std::remove_reference<std::basic_string<char, std::char_traits<char>, std::allocator<char> > &>::type", private, "\"cmConfigure.h\"", public ] },
{ symbol: [ "std::remove_reference<Defer &>::type", private, "\"cmConfigure.h\"", public ] },
+ { symbol: [ "std::remove_reference<dap::StoppedEvent &>::type", private, "\"cmConfigure.h\"", public ] },
# Wrappers for 3rd-party libraries
{ include: [ "@<.*curl/curlver.h>", private, "<cm3p/curl/curl.h>", public ] },
diff --git a/bootstrap b/bootstrap
index a056edf..109e450 100755
--- a/bootstrap
+++ b/bootstrap
@@ -80,6 +80,7 @@ cmake_init_file=""
cmake_bootstrap_system_libs=""
cmake_bootstrap_qt_gui=""
cmake_bootstrap_qt_qmake=""
+cmake_bootstrap_debugger=""
cmake_sphinx_info=""
cmake_sphinx_man=""
cmake_sphinx_html=""
@@ -697,6 +698,9 @@ Configuration:
--no-qt-gui do not build the Qt-based GUI (default)
--qt-qmake=<qmake> use <qmake> as the qmake executable to find Qt
+ --debugger enable debugger support (default if supported)
+ --no-debugger disable debugger support
+
--sphinx-info build Info manual with Sphinx
--sphinx-man build man pages with Sphinx
--sphinx-html build html help with Sphinx
@@ -962,6 +966,8 @@ while test $# != 0; do
--qt-gui) cmake_bootstrap_qt_gui="1" ;;
--no-qt-gui) cmake_bootstrap_qt_gui="0" ;;
--qt-qmake=*) cmake_bootstrap_qt_qmake=`cmake_arg "$1"` ;;
+ --debugger) cmake_bootstrap_debugger="1" ;;
+ --no-debugger) cmake_bootstrap_debugger="0" ;;
--sphinx-info) cmake_sphinx_info="1" ;;
--sphinx-man) cmake_sphinx_man="1" ;;
--sphinx-html) cmake_sphinx_html="1" ;;
@@ -1987,6 +1993,11 @@ if test "x${cmake_bootstrap_qt_qmake}" != "x"; then
set (QT_QMAKE_EXECUTABLE "'"${cmake_bootstrap_qt_qmake}"'" CACHE FILEPATH "Location of Qt qmake" FORCE)
' >> "${cmake_bootstrap_dir}/InitialCacheFlags.cmake"
fi
+if test "x${cmake_bootstrap_debugger}" != "x"; then
+ echo '
+set (CMake_ENABLE_DEBUGGER '"${cmake_bootstrap_debugger}"' CACHE BOOL "Enable CMake debugger support" FORCE)
+' >> "${cmake_bootstrap_dir}/InitialCacheFlags.cmake"
+fi
if test "x${cmake_sphinx_info}" != "x"; then
echo '
set (SPHINX_INFO "'"${cmake_sphinx_info}"'" CACHE BOOL "Build Info manual with Sphinx" FORCE)