mirror of
https://github.com/Kitware/CMake.git
synced 2026-01-06 13:51:33 -06:00
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>
This commit is contained in:
@@ -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}")
|
||||
|
||||
@@ -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
|
||||
|
||||
5
Help/release/dev/cmake-debugger.rst
Normal file
5
Help/release/dev/cmake-debugger.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
cmake-debugger
|
||||
--------------
|
||||
|
||||
* :manual:`cmake(1)` now supports interactive debugging of the CMake language.
|
||||
See the :option:`--debugger <cmake --debugger>` option.
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
462
Source/cmDebuggerAdapter.cxx
Normal file
462
Source/cmDebuggerAdapter.cxx
Normal file
@@ -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
|
||||
93
Source/cmDebuggerAdapter.h
Normal file
93
Source/cmDebuggerAdapter.h
Normal file
@@ -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
|
||||
200
Source/cmDebuggerBreakpointManager.cxx
Normal file
200
Source/cmDebuggerBreakpointManager.cxx
Normal file
@@ -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
|
||||
61
Source/cmDebuggerBreakpointManager.h
Normal file
61
Source/cmDebuggerBreakpointManager.h
Normal file
@@ -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
|
||||
129
Source/cmDebuggerExceptionManager.cxx
Normal file
129
Source/cmDebuggerExceptionManager.cxx
Normal file
@@ -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
|
||||
70
Source/cmDebuggerExceptionManager.h
Normal file
70
Source/cmDebuggerExceptionManager.h
Normal file
@@ -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
|
||||
293
Source/cmDebuggerPipeConnection.cxx
Normal file
293
Source/cmDebuggerPipeConnection.cxx
Normal file
@@ -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
|
||||
139
Source/cmDebuggerPipeConnection.h
Normal file
139
Source/cmDebuggerPipeConnection.h
Normal file
@@ -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
|
||||
80
Source/cmDebuggerProtocol.cxx
Normal file
80
Source/cmDebuggerProtocol.cxx
Normal file
@@ -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
|
||||
191
Source/cmDebuggerProtocol.h
Normal file
191
Source/cmDebuggerProtocol.h
Normal file
@@ -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
|
||||
14
Source/cmDebuggerSourceBreakpoint.cxx
Normal file
14
Source/cmDebuggerSourceBreakpoint.cxx
Normal file
@@ -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
|
||||
26
Source/cmDebuggerSourceBreakpoint.h
Normal file
26
Source/cmDebuggerSourceBreakpoint.h
Normal file
@@ -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
|
||||
28
Source/cmDebuggerStackFrame.cxx
Normal file
28
Source/cmDebuggerStackFrame.cxx
Normal file
@@ -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
|
||||
33
Source/cmDebuggerStackFrame.h
Normal file
33
Source/cmDebuggerStackFrame.h
Normal file
@@ -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
|
||||
150
Source/cmDebuggerThread.cxx
Normal file
150
Source/cmDebuggerThread.cxx
Normal file
@@ -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
|
||||
59
Source/cmDebuggerThread.h
Normal file
59
Source/cmDebuggerThread.h
Normal file
@@ -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
|
||||
47
Source/cmDebuggerThreadManager.cxx
Normal file
47
Source/cmDebuggerThreadManager.cxx
Normal file
@@ -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
|
||||
38
Source/cmDebuggerThreadManager.h
Normal file
38
Source/cmDebuggerThreadManager.h
Normal file
@@ -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
|
||||
133
Source/cmDebuggerVariables.cxx
Normal file
133
Source/cmDebuggerVariables.cxx
Normal file
@@ -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
|
||||
124
Source/cmDebuggerVariables.h
Normal file
124
Source/cmDebuggerVariables.h
Normal file
@@ -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
|
||||
644
Source/cmDebuggerVariablesHelper.cxx
Normal file
644
Source/cmDebuggerVariablesHelper.cxx
Normal file
@@ -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
|
||||
106
Source/cmDebuggerVariablesHelper.h
Normal file
106
Source/cmDebuggerVariablesHelper.h
Normal file
@@ -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
|
||||
38
Source/cmDebuggerVariablesManager.cxx
Normal file
38
Source/cmDebuggerVariablesManager.cxx
Normal file
@@ -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
|
||||
40
Source/cmDebuggerVariablesManager.h
Normal file
40
Source/cmDebuggerVariablesManager.h
Normal file
@@ -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
|
||||
@@ -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
|
||||
@@ -423,6 +427,13 @@ public:
|
||||
cmStrCat(lfc.FilePath, ':', std::to_string(lfc.Line));
|
||||
return argsValue;
|
||||
});
|
||||
#endif
|
||||
#ifdef CMake_ENABLE_DEBUGGER
|
||||
if (this->Makefile->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
|
||||
this->Makefile->GetCMakeInstance()
|
||||
->GetDebugAdapter()
|
||||
->OnBeginFunctionCall(mf, lfc.FilePath, lff);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
115
Source/cmake.cxx
115
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)
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
9
Tests/CMakeLib/DebuggerSample/CMakeLists.txt
Normal file
9
Tests/CMakeLib/DebuggerSample/CMakeLists.txt
Normal file
@@ -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)
|
||||
1
Tests/CMakeLib/DebuggerSample/script.cmake
Normal file
1
Tests/CMakeLib/DebuggerSample/script.cmake
Normal file
@@ -0,0 +1 @@
|
||||
message(STATUS "This is an example script")
|
||||
30
Tests/CMakeLib/testCommon.h
Normal file
30
Tests/CMakeLib/testCommon.h
Normal file
@@ -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")
|
||||
99
Tests/CMakeLib/testDebugger.h
Normal file
99
Tests/CMakeLib/testDebugger.h
Normal file
@@ -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;
|
||||
};
|
||||
173
Tests/CMakeLib/testDebuggerAdapter.cxx
Normal file
173
Tests/CMakeLib/testDebuggerAdapter.cxx
Normal file
@@ -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,
|
||||
});
|
||||
}
|
||||
184
Tests/CMakeLib/testDebuggerAdapterPipe.cxx
Normal file
184
Tests/CMakeLib/testDebuggerAdapterPipe.cxx
Normal file
@@ -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,
|
||||
});
|
||||
}
|
||||
172
Tests/CMakeLib/testDebuggerBreakpointManager.cxx
Normal file
172
Tests/CMakeLib/testDebuggerBreakpointManager.cxx
Normal file
@@ -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,
|
||||
});
|
||||
}
|
||||
218
Tests/CMakeLib/testDebuggerNamedPipe.cxx
Normal file
218
Tests/CMakeLib/testDebuggerNamedPipe.cxx
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
185
Tests/CMakeLib/testDebuggerVariables.cxx
Normal file
185
Tests/CMakeLib/testDebuggerVariables.cxx
Normal file
@@ -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,
|
||||
});
|
||||
}
|
||||
587
Tests/CMakeLib/testDebuggerVariablesHelper.cxx
Normal file
587
Tests/CMakeLib/testDebuggerVariablesHelper.cxx
Normal file
@@ -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,
|
||||
});
|
||||
}
|
||||
50
Tests/CMakeLib/testDebuggerVariablesManager.cxx
Normal file
50
Tests/CMakeLib/testDebuggerVariablesManager.cxx
Normal file
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,2 @@
|
||||
^CMake Error: No file specified for --debugger-dap-log
|
||||
CMake Error: Run 'cmake --help' for all supported options\.$
|
||||
@@ -0,0 +1 @@
|
||||
message(FATAL_ERROR "This should not be reached.")
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,2 @@
|
||||
^CMake Error: No path specified for --debugger-pipe
|
||||
CMake Error: Run 'cmake --help' for all supported options\.$
|
||||
1
Tests/RunCMake/CommandLine/DebuggerArgMissingPipe.cmake
Normal file
1
Tests/RunCMake/CommandLine/DebuggerArgMissingPipe.cmake
Normal file
@@ -0,0 +1 @@
|
||||
message(FATAL_ERROR "This should not be reached.")
|
||||
@@ -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()
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,2 @@
|
||||
^CMake Error: CMake was not built with support for --debugger
|
||||
CMake Error: Run 'cmake --help' for all supported options\.$
|
||||
1
Tests/RunCMake/CommandLine/DebuggerNotSupported.cmake
Normal file
1
Tests/RunCMake/CommandLine/DebuggerNotSupported.cmake
Normal file
@@ -0,0 +1 @@
|
||||
message(FATAL_ERROR "This should not be reached.")
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -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\.$
|
||||
@@ -0,0 +1 @@
|
||||
message(FATAL_ERROR "This should not be reached.")
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,2 @@
|
||||
^CMake Error: CMake was not built with support for --debugger-pipe
|
||||
CMake Error: Run 'cmake --help' for all supported options\.$
|
||||
@@ -0,0 +1 @@
|
||||
message(FATAL_ERROR "This should not be reached.")
|
||||
@@ -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":{.*}}$
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ] },
|
||||
|
||||
11
bootstrap
11
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)
|
||||
|
||||
Reference in New Issue
Block a user