diff options
Diffstat (limited to 'Source')
84 files changed, 3931 insertions, 292 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 2354f3d..bcaf890 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -762,6 +762,38 @@ target_link_libraries( ZLIB::ZLIB ) +if(CMake_ENABLE_DEBUGGER) + target_sources( + CMakeLib + PRIVATE + cmDebuggerAdapter.cxx + cmDebuggerAdapter.h + cmDebuggerBreakpointManager.cxx + cmDebuggerBreakpointManager.h + cmDebuggerExceptionManager.cxx + cmDebuggerExceptionManager.h + cmDebuggerPipeConnection.cxx + cmDebuggerPipeConnection.h + cmDebuggerProtocol.cxx + cmDebuggerProtocol.h + cmDebuggerSourceBreakpoint.cxx + cmDebuggerSourceBreakpoint.h + cmDebuggerStackFrame.cxx + cmDebuggerStackFrame.h + cmDebuggerThread.cxx + cmDebuggerThread.h + cmDebuggerThreadManager.cxx + cmDebuggerThreadManager.h + cmDebuggerVariables.cxx + cmDebuggerVariables.h + cmDebuggerVariablesHelper.cxx + cmDebuggerVariablesHelper.h + cmDebuggerVariablesManager.cxx + cmDebuggerVariablesManager.h + ) + target_link_libraries(CMakeLib PUBLIC cppdap::cppdap) +endif() + # Check if we can build the Mach-O parser. if(CMake_USE_MACH_PARSER) target_sources( diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake index dd759da..00d3236 100644 --- a/Source/CMakeVersion.cmake +++ b/Source/CMakeVersion.cmake @@ -1,7 +1,7 @@ # CMake version number components. set(CMake_VERSION_MAJOR 3) set(CMake_VERSION_MINOR 26) -set(CMake_VERSION_PATCH 20230519) +set(CMake_VERSION_PATCH 20230531) #set(CMake_VERSION_RC 0) set(CMake_VERSION_IS_DIRTY 0) diff --git a/Source/CPack/cmCPackInnoSetupGenerator.cxx b/Source/CPack/cmCPackInnoSetupGenerator.cxx index d8825d4..5d2c208 100644 --- a/Source/CPack/cmCPackInnoSetupGenerator.cxx +++ b/Source/CPack/cmCPackInnoSetupGenerator.cxx @@ -277,7 +277,7 @@ bool cmCPackInnoSetupGenerator::ProcessSetupSection() return false; } - const std::string& architecture = GetOption("CPACK_INNOSETUP_ARCHITECTURE"); + cmValue const architecture = GetOption("CPACK_INNOSETUP_ARCHITECTURE"); if (architecture != "x86" && architecture != "x64" && architecture != "arm64" && architecture != "ia64") { cmCPackLogger(cmCPackLog::LOG_ERROR, diff --git a/Source/CTest/cmCTestBuildAndTestHandler.cxx b/Source/CTest/cmCTestBuildAndTestHandler.cxx index cece98e..5feb953 100644 --- a/Source/CTest/cmCTestBuildAndTestHandler.cxx +++ b/Source/CTest/cmCTestBuildAndTestHandler.cxx @@ -246,7 +246,6 @@ int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring) return 1; } } - std::string output; const char* config = nullptr; if (!this->CTest->GetConfigType().empty()) { config = this->CTest->GetConfigType().c_str(); @@ -259,9 +258,8 @@ int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring) PackageResolveMode::Disable); int retVal = cm.GetGlobalGenerator()->Build( cmake::NO_BUILD_PARALLEL_LEVEL, this->SourceDir, this->BinaryDir, - this->BuildProject, { tar }, output, this->BuildMakeProgram, config, + this->BuildProject, { tar }, out, this->BuildMakeProgram, config, buildOptions, false, remainingTime); - out << output; // if the build failed then return if (retVal) { if (outstring) { diff --git a/Source/CursesDialog/form/frm_def.c b/Source/CursesDialog/form/frm_def.c index 645b3ba..569057b 100644 --- a/Source/CursesDialog/form/frm_def.c +++ b/Source/CursesDialog/form/frm_def.c @@ -220,6 +220,10 @@ static int Connect_Fields(FORM * form, FIELD ** fields) for(page_nr = 0;page_nr < form->maxpage; page_nr++) { FIELD *fld = (FIELD *)0; + #ifdef __clang_analyzer__ + /* Tell clang-analyzer the loop body runs at least once. */ + assert(form->page[page_nr].pmin <= form->page[page_nr].pmax); + #endif for(j = form->page[page_nr].pmin;j <= form->page[page_nr].pmax;j++) { fields[j]->index = j; diff --git a/Source/Modules/CMakeBuildUtilities.cmake b/Source/Modules/CMakeBuildUtilities.cmake index d6e3e88..c891fe9 100644 --- a/Source/Modules/CMakeBuildUtilities.cmake +++ b/Source/Modules/CMakeBuildUtilities.cmake @@ -376,3 +376,19 @@ if(BUILD_CursesDialog) message(FATAL_ERROR "CMAKE_USE_SYSTEM_FORM in ON but CURSES_FORM_LIBRARY is not set!") endif() endif() + +#--------------------------------------------------------------------- +# Build cppdap library. +if(CMake_ENABLE_DEBUGGER) + if(CMAKE_USE_SYSTEM_CPPDAP) + find_package(cppdap CONFIG) + if(NOT cppdap_FOUND) + message(FATAL_ERROR + "CMAKE_USE_SYSTEM_CPPDAP is ON but a cppdap is not found!") + endif() + else() + add_subdirectory(Utilities/cmcppdap) + add_library(cppdap::cppdap ALIAS cmcppdap) + CMAKE_SET_TARGET_FOLDER(cppdap "Utilities/3rdParty") + endif() +endif() diff --git a/Source/cmCMakeLanguageCommand.cxx b/Source/cmCMakeLanguageCommand.cxx index 68e658c..c7e9209 100644 --- a/Source/cmCMakeLanguageCommand.cxx +++ b/Source/cmCMakeLanguageCommand.cxx @@ -303,7 +303,7 @@ bool cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER( state->SetDependencyProvider({ parsedArgs.Command, methods }); state->SetGlobalProperty( fcmasProperty, - supportsFetchContentMakeAvailableSerial ? parsedArgs.Command.c_str() : ""); + supportsFetchContentMakeAvailableSerial ? parsedArgs.Command : ""); return true; } diff --git a/Source/cmCPluginAPI.cxx b/Source/cmCPluginAPI.cxx index abec968..c2c5bdb 100644 --- a/Source/cmCPluginAPI.cxx +++ b/Source/cmCPluginAPI.cxx @@ -615,7 +615,11 @@ static void CCONV cmSourceFileSetProperty(void* arg, const char* prop, { cmCPluginAPISourceFile* sf = static_cast<cmCPluginAPISourceFile*>(arg); if (cmSourceFile* rsf = sf->RealSourceFile) { - rsf->SetProperty(prop, value); + if (value == nullptr) { + rsf->SetProperty(prop, nullptr); + } else { + rsf->SetProperty(prop, value); + } } else if (prop) { if (!value) { value = "NOTFOUND"; diff --git a/Source/cmCPluginAPI.h b/Source/cmCPluginAPI.h index 13a93b7..92dff57 100644 --- a/Source/cmCPluginAPI.h +++ b/Source/cmCPluginAPI.h @@ -32,7 +32,7 @@ cmCPLuginAPI.cxx typedef struct { /*========================================================================= - Here we define the set of functions that a plugin may call. The first goup + Here we define the set of functions that a plugin may call. The first group of functions are utility functions that are specific to the plugin API =========================================================================*/ /* set/Get the ClientData in the cmLoadedCommandInfo structure, this is how diff --git a/Source/cmCacheManager.cxx b/Source/cmCacheManager.cxx index d95dcc4..8633de1 100644 --- a/Source/cmCacheManager.cxx +++ b/Source/cmCacheManager.cxx @@ -84,7 +84,7 @@ bool cmCacheManager::LoadCache(const std::string& path, bool internal, continue; } } - e.SetProperty("HELPSTRING", helpString.c_str()); + e.SetProperty("HELPSTRING", helpString); if (cmState::ParseCacheEntry(realbuffer, entryKey, e.Value, e.Type)) { if (excludes.find(entryKey) == excludes.end()) { // Load internal values if internal is set. @@ -102,7 +102,7 @@ bool cmCacheManager::LoadCache(const std::string& path, bool internal, " loaded from external file. " "To change this value edit this file: ", path, "/CMakeCache.txt"); - e.SetProperty("HELPSTRING", helpString.c_str()); + e.SetProperty("HELPSTRING", helpString); } if (!this->ReadPropertyEntry(entryKey, e)) { e.Initialized = true; @@ -186,11 +186,11 @@ bool cmCacheManager::ReadPropertyEntry(const std::string& entryKey, std::string key = entryKey.substr(0, entryKey.size() - plen); if (auto* entry = this->GetCacheEntry(key)) { // Store this property on its entry. - entry->SetProperty(p, e.Value.c_str()); + entry->SetProperty(p, e.Value); } else { // Create an entry and store the property. CacheEntry& ne = this->Cache[key]; - ne.SetProperty(p, e.Value.c_str()); + ne.SetProperty(p, e.Value); } return true; } @@ -541,10 +541,11 @@ void cmCacheManager::AddCacheEntry(const std::string& key, cmValue value, cmSystemTools::ConvertToUnixSlashes(e.Value); } } - e.SetProperty("HELPSTRING", - helpString - ? helpString - : "(This variable does not exist and should not be used)"); + e.SetProperty( + "HELPSTRING", + helpString ? std::string{ helpString } + : std::string{ + "(This variable does not exist and should not be used)" }); } void cmCacheManager::CacheEntry::SetValue(cmValue value) @@ -580,12 +581,12 @@ bool cmCacheManager::CacheEntry::GetPropertyAsBool( } void cmCacheManager::CacheEntry::SetProperty(const std::string& prop, - const char* value) + const std::string& value) { if (prop == "TYPE") { - this->Type = cmState::StringToCacheEntryType(value ? value : "STRING"); + this->Type = cmState::StringToCacheEntryType(value); } else if (prop == "VALUE") { - this->Value = value ? value : ""; + this->Value = value; } else { this->Properties.SetProperty(prop, value); } @@ -593,7 +594,19 @@ void cmCacheManager::CacheEntry::SetProperty(const std::string& prop, void cmCacheManager::CacheEntry::SetProperty(const std::string& p, bool v) { - this->SetProperty(p, v ? "ON" : "OFF"); + this->SetProperty(p, v ? std::string{ "ON" } : std::string{ "OFF" }); +} + +void cmCacheManager::CacheEntry::SetProperty(const std::string& prop, + std::nullptr_t) +{ + if (prop == "TYPE") { + this->Type = cmState::StringToCacheEntryType("STRING"); + } else if (prop == "VALUE") { + this->Value = ""; + } else { + this->Properties.SetProperty(prop, cmValue{ nullptr }); + } } void cmCacheManager::CacheEntry::AppendProperty(const std::string& prop, diff --git a/Source/cmCacheManager.h b/Source/cmCacheManager.h index bc3fb51..a2da0b5 100644 --- a/Source/cmCacheManager.h +++ b/Source/cmCacheManager.h @@ -39,8 +39,9 @@ class cmCacheManager std::vector<std::string> GetPropertyList() const; cmValue GetProperty(const std::string& property) const; bool GetPropertyAsBool(const std::string& property) const; - void SetProperty(const std::string& property, const char* value); + void SetProperty(const std::string& property, const std::string& value); void SetProperty(const std::string& property, bool value); + void SetProperty(const std::string& property, std::nullptr_t); void AppendProperty(const std::string& property, const std::string& value, bool asString = false); @@ -127,7 +128,7 @@ public: std::string const& value) { if (auto* entry = this->GetCacheEntry(key)) { - entry->SetProperty(propName, value.c_str()); + entry->SetProperty(propName, value); } } diff --git a/Source/cmCommandArgumentParserHelper.cxx b/Source/cmCommandArgumentParserHelper.cxx index 2ed04e5..a20f5a5 100644 --- a/Source/cmCommandArgumentParserHelper.cxx +++ b/Source/cmCommandArgumentParserHelper.cxx @@ -96,7 +96,8 @@ const char* cmCommandArgumentParserHelper::ExpandVariable(const char* var) } if (this->FileLine >= 0 && strcmp(var, "CMAKE_CURRENT_LIST_LINE") == 0) { std::string line; - cmListFileContext const& top = this->Makefile->GetBacktrace().Top(); + cmListFileBacktrace bt = this->Makefile->GetBacktrace(); + cmListFileContext const& top = bt.Top(); if (top.DeferId) { line = cmStrCat("DEFERRED:"_s, *top.DeferId); } else { diff --git a/Source/cmCommonTargetGenerator.cxx b/Source/cmCommonTargetGenerator.cxx index 2615494..e635dd9 100644 --- a/Source/cmCommonTargetGenerator.cxx +++ b/Source/cmCommonTargetGenerator.cxx @@ -236,7 +236,7 @@ std::string cmCommonTargetGenerator::GetManifests(const std::string& config) manifests.reserve(manifest_srcs.size()); std::string lang = this->GeneratorTarget->GetLinkerLanguage(config); - std::string const& manifestFlag = + std::string manifestFlag = this->Makefile->GetDefinition("CMAKE_" + lang + "_LINKER_MANIFEST_FLAG"); for (cmSourceFile const* manifest_src : manifest_srcs) { manifests.push_back(manifestFlag + diff --git a/Source/cmConditionEvaluator.cxx b/Source/cmConditionEvaluator.cxx index 288e107..6f9f541 100644 --- a/Source/cmConditionEvaluator.cxx +++ b/Source/cmConditionEvaluator.cxx @@ -741,8 +741,8 @@ bool cmConditionEvaluator::HandleLevel2(cmArgumentList& newArgs, keyVERSION_LESS_EQUAL, keyVERSION_GREATER, keyVERSION_GREATER_EQUAL, keyVERSION_EQUAL))) { const auto op = MATCH2CMPOP[matchNo - 1]; - const std::string& lhs = this->GetVariableOrString(*args.current); - const std::string& rhs = this->GetVariableOrString(*args.nextnext); + const cmValue lhs = this->GetVariableOrString(*args.current); + const cmValue rhs = this->GetVariableOrString(*args.nextnext); const auto result = cmSystemTools::VersionCompare(op, lhs, rhs); newArgs.ReduceTwoArgs(result, args); } diff --git a/Source/cmConfigure.cmake.h.in b/Source/cmConfigure.cmake.h.in index 3f19a11..de74716 100644 --- a/Source/cmConfigure.cmake.h.in +++ b/Source/cmConfigure.cmake.h.in @@ -20,6 +20,7 @@ #cmakedefine HAVE_ENVIRON_NOT_REQUIRE_PROTOTYPE #cmakedefine HAVE_UNSETENV +#cmakedefine CMake_ENABLE_DEBUGGER #cmakedefine CMake_USE_MACH_PARSER #cmakedefine CMake_USE_XCOFF_PARSER #cmakedefine CMAKE_USE_WMAKE diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx index 7623ccf..2c1480a 100644 --- a/Source/cmCustomCommandGenerator.cxx +++ b/Source/cmCustomCommandGenerator.cxx @@ -332,9 +332,9 @@ const char* cmCustomCommandGenerator::GetArgv0Location(unsigned int c) const bool cmCustomCommandGenerator::HasOnlyEmptyCommandLines() const { - for (size_t i = 0; i < this->CommandLines.size(); ++i) { - for (size_t j = 0; j < this->CommandLines[i].size(); ++j) { - if (!this->CommandLines[i][j].empty()) { + for (cmCustomCommandLine const& ccl : this->CommandLines) { + for (std::string const& cl : ccl) { + if (!cl.empty()) { return false; } } diff --git a/Source/cmDebuggerAdapter.cxx b/Source/cmDebuggerAdapter.cxx new file mode 100644 index 0000000..d03f79d --- /dev/null +++ b/Source/cmDebuggerAdapter.cxx @@ -0,0 +1,462 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmConfigure.h" // IWYU pragma: keep + +#include "cmDebuggerAdapter.h" + +#include <algorithm> +#include <climits> +#include <condition_variable> +#include <cstdint> +#include <functional> +#include <iostream> +#include <stdexcept> +#include <utility> + +#include <cm/memory> +#include <cm/optional> + +#include <cm3p/cppdap/io.h> // IWYU pragma: keep +#include <cm3p/cppdap/protocol.h> +#include <cm3p/cppdap/session.h> + +#include "cmDebuggerBreakpointManager.h" +#include "cmDebuggerExceptionManager.h" +#include "cmDebuggerProtocol.h" +#include "cmDebuggerSourceBreakpoint.h" // IWYU pragma: keep +#include "cmDebuggerStackFrame.h" +#include "cmDebuggerThread.h" +#include "cmDebuggerThreadManager.h" +#include "cmListFileCache.h" +#include "cmMakefile.h" +#include "cmValue.h" +#include "cmVersionConfig.h" +#include <cmcppdap/include/dap/optional.h> +#include <cmcppdap/include/dap/types.h> + +namespace cmDebugger { + +// Event provides a basic wait and signal synchronization primitive. +class SyncEvent +{ +public: + // Wait() blocks until the event is fired. + void Wait() + { + std::unique_lock<std::mutex> lock(Mutex); + Cv.wait(lock, [&] { return Fired; }); + } + + // Fire() sets signals the event, and unblocks any calls to Wait(). + void Fire() + { + std::unique_lock<std::mutex> lock(Mutex); + Fired = true; + Cv.notify_all(); + } + +private: + std::mutex Mutex; + std::condition_variable Cv; + bool Fired = false; +}; + +class Semaphore +{ +public: + Semaphore(int count_ = 0) + : Count(count_) + { + } + + inline void Notify() + { + std::unique_lock<std::mutex> lock(Mutex); + Count++; + // notify the waiting thread + Cv.notify_one(); + } + + inline void Wait() + { + std::unique_lock<std::mutex> lock(Mutex); + while (Count == 0) { + // wait on the mutex until notify is called + Cv.wait(lock); + } + Count--; + } + +private: + std::mutex Mutex; + std::condition_variable Cv; + int Count; +}; + +cmDebuggerAdapter::cmDebuggerAdapter( + std::shared_ptr<cmDebuggerConnection> connection, + std::string const& dapLogPath) + : cmDebuggerAdapter(std::move(connection), + dapLogPath.empty() + ? cm::nullopt + : cm::optional<std::shared_ptr<dap::Writer>>( + dap::file(dapLogPath.c_str()))) +{ +} + +cmDebuggerAdapter::cmDebuggerAdapter( + std::shared_ptr<cmDebuggerConnection> connection, + cm::optional<std::shared_ptr<dap::Writer>> logger) + : Connection(std::move(connection)) + , SessionActive(true) + , DisconnectEvent(cm::make_unique<SyncEvent>()) + , ConfigurationDoneEvent(cm::make_unique<SyncEvent>()) + , ContinueSem(cm::make_unique<Semaphore>()) + , ThreadManager(cm::make_unique<cmDebuggerThreadManager>()) +{ + if (logger.has_value()) { + SessionLog = std::move(logger.value()); + } + ClearStepRequests(); + + Session = dap::Session::create(); + BreakpointManager = + cm::make_unique<cmDebuggerBreakpointManager>(Session.get()); + ExceptionManager = + cm::make_unique<cmDebuggerExceptionManager>(Session.get()); + + // Handle errors reported by the Session. These errors include protocol + // parsing errors and receiving messages with no handler. + Session->onError([this](const char* msg) { + if (SessionLog) { + dap::writef(SessionLog, "dap::Session error: %s\n", msg); + } + + std::cout << "[CMake Debugger] DAP session error: " << msg << std::endl; + + BreakpointManager->ClearAll(); + ExceptionManager->ClearAll(); + ClearStepRequests(); + ContinueSem->Notify(); + DisconnectEvent->Fire(); + SessionActive.store(false); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize + Session->registerHandler([this](const dap::CMakeInitializeRequest& req) { + SupportsVariableType = req.supportsVariableType.value(false); + dap::CMakeInitializeResponse response; + response.supportsConfigurationDoneRequest = true; + response.cmakeVersion.major = CMake_VERSION_MAJOR; + response.cmakeVersion.minor = CMake_VERSION_MINOR; + response.cmakeVersion.patch = CMake_VERSION_PATCH; + response.cmakeVersion.full = CMake_VERSION; + ExceptionManager->HandleInitializeRequest(response); + return response; + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Events_Initialized + Session->registerSentHandler( + [&](const dap::ResponseOrError<dap::CMakeInitializeResponse>&) { + Session->send(dap::InitializedEvent()); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Threads + Session->registerHandler([this](const dap::ThreadsRequest& req) { + (void)req; + std::unique_lock<std::mutex> lock(Mutex); + dap::ThreadsResponse response; + dap::Thread thread; + thread.id = DefaultThread->GetId(); + thread.name = DefaultThread->GetName(); + response.threads.push_back(thread); + return response; + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StackTrace + Session->registerHandler([this](const dap::StackTraceRequest& request) + -> dap::ResponseOrError<dap::StackTraceResponse> { + std::unique_lock<std::mutex> lock(Mutex); + + cm::optional<dap::StackTraceResponse> response = + ThreadManager->GetThreadStackTraceResponse(request.threadId); + if (response.has_value()) { + return response.value(); + } + + return dap::Error("Unknown threadId '%d'", int(request.threadId)); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Scopes + Session->registerHandler([this](const dap::ScopesRequest& request) + -> dap::ResponseOrError<dap::ScopesResponse> { + std::unique_lock<std::mutex> lock(Mutex); + return DefaultThread->GetScopesResponse(request.frameId, + SupportsVariableType); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Variables + Session->registerHandler([this](const dap::VariablesRequest& request) + -> dap::ResponseOrError<dap::VariablesResponse> { + return DefaultThread->GetVariablesResponse(request); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Pause + Session->registerHandler([this](const dap::PauseRequest& req) { + (void)req; + PauseRequest.store(true); + return dap::PauseResponse(); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Continue + Session->registerHandler([this](const dap::ContinueRequest& req) { + (void)req; + ContinueSem->Notify(); + return dap::ContinueResponse(); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Next + Session->registerHandler([this](const dap::NextRequest& req) { + (void)req; + NextStepFrom.store(DefaultThread->GetStackFrameSize()); + ContinueSem->Notify(); + return dap::NextResponse(); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StepIn + Session->registerHandler([this](const dap::StepInRequest& req) { + (void)req; + // This would stop after stepped in, single line stepped or stepped out. + StepInRequest.store(true); + ContinueSem->Notify(); + return dap::StepInResponse(); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StepOut + Session->registerHandler([this](const dap::StepOutRequest& req) { + (void)req; + StepOutDepth.store(DefaultThread->GetStackFrameSize() - 1); + ContinueSem->Notify(); + return dap::StepOutResponse(); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Launch + Session->registerHandler([](const dap::LaunchRequest& req) { + (void)req; + return dap::LaunchResponse(); + }); + + // Handler for disconnect requests + Session->registerHandler([this](const dap::DisconnectRequest& request) { + (void)request; + BreakpointManager->ClearAll(); + ExceptionManager->ClearAll(); + ClearStepRequests(); + ContinueSem->Notify(); + DisconnectEvent->Fire(); + SessionActive.store(false); + return dap::DisconnectResponse(); + }); + + Session->registerHandler([this](const dap::EvaluateRequest& request) { + dap::EvaluateResponse response; + if (request.frameId.has_value()) { + std::shared_ptr<cmDebuggerStackFrame> frame = + DefaultThread->GetStackFrame(request.frameId.value()); + + auto var = frame->GetMakefile()->GetDefinition(request.expression); + if (var) { + response.type = "string"; + response.result = var; + return response; + } + } + + return response; + }); + + // The ConfigurationDone request is made by the client once all configuration + // requests have been made. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_ConfigurationDone + Session->registerHandler([this](const dap::ConfigurationDoneRequest& req) { + (void)req; + ConfigurationDoneEvent->Fire(); + return dap::ConfigurationDoneResponse(); + }); + + std::string errorMessage; + if (!Connection->StartListening(errorMessage)) { + throw std::runtime_error(errorMessage); + } + + // Connect to the client. Write a well-known message to stdout so that + // clients know it is safe to attempt to connect. + std::cout << "Waiting for debugger client to connect..." << std::endl; + Connection->WaitForConnection(); + std::cout << "Debugger client connected." << std::endl; + + if (SessionLog) { + Session->connect(spy(Connection->GetReader(), SessionLog), + spy(Connection->GetWriter(), SessionLog)); + } else { + Session->connect(Connection->GetReader(), Connection->GetWriter()); + } + + // Start the processing thread. + SessionThread = std::thread([this] { + while (SessionActive.load()) { + if (auto payload = Session->getPayload()) { + payload(); + } + } + }); + + ConfigurationDoneEvent->Wait(); + + DefaultThread = ThreadManager->StartThread("CMake script"); + dap::ThreadEvent threadEvent; + threadEvent.reason = "started"; + threadEvent.threadId = DefaultThread->GetId(); + Session->send(threadEvent); +} + +cmDebuggerAdapter::~cmDebuggerAdapter() +{ + if (SessionThread.joinable()) { + SessionThread.join(); + } + + Session.reset(nullptr); + + if (SessionLog) { + SessionLog->close(); + } +} + +void cmDebuggerAdapter::ReportExitCode(int exitCode) +{ + ThreadManager->EndThread(DefaultThread); + dap::ThreadEvent threadEvent; + threadEvent.reason = "exited"; + threadEvent.threadId = DefaultThread->GetId(); + DefaultThread.reset(); + + dap::ExitedEvent exitEvent; + exitEvent.exitCode = exitCode; + + dap::TerminatedEvent terminatedEvent; + + if (SessionActive.load()) { + Session->send(threadEvent); + Session->send(exitEvent); + Session->send(terminatedEvent); + } + + // Wait until disconnected or error. + DisconnectEvent->Wait(); +} + +void cmDebuggerAdapter::OnFileParsedSuccessfully( + std::string const& sourcePath, + std::vector<cmListFileFunction> const& functions) +{ + BreakpointManager->SourceFileLoaded(sourcePath, functions); +} + +void cmDebuggerAdapter::OnBeginFunctionCall(cmMakefile* mf, + std::string const& sourcePath, + cmListFileFunction const& lff) +{ + std::unique_lock<std::mutex> lock(Mutex); + DefaultThread->PushStackFrame(mf, sourcePath, lff); + + if (lff.Line() == 0) { + // File just loaded, continue to first valid function call. + return; + } + + auto hits = BreakpointManager->GetBreakpoints(sourcePath, lff.Line()); + lock.unlock(); + + bool waitSem = false; + dap::StoppedEvent stoppedEvent; + stoppedEvent.allThreadsStopped = true; + stoppedEvent.threadId = DefaultThread->GetId(); + if (!hits.empty()) { + ClearStepRequests(); + waitSem = true; + + dap::array<dap::integer> hitBreakpoints; + hitBreakpoints.resize(hits.size()); + std::transform(hits.begin(), hits.end(), hitBreakpoints.begin(), + [&](const int64_t& id) { return dap::integer(id); }); + stoppedEvent.reason = "breakpoint"; + stoppedEvent.hitBreakpointIds = hitBreakpoints; + } + + if (long(DefaultThread->GetStackFrameSize()) <= NextStepFrom.load() || + StepInRequest.load() || + long(DefaultThread->GetStackFrameSize()) <= StepOutDepth.load()) { + ClearStepRequests(); + waitSem = true; + + stoppedEvent.reason = "step"; + } + + if (PauseRequest.load()) { + ClearStepRequests(); + waitSem = true; + + stoppedEvent.reason = "pause"; + } + + if (waitSem) { + Session->send(stoppedEvent); + ContinueSem->Wait(); + } +} + +void cmDebuggerAdapter::OnEndFunctionCall() +{ + DefaultThread->PopStackFrame(); +} + +static std::shared_ptr<cmListFileFunction> listFileFunction; + +void cmDebuggerAdapter::OnBeginFileParse(cmMakefile* mf, + std::string const& sourcePath) +{ + std::unique_lock<std::mutex> lock(Mutex); + + listFileFunction = std::make_shared<cmListFileFunction>( + sourcePath, 0, 0, std::vector<cmListFileArgument>()); + DefaultThread->PushStackFrame(mf, sourcePath, *listFileFunction); +} + +void cmDebuggerAdapter::OnEndFileParse() +{ + DefaultThread->PopStackFrame(); + listFileFunction = nullptr; +} + +void cmDebuggerAdapter::OnMessageOutput(MessageType t, std::string const& text) +{ + cm::optional<dap::StoppedEvent> stoppedEvent = + ExceptionManager->RaiseExceptionIfAny(t, text); + if (stoppedEvent.has_value()) { + stoppedEvent->threadId = DefaultThread->GetId(); + Session->send(*stoppedEvent); + ContinueSem->Wait(); + } +} + +void cmDebuggerAdapter::ClearStepRequests() +{ + NextStepFrom.store(INT_MIN); + StepInRequest.store(false); + StepOutDepth.store(INT_MIN); + PauseRequest.store(false); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerAdapter.h b/Source/cmDebuggerAdapter.h new file mode 100644 index 0000000..f261d88 --- /dev/null +++ b/Source/cmDebuggerAdapter.h @@ -0,0 +1,93 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <atomic> +#include <cstdint> +#include <memory> +#include <mutex> +#include <string> +#include <thread> +#include <vector> + +#include <cm/optional> + +#include <cm3p/cppdap/io.h> // IWYU pragma: keep + +#include "cmMessageType.h" + +class cmListFileFunction; +class cmMakefile; + +namespace cmDebugger { +class Semaphore; +class SyncEvent; +class cmDebuggerBreakpointManager; +class cmDebuggerExceptionManager; +class cmDebuggerThread; +class cmDebuggerThreadManager; +} + +namespace dap { +class Session; +} + +namespace cmDebugger { + +class cmDebuggerConnection +{ +public: + virtual ~cmDebuggerConnection() = default; + virtual bool StartListening(std::string& errorMessage) = 0; + virtual void WaitForConnection() = 0; + virtual std::shared_ptr<dap::Reader> GetReader() = 0; + virtual std::shared_ptr<dap::Writer> GetWriter() = 0; +}; + +class cmDebuggerAdapter +{ +public: + cmDebuggerAdapter(std::shared_ptr<cmDebuggerConnection> connection, + std::string const& dapLogPath); + cmDebuggerAdapter(std::shared_ptr<cmDebuggerConnection> connection, + cm::optional<std::shared_ptr<dap::Writer>> logger); + ~cmDebuggerAdapter(); + + void ReportExitCode(int exitCode); + + void OnFileParsedSuccessfully( + std::string const& sourcePath, + std::vector<cmListFileFunction> const& functions); + void OnBeginFunctionCall(cmMakefile* mf, std::string const& sourcePath, + cmListFileFunction const& lff); + void OnEndFunctionCall(); + void OnBeginFileParse(cmMakefile* mf, std::string const& sourcePath); + void OnEndFileParse(); + + void OnMessageOutput(MessageType t, std::string const& text); + +private: + void ClearStepRequests(); + std::shared_ptr<cmDebuggerConnection> Connection; + std::unique_ptr<dap::Session> Session; + std::shared_ptr<dap::Writer> SessionLog; + std::thread SessionThread; + std::atomic<bool> SessionActive; + std::mutex Mutex; + std::unique_ptr<SyncEvent> DisconnectEvent; + std::unique_ptr<SyncEvent> ConfigurationDoneEvent; + std::unique_ptr<Semaphore> ContinueSem; + std::atomic<int64_t> NextStepFrom; + std::atomic<bool> StepInRequest; + std::atomic<int64_t> StepOutDepth; + std::atomic<bool> PauseRequest; + std::unique_ptr<cmDebuggerThreadManager> ThreadManager; + std::shared_ptr<cmDebuggerThread> DefaultThread; + std::unique_ptr<cmDebuggerBreakpointManager> BreakpointManager; + std::unique_ptr<cmDebuggerExceptionManager> ExceptionManager; + bool SupportsVariableType; +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerBreakpointManager.cxx b/Source/cmDebuggerBreakpointManager.cxx new file mode 100644 index 0000000..152f0f5 --- /dev/null +++ b/Source/cmDebuggerBreakpointManager.cxx @@ -0,0 +1,200 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmDebuggerBreakpointManager.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <memory> + +#include <cm3p/cppdap/optional.h> +#include <cm3p/cppdap/session.h> +#include <cm3p/cppdap/types.h> + +#include "cmDebuggerSourceBreakpoint.h" +#include "cmListFileCache.h" +#include "cmSystemTools.h" + +namespace cmDebugger { + +cmDebuggerBreakpointManager::cmDebuggerBreakpointManager( + dap::Session* dapSession) + : DapSession(dapSession) +{ + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetBreakpoints + DapSession->registerHandler([&](const dap::SetBreakpointsRequest& request) { + return HandleSetBreakpointsRequest(request); + }); +} + +int64_t cmDebuggerBreakpointManager::FindFunctionStartLine( + std::string const& sourcePath, int64_t line) +{ + auto location = + find_if(ListFileFunctionLines[sourcePath].begin(), + ListFileFunctionLines[sourcePath].end(), + [=](cmDebuggerFunctionLocation const& loc) { + return loc.StartLine <= line && loc.EndLine >= line; + }); + + if (location != ListFileFunctionLines[sourcePath].end()) { + return location->StartLine; + } + + return 0; +} + +int64_t cmDebuggerBreakpointManager::CalibrateBreakpointLine( + std::string const& sourcePath, int64_t line) +{ + auto location = find_if(ListFileFunctionLines[sourcePath].begin(), + ListFileFunctionLines[sourcePath].end(), + [=](cmDebuggerFunctionLocation const& loc) { + return loc.StartLine >= line; + }); + + if (location != ListFileFunctionLines[sourcePath].end()) { + return location->StartLine; + } + + if (!ListFileFunctionLines[sourcePath].empty() && + ListFileFunctionLines[sourcePath].back().EndLine <= line) { + // return last function start line for any breakpoints after. + return ListFileFunctionLines[sourcePath].back().StartLine; + } + + return 0; +} + +dap::SetBreakpointsResponse +cmDebuggerBreakpointManager::HandleSetBreakpointsRequest( + dap::SetBreakpointsRequest const& request) +{ + std::unique_lock<std::mutex> lock(Mutex); + + dap::SetBreakpointsResponse response; + + auto sourcePath = + cmSystemTools::GetActualCaseForPath(request.source.path.value()); + const dap::array<dap::SourceBreakpoint> defaultValue{}; + const auto& breakpoints = request.breakpoints.value(defaultValue); + if (ListFileFunctionLines.find(sourcePath) != ListFileFunctionLines.end()) { + // The file has loaded, we can validate breakpoints. + if (Breakpoints.find(sourcePath) != Breakpoints.end()) { + Breakpoints[sourcePath].clear(); + } + response.breakpoints.resize(breakpoints.size()); + for (size_t i = 0; i < breakpoints.size(); i++) { + int64_t correctedLine = + CalibrateBreakpointLine(sourcePath, breakpoints[i].line); + if (correctedLine > 0) { + Breakpoints[sourcePath].emplace_back(NextBreakpointId++, + correctedLine); + response.breakpoints[i].id = Breakpoints[sourcePath].back().GetId(); + response.breakpoints[i].line = + Breakpoints[sourcePath].back().GetLine(); + response.breakpoints[i].verified = true; + } else { + response.breakpoints[i].verified = false; + response.breakpoints[i].line = breakpoints[i].line; + } + dap::Source dapSrc; + dapSrc.path = sourcePath; + response.breakpoints[i].source = dapSrc; + } + } else { + // The file has not loaded, validate breakpoints later. + ListFilePendingValidations.emplace(sourcePath); + + response.breakpoints.resize(breakpoints.size()); + for (size_t i = 0; i < breakpoints.size(); i++) { + Breakpoints[sourcePath].emplace_back(NextBreakpointId++, + breakpoints[i].line); + response.breakpoints[i].id = Breakpoints[sourcePath].back().GetId(); + response.breakpoints[i].line = Breakpoints[sourcePath].back().GetLine(); + response.breakpoints[i].verified = false; + dap::Source dapSrc; + dapSrc.path = sourcePath; + response.breakpoints[i].source = dapSrc; + } + } + + return response; +} + +void cmDebuggerBreakpointManager::SourceFileLoaded( + std::string const& sourcePath, + std::vector<cmListFileFunction> const& functions) +{ + std::unique_lock<std::mutex> lock(Mutex); + if (ListFileFunctionLines.find(sourcePath) != ListFileFunctionLines.end()) { + // this is not expected. + return; + } + + for (cmListFileFunction const& func : functions) { + ListFileFunctionLines[sourcePath].emplace_back( + cmDebuggerFunctionLocation{ func.Line(), func.LineEnd() }); + } + + if (ListFilePendingValidations.find(sourcePath) == + ListFilePendingValidations.end()) { + return; + } + + ListFilePendingValidations.erase(sourcePath); + + for (size_t i = 0; i < Breakpoints[sourcePath].size(); i++) { + dap::BreakpointEvent breakpointEvent; + breakpointEvent.breakpoint.id = Breakpoints[sourcePath][i].GetId(); + breakpointEvent.breakpoint.line = Breakpoints[sourcePath][i].GetLine(); + auto source = dap::Source(); + source.path = sourcePath; + breakpointEvent.breakpoint.source = source; + int64_t correctedLine = CalibrateBreakpointLine( + sourcePath, Breakpoints[sourcePath][i].GetLine()); + if (correctedLine != Breakpoints[sourcePath][i].GetLine()) { + Breakpoints[sourcePath][i].ChangeLine(correctedLine); + } + breakpointEvent.reason = "changed"; + breakpointEvent.breakpoint.verified = (correctedLine > 0); + if (breakpointEvent.breakpoint.verified) { + breakpointEvent.breakpoint.line = correctedLine; + } else { + Breakpoints[sourcePath][i].Invalid(); + } + + DapSession->send(breakpointEvent); + } +} + +std::vector<int64_t> cmDebuggerBreakpointManager::GetBreakpoints( + std::string const& sourcePath, int64_t line) +{ + std::unique_lock<std::mutex> lock(Mutex); + const auto& all = Breakpoints[sourcePath]; + std::vector<int64_t> breakpoints; + if (all.empty()) { + return breakpoints; + } + + auto it = all.begin(); + + while ((it = std::find_if( + it, all.end(), [&](const cmDebuggerSourceBreakpoint& breakpoint) { + return (breakpoint.GetIsValid() && breakpoint.GetLine() == line); + })) != all.end()) { + breakpoints.emplace_back(it->GetId()); + ++it; + } + + return breakpoints; +} + +void cmDebuggerBreakpointManager::ClearAll() +{ + std::unique_lock<std::mutex> lock(Mutex); + Breakpoints.clear(); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerBreakpointManager.h b/Source/cmDebuggerBreakpointManager.h new file mode 100644 index 0000000..a4e5df5 --- /dev/null +++ b/Source/cmDebuggerBreakpointManager.h @@ -0,0 +1,61 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <cstdint> +#include <mutex> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#include <cm3p/cppdap/protocol.h> + +class cmListFileFunction; + +namespace cmDebugger { +class cmDebuggerSourceBreakpoint; +} + +namespace dap { +class Session; +} + +namespace cmDebugger { + +struct cmDebuggerFunctionLocation +{ + int64_t StartLine; + int64_t EndLine; +}; + +/** The breakpoint manager. */ +class cmDebuggerBreakpointManager +{ + dap::Session* DapSession; + std::mutex Mutex; + std::unordered_map<std::string, std::vector<cmDebuggerSourceBreakpoint>> + Breakpoints; + std::unordered_map<std::string, + std::vector<struct cmDebuggerFunctionLocation>> + ListFileFunctionLines; + std::unordered_set<std::string> ListFilePendingValidations; + int64_t NextBreakpointId = 0; + + dap::SetBreakpointsResponse HandleSetBreakpointsRequest( + dap::SetBreakpointsRequest const& request); + int64_t FindFunctionStartLine(std::string const& sourcePath, int64_t line); + int64_t CalibrateBreakpointLine(std::string const& sourcePath, int64_t line); + +public: + cmDebuggerBreakpointManager(dap::Session* dapSession); + void SourceFileLoaded(std::string const& sourcePath, + std::vector<cmListFileFunction> const& functions); + std::vector<int64_t> GetBreakpoints(std::string const& sourcePath, + int64_t line); + void ClearAll(); +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerExceptionManager.cxx b/Source/cmDebuggerExceptionManager.cxx new file mode 100644 index 0000000..a27426c --- /dev/null +++ b/Source/cmDebuggerExceptionManager.cxx @@ -0,0 +1,129 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmDebuggerExceptionManager.h" + +#include <utility> +#include <vector> + +#include <cm3p/cppdap/optional.h> +#include <cm3p/cppdap/session.h> +#include <cm3p/cppdap/types.h> + +#include "cmDebuggerProtocol.h" +#include "cmMessageType.h" + +namespace cmDebugger { + +cmDebuggerExceptionManager::cmDebuggerExceptionManager( + dap::Session* dapSession) + : DapSession(dapSession) +{ + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetExceptionBreakpoints + DapSession->registerHandler( + [&](const dap::SetExceptionBreakpointsRequest& request) { + return HandleSetExceptionBreakpointsRequest(request); + }); + + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_ExceptionInfo + DapSession->registerHandler([&](const dap::ExceptionInfoRequest& request) { + (void)request; + return HandleExceptionInfoRequest(); + }); + + ExceptionMap[MessageType::AUTHOR_WARNING] = + cmDebuggerExceptionFilter{ "AUTHOR_WARNING", "Warning (dev)" }; + ExceptionMap[MessageType::AUTHOR_ERROR] = + cmDebuggerExceptionFilter{ "AUTHOR_ERROR", "Error (dev)" }; + ExceptionMap[MessageType::FATAL_ERROR] = + cmDebuggerExceptionFilter{ "FATAL_ERROR", "Fatal error" }; + ExceptionMap[MessageType::INTERNAL_ERROR] = + cmDebuggerExceptionFilter{ "INTERNAL_ERROR", "Internal error" }; + ExceptionMap[MessageType::MESSAGE] = + cmDebuggerExceptionFilter{ "MESSAGE", "Other messages" }; + ExceptionMap[MessageType::WARNING] = + cmDebuggerExceptionFilter{ "WARNING", "Warning" }; + ExceptionMap[MessageType::LOG] = + cmDebuggerExceptionFilter{ "LOG", "Debug log" }; + ExceptionMap[MessageType::DEPRECATION_ERROR] = + cmDebuggerExceptionFilter{ "DEPRECATION_ERROR", "Deprecation error" }; + ExceptionMap[MessageType::DEPRECATION_WARNING] = + cmDebuggerExceptionFilter{ "DEPRECATION_WARNING", "Deprecation warning" }; + RaiseExceptions["AUTHOR_ERROR"] = true; + RaiseExceptions["FATAL_ERROR"] = true; + RaiseExceptions["INTERNAL_ERROR"] = true; + RaiseExceptions["DEPRECATION_ERROR"] = true; +} + +dap::SetExceptionBreakpointsResponse +cmDebuggerExceptionManager::HandleSetExceptionBreakpointsRequest( + dap::SetExceptionBreakpointsRequest const& request) +{ + std::unique_lock<std::mutex> lock(Mutex); + dap::SetExceptionBreakpointsResponse response; + RaiseExceptions.clear(); + for (const auto& filter : request.filters) { + RaiseExceptions[filter] = true; + } + + return response; +} + +dap::ExceptionInfoResponse +cmDebuggerExceptionManager::HandleExceptionInfoRequest() +{ + std::unique_lock<std::mutex> lock(Mutex); + + dap::ExceptionInfoResponse response; + if (TheException.has_value()) { + response.exceptionId = TheException->Id; + response.breakMode = "always"; + response.description = TheException->Description; + TheException = {}; + } + return response; +} + +void cmDebuggerExceptionManager::HandleInitializeRequest( + dap::CMakeInitializeResponse& response) +{ + std::unique_lock<std::mutex> lock(Mutex); + response.supportsExceptionInfoRequest = true; + + dap::array<dap::ExceptionBreakpointsFilter> exceptionBreakpointFilters; + for (auto& pair : ExceptionMap) { + dap::ExceptionBreakpointsFilter filter; + filter.filter = pair.second.Filter; + filter.label = pair.second.Label; + filter.def = RaiseExceptions[filter.filter]; + exceptionBreakpointFilters.emplace_back(filter); + } + + response.exceptionBreakpointFilters = exceptionBreakpointFilters; +} + +cm::optional<dap::StoppedEvent> +cmDebuggerExceptionManager::RaiseExceptionIfAny(MessageType t, + std::string const& text) +{ + cm::optional<dap::StoppedEvent> maybeStoppedEvent; + std::unique_lock<std::mutex> lock(Mutex); + if (RaiseExceptions[ExceptionMap[t].Filter]) { + dap::StoppedEvent stoppedEvent; + stoppedEvent.allThreadsStopped = true; + stoppedEvent.reason = "exception"; + stoppedEvent.description = "Pause on exception"; + stoppedEvent.text = text; + TheException = cmDebuggerException{ ExceptionMap[t].Filter, text }; + maybeStoppedEvent = std::move(stoppedEvent); + } + + return maybeStoppedEvent; +} + +void cmDebuggerExceptionManager::ClearAll() +{ + std::unique_lock<std::mutex> lock(Mutex); + RaiseExceptions.clear(); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerExceptionManager.h b/Source/cmDebuggerExceptionManager.h new file mode 100644 index 0000000..b819128 --- /dev/null +++ b/Source/cmDebuggerExceptionManager.h @@ -0,0 +1,70 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <cstddef> +#include <functional> +#include <mutex> +#include <string> +#include <unordered_map> + +#include <cm/optional> + +#include <cm3p/cppdap/protocol.h> + +#include "cmMessageType.h" + +namespace dap { +class Session; +struct CMakeInitializeResponse; +} + +namespace cmDebugger { + +struct cmDebuggerException +{ + std::string Id; + std::string Description; +}; + +struct cmDebuggerExceptionFilter +{ + std::string Filter; + std::string Label; +}; + +/** The exception manager. */ +class cmDebuggerExceptionManager +{ + // Some older C++ standard libraries cannot hash an enum class by default. + struct MessageTypeHash + { + std::size_t operator()(MessageType t) const + { + return std::hash<int>{}(static_cast<int>(t)); + } + }; + + dap::Session* DapSession; + std::mutex Mutex; + std::unordered_map<std::string, bool> RaiseExceptions; + std::unordered_map<MessageType, cmDebuggerExceptionFilter, MessageTypeHash> + ExceptionMap; + cm::optional<cmDebuggerException> TheException; + + dap::SetExceptionBreakpointsResponse HandleSetExceptionBreakpointsRequest( + dap::SetExceptionBreakpointsRequest const& request); + + dap::ExceptionInfoResponse HandleExceptionInfoRequest(); + +public: + cmDebuggerExceptionManager(dap::Session* dapSession); + void HandleInitializeRequest(dap::CMakeInitializeResponse& response); + cm::optional<dap::StoppedEvent> RaiseExceptionIfAny(MessageType t, + std::string const& text); + void ClearAll(); +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerPipeConnection.cxx b/Source/cmDebuggerPipeConnection.cxx new file mode 100644 index 0000000..1b54346 --- /dev/null +++ b/Source/cmDebuggerPipeConnection.cxx @@ -0,0 +1,293 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmDebuggerPipeConnection.h" + +#include <algorithm> +#include <cassert> +#include <cstring> +#include <stdexcept> +#include <utility> + +namespace cmDebugger { + +struct write_req_t +{ + uv_write_t req; + uv_buf_t buf; +}; + +cmDebuggerPipeBase::cmDebuggerPipeBase(std::string name) + : PipeName(std::move(name)) +{ + Loop.init(); + LoopExit.init( + *Loop, [](uv_async_t* handle) { uv_stop((uv_loop_t*)handle->data); }, + Loop); + WriteEvent.init( + *Loop, + [](uv_async_t* handle) { + auto* conn = static_cast<cmDebuggerPipeBase*>(handle->data); + conn->WriteInternal(); + }, + this); + PipeClose.init( + *Loop, + [](uv_async_t* handle) { + auto* conn = static_cast<cmDebuggerPipeBase*>(handle->data); + if (conn->Pipe.get()) { + conn->Pipe->data = nullptr; + conn->Pipe.reset(); + } + }, + this); +} + +void cmDebuggerPipeBase::WaitForConnection() +{ + std::unique_lock<std::mutex> lock(Mutex); + Connected.wait(lock, [this] { return isOpen() || FailedToOpen; }); + if (FailedToOpen) { + throw std::runtime_error("Failed to open debugger connection."); + } +} + +void cmDebuggerPipeBase::close() +{ + std::unique_lock<std::mutex> lock(Mutex); + + CloseConnection(); + PipeClose.send(); + lock.unlock(); + ReadReady.notify_all(); +} + +size_t cmDebuggerPipeBase::read(void* buffer, size_t n) +{ + std::unique_lock<std::mutex> lock(Mutex); + ReadReady.wait(lock, [this] { return !isOpen() || !ReadBuffer.empty(); }); + + if (!isOpen() && ReadBuffer.empty()) { + return 0; + } + + auto size = std::min(n, ReadBuffer.size()); + memcpy(buffer, ReadBuffer.data(), size); + ReadBuffer.erase(0, size); + return size; +} + +bool cmDebuggerPipeBase::write(const void* buffer, size_t n) +{ + std::unique_lock<std::mutex> lock(Mutex); + WriteBuffer.append(static_cast<const char*>(buffer), n); + lock.unlock(); + WriteEvent.send(); + + lock.lock(); + WriteComplete.wait(lock, [this] { return WriteBuffer.empty(); }); + return true; +} + +void cmDebuggerPipeBase::StopLoop() +{ + LoopExit.send(); + + if (LoopThread.joinable()) { + LoopThread.join(); + } +} + +void cmDebuggerPipeBase::BufferData(const std::string& data) +{ + std::unique_lock<std::mutex> lock(Mutex); + ReadBuffer += data; + lock.unlock(); + ReadReady.notify_all(); +} + +void cmDebuggerPipeBase::WriteInternal() +{ + std::unique_lock<std::mutex> lock(Mutex); + auto n = WriteBuffer.length(); + assert(this->Pipe.get()); + write_req_t* req = new write_req_t; + req->req.data = &WriteComplete; + char* rawBuffer = new char[n]; + req->buf = uv_buf_init(rawBuffer, static_cast<unsigned int>(n)); + memcpy(req->buf.base, WriteBuffer.data(), n); + WriteBuffer.clear(); + lock.unlock(); + + uv_write( + reinterpret_cast<uv_write_t*>(req), this->Pipe, &req->buf, 1, + [](uv_write_t* cb_req, int status) { + (void)status; // We need to free memory even if the write failed. + write_req_t* wr = reinterpret_cast<write_req_t*>(cb_req); + reinterpret_cast<std::condition_variable*>(wr->req.data)->notify_all(); + delete[] (wr->buf.base); + delete wr; + }); + +#ifdef __clang_analyzer__ + // Tell clang-analyzer that 'rawBuffer' does not leak. + // We pass ownership to the closure. + delete[] rawBuffer; +#endif +} + +cmDebuggerPipeConnection::cmDebuggerPipeConnection(std::string name) + : cmDebuggerPipeBase(std::move(name)) +{ + ServerPipeClose.init( + *Loop, + [](uv_async_t* handle) { + auto* conn = static_cast<cmDebuggerPipeConnection*>(handle->data); + if (conn->ServerPipe.get()) { + conn->ServerPipe->data = nullptr; + conn->ServerPipe.reset(); + } + }, + this); +} + +cmDebuggerPipeConnection::~cmDebuggerPipeConnection() +{ + StopLoop(); +} + +bool cmDebuggerPipeConnection::StartListening(std::string& errorMessage) +{ + this->ServerPipe.init(*Loop, 0, + static_cast<cmDebuggerPipeConnection*>(this)); + + int r; + if ((r = uv_pipe_bind(this->ServerPipe, this->PipeName.c_str())) != 0) { + errorMessage = + "Internal Error with " + this->PipeName + ": " + uv_err_name(r); + return false; + } + + r = uv_listen(this->ServerPipe, 1, [](uv_stream_t* stream, int status) { + if (status >= 0) { + auto* conn = static_cast<cmDebuggerPipeConnection*>(stream->data); + if (conn) { + conn->Connect(stream); + } + } + }); + + if (r != 0) { + errorMessage = + "Internal Error listening on " + this->PipeName + ": " + uv_err_name(r); + return false; + } + + // Start the libuv event loop thread so that a client can connect. + LoopThread = std::thread([this] { uv_run(Loop, UV_RUN_DEFAULT); }); + + StartedListening.set_value(); + + return true; +} + +std::shared_ptr<dap::Reader> cmDebuggerPipeConnection::GetReader() +{ + return std::static_pointer_cast<dap::Reader>(shared_from_this()); +} + +std::shared_ptr<dap::Writer> cmDebuggerPipeConnection::GetWriter() +{ + return std::static_pointer_cast<dap::Writer>(shared_from_this()); +} + +bool cmDebuggerPipeConnection::isOpen() +{ + return this->Pipe.get() != nullptr; +} + +void cmDebuggerPipeConnection::CloseConnection() +{ + ServerPipeClose.send(); +} + +void cmDebuggerPipeConnection::Connect(uv_stream_t* server) +{ + if (this->Pipe.get()) { + // Accept and close all pipes but the first: + cm::uv_pipe_ptr rejectPipe; + + rejectPipe.init(*Loop, 0); + uv_accept(server, rejectPipe); + + return; + } + + cm::uv_pipe_ptr ClientPipe; + ClientPipe.init(*Loop, 0, static_cast<cmDebuggerPipeConnection*>(this)); + + if (uv_accept(server, ClientPipe) != 0) { + return; + } + + StartReading<cmDebuggerPipeConnection>(ClientPipe); + + std::unique_lock<std::mutex> lock(Mutex); + Pipe = std::move(ClientPipe); + lock.unlock(); + Connected.notify_all(); +} + +cmDebuggerPipeClient::~cmDebuggerPipeClient() +{ + StopLoop(); +} + +void cmDebuggerPipeClient::Start() +{ + this->Pipe.init(*Loop, 0, static_cast<cmDebuggerPipeClient*>(this)); + + uv_connect_t* connect = new uv_connect_t; + connect->data = this; + uv_pipe_connect( + connect, Pipe, PipeName.c_str(), [](uv_connect_t* cb_connect, int status) { + auto* conn = static_cast<cmDebuggerPipeClient*>(cb_connect->data); + if (status >= 0) { + conn->Connect(); + } else { + conn->FailConnection(); + } + delete cb_connect; + }); + + // Start the libuv event loop so that the pipe can connect. + LoopThread = std::thread([this] { uv_run(Loop, UV_RUN_DEFAULT); }); +} + +bool cmDebuggerPipeClient::isOpen() +{ + return IsConnected; +} + +void cmDebuggerPipeClient::CloseConnection() +{ + IsConnected = false; +} + +void cmDebuggerPipeClient::Connect() +{ + StartReading<cmDebuggerPipeClient>(Pipe); + std::unique_lock<std::mutex> lock(Mutex); + IsConnected = true; + lock.unlock(); + Connected.notify_all(); +} + +void cmDebuggerPipeClient::FailConnection() +{ + std::unique_lock<std::mutex> lock(Mutex); + FailedToOpen = true; + lock.unlock(); + Connected.notify_all(); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerPipeConnection.h b/Source/cmDebuggerPipeConnection.h new file mode 100644 index 0000000..0991ff7 --- /dev/null +++ b/Source/cmDebuggerPipeConnection.h @@ -0,0 +1,139 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <condition_variable> +#include <cstddef> +#include <future> +#include <memory> +#include <mutex> +#include <string> +#include <thread> + +#include <cm3p/cppdap/io.h> +#include <cm3p/uv.h> + +#include "cmDebuggerAdapter.h" +#include "cmUVHandlePtr.h" + +namespace cmDebugger { + +class cmDebuggerPipeBase : public dap::ReaderWriter +{ +public: + cmDebuggerPipeBase(std::string name); + + void WaitForConnection(); + + // dap::ReaderWriter implementation + + void close() final; + size_t read(void* buffer, size_t n) final; + bool write(const void* buffer, size_t n) final; + +protected: + virtual void CloseConnection(){}; + template <typename T> + void StartReading(uv_stream_t* stream) + { + uv_read_start( + stream, + // alloc_cb + [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { + (void)handle; + char* rawBuffer = new char[suggested_size]; + *buf = + uv_buf_init(rawBuffer, static_cast<unsigned int>(suggested_size)); + }, + // read_cb + [](uv_stream_t* readStream, ssize_t nread, const uv_buf_t* buf) { + auto conn = static_cast<T*>(readStream->data); + if (conn) { + if (nread >= 0) { + conn->BufferData(std::string(buf->base, buf->base + nread)); + } else { + conn->close(); + } + } + delete[] (buf->base); + }); + } + void StopLoop(); + + const std::string PipeName; + std::thread LoopThread; + cm::uv_loop_ptr Loop; + cm::uv_pipe_ptr Pipe; + std::mutex Mutex; + std::condition_variable Connected; + bool FailedToOpen = false; + +private: + void BufferData(const std::string& data); + void WriteInternal(); + + cm::uv_async_ptr LoopExit; + cm::uv_async_ptr WriteEvent; + cm::uv_async_ptr PipeClose; + std::string WriteBuffer; + std::string ReadBuffer; + std::condition_variable ReadReady; + std::condition_variable WriteComplete; +}; + +class cmDebuggerPipeConnection + : public cmDebuggerPipeBase + , public cmDebuggerConnection + , public std::enable_shared_from_this<cmDebuggerPipeConnection> +{ +public: + cmDebuggerPipeConnection(std::string name); + ~cmDebuggerPipeConnection() override; + + void WaitForConnection() override + { + cmDebuggerPipeBase::WaitForConnection(); + } + + bool StartListening(std::string& errorMessage) override; + std::shared_ptr<dap::Reader> GetReader() override; + std::shared_ptr<dap::Writer> GetWriter() override; + + // dap::ReaderWriter implementation + + bool isOpen() override; + + // Used for unit test synchronization + std::promise<void> StartedListening; + +private: + void CloseConnection() override; + void Connect(uv_stream_t* server); + + cm::uv_pipe_ptr ServerPipe; + cm::uv_async_ptr ServerPipeClose; +}; + +class cmDebuggerPipeClient : public cmDebuggerPipeBase +{ +public: + using cmDebuggerPipeBase::cmDebuggerPipeBase; + ~cmDebuggerPipeClient() override; + + void Start(); + + // dap::ReaderWriter implementation + + bool isOpen() override; + +private: + void CloseConnection() override; + void Connect(); + void FailConnection(); + + bool IsConnected = false; +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerProtocol.cxx b/Source/cmDebuggerProtocol.cxx new file mode 100644 index 0000000..505de35 --- /dev/null +++ b/Source/cmDebuggerProtocol.cxx @@ -0,0 +1,80 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDebuggerProtocol.h" + +#include <string> + +namespace dap { +DAP_IMPLEMENT_STRUCT_TYPEINFO(CMakeVersion, "", DAP_FIELD(major, "major"), + DAP_FIELD(minor, "minor"), + DAP_FIELD(patch, "patch"), + DAP_FIELD(full, "full")); + +DAP_IMPLEMENT_STRUCT_TYPEINFO( + CMakeInitializeResponse, "", + DAP_FIELD(additionalModuleColumns, "additionalModuleColumns"), + DAP_FIELD(completionTriggerCharacters, "completionTriggerCharacters"), + DAP_FIELD(exceptionBreakpointFilters, "exceptionBreakpointFilters"), + DAP_FIELD(supportSuspendDebuggee, "supportSuspendDebuggee"), + DAP_FIELD(supportTerminateDebuggee, "supportTerminateDebuggee"), + DAP_FIELD(supportedChecksumAlgorithms, "supportedChecksumAlgorithms"), + DAP_FIELD(supportsBreakpointLocationsRequest, + "supportsBreakpointLocationsRequest"), + DAP_FIELD(supportsCancelRequest, "supportsCancelRequest"), + DAP_FIELD(supportsClipboardContext, "supportsClipboardContext"), + DAP_FIELD(supportsCompletionsRequest, "supportsCompletionsRequest"), + DAP_FIELD(supportsConditionalBreakpoints, "supportsConditionalBreakpoints"), + DAP_FIELD(supportsConfigurationDoneRequest, + "supportsConfigurationDoneRequest"), + DAP_FIELD(supportsDataBreakpoints, "supportsDataBreakpoints"), + DAP_FIELD(supportsDelayedStackTraceLoading, + "supportsDelayedStackTraceLoading"), + DAP_FIELD(supportsDisassembleRequest, "supportsDisassembleRequest"), + DAP_FIELD(supportsEvaluateForHovers, "supportsEvaluateForHovers"), + DAP_FIELD(supportsExceptionFilterOptions, "supportsExceptionFilterOptions"), + DAP_FIELD(supportsExceptionInfoRequest, "supportsExceptionInfoRequest"), + DAP_FIELD(supportsExceptionOptions, "supportsExceptionOptions"), + DAP_FIELD(supportsFunctionBreakpoints, "supportsFunctionBreakpoints"), + DAP_FIELD(supportsGotoTargetsRequest, "supportsGotoTargetsRequest"), + DAP_FIELD(supportsHitConditionalBreakpoints, + "supportsHitConditionalBreakpoints"), + DAP_FIELD(supportsInstructionBreakpoints, "supportsInstructionBreakpoints"), + DAP_FIELD(supportsLoadedSourcesRequest, "supportsLoadedSourcesRequest"), + DAP_FIELD(supportsLogPoints, "supportsLogPoints"), + DAP_FIELD(supportsModulesRequest, "supportsModulesRequest"), + DAP_FIELD(supportsReadMemoryRequest, "supportsReadMemoryRequest"), + DAP_FIELD(supportsRestartFrame, "supportsRestartFrame"), + DAP_FIELD(supportsRestartRequest, "supportsRestartRequest"), + DAP_FIELD(supportsSetExpression, "supportsSetExpression"), + DAP_FIELD(supportsSetVariable, "supportsSetVariable"), + DAP_FIELD(supportsSingleThreadExecutionRequests, + "supportsSingleThreadExecutionRequests"), + DAP_FIELD(supportsStepBack, "supportsStepBack"), + DAP_FIELD(supportsStepInTargetsRequest, "supportsStepInTargetsRequest"), + DAP_FIELD(supportsSteppingGranularity, "supportsSteppingGranularity"), + DAP_FIELD(supportsTerminateRequest, "supportsTerminateRequest"), + DAP_FIELD(supportsTerminateThreadsRequest, + "supportsTerminateThreadsRequest"), + DAP_FIELD(supportsValueFormattingOptions, "supportsValueFormattingOptions"), + DAP_FIELD(supportsWriteMemoryRequest, "supportsWriteMemoryRequest"), + DAP_FIELD(cmakeVersion, "cmakeVersion")); + +DAP_IMPLEMENT_STRUCT_TYPEINFO( + CMakeInitializeRequest, "initialize", DAP_FIELD(adapterID, "adapterID"), + DAP_FIELD(clientID, "clientID"), DAP_FIELD(clientName, "clientName"), + DAP_FIELD(columnsStartAt1, "columnsStartAt1"), + DAP_FIELD(linesStartAt1, "linesStartAt1"), DAP_FIELD(locale, "locale"), + DAP_FIELD(pathFormat, "pathFormat"), + DAP_FIELD(supportsArgsCanBeInterpretedByShell, + "supportsArgsCanBeInterpretedByShell"), + DAP_FIELD(supportsInvalidatedEvent, "supportsInvalidatedEvent"), + DAP_FIELD(supportsMemoryEvent, "supportsMemoryEvent"), + DAP_FIELD(supportsMemoryReferences, "supportsMemoryReferences"), + DAP_FIELD(supportsProgressReporting, "supportsProgressReporting"), + DAP_FIELD(supportsRunInTerminalRequest, "supportsRunInTerminalRequest"), + DAP_FIELD(supportsStartDebuggingRequest, "supportsStartDebuggingRequest"), + DAP_FIELD(supportsVariablePaging, "supportsVariablePaging"), + DAP_FIELD(supportsVariableType, "supportsVariableType")); + +} // namespace dap diff --git a/Source/cmDebuggerProtocol.h b/Source/cmDebuggerProtocol.h new file mode 100644 index 0000000..4334aed --- /dev/null +++ b/Source/cmDebuggerProtocol.h @@ -0,0 +1,191 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <vector> + +#include <cm3p/cppdap/protocol.h> + +#include <cmcppdap/include/dap/optional.h> +#include <cmcppdap/include/dap/typeof.h> +#include <cmcppdap/include/dap/types.h> + +namespace dap { + +// Represents the cmake version. +struct CMakeVersion : public InitializeResponse +{ + // The major version number. + integer major; + // The minor version number. + integer minor; + // The patch number. + integer patch; + // The full version string. + string full; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(CMakeVersion); + +// Response to `initialize` request. +struct CMakeInitializeResponse : public Response +{ + // The set of additional module information exposed by the debug adapter. + optional<array<ColumnDescriptor>> additionalModuleColumns; + // The set of characters that should trigger completion in a REPL. If not + // specified, the UI should assume the `.` character. + optional<array<string>> completionTriggerCharacters; + // Available exception filter options for the `setExceptionBreakpoints` + // request. + optional<array<ExceptionBreakpointsFilter>> exceptionBreakpointFilters; + // The debug adapter supports the `suspendDebuggee` attribute on the + // `disconnect` request. + optional<boolean> supportSuspendDebuggee; + // The debug adapter supports the `terminateDebuggee` attribute on the + // `disconnect` request. + optional<boolean> supportTerminateDebuggee; + // Checksum algorithms supported by the debug adapter. + optional<array<ChecksumAlgorithm>> supportedChecksumAlgorithms; + // The debug adapter supports the `breakpointLocations` request. + optional<boolean> supportsBreakpointLocationsRequest; + // The debug adapter supports the `cancel` request. + optional<boolean> supportsCancelRequest; + // The debug adapter supports the `clipboard` context value in the `evaluate` + // request. + optional<boolean> supportsClipboardContext; + // The debug adapter supports the `completions` request. + optional<boolean> supportsCompletionsRequest; + // The debug adapter supports conditional breakpoints. + optional<boolean> supportsConditionalBreakpoints; + // The debug adapter supports the `configurationDone` request. + optional<boolean> supportsConfigurationDoneRequest; + // The debug adapter supports data breakpoints. + optional<boolean> supportsDataBreakpoints; + // The debug adapter supports the delayed loading of parts of the stack, + // which requires that both the `startFrame` and `levels` arguments and the + // `totalFrames` result of the `stackTrace` request are supported. + optional<boolean> supportsDelayedStackTraceLoading; + // The debug adapter supports the `disassemble` request. + optional<boolean> supportsDisassembleRequest; + // The debug adapter supports a (side effect free) `evaluate` request for + // data hovers. + optional<boolean> supportsEvaluateForHovers; + // The debug adapter supports `filterOptions` as an argument on the + // `setExceptionBreakpoints` request. + optional<boolean> supportsExceptionFilterOptions; + // The debug adapter supports the `exceptionInfo` request. + optional<boolean> supportsExceptionInfoRequest; + // The debug adapter supports `exceptionOptions` on the + // `setExceptionBreakpoints` request. + optional<boolean> supportsExceptionOptions; + // The debug adapter supports function breakpoints. + optional<boolean> supportsFunctionBreakpoints; + // The debug adapter supports the `gotoTargets` request. + optional<boolean> supportsGotoTargetsRequest; + // The debug adapter supports breakpoints that break execution after a + // specified number of hits. + optional<boolean> supportsHitConditionalBreakpoints; + // The debug adapter supports adding breakpoints based on instruction + // references. + optional<boolean> supportsInstructionBreakpoints; + // The debug adapter supports the `loadedSources` request. + optional<boolean> supportsLoadedSourcesRequest; + // The debug adapter supports log points by interpreting the `logMessage` + // attribute of the `SourceBreakpoint`. + optional<boolean> supportsLogPoints; + // The debug adapter supports the `modules` request. + optional<boolean> supportsModulesRequest; + // The debug adapter supports the `readMemory` request. + optional<boolean> supportsReadMemoryRequest; + // The debug adapter supports restarting a frame. + optional<boolean> supportsRestartFrame; + // The debug adapter supports the `restart` request. In this case a client + // should not implement `restart` by terminating and relaunching the adapter + // but by calling the `restart` request. + optional<boolean> supportsRestartRequest; + // The debug adapter supports the `setExpression` request. + optional<boolean> supportsSetExpression; + // The debug adapter supports setting a variable to a value. + optional<boolean> supportsSetVariable; + // The debug adapter supports the `singleThread` property on the execution + // requests (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`, + // `stepBack`). + optional<boolean> supportsSingleThreadExecutionRequests; + // The debug adapter supports stepping back via the `stepBack` and + // `reverseContinue` requests. + optional<boolean> supportsStepBack; + // The debug adapter supports the `stepInTargets` request. + optional<boolean> supportsStepInTargetsRequest; + // The debug adapter supports stepping granularities (argument `granularity`) + // for the stepping requests. + optional<boolean> supportsSteppingGranularity; + // The debug adapter supports the `terminate` request. + optional<boolean> supportsTerminateRequest; + // The debug adapter supports the `terminateThreads` request. + optional<boolean> supportsTerminateThreadsRequest; + // The debug adapter supports a `format` attribute on the `stackTrace`, + // `variables`, and `evaluate` requests. + optional<boolean> supportsValueFormattingOptions; + // The debug adapter supports the `writeMemory` request. + optional<boolean> supportsWriteMemoryRequest; + // The CMake version. + CMakeVersion cmakeVersion; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(CMakeInitializeResponse); + +// The `initialize` request is sent as the first request from the client to the +// debug adapter in order to configure it with client capabilities and to +// retrieve capabilities from the debug adapter. Until the debug adapter has +// responded with an `initialize` response, the client must not send any +// additional requests or events to the debug adapter. In addition the debug +// adapter is not allowed to send any requests or events to the client until it +// has responded with an `initialize` response. The `initialize` request may +// only be sent once. +struct CMakeInitializeRequest : public Request +{ + using Response = CMakeInitializeResponse; + // The ID of the debug adapter. + string adapterID; + // The ID of the client using this adapter. + optional<string> clientID; + // The human-readable name of the client using this adapter. + optional<string> clientName; + // If true all column numbers are 1-based (default). + optional<boolean> columnsStartAt1; + // If true all line numbers are 1-based (default). + optional<boolean> linesStartAt1; + // The ISO-639 locale of the client using this adapter, e.g. en-US or de-CH. + optional<string> locale; + // Determines in what format paths are specified. The default is `path`, + // which is the native format. + // + // May be one of the following enumeration values: + // 'path', 'uri' + optional<string> pathFormat; + // Client supports the `argsCanBeInterpretedByShell` attribute on the + // `runInTerminal` request. + optional<boolean> supportsArgsCanBeInterpretedByShell; + // Client supports the `invalidated` event. + optional<boolean> supportsInvalidatedEvent; + // Client supports the `memory` event. + optional<boolean> supportsMemoryEvent; + // Client supports memory references. + optional<boolean> supportsMemoryReferences; + // Client supports progress reporting. + optional<boolean> supportsProgressReporting; + // Client supports the `runInTerminal` request. + optional<boolean> supportsRunInTerminalRequest; + // Client supports the `startDebugging` request. + optional<boolean> supportsStartDebuggingRequest; + // Client supports the paging of variables. + optional<boolean> supportsVariablePaging; + // Client supports the `type` attribute for variables. + optional<boolean> supportsVariableType; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(CMakeInitializeRequest); + +} // namespace dap diff --git a/Source/cmDebuggerSourceBreakpoint.cxx b/Source/cmDebuggerSourceBreakpoint.cxx new file mode 100644 index 0000000..d4665e6 --- /dev/null +++ b/Source/cmDebuggerSourceBreakpoint.cxx @@ -0,0 +1,14 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmDebuggerSourceBreakpoint.h" + +namespace cmDebugger { + +cmDebuggerSourceBreakpoint::cmDebuggerSourceBreakpoint(int64_t id, + int64_t line) + : Id(id) + , Line(line) +{ +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerSourceBreakpoint.h b/Source/cmDebuggerSourceBreakpoint.h new file mode 100644 index 0000000..f6d6cac --- /dev/null +++ b/Source/cmDebuggerSourceBreakpoint.h @@ -0,0 +1,26 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <cstdint> + +namespace cmDebugger { + +class cmDebuggerSourceBreakpoint +{ + int64_t Id; + int64_t Line; + bool IsValid = true; + +public: + cmDebuggerSourceBreakpoint(int64_t id, int64_t line); + int64_t GetId() const noexcept { return this->Id; } + int64_t GetLine() const noexcept { return this->Line; } + void ChangeLine(int64_t line) noexcept { this->Line = line; } + bool GetIsValid() const noexcept { return this->IsValid; } + void Invalid() noexcept { this->IsValid = false; } +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerStackFrame.cxx b/Source/cmDebuggerStackFrame.cxx new file mode 100644 index 0000000..789b0a5 --- /dev/null +++ b/Source/cmDebuggerStackFrame.cxx @@ -0,0 +1,28 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmDebuggerStackFrame.h" + +#include <utility> + +#include "cmListFileCache.h" + +namespace cmDebugger { + +std::atomic<int64_t> cmDebuggerStackFrame::NextId(1); + +cmDebuggerStackFrame::cmDebuggerStackFrame(cmMakefile* mf, + std::string sourcePath, + cmListFileFunction const& lff) + : Id(NextId.fetch_add(1)) + , FileName(std::move(sourcePath)) + , Function(lff) + , Makefile(mf) +{ +} + +int64_t cmDebuggerStackFrame::GetLine() const noexcept +{ + return this->Function.Line(); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerStackFrame.h b/Source/cmDebuggerStackFrame.h new file mode 100644 index 0000000..dc3b2ab --- /dev/null +++ b/Source/cmDebuggerStackFrame.h @@ -0,0 +1,33 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <atomic> +#include <cstdint> +#include <string> + +class cmListFileFunction; +class cmMakefile; + +namespace cmDebugger { + +class cmDebuggerStackFrame +{ + static std::atomic<std::int64_t> NextId; + std::int64_t Id; + std::string FileName; + cmListFileFunction const& Function; + cmMakefile* Makefile; + +public: + cmDebuggerStackFrame(cmMakefile* mf, std::string sourcePath, + cmListFileFunction const& lff); + int64_t GetId() const noexcept { return this->Id; } + std::string const& GetFileName() const noexcept { return this->FileName; } + int64_t GetLine() const noexcept; + cmMakefile* GetMakefile() const noexcept { return this->Makefile; } +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerThread.cxx b/Source/cmDebuggerThread.cxx new file mode 100644 index 0000000..fd52f5a --- /dev/null +++ b/Source/cmDebuggerThread.cxx @@ -0,0 +1,150 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDebuggerThread.h" + +#include <cstdint> +#include <utility> + +#include <cm3p/cppdap/optional.h> +#include <cm3p/cppdap/types.h> + +#include "cmDebuggerStackFrame.h" +#include "cmDebuggerVariables.h" +#include "cmDebuggerVariablesHelper.h" +#include "cmDebuggerVariablesManager.h" + +namespace cmDebugger { + +cmDebuggerThread::cmDebuggerThread(int64_t id, std::string name) + : Id(id) + , Name(std::move(name)) + , VariablesManager(std::make_shared<cmDebuggerVariablesManager>()) +{ +} + +void cmDebuggerThread::PushStackFrame(cmMakefile* mf, + std::string const& sourcePath, + cmListFileFunction const& lff) +{ + std::unique_lock<std::mutex> lock(Mutex); + Frames.emplace_back( + std::make_shared<cmDebuggerStackFrame>(mf, sourcePath, lff)); + FrameMap.insert({ Frames.back()->GetId(), Frames.back() }); +} + +void cmDebuggerThread::PopStackFrame() +{ + std::unique_lock<std::mutex> lock(Mutex); + FrameMap.erase(Frames.back()->GetId()); + FrameScopes.erase(Frames.back()->GetId()); + FrameVariables.erase(Frames.back()->GetId()); + Frames.pop_back(); +} + +std::shared_ptr<cmDebuggerStackFrame> cmDebuggerThread::GetTopStackFrame() +{ + std::unique_lock<std::mutex> lock(Mutex); + if (!Frames.empty()) { + return Frames.back(); + } + + return {}; +} + +std::shared_ptr<cmDebuggerStackFrame> cmDebuggerThread::GetStackFrame( + int64_t frameId) +{ + std::unique_lock<std::mutex> lock(Mutex); + auto it = FrameMap.find(frameId); + + if (it == FrameMap.end()) { + return {}; + } + + return it->second; +} + +dap::ScopesResponse cmDebuggerThread::GetScopesResponse( + int64_t frameId, bool supportsVariableType) +{ + std::unique_lock<std::mutex> lock(Mutex); + auto it = FrameScopes.find(frameId); + + if (it != FrameScopes.end()) { + dap::ScopesResponse response; + response.scopes = it->second; + return response; + } + + auto it2 = FrameMap.find(frameId); + if (it2 == FrameMap.end()) { + return dap::ScopesResponse(); + } + + std::shared_ptr<cmDebuggerStackFrame> frame = it2->second; + std::shared_ptr<cmDebuggerVariables> localVariables = + cmDebuggerVariablesHelper::Create(VariablesManager, "Locals", + supportsVariableType, frame); + + FrameVariables[frameId].emplace_back(localVariables); + + dap::Scope scope; + scope.name = localVariables->GetName(); + scope.presentationHint = "locals"; + scope.variablesReference = localVariables->GetId(); + + dap::Source source; + source.name = frame->GetFileName(); + source.path = source.name; + scope.source = source; + + FrameScopes[frameId].push_back(scope); + + dap::ScopesResponse response; + response.scopes.push_back(scope); + return response; +} + +dap::VariablesResponse cmDebuggerThread::GetVariablesResponse( + dap::VariablesRequest const& request) +{ + std::unique_lock<std::mutex> lock(Mutex); + dap::VariablesResponse response; + response.variables = VariablesManager->HandleVariablesRequest(request); + return response; +} + +dap::StackTraceResponse GetStackTraceResponse( + std::shared_ptr<cmDebuggerThread> const& thread) +{ + dap::StackTraceResponse response; + std::unique_lock<std::mutex> lock(thread->Mutex); + for (int i = static_cast<int>(thread->Frames.size()) - 1; i >= 0; --i) { + dap::Source source; + source.name = thread->Frames[i]->GetFileName(); + source.path = source.name; + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Warray-bounds" +#endif + dap::StackFrame stackFrame; +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + stackFrame.line = thread->Frames[i]->GetLine(); + stackFrame.column = 1; + stackFrame.name = thread->Frames[i]->GetFileName() + " Line " + + std::to_string(stackFrame.line); + stackFrame.id = thread->Frames[i]->GetId(); + stackFrame.source = source; + + response.stackFrames.push_back(stackFrame); + } + + response.totalFrames = response.stackFrames.size(); + return response; +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerThread.h b/Source/cmDebuggerThread.h new file mode 100644 index 0000000..65ee2cf --- /dev/null +++ b/Source/cmDebuggerThread.h @@ -0,0 +1,59 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <mutex> +#include <string> +#include <unordered_map> +#include <vector> + +#include <cm3p/cppdap/protocol.h> + +class cmListFileFunction; +class cmMakefile; + +namespace cmDebugger { +class cmDebuggerStackFrame; +class cmDebuggerVariables; +class cmDebuggerVariablesManager; +} + +namespace cmDebugger { + +class cmDebuggerThread +{ + int64_t Id; + std::string Name; + std::vector<std::shared_ptr<cmDebuggerStackFrame>> Frames; + std::unordered_map<int64_t, std::shared_ptr<cmDebuggerStackFrame>> FrameMap; + std::mutex Mutex; + std::unordered_map<int64_t, std::vector<dap::Scope>> FrameScopes; + std::unordered_map<int64_t, + std::vector<std::shared_ptr<cmDebuggerVariables>>> + FrameVariables; + std::shared_ptr<cmDebuggerVariablesManager> VariablesManager; + +public: + cmDebuggerThread(int64_t id, std::string name); + int64_t GetId() const { return this->Id; } + const std::string& GetName() const { return this->Name; } + void PushStackFrame(cmMakefile* mf, std::string const& sourcePath, + cmListFileFunction const& lff); + void PopStackFrame(); + std::shared_ptr<cmDebuggerStackFrame> GetTopStackFrame(); + std::shared_ptr<cmDebuggerStackFrame> GetStackFrame(int64_t frameId); + size_t GetStackFrameSize() const { return this->Frames.size(); } + dap::ScopesResponse GetScopesResponse(int64_t frameId, + bool supportsVariableType); + dap::VariablesResponse GetVariablesResponse( + dap::VariablesRequest const& request); + friend dap::StackTraceResponse GetStackTraceResponse( + std::shared_ptr<cmDebuggerThread> const& thread); +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerThreadManager.cxx b/Source/cmDebuggerThreadManager.cxx new file mode 100644 index 0000000..0eb443b --- /dev/null +++ b/Source/cmDebuggerThreadManager.cxx @@ -0,0 +1,47 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDebuggerThreadManager.h" + +#include <algorithm> + +#include <cm3p/cppdap/protocol.h> + +#include "cmDebuggerThread.h" + +namespace cmDebugger { + +std::atomic<int64_t> cmDebuggerThreadManager::NextThreadId(1); + +std::shared_ptr<cmDebuggerThread> cmDebuggerThreadManager::StartThread( + std::string const& name) +{ + std::shared_ptr<cmDebuggerThread> thread = + std::make_shared<cmDebuggerThread>( + cmDebuggerThreadManager::NextThreadId.fetch_add(1), name); + Threads.emplace_back(thread); + return thread; +} + +void cmDebuggerThreadManager::EndThread( + std::shared_ptr<cmDebuggerThread> const& thread) +{ + Threads.remove(thread); +} + +cm::optional<dap::StackTraceResponse> +cmDebuggerThreadManager::GetThreadStackTraceResponse(int64_t id) +{ + auto it = find_if(Threads.begin(), Threads.end(), + [&](const std::shared_ptr<cmDebuggerThread>& t) { + return t->GetId() == id; + }); + + if (it == Threads.end()) { + return {}; + } + + return GetStackTraceResponse(*it); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerThreadManager.h b/Source/cmDebuggerThreadManager.h new file mode 100644 index 0000000..934cf85 --- /dev/null +++ b/Source/cmDebuggerThreadManager.h @@ -0,0 +1,38 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <atomic> +#include <cstdint> +#include <list> +#include <memory> +#include <string> + +#include <cm/optional> + +namespace cmDebugger { +class cmDebuggerThread; +} + +namespace dap { +struct StackTraceResponse; +} + +namespace cmDebugger { + +class cmDebuggerThreadManager +{ + static std::atomic<std::int64_t> NextThreadId; + std::list<std::shared_ptr<cmDebuggerThread>> Threads; + +public: + cmDebuggerThreadManager() = default; + std::shared_ptr<cmDebuggerThread> StartThread(std::string const& name); + void EndThread(std::shared_ptr<cmDebuggerThread> const& thread); + cm::optional<dap::StackTraceResponse> GetThreadStackTraceResponse( + std::int64_t id); +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerVariables.cxx b/Source/cmDebuggerVariables.cxx new file mode 100644 index 0000000..40fe41f --- /dev/null +++ b/Source/cmDebuggerVariables.cxx @@ -0,0 +1,133 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDebuggerVariables.h" + +#include <algorithm> +#include <vector> + +#include <cm3p/cppdap/optional.h> +#include <cm3p/cppdap/protocol.h> +#include <cm3p/cppdap/types.h> + +#include "cmDebuggerVariablesManager.h" + +namespace cmDebugger { + +namespace { +const dap::VariablePresentationHint PrivatePropertyHint = { {}, + "property", + {}, + "private" }; +const dap::VariablePresentationHint PrivateDataHint = { {}, + "data", + {}, + "private" }; +} + +std::atomic<int64_t> cmDebuggerVariables::NextId(1); + +cmDebuggerVariables::cmDebuggerVariables( + std::shared_ptr<cmDebuggerVariablesManager> variablesManager, + std::string name, bool supportsVariableType) + : Id(NextId.fetch_add(1)) + , Name(std::move(name)) + , SupportsVariableType(supportsVariableType) + , VariablesManager(std::move(variablesManager)) +{ + VariablesManager->RegisterHandler( + Id, [this](dap::VariablesRequest const& request) { + (void)request; + return this->HandleVariablesRequest(); + }); +} + +cmDebuggerVariables::cmDebuggerVariables( + std::shared_ptr<cmDebuggerVariablesManager> variablesManager, + std::string name, bool supportsVariableType, + std::function<std::vector<cmDebuggerVariableEntry>()> getKeyValuesFunction) + : Id(NextId.fetch_add(1)) + , Name(std::move(name)) + , GetKeyValuesFunction(std::move(getKeyValuesFunction)) + , SupportsVariableType(supportsVariableType) + , VariablesManager(std::move(variablesManager)) +{ + VariablesManager->RegisterHandler( + Id, [this](dap::VariablesRequest const& request) { + (void)request; + return this->HandleVariablesRequest(); + }); +} + +void cmDebuggerVariables::AddSubVariables( + std::shared_ptr<cmDebuggerVariables> const& variables) +{ + if (variables != nullptr) { + SubVariables.emplace_back(variables); + } +} + +dap::array<dap::Variable> cmDebuggerVariables::HandleVariablesRequest() +{ + dap::array<dap::Variable> variables; + + if (GetKeyValuesFunction != nullptr) { + auto values = GetKeyValuesFunction(); + for (auto const& entry : values) { + if (IgnoreEmptyStringEntries && entry.Type == "string" && + entry.Value.empty()) { + continue; + } + variables.push_back(dap::Variable{ {}, + {}, + {}, + entry.Name, + {}, + PrivateDataHint, + entry.Type, + entry.Value, + 0 }); + } + } + + EnumerateSubVariablesIfAny(variables); + + if (EnableSorting) { + std::sort(variables.begin(), variables.end(), + [](dap::Variable const& a, dap::Variable const& b) { + return a.name < b.name; + }); + } + return variables; +} + +void cmDebuggerVariables::EnumerateSubVariablesIfAny( + dap::array<dap::Variable>& toBeReturned) const +{ + dap::array<dap::Variable> ret; + for (auto const& variables : SubVariables) { + toBeReturned.emplace_back( + dap::Variable{ {}, + {}, + {}, + variables->GetName(), + {}, + PrivatePropertyHint, + SupportsVariableType ? "collection" : nullptr, + variables->GetValue(), + variables->GetId() }); + } +} + +void cmDebuggerVariables::ClearSubVariables() +{ + SubVariables.clear(); +} + +cmDebuggerVariables::~cmDebuggerVariables() +{ + ClearSubVariables(); + VariablesManager->UnregisterHandler(Id); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerVariables.h b/Source/cmDebuggerVariables.h new file mode 100644 index 0000000..eaaf2a8 --- /dev/null +++ b/Source/cmDebuggerVariables.h @@ -0,0 +1,124 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <atomic> +#include <cstdint> +#include <functional> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include <cm3p/cppdap/types.h> // IWYU pragma: keep + +namespace cmDebugger { +class cmDebuggerVariablesManager; +} + +namespace dap { +struct Variable; +} + +namespace cmDebugger { + +struct cmDebuggerVariableEntry +{ + cmDebuggerVariableEntry() + : cmDebuggerVariableEntry("", "", "") + { + } + cmDebuggerVariableEntry(std::string name, std::string value, + std::string type) + : Name(std::move(name)) + , Value(std::move(value)) + , Type(std::move(type)) + { + } + cmDebuggerVariableEntry(std::string name, std::string value) + : Name(std::move(name)) + , Value(std::move(value)) + , Type("string") + { + } + cmDebuggerVariableEntry(std::string name, const char* value) + : Name(std::move(name)) + , Value(value == nullptr ? "" : value) + , Type("string") + { + } + cmDebuggerVariableEntry(std::string name, bool value) + : Name(std::move(name)) + , Value(value ? "TRUE" : "FALSE") + , Type("bool") + { + } + cmDebuggerVariableEntry(std::string name, int64_t value) + : Name(std::move(name)) + , Value(std::to_string(value)) + , Type("int") + { + } + cmDebuggerVariableEntry(std::string name, int value) + : Name(std::move(name)) + , Value(std::to_string(value)) + , Type("int") + { + } + std::string const Name; + std::string const Value; + std::string const Type; +}; + +class cmDebuggerVariables +{ + static std::atomic<int64_t> NextId; + int64_t Id; + std::string Name; + std::string Value; + + std::function<std::vector<cmDebuggerVariableEntry>()> GetKeyValuesFunction; + std::vector<std::shared_ptr<cmDebuggerVariables>> SubVariables; + bool IgnoreEmptyStringEntries = false; + bool EnableSorting = true; + + virtual dap::array<dap::Variable> HandleVariablesRequest(); + friend class cmDebuggerVariablesManager; + +protected: + const bool SupportsVariableType; + std::shared_ptr<cmDebuggerVariablesManager> VariablesManager; + void EnumerateSubVariablesIfAny( + dap::array<dap::Variable>& toBeReturned) const; + void ClearSubVariables(); + +public: + cmDebuggerVariables( + std::shared_ptr<cmDebuggerVariablesManager> variablesManager, + std::string name, bool supportsVariableType); + cmDebuggerVariables( + std::shared_ptr<cmDebuggerVariablesManager> variablesManager, + std::string name, bool supportsVariableType, + std::function<std::vector<cmDebuggerVariableEntry>()> getKeyValuesFunc); + inline int64_t GetId() const noexcept { return this->Id; } + inline std::string GetName() const noexcept { return this->Name; } + inline std::string GetValue() const noexcept { return this->Value; } + inline void SetValue(std::string const& value) noexcept + { + this->Value = value; + } + void AddSubVariables(std::shared_ptr<cmDebuggerVariables> const& variables); + inline void SetIgnoreEmptyStringEntries(bool value) noexcept + { + this->IgnoreEmptyStringEntries = value; + } + inline void SetEnableSorting(bool value) noexcept + { + this->EnableSorting = value; + } + virtual ~cmDebuggerVariables(); +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerVariablesHelper.cxx b/Source/cmDebuggerVariablesHelper.cxx new file mode 100644 index 0000000..42ce5e7 --- /dev/null +++ b/Source/cmDebuggerVariablesHelper.cxx @@ -0,0 +1,644 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDebuggerVariablesHelper.h" + +#include <algorithm> +#include <cstddef> +#include <functional> +#include <iomanip> +#include <map> +#include <sstream> + +#include "cm_codecvt.hxx" + +#include "cmDebuggerStackFrame.h" +#include "cmDebuggerVariables.h" +#include "cmFileSet.h" +#include "cmGlobalGenerator.h" +#include "cmList.h" +#include "cmListFileCache.h" +#include "cmMakefile.h" +#include "cmPropertyMap.h" +#include "cmState.h" +#include "cmStateSnapshot.h" +#include "cmTarget.h" +#include "cmTest.h" +#include "cmValue.h" +#include "cmake.h" + +namespace cmDebugger { + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::Create( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + cmPolicies::PolicyMap const& policyMap) +{ + static std::map<cmPolicies::PolicyStatus, std::string> policyStatusString = { + { cmPolicies::PolicyStatus::OLD, "OLD" }, + { cmPolicies::PolicyStatus::WARN, "WARN" }, + { cmPolicies::PolicyStatus::NEW, "NEW" }, + { cmPolicies::PolicyStatus::REQUIRED_IF_USED, "REQUIRED_IF_USED" }, + { cmPolicies::PolicyStatus::REQUIRED_ALWAYS, "REQUIRED_ALWAYS" } + }; + + return std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(cmPolicies::CMPCOUNT); + for (int i = 0; i < cmPolicies::CMPCOUNT; ++i) { + if (policyMap.IsDefined(static_cast<cmPolicies::PolicyID>(i))) { + auto status = policyMap.Get(static_cast<cmPolicies::PolicyID>(i)); + std::ostringstream ss; + ss << "CMP" << std::setfill('0') << std::setw(4) << i; + ret.emplace_back(ss.str(), policyStatusString[status]); + } + } + return ret; + }); +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<std::pair<std::string, std::string>> const& list) +{ + if (list.empty()) { + return {}; + } + + auto listVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(list.size()); + for (auto const& kv : list) { + ret.emplace_back(kv.first, kv.second); + } + return ret; + }); + + listVariables->SetValue(std::to_string(list.size())); + return listVariables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + cmBTStringRange const& entries) +{ + if (entries.empty()) { + return {}; + } + + auto sourceEntries = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType); + + for (auto const& entry : entries) { + auto arrayVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, entry.Value, supportsVariableType, [=]() { + cmList items{ entry.Value }; + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(items.size()); + int i = 0; + for (std::string const& item : items) { + ret.emplace_back("[" + std::to_string(i++) + "]", item); + } + return ret; + }); + arrayVariables->SetEnableSorting(false); + sourceEntries->AddSubVariables(arrayVariables); + } + + sourceEntries->SetValue(std::to_string(entries.size())); + return sourceEntries; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::set<std::string> const& values) +{ + if (values.empty()) { + return {}; + } + + auto arrayVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(values.size()); + int i = 0; + for (std::string const& value : values) { + ret.emplace_back("[" + std::to_string(i++) + "]", value); + } + return ret; + }); + arrayVariables->SetValue(std::to_string(values.size())); + arrayVariables->SetEnableSorting(false); + return arrayVariables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<std::string> const& values) +{ + if (values.empty()) { + return {}; + } + + auto arrayVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(values.size()); + int i = 0; + for (std::string const& value : values) { + ret.emplace_back("[" + std::to_string(i++) + "]", value); + } + return ret; + }); + + arrayVariables->SetValue(std::to_string(values.size())); + arrayVariables->SetEnableSorting(false); + return arrayVariables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<BT<std::string>> const& list) +{ + if (list.empty()) { + return {}; + } + + auto variables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(list.size()); + int i = 0; + for (auto const& item : list) { + ret.emplace_back("[" + std::to_string(i++) + "]", item.Value); + } + + return ret; + }); + + variables->SetValue(std::to_string(list.size())); + variables->SetEnableSorting(false); + return variables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, cmFileSet* fileSet) +{ + if (fileSet == nullptr) { + return {}; + } + + static auto visibilityString = [](cmFileSetVisibility visibility) { + switch (visibility) { + case cmFileSetVisibility::Private: + return "Private"; + case cmFileSetVisibility::Public: + return "Public"; + case cmFileSetVisibility::Interface: + return "Interface"; + default: + return "Unknown"; + } + }; + + auto variables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret{ + { "Name", fileSet->GetName() }, + { "Type", fileSet->GetType() }, + { "Visibility", visibilityString(fileSet->GetVisibility()) }, + }; + + return ret; + }); + + variables->AddSubVariables(CreateIfAny(variablesManager, "Directories", + supportsVariableType, + fileSet->GetDirectoryEntries())); + variables->AddSubVariables(CreateIfAny(variablesManager, "Files", + supportsVariableType, + fileSet->GetFileEntries())); + return variables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<cmFileSet*> const& fileSets) +{ + if (fileSets.empty()) { + return {}; + } + + auto fileSetsVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType); + + for (auto const& fileSet : fileSets) { + fileSetsVariables->AddSubVariables(CreateIfAny( + variablesManager, fileSet->GetName(), supportsVariableType, fileSet)); + } + + return fileSetsVariables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<cmTarget*> const& targets) +{ + if (targets.empty()) { + return {}; + } + + auto targetsVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType); + + for (auto const& target : targets) { + auto targetVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, target->GetName(), supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret = { + { "InstallPath", target->GetInstallPath() }, + { "IsAIX", target->IsAIX() }, + { "IsAndroidGuiExecutable", target->IsAndroidGuiExecutable() }, + { "IsAppBundleOnApple", target->IsAppBundleOnApple() }, + { "IsDLLPlatform", target->IsDLLPlatform() }, + { "IsExecutableWithExports", target->IsExecutableWithExports() }, + { "IsFrameworkOnApple", target->IsFrameworkOnApple() }, + { "IsImported", target->IsImported() }, + { "IsImportedGloballyVisible", target->IsImportedGloballyVisible() }, + { "IsPerConfig", target->IsPerConfig() }, + { "Name", target->GetName() }, + { "RuntimeInstallPath", target->GetRuntimeInstallPath() }, + { "Type", cmState::GetTargetTypeName(target->GetType()) }, + }; + + return ret; + }); + targetVariables->SetValue(cmState::GetTargetTypeName(target->GetType())); + + targetVariables->AddSubVariables(Create(variablesManager, "PolicyMap", + supportsVariableType, + target->GetPolicyMap())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "Properties", supportsVariableType, + target->GetProperties().GetList())); + + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "IncludeDirectories", supportsVariableType, + target->GetIncludeDirectoriesEntries())); + targetVariables->AddSubVariables(CreateIfAny(variablesManager, "Sources", + supportsVariableType, + target->GetSourceEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "CompileDefinitions", supportsVariableType, + target->GetCompileDefinitionsEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "CompileFeatures", supportsVariableType, + target->GetCompileFeaturesEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "CompileOptions", supportsVariableType, + target->GetCompileOptionsEntries())); + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "CxxModuleHeaderSets", supportsVariableType, + target->GetCxxModuleHeaderSetsEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "CxxModuleSets", supportsVariableType, + target->GetCxxModuleSetsEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "HeaderSets", supportsVariableType, + target->GetHeaderSetsEntries())); + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "InterfaceCxxModuleHeaderSets", supportsVariableType, + target->GetInterfaceCxxModuleHeaderSetsEntries())); + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "InterfaceHeaderSets", supportsVariableType, + target->GetInterfaceHeaderSetsEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "LinkDirectories", supportsVariableType, + target->GetLinkDirectoriesEntries())); + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "LinkImplementations", supportsVariableType, + target->GetLinkImplementationEntries())); + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "LinkInterfaceDirects", supportsVariableType, + target->GetLinkInterfaceDirectEntries())); + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "LinkInterfaceDirectExcludes", supportsVariableType, + target->GetLinkInterfaceDirectExcludeEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "LinkInterfaces", supportsVariableType, + target->GetLinkInterfaceEntries())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "LinkOptions", supportsVariableType, + target->GetLinkOptionsEntries())); + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "SystemIncludeDirectories", supportsVariableType, + target->GetSystemIncludeDirectories())); + targetVariables->AddSubVariables(CreateIfAny(variablesManager, "Makefile", + supportsVariableType, + target->GetMakefile())); + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "GlobalGenerator", supportsVariableType, + target->GetGlobalGenerator())); + + std::vector<cmFileSet*> allFileSets; + auto allFileSetNames = target->GetAllFileSetNames(); + allFileSets.reserve(allFileSetNames.size()); + for (auto const& fileSetName : allFileSetNames) { + allFileSets.emplace_back(target->GetFileSet(fileSetName)); + } + targetVariables->AddSubVariables(CreateIfAny( + variablesManager, "AllFileSets", supportsVariableType, allFileSets)); + + std::vector<cmFileSet*> allInterfaceFileSets; + auto allInterfaceFileSetNames = target->GetAllInterfaceFileSets(); + allInterfaceFileSets.reserve(allInterfaceFileSetNames.size()); + for (auto const& interfaceFileSetName : allInterfaceFileSetNames) { + allInterfaceFileSets.emplace_back( + target->GetFileSet(interfaceFileSetName)); + } + targetVariables->AddSubVariables( + CreateIfAny(variablesManager, "AllInterfaceFileSets", + supportsVariableType, allInterfaceFileSets)); + + targetVariables->SetIgnoreEmptyStringEntries(true); + targetsVariables->AddSubVariables(targetVariables); + } + + targetsVariables->SetValue(std::to_string(targets.size())); + return targetsVariables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::Create( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::shared_ptr<cmDebuggerStackFrame> const& frame) +{ + auto variables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + return std::vector<cmDebuggerVariableEntry>{ { "CurrentLine", + frame->GetLine() } }; + }); + + auto closureKeys = frame->GetMakefile()->GetStateSnapshot().ClosureKeys(); + auto locals = std::make_shared<cmDebuggerVariables>( + variablesManager, "Locals", supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(closureKeys.size()); + for (auto const& key : closureKeys) { + ret.emplace_back( + key, frame->GetMakefile()->GetStateSnapshot().GetDefinition(key)); + } + return ret; + }); + locals->SetValue(std::to_string(closureKeys.size())); + variables->AddSubVariables(locals); + + std::function<bool(std::string const&)> isDirectory = + [](std::string const& key) { + size_t pos1 = key.rfind("_DIR"); + size_t pos2 = key.rfind("_DIRECTORY"); + return !((pos1 == std::string::npos || pos1 != key.size() - 4) && + (pos2 == std::string::npos || pos2 != key.size() - 10)); + }; + auto directorySize = + std::count_if(closureKeys.begin(), closureKeys.end(), isDirectory); + auto directories = std::make_shared<cmDebuggerVariables>( + variablesManager, "Directories", supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + ret.reserve(directorySize); + for (auto const& key : closureKeys) { + if (isDirectory(key)) { + ret.emplace_back( + key, frame->GetMakefile()->GetStateSnapshot().GetDefinition(key)); + } + } + return ret; + }); + directories->SetValue(std::to_string(directorySize)); + variables->AddSubVariables(directories); + + auto cacheVariables = std::make_shared<cmDebuggerVariables>( + variablesManager, "CacheVariables", supportsVariableType); + auto* state = frame->GetMakefile()->GetCMakeInstance()->GetState(); + auto keys = state->GetCacheEntryKeys(); + for (auto const& key : keys) { + auto entry = std::make_shared<cmDebuggerVariables>( + variablesManager, + key + ":" + + cmState::CacheEntryTypeToString(state->GetCacheEntryType(key)), + supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret; + auto properties = state->GetCacheEntryPropertyList(key); + ret.reserve(properties.size() + 2); + for (auto const& propertyName : properties) { + ret.emplace_back(propertyName, + state->GetCacheEntryProperty(key, propertyName)); + } + + ret.emplace_back( + "TYPE", + cmState::CacheEntryTypeToString(state->GetCacheEntryType(key))); + ret.emplace_back("VALUE", state->GetCacheEntryValue(key)); + return ret; + }); + + entry->SetValue(state->GetCacheEntryValue(key)); + cacheVariables->AddSubVariables(entry); + } + + cacheVariables->SetValue(std::to_string(keys.size())); + variables->AddSubVariables(cacheVariables); + + auto targetVariables = + CreateIfAny(variablesManager, "Targets", supportsVariableType, + frame->GetMakefile()->GetOrderedTargets()); + + variables->AddSubVariables(targetVariables); + std::vector<cmTest*> tests; + frame->GetMakefile()->GetTests( + frame->GetMakefile()->GetDefaultConfiguration(), tests); + variables->AddSubVariables( + CreateIfAny(variablesManager, "Tests", supportsVariableType, tests)); + + return variables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, cmTest* test) +{ + if (test == nullptr) { + return {}; + } + + auto variables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret{ + { "CommandExpandLists", test->GetCommandExpandLists() }, + { "Name", test->GetName() }, + { "OldStyle", test->GetOldStyle() }, + }; + + return ret; + }); + + variables->AddSubVariables(CreateIfAny( + variablesManager, "Command", supportsVariableType, test->GetCommand())); + + variables->AddSubVariables(CreateIfAny(variablesManager, "Properties", + supportsVariableType, + test->GetProperties().GetList())); + return variables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<cmTest*> const& tests) +{ + if (tests.empty()) { + return {}; + } + + auto variables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType); + + for (auto const& test : tests) { + variables->AddSubVariables(CreateIfAny(variablesManager, test->GetName(), + supportsVariableType, test)); + } + variables->SetValue(std::to_string(tests.size())); + return variables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, cmMakefile* mf) +{ + if (mf == nullptr) { + return {}; + } + + auto AppleSDKTypeString = [&](cmMakefile::AppleSDK sdk) { + switch (sdk) { + case cmMakefile::AppleSDK::MacOS: + return "MacOS"; + case cmMakefile::AppleSDK::IPhoneOS: + return "IPhoneOS"; + case cmMakefile::AppleSDK::IPhoneSimulator: + return "IPhoneSimulator"; + case cmMakefile::AppleSDK::AppleTVOS: + return "AppleTVOS"; + case cmMakefile::AppleSDK::AppleTVSimulator: + return "AppleTVSimulator"; + default: + return "Unknown"; + } + }; + + auto variables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret = { + { "DefineFlags", mf->GetDefineFlags() }, + { "DirectoryId", mf->GetDirectoryId().String }, + { "IsRootMakefile", mf->IsRootMakefile() }, + { "HomeDirectory", mf->GetHomeDirectory() }, + { "HomeOutputDirectory", mf->GetHomeOutputDirectory() }, + { "CurrentSourceDirectory", mf->GetCurrentSourceDirectory() }, + { "CurrentBinaryDirectory", mf->GetCurrentBinaryDirectory() }, + { "PlatformIs32Bit", mf->PlatformIs32Bit() }, + { "PlatformIs64Bit", mf->PlatformIs64Bit() }, + { "PlatformIsx32", mf->PlatformIsx32() }, + { "AppleSDKType", AppleSDKTypeString(mf->GetAppleSDKType()) }, + { "PlatformIsAppleEmbedded", mf->PlatformIsAppleEmbedded() } + }; + + return ret; + }); + + variables->AddSubVariables(CreateIfAny( + variablesManager, "ListFiles", supportsVariableType, mf->GetListFiles())); + variables->AddSubVariables(CreateIfAny(variablesManager, "OutputFiles", + supportsVariableType, + mf->GetOutputFiles())); + + variables->SetIgnoreEmptyStringEntries(true); + variables->SetValue(mf->GetDirectoryId().String); + return variables; +} + +std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, cmGlobalGenerator* gen) +{ + if (gen == nullptr) { + return {}; + } + + auto makeFileEncodingString = [](codecvt::Encoding encoding) { + switch (encoding) { + case codecvt::Encoding::None: + return "None"; + case codecvt::Encoding::UTF8: + return "UTF8"; + case codecvt::Encoding::UTF8_WITH_BOM: + return "UTF8_WITH_BOM"; + case codecvt::Encoding::ANSI: + return "ANSI"; + case codecvt::Encoding::ConsoleOutput: + return "ConsoleOutput"; + default: + return "Unknown"; + } + }; + + auto variables = std::make_shared<cmDebuggerVariables>( + variablesManager, name, supportsVariableType, [=]() { + std::vector<cmDebuggerVariableEntry> ret = { + { "AllTargetName", gen->GetAllTargetName() }, + { "CleanTargetName", gen->GetCleanTargetName() }, + { "EditCacheCommand", gen->GetEditCacheCommand() }, + { "EditCacheTargetName", gen->GetEditCacheTargetName() }, + { "ExtraGeneratorName", gen->GetExtraGeneratorName() }, + { "ForceUnixPaths", gen->GetForceUnixPaths() }, + { "InstallLocalTargetName", gen->GetInstallLocalTargetName() }, + { "InstallStripTargetName", gen->GetInstallStripTargetName() }, + { "InstallTargetName", gen->GetInstallTargetName() }, + { "IsMultiConfig", gen->IsMultiConfig() }, + { "Name", gen->GetName() }, + { "MakefileEncoding", + makeFileEncodingString(gen->GetMakefileEncoding()) }, + { "PackageSourceTargetName", gen->GetPackageSourceTargetName() }, + { "PackageTargetName", gen->GetPackageTargetName() }, + { "PreinstallTargetName", gen->GetPreinstallTargetName() }, + { "NeedSymbolicMark", gen->GetNeedSymbolicMark() }, + { "RebuildCacheTargetName", gen->GetRebuildCacheTargetName() }, + { "TestTargetName", gen->GetTestTargetName() }, + { "UseLinkScript", gen->GetUseLinkScript() }, + }; + + return ret; + }); + + if (gen->GetInstallComponents() != nullptr) { + variables->AddSubVariables( + CreateIfAny(variablesManager, "InstallComponents", supportsVariableType, + *gen->GetInstallComponents())); + } + + variables->SetIgnoreEmptyStringEntries(true); + variables->SetValue(gen->GetName()); + + return variables; +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerVariablesHelper.h b/Source/cmDebuggerVariablesHelper.h new file mode 100644 index 0000000..9b11eaf --- /dev/null +++ b/Source/cmDebuggerVariablesHelper.h @@ -0,0 +1,106 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "cmAlgorithms.h" +#include "cmPolicies.h" + +class cmFileSet; +class cmGlobalGenerator; +class cmMakefile; +class cmTarget; +class cmTest; + +namespace cmDebugger { +class cmDebuggerStackFrame; +class cmDebuggerVariables; +class cmDebuggerVariablesManager; +} + +template <typename T> +class BT; + +namespace cmDebugger { + +class cmDebuggerVariablesHelper +{ + cmDebuggerVariablesHelper() = default; + +public: + static std::shared_ptr<cmDebuggerVariables> Create( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + cmPolicies::PolicyMap const& policyMap); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<std::pair<std::string, std::string>> const& list); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + cmBTStringRange const& entries); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::set<std::string> const& values); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<std::string> const& values); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<BT<std::string>> const& list); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, cmFileSet* fileSet); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<cmFileSet*> const& fileSets); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, cmTest* test); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<cmTest*> const& tests); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::vector<cmTarget*> const& targets); + + static std::shared_ptr<cmDebuggerVariables> Create( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + std::shared_ptr<cmDebuggerStackFrame> const& frame); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, cmMakefile* mf); + + static std::shared_ptr<cmDebuggerVariables> CreateIfAny( + std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager, + std::string const& name, bool supportsVariableType, + cmGlobalGenerator* gen); +}; + +} // namespace cmDebugger diff --git a/Source/cmDebuggerVariablesManager.cxx b/Source/cmDebuggerVariablesManager.cxx new file mode 100644 index 0000000..9b9b476 --- /dev/null +++ b/Source/cmDebuggerVariablesManager.cxx @@ -0,0 +1,38 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDebuggerVariablesManager.h" + +#include <utility> + +#include <cm3p/cppdap/protocol.h> +#include <cm3p/cppdap/types.h> + +namespace cmDebugger { + +void cmDebuggerVariablesManager::RegisterHandler( + int64_t id, + std::function<dap::array<dap::Variable>(dap::VariablesRequest const&)> + handler) +{ + VariablesHandlers[id] = std::move(handler); +} + +void cmDebuggerVariablesManager::UnregisterHandler(int64_t id) +{ + VariablesHandlers.erase(id); +} + +dap::array<dap::Variable> cmDebuggerVariablesManager::HandleVariablesRequest( + dap::VariablesRequest const& request) +{ + auto it = VariablesHandlers.find(request.variablesReference); + + if (it != VariablesHandlers.end()) { + return it->second(request); + } + + return dap::array<dap::Variable>(); +} + +} // namespace cmDebugger diff --git a/Source/cmDebuggerVariablesManager.h b/Source/cmDebuggerVariablesManager.h new file mode 100644 index 0000000..c219164 --- /dev/null +++ b/Source/cmDebuggerVariablesManager.h @@ -0,0 +1,40 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <cstdint> +#include <functional> +#include <unordered_map> +#include <vector> + +#include <cm3p/cppdap/types.h> // IWYU pragma: keep + +namespace dap { +struct Variable; +struct VariablesRequest; +} + +namespace cmDebugger { + +class cmDebuggerVariablesManager +{ + std::unordered_map< + int64_t, + std::function<dap::array<dap::Variable>(dap::VariablesRequest const&)>> + VariablesHandlers; + void RegisterHandler( + int64_t id, + std::function<dap::array<dap::Variable>(dap::VariablesRequest const&)> + handler); + void UnregisterHandler(int64_t id); + friend class cmDebuggerVariables; + +public: + cmDebuggerVariablesManager() = default; + dap::array<dap::Variable> HandleVariablesRequest( + dap::VariablesRequest const& request); +}; + +} // namespace cmDebugger diff --git a/Source/cmDependsFortran.cxx b/Source/cmDependsFortran.cxx index aede3fe..d038db7 100644 --- a/Source/cmDependsFortran.cxx +++ b/Source/cmDependsFortran.cxx @@ -416,7 +416,7 @@ bool cmDependsFortran::WriteDependenciesReal(std::string const& obj, // file is not updated. In such case the stamp file will be always // older than its prerequisite and trigger cmake_copy_f90_mod // on each new build. This is expected behavior for incremental - // builds and can not be changed without preforming recursive make + // builds and can not be changed without performing recursive make // calls that would considerably slow down the building process. makeDepends << stampFileForMake << ": " << obj_m << '\n'; makeDepends << "\t$(CMAKE_COMMAND) -E cmake_copy_f90_mod " << modFile diff --git a/Source/cmDependsJavaParserHelper.cxx b/Source/cmDependsJavaParserHelper.cxx index 0c5d310..6e617f6 100644 --- a/Source/cmDependsJavaParserHelper.cxx +++ b/Source/cmDependsJavaParserHelper.cxx @@ -155,7 +155,7 @@ void cmDependsJavaParserHelper::CheckEmpty( void cmDependsJavaParserHelper::PrepareElement( cmDependsJavaParserHelper::ParserType* me) { - // Inititalize self + // Initialize self me->str = nullptr; } diff --git a/Source/cmDyndepCollation.cxx b/Source/cmDyndepCollation.cxx index f45d81b..80e1357 100644 --- a/Source/cmDyndepCollation.cxx +++ b/Source/cmDyndepCollation.cxx @@ -358,6 +358,10 @@ cmDyndepCollation::ParseExportInfo(Json::Value const& tdi) fsi.Name = tdi_cxx_module_info["name"].asString(); fsi.RelativeDirectory = tdi_cxx_module_info["relative-directory"].asString(); + if (!fsi.RelativeDirectory.empty() && + fsi.RelativeDirectory.back() != '/') { + fsi.RelativeDirectory = cmStrCat(fsi.RelativeDirectory, '/'); + } fsi.SourcePath = tdi_cxx_module_info["source"].asString(); fsi.Type = tdi_cxx_module_info["type"].asString(); fsi.Visibility = cmFileSetVisibilityFromName( diff --git a/Source/cmExportBuildFileGenerator.cxx b/Source/cmExportBuildFileGenerator.cxx index 437ae69..a3637d8 100644 --- a/Source/cmExportBuildFileGenerator.cxx +++ b/Source/cmExportBuildFileGenerator.cxx @@ -542,6 +542,12 @@ bool cmExportBuildFileGenerator::GenerateImportCxxModuleConfigTargetInclusion( os.SetCopyIfDifferent(true); for (auto const* tgt : this->ExportedTargets) { + // Only targets with C++ module sources will have a + // collator-generated install script. + if (!tgt->HaveCxx20ModuleSources()) { + continue; + } + os << "include(\"${CMAKE_CURRENT_LIST_DIR}/target-" << tgt->GetExportName() << '-' << config << ".cmake\")\n"; } diff --git a/Source/cmExportInstallFileGenerator.cxx b/Source/cmExportInstallFileGenerator.cxx index 51c91f3..df119ae 100644 --- a/Source/cmExportInstallFileGenerator.cxx +++ b/Source/cmExportInstallFileGenerator.cxx @@ -752,6 +752,12 @@ bool cmExportInstallFileGenerator:: auto& prop_files = this->ConfigCxxModuleTargetFiles[config]; for (auto const* tgt : this->ExportedTargets) { + // Only targets with C++ module sources will have a + // collator-generated install script. + if (!tgt->HaveCxx20ModuleSources()) { + continue; + } + auto prop_filename = cmStrCat("target-", tgt->GetExportName(), '-', filename_config, ".cmake"); prop_files.emplace_back(cmStrCat(dest, prop_filename)); diff --git a/Source/cmExternalMakefileProjectGenerator.cxx b/Source/cmExternalMakefileProjectGenerator.cxx index 5895d66..5fecb35 100644 --- a/Source/cmExternalMakefileProjectGenerator.cxx +++ b/Source/cmExternalMakefileProjectGenerator.cxx @@ -17,14 +17,13 @@ void cmExternalMakefileProjectGenerator::EnableLanguage( std::string cmExternalMakefileProjectGenerator::CreateFullGeneratorName( const std::string& globalGenerator, const std::string& extraGenerator) { - std::string fullName; - if (!globalGenerator.empty()) { - if (!extraGenerator.empty()) { - fullName = cmStrCat(extraGenerator, " - "); - } - fullName += globalGenerator; + if (globalGenerator.empty()) { + return {}; } - return fullName; + if (extraGenerator.empty()) { + return globalGenerator; + } + return cmStrCat(extraGenerator, " - ", globalGenerator); } bool cmExternalMakefileProjectGenerator::Open( diff --git a/Source/cmFileAPI.cxx b/Source/cmFileAPI.cxx index d1d3d25..8b98916 100644 --- a/Source/cmFileAPI.cxx +++ b/Source/cmFileAPI.cxx @@ -728,7 +728,7 @@ std::string cmFileAPI::NoSupportedVersion( // The "codemodel" object kind. // Update Help/manual/cmake-file-api.7.rst when updating this constant. -static unsigned int const CodeModelV2Minor = 5; +static unsigned int const CodeModelV2Minor = 6; void cmFileAPI::BuildClientRequestCodeModel( ClientRequest& r, std::vector<RequestVersion> const& versions) diff --git a/Source/cmFileAPICodemodel.cxx b/Source/cmFileAPICodemodel.cxx index 4a8716f..280ebb0 100644 --- a/Source/cmFileAPICodemodel.cxx +++ b/Source/cmFileAPICodemodel.cxx @@ -328,6 +328,7 @@ struct CompileData std::vector<JBT<std::string>> Defines; std::vector<JBT<std::string>> PrecompileHeaders; std::vector<IncludeEntry> Includes; + std::vector<IncludeEntry> Frameworks; friend bool operator==(CompileData const& l, CompileData const& r) { @@ -335,7 +336,7 @@ struct CompileData l.Flags == r.Flags && l.Defines == r.Defines && l.PrecompileHeaders == r.PrecompileHeaders && l.LanguageStandard == r.LanguageStandard && - l.Includes == r.Includes); + l.Includes == r.Includes && l.Frameworks == r.Frameworks); } }; } @@ -356,6 +357,12 @@ struct hash<CompileData> hash<Json::ArrayIndex>()(i.Path.Backtrace.Index) ^ (i.IsSystem ? std::numeric_limits<size_t>::max() : 0)); } + for (auto const& i : in.Frameworks) { + result = result ^ + (hash<std::string>()(i.Path.Value) ^ + hash<Json::ArrayIndex>()(i.Path.Backtrace.Index) ^ + (i.IsSystem ? std::numeric_limits<size_t>::max() : 0)); + } for (auto const& i : in.Flags) { result = result ^ hash<std::string>()(i.Value) ^ hash<Json::ArrayIndex>()(i.Backtrace.Index); @@ -468,6 +475,7 @@ class Target Json::Value DumpPaths(); Json::Value DumpCompileData(CompileData const& cd); Json::Value DumpInclude(CompileData::IncludeEntry const& inc); + Json::Value DumpFramework(CompileData::IncludeEntry const& fw); Json::Value DumpPrecompileHeader(JBT<std::string> const& header); Json::Value DumpLanguageStandard(JBTs<std::string> const& standard); Json::Value DumpDefine(JBT<std::string> const& def); @@ -1294,9 +1302,15 @@ void Target::ProcessLanguage(std::string const& lang) std::vector<BT<std::string>> includePathList = lg->GetIncludeDirectories(this->GT, lang, this->Config); for (BT<std::string> const& i : includePathList) { - cd.Includes.emplace_back( - this->ToJBT(i), - this->GT->IsSystemIncludeDirectory(i.Value, this->Config, lang)); + if (this->GT->IsApple() && cmSystemTools::IsPathToFramework(i.Value)) { + cd.Frameworks.emplace_back( + this->ToJBT(i), + this->GT->IsSystemIncludeDirectory(i.Value, this->Config, lang)); + } else { + cd.Includes.emplace_back( + this->ToJBT(i), + this->GT->IsSystemIncludeDirectory(i.Value, this->Config, lang)); + } } std::vector<BT<std::string>> precompileHeaders = this->GT->GetPrecompileHeaders(this->Config, lang); @@ -1408,7 +1422,11 @@ CompileData Target::BuildCompileData(cmSourceFile* sf) bool const isSystemInclude = this->GT->IsSystemIncludeDirectory(i, this->Config, fd.Language); BT<std::string> include(i, tmpInclude.Backtrace); - fd.Includes.emplace_back(this->ToJBT(include), isSystemInclude); + if (this->GT->IsApple() && cmSystemTools::IsPathToFramework(i)) { + fd.Frameworks.emplace_back(this->ToJBT(include), isSystemInclude); + } else { + fd.Includes.emplace_back(this->ToJBT(include), isSystemInclude); + } } } } @@ -1481,6 +1499,13 @@ CompileData Target::MergeCompileData(CompileData const& fd) cd.Includes.insert(cd.Includes.end(), td.Includes.begin(), td.Includes.end()); + // Use source-specific frameworks followed by target-wide frameworks. + cd.Frameworks.reserve(fd.Frameworks.size() + td.Frameworks.size()); + cd.Frameworks.insert(cd.Frameworks.end(), fd.Frameworks.begin(), + fd.Frameworks.end()); + cd.Frameworks.insert(cd.Frameworks.end(), td.Frameworks.begin(), + td.Frameworks.end()); + // Use target-wide defines followed by source-specific defines. cd.Defines.reserve(td.Defines.size() + fd.Defines.size()); cd.Defines.insert(cd.Defines.end(), td.Defines.begin(), td.Defines.end()); @@ -1696,6 +1721,13 @@ Json::Value Target::DumpCompileData(CompileData const& cd) } result["includes"] = includes; } + if (!cd.Frameworks.empty()) { + Json::Value frameworks = Json::arrayValue; + for (auto const& i : cd.Frameworks) { + frameworks.append(this->DumpFramework(i)); + } + result["frameworks"] = frameworks; + } if (!cd.Defines.empty()) { Json::Value defines = Json::arrayValue; for (JBT<std::string> const& d : cd.Defines) { @@ -1729,6 +1761,12 @@ Json::Value Target::DumpInclude(CompileData::IncludeEntry const& inc) return include; } +Json::Value Target::DumpFramework(CompileData::IncludeEntry const& fw) +{ + // for now, idem as include + return this->DumpInclude(fw); +} + Json::Value Target::DumpPrecompileHeader(JBT<std::string> const& header) { Json::Value precompileHeader = Json::objectValue; diff --git a/Source/cmFindPackageCommand.cxx b/Source/cmFindPackageCommand.cxx index 98b085c..1c2a937 100644 --- a/Source/cmFindPackageCommand.cxx +++ b/Source/cmFindPackageCommand.cxx @@ -1804,11 +1804,11 @@ void cmFindPackageCommand::AppendToFoundProperty(const bool found) notFoundContents.push_back(this->Name); } - this->Makefile->GetState()->SetGlobalProperty( - "PACKAGES_FOUND", foundContents.to_string().c_str()); + this->Makefile->GetState()->SetGlobalProperty("PACKAGES_FOUND", + foundContents.to_string()); - this->Makefile->GetState()->SetGlobalProperty( - "PACKAGES_NOT_FOUND", notFoundContents.to_string().c_str()); + this->Makefile->GetState()->SetGlobalProperty("PACKAGES_NOT_FOUND", + notFoundContents.to_string()); } void cmFindPackageCommand::AppendSuccessInformation() @@ -1845,7 +1845,7 @@ void cmFindPackageCommand::AppendSuccessInformation() cmStrCat(this->VersionExact ? "==" : ">=", ' ', this->Version); } this->Makefile->GetState()->SetGlobalProperty(versionInfoPropName, - versionInfo.c_str()); + versionInfo); if (this->Required) { std::string const requiredInfoPropName = cmStrCat("_CMAKE_", this->Name, "_TYPE"); diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index 32f0cbd..f8455c8 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -59,8 +59,6 @@ namespace { using LinkInterfaceFor = cmGeneratorTarget::LinkInterfaceFor; -const cmsys::RegularExpression FrameworkRegularExpression( - "^(.*/)?([^/]*)\\.framework/(.*)$"); const std::string kINTERFACE_LINK_LIBRARIES = "INTERFACE_LINK_LIBRARIES"; const std::string kINTERFACE_LINK_LIBRARIES_DIRECT = "INTERFACE_LINK_LIBRARIES_DIRECT"; @@ -2434,11 +2432,10 @@ std::string cmGeneratorTarget::GetSOName( } // Use the soname given if any. if (this->IsFrameworkOnApple()) { - cmsys::RegularExpressionMatch match; - if (FrameworkRegularExpression.find(info->SOName.c_str(), match)) { - auto frameworkName = match.match(2); - auto fileName = match.match(3); - return cmStrCat(frameworkName, ".framework/", fileName); + auto fwDescriptor = this->GetGlobalGenerator()->SplitFrameworkPath( + info->SOName, cmGlobalGenerator::FrameworkFormat::Strict); + if (fwDescriptor) { + return fwDescriptor->GetVersionedName(); } } if (cmHasLiteralPrefix(info->SOName, "@rpath/")) { @@ -7036,13 +7033,10 @@ std::string cmGeneratorTarget::GetDirectory( if (this->IsImported()) { auto fullPath = this->Target->ImportedGetFullPath(config, artifact); if (this->IsFrameworkOnApple()) { - cmsys::RegularExpressionMatch match; - if (FrameworkRegularExpression.find(fullPath.c_str(), match)) { - auto path = match.match(1); - if (!path.empty()) { - path.erase(path.length() - 1); - } - return path; + auto fwDescriptor = this->GetGlobalGenerator()->SplitFrameworkPath( + fullPath, cmGlobalGenerator::FrameworkFormat::Strict); + if (fwDescriptor) { + return fwDescriptor->Directory; } } // Return the directory from which the target is imported. diff --git a/Source/cmGetTestPropertyCommand.cxx b/Source/cmGetTestPropertyCommand.cxx index a4ac9f6..36446c9 100644 --- a/Source/cmGetTestPropertyCommand.cxx +++ b/Source/cmGetTestPropertyCommand.cxx @@ -25,7 +25,7 @@ bool cmGetTestPropertyCommand(std::vector<std::string> const& args, prop = test->GetProperty(args[1]); } if (prop) { - mf.AddDefinition(var, prop->c_str()); + mf.AddDefinition(var, prop); return true; } } diff --git a/Source/cmGlobalCommonGenerator.cxx b/Source/cmGlobalCommonGenerator.cxx index 7a44452..513e3bf 100644 --- a/Source/cmGlobalCommonGenerator.cxx +++ b/Source/cmGlobalCommonGenerator.cxx @@ -34,8 +34,8 @@ cmGlobalCommonGenerator::ComputeDirectoryTargets() const { std::map<std::string, DirectoryTarget> dirTargets; for (const auto& lg : this->LocalGenerators) { - std::string const& currentBinaryDir( - lg->GetStateSnapshot().GetDirectory().GetCurrentBinary()); + std::string currentBinaryDir = + lg->GetStateSnapshot().GetDirectory().GetCurrentBinary(); DirectoryTarget& dirTarget = dirTargets[currentBinaryDir]; dirTarget.LG = lg.get(); const std::vector<std::string>& configs = @@ -68,7 +68,7 @@ cmGlobalCommonGenerator::ComputeDirectoryTargets() const for (cmStateSnapshot dir = lg->GetStateSnapshot().GetBuildsystemDirectoryParent(); dir.IsValid(); dir = dir.GetBuildsystemDirectoryParent()) { - std::string const& d = dir.GetDirectory().GetCurrentBinary(); + std::string d = dir.GetDirectory().GetCurrentBinary(); dirTargets[d].Targets.emplace_back(t); } } diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index 040f500..7e6b16a 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -47,6 +47,7 @@ #include "cmMSVC60LinkLineComputer.h" #include "cmMakefile.h" #include "cmMessageType.h" +#include "cmOutputConverter.h" #include "cmPolicies.h" #include "cmRange.h" #include "cmSourceFile.h" @@ -73,6 +74,23 @@ const std::string kCMAKE_PLATFORM_INFO_INITIALIZED = class cmInstalledFile; +namespace detail { +std::string GeneratedMakeCommand::QuotedPrintable() const +{ + std::string output; + const char* sep = ""; + int flags = 0; +#if !defined(_WIN32) + flags |= cmOutputConverter::Shell_Flag_IsUnix; +#endif + for (auto const& arg : this->PrimaryCommand) { + output += cmStrCat(sep, cmOutputConverter::EscapeForShell(arg, flags)); + sep = " "; + } + return output; +} +} + bool cmTarget::StrictTargetComparison::operator()(cmTarget const* t1, cmTarget const* t2) const { @@ -268,7 +286,7 @@ void cmGlobalGenerator::ResolveLanguageCompiler(const std::string& lang, changeVars += ";"; changeVars += *cname; this->GetCMakeInstance()->GetState()->SetGlobalProperty( - "__CMAKE_DELETE_CACHE_CHANGE_VARS_", changeVars.c_str()); + "__CMAKE_DELETE_CACHE_CHANGE_VARS_", changeVars); } } } @@ -2058,9 +2076,12 @@ int cmGlobalGenerator::TryCompile(int jobs, const std::string& srcdir, mf->GetSafeDefinition("CMAKE_TRY_COMPILE_CONFIGURATION"); cmBuildOptions defaultBuildOptions(false, fast, PackageResolveMode::Disable); - return this->Build(jobs, srcdir, bindir, projectName, newTarget, output, "", - config, defaultBuildOptions, true, - this->TryCompileTimeout); + std::stringstream ostr; + auto ret = + this->Build(jobs, srcdir, bindir, projectName, newTarget, ostr, "", config, + defaultBuildOptions, true, this->TryCompileTimeout); + output = ostr.str(); + return ret; } std::vector<cmGlobalGenerator::GeneratedMakeCommand> @@ -2085,7 +2106,7 @@ void cmGlobalGenerator::PrintBuildCommandAdvice(std::ostream& /*os*/, int cmGlobalGenerator::Build( int jobs, const std::string& /*unused*/, const std::string& bindir, const std::string& projectName, const std::vector<std::string>& targets, - std::string& output, const std::string& makeCommandCSTR, + std::ostream& ostr, const std::string& makeCommandCSTR, const std::string& config, const cmBuildOptions& buildOptions, bool verbose, cmDuration timeout, cmSystemTools::OutputOption outputflag, std::vector<std::string> const& nativeOptions) @@ -2096,16 +2117,13 @@ int cmGlobalGenerator::Build( * Run an executable command and put the stdout in output. */ cmWorkingDirectory workdir(bindir); - output += "Change Dir: "; - output += bindir; - output += "\n"; + ostr << "Change Dir: '" << bindir << '\'' << std::endl; if (workdir.Failed()) { cmSystemTools::SetRunCommandHideConsole(hideconsole); std::string err = cmStrCat("Failed to change directory: ", std::strerror(workdir.GetLastResult())); cmSystemTools::Error(err); - output += err; - output += "\n"; + ostr << err << std::endl; return 1; } std::string realConfig = config; @@ -2134,9 +2152,8 @@ int cmGlobalGenerator::Build( this->GenerateBuildCommand(makeCommandCSTR, projectName, bindir, { "clean" }, realConfig, jobs, verbose, buildOptions); - output += "\nRun Clean Command:"; - output += cleanCommand.front().Printable(); - output += "\n"; + ostr << "\nRun Clean Command: " << cleanCommand.front().QuotedPrintable() + << std::endl; if (cleanCommand.size() != 1) { this->GetCMakeInstance()->IssueMessage(MessageType::INTERNAL_ERROR, "The generator did not produce " @@ -2149,27 +2166,33 @@ int cmGlobalGenerator::Build( nullptr, outputflag, timeout)) { cmSystemTools::SetRunCommandHideConsole(hideconsole); cmSystemTools::Error("Generator: execution of make clean failed."); - output += *outputPtr; - output += "\nGenerator: execution of make clean failed.\n"; + ostr << *outputPtr << "\nGenerator: execution of make clean failed." + << std::endl; return 1; } - output += *outputPtr; + ostr << *outputPtr; } // now build std::string makeCommandStr; - output += "\nRun Build Command(s):"; + std::string outputMakeCommandStr; + bool isWatcomWMake = this->CMakeInstance->GetState()->UseWatcomWMake(); + bool needBuildOutput = isWatcomWMake; + std::string buildOutput; + ostr << "\nRun Build Command(s): "; retVal = 0; for (auto command = makeCommand.begin(); command != makeCommand.end() && retVal == 0; ++command) { makeCommandStr = command->Printable(); - if (command != makeCommand.end()) { + outputMakeCommandStr = command->QuotedPrintable(); + if ((command + 1) != makeCommand.end()) { makeCommandStr += " && "; + outputMakeCommandStr += " && "; } - output += makeCommandStr; + ostr << outputMakeCommandStr << std::endl; if (!cmSystemTools::RunSingleCommand(command->PrimaryCommand, outputPtr, outputPtr, &retVal, nullptr, outputflag, timeout)) { @@ -2177,21 +2200,24 @@ int cmGlobalGenerator::Build( cmSystemTools::Error( "Generator: execution of make failed. Make command was: " + makeCommandStr); - output += *outputPtr; - output += "\nGenerator: execution of make failed. Make command was: " + - makeCommandStr + "\n"; + ostr << *outputPtr + << "\nGenerator: execution of make failed. Make command was: " + << outputMakeCommandStr << std::endl; return 1; } - output += *outputPtr; + ostr << *outputPtr << std::flush; + if (needBuildOutput) { + buildOutput += *outputPtr; + } } - output += "\n"; + ostr << std::endl; cmSystemTools::SetRunCommandHideConsole(hideconsole); // The OpenWatcom tools do not return an error code when a link // library is not found! - if (this->CMakeInstance->GetState()->UseWatcomWMake() && retVal == 0 && - output.find("W1008: cannot open") != std::string::npos) { + if (isWatcomWMake && retVal == 0 && + buildOutput.find("W1008: cannot open") != std::string::npos) { retVal = 1; } @@ -2598,14 +2624,14 @@ cmGlobalGenerator::SplitFrameworkPath(const std::string& path, // or (/path/to/)?FwName.framework/FwName(.tbd)? // or (/path/to/)?FwName.framework/Versions/*/FwName(.tbd)? static cmsys::RegularExpression frameworkPath( - "((.+)/)?(.+)\\.framework(/Versions/[^/]+)?(/(.+))?$"); + "((.+)/)?([^/]+)\\.framework(/Versions/([^/]+))?(/(.+))?$"); auto ext = cmSystemTools::GetFilenameLastExtension(path); if ((ext.empty() || ext == ".tbd" || ext == ".framework") && frameworkPath.find(path)) { auto name = frameworkPath.match(3); auto libname = - cmSystemTools::GetFilenameWithoutExtension(frameworkPath.match(6)); + cmSystemTools::GetFilenameWithoutExtension(frameworkPath.match(7)); if (format == FrameworkFormat::Strict && libname.empty()) { return cm::nullopt; } @@ -2614,11 +2640,12 @@ cmGlobalGenerator::SplitFrameworkPath(const std::string& path, } if (libname.empty() || name.size() == libname.size()) { - return FrameworkDescriptor{ frameworkPath.match(2), name }; + return FrameworkDescriptor{ frameworkPath.match(2), + frameworkPath.match(5), name }; } - return FrameworkDescriptor{ frameworkPath.match(2), name, - libname.substr(name.size()) }; + return FrameworkDescriptor{ frameworkPath.match(2), frameworkPath.match(5), + name, libname.substr(name.size()) }; } if (format == FrameworkFormat::Extended) { diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index 79fe52c..01afabd 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -85,6 +85,7 @@ struct GeneratedMakeCommand } std::string Printable() const { return cmJoin(this->PrimaryCommand, " "); } + std::string QuotedPrintable() const; std::vector<std::string> PrimaryCommand; bool RequiresOutputForward = false; @@ -233,7 +234,7 @@ public: int Build( int jobs, const std::string& srcdir, const std::string& bindir, const std::string& projectName, - std::vector<std::string> const& targetNames, std::string& output, + std::vector<std::string> const& targetNames, std::ostream& ostr, const std::string& makeProgram, const std::string& config, const cmBuildOptions& buildOptions, bool verbose, cmDuration timeout, cmSystemTools::OutputOption outputflag = cmSystemTools::OUTPUT_NONE, @@ -384,9 +385,17 @@ public: , Name(std::move(name)) { } - FrameworkDescriptor(std::string directory, std::string name, - std::string suffix) + FrameworkDescriptor(std::string directory, std::string version, + std::string name) : Directory(std::move(directory)) + , Version(std::move(version)) + , Name(std::move(name)) + { + } + FrameworkDescriptor(std::string directory, std::string version, + std::string name, std::string suffix) + : Directory(std::move(directory)) + , Version(std::move(version)) , Name(std::move(name)) , Suffix(std::move(suffix)) { @@ -400,6 +409,13 @@ public: { return cmStrCat(this->Name, ".framework/"_s, this->Name, this->Suffix); } + std::string GetVersionedName() const + { + return this->Version.empty() + ? this->GetFullName() + : cmStrCat(this->Name, ".framework/Versions/"_s, this->Version, '/', + this->Name, this->Suffix); + } std::string GetFrameworkPath() const { return this->Directory.empty() @@ -412,8 +428,15 @@ public: ? this->GetFullName() : cmStrCat(this->Directory, '/', this->GetFullName()); } + std::string GetVersionedPath() const + { + return this->Directory.empty() + ? this->GetVersionedName() + : cmStrCat(this->Directory, '/', this->GetVersionedName()); + } const std::string Directory; + const std::string Version; const std::string Name; const std::string Suffix; }; diff --git a/Source/cmGlobalGhsMultiGenerator.cxx b/Source/cmGlobalGhsMultiGenerator.cxx index 578e805..2453bfc 100644 --- a/Source/cmGlobalGhsMultiGenerator.cxx +++ b/Source/cmGlobalGhsMultiGenerator.cxx @@ -101,11 +101,11 @@ bool cmGlobalGhsMultiGenerator::SetGeneratorToolset(std::string const& ts, /* check if the toolset changed from last generate */ if (cmNonempty(prevTool) && !cmSystemTools::ComparePath(gbuild, *prevTool)) { - std::string const& e = - cmStrCat("toolset build tool: ", gbuild, - "\nDoes not match the previously used build tool: ", *prevTool, - "\nEither remove the CMakeCache.txt file and CMakeFiles " - "directory or choose a different binary directory."); + std::string const& e = cmStrCat( + "toolset build tool: ", gbuild, '\n', + "Does not match the previously used build tool: ", *prevTool, '\n', + "Either remove the CMakeCache.txt file and CMakeFiles " + "directory or choose a different binary directory."); mf->IssueMessage(MessageType::FATAL_ERROR, e); return false; } diff --git a/Source/cmGlobalVisualStudio7Generator.cxx b/Source/cmGlobalVisualStudio7Generator.cxx index 694698e..b254777 100644 --- a/Source/cmGlobalVisualStudio7Generator.cxx +++ b/Source/cmGlobalVisualStudio7Generator.cxx @@ -716,7 +716,7 @@ std::set<std::string> cmGlobalVisualStudio7Generator::IsPartOfDefaultBuild( cmGeneratorTarget const* target) { std::set<std::string> activeConfigs; - // if it is a utilitiy target then only make it part of the + // if it is a utility target then only make it part of the // default build if another target depends on it int type = target->GetType(); if (type == cmStateEnums::GLOBAL_TARGET) { diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 01afc44..92ba2d4 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -68,6 +68,10 @@ # include "cmVariableWatch.h" #endif +#ifdef CMake_ENABLE_DEBUGGER +# include "cmDebuggerAdapter.h" +#endif + #ifndef __has_feature # define __has_feature(x) 0 #endif @@ -424,6 +428,13 @@ public: return argsValue; }); #endif +#ifdef CMake_ENABLE_DEBUGGER + if (this->Makefile->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->Makefile->GetCMakeInstance() + ->GetDebugAdapter() + ->OnBeginFunctionCall(mf, lfc.FilePath, lff); + } +#endif } ~cmMakefileCall() @@ -434,6 +445,13 @@ public: this->Makefile->ExecutionStatusStack.pop_back(); --this->Makefile->RecursionDepth; this->Makefile->Backtrace = this->Makefile->Backtrace.Pop(); +#ifdef CMake_ENABLE_DEBUGGER + if (this->Makefile->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->Makefile->GetCMakeInstance() + ->GetDebugAdapter() + ->OnEndFunctionCall(); + } +#endif } cmMakefileCall(const cmMakefileCall&) = delete; @@ -663,12 +681,33 @@ bool cmMakefile::ReadDependentFile(const std::string& filename, IncludeScope incScope(this, filenametoread, noPolicyScope); +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse( + this, filenametoread); + } +#endif + cmListFile listFile; if (!listFile.ParseFile(filenametoread.c_str(), this->GetMessenger(), this->Backtrace)) { +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); + } +#endif + return false; } +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); + this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully( + filenametoread, listFile.Functions); + } +#endif + this->RunListFile(listFile, filenametoread); if (cmSystemTools::GetFatalErrorOccurred()) { incScope.Quiet(); @@ -764,12 +803,33 @@ bool cmMakefile::ReadListFile(const std::string& filename) ListFileScope scope(this, filenametoread); +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse( + this, filenametoread); + } +#endif + cmListFile listFile; if (!listFile.ParseFile(filenametoread.c_str(), this->GetMessenger(), this->Backtrace)) { +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); + } +#endif + return false; } +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); + this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully( + filenametoread, listFile.Functions); + } +#endif + this->RunListFile(listFile, filenametoread); if (cmSystemTools::GetFatalErrorOccurred()) { scope.Quiet(); @@ -791,6 +851,13 @@ bool cmMakefile::ReadListFileAsString(const std::string& content, return false; } +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully( + filenametoread, listFile.Functions); + } +#endif + this->RunListFile(listFile, filenametoread); if (cmSystemTools::GetFatalErrorOccurred()) { scope.Quiet(); @@ -1658,11 +1725,33 @@ void cmMakefile::Configure() assert(cmSystemTools::FileExists(currentStart, true)); this->AddDefinition("CMAKE_PARENT_LIST_FILE", currentStart); +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse( + this, currentStart); + } +#endif + cmListFile listFile; if (!listFile.ParseFile(currentStart.c_str(), this->GetMessenger(), this->Backtrace)) { +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); + } +#endif + return; } + +#ifdef CMake_ENABLE_DEBUGGER + if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) { + this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse(); + this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully( + currentStart, listFile.Functions); + } +#endif + if (this->IsRootMakefile()) { bool hasVersion = false; // search for the right policy command @@ -3769,6 +3858,12 @@ void cmMakefile::DisplayStatus(const std::string& message, float s) const return; } cm->UpdateProgress(message, s); + +#ifdef CMake_ENABLE_DEBUGGER + if (cm->GetDebugAdapter() != nullptr) { + cm->GetDebugAdapter()->OnMessageOutput(MessageType::MESSAGE, message); + } +#endif } std::string cmMakefile::GetModulesFile(const std::string& filename, @@ -4044,10 +4139,6 @@ int cmMakefile::ConfigureFile(const std::string& infile, return res; } -void cmMakefile::SetProperty(const std::string& prop, const char* value) -{ - this->StateSnapshot.GetDirectory().SetProperty(prop, value, this->Backtrace); -} void cmMakefile::SetProperty(const std::string& prop, cmValue value) { this->StateSnapshot.GetDirectory().SetProperty(prop, value, this->Backtrace); diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index d1f5be5..6fdadab 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -425,7 +425,7 @@ public: */ void SetIncludeRegularExpression(const std::string& regex) { - this->SetProperty("INCLUDE_REGULAR_EXPRESSION", regex.c_str()); + this->SetProperty("INCLUDE_REGULAR_EXPRESSION", regex); } const std::string& GetIncludeRegularExpression() const { @@ -801,8 +801,11 @@ public: std::string& debugBuffer) const; //! Set/Get a property of this directory - void SetProperty(const std::string& prop, const char* value); void SetProperty(const std::string& prop, cmValue value); + void SetProperty(const std::string& prop, std::nullptr_t) + { + this->SetProperty(prop, cmValue{ nullptr }); + } void SetProperty(const std::string& prop, const std::string& value) { this->SetProperty(prop, cmValue(value)); diff --git a/Source/cmMakefileLibraryTargetGenerator.cxx b/Source/cmMakefileLibraryTargetGenerator.cxx index b07a74b..fc3caa1 100644 --- a/Source/cmMakefileLibraryTargetGenerator.cxx +++ b/Source/cmMakefileLibraryTargetGenerator.cxx @@ -785,7 +785,7 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules( if (this->GeneratorTarget->HasSOName(this->GetConfigName())) { vars.SONameFlag = this->Makefile->GetSONameFlag(linkLanguage); targetOutSOName = this->LocalGenerator->ConvertToOutputFormat( - this->TargetNames.SharedObject.c_str(), cmOutputConverter::SHELL); + this->TargetNames.SharedObject, cmOutputConverter::SHELL); vars.TargetSOName = targetOutSOName.c_str(); } vars.LinkFlags = linkFlags.c_str(); diff --git a/Source/cmMessageCommand.cxx b/Source/cmMessageCommand.cxx index baf40f8..68b3a5d 100644 --- a/Source/cmMessageCommand.cxx +++ b/Source/cmMessageCommand.cxx @@ -3,6 +3,7 @@ #include "cmMessageCommand.h" #include <cassert> +#include <memory> #include <utility> #include <cm/string_view> @@ -19,6 +20,10 @@ #include "cmSystemTools.h" #include "cmake.h" +#ifdef CMake_ENABLE_DEBUGGER +# include "cmDebuggerAdapter.h" +#endif + namespace { enum class CheckingType @@ -202,6 +207,12 @@ bool cmMessageCommand(std::vector<std::string> const& args, case Message::LogLevel::LOG_NOTICE: cmSystemTools::Message(IndentText(message, mf)); +#ifdef CMake_ENABLE_DEBUGGER + if (mf.GetCMakeInstance()->GetDebugAdapter() != nullptr) { + mf.GetCMakeInstance()->GetDebugAdapter()->OnMessageOutput(type, + message); + } +#endif break; case Message::LogLevel::LOG_STATUS: diff --git a/Source/cmMessenger.cxx b/Source/cmMessenger.cxx index ff513be..4e975d1 100644 --- a/Source/cmMessenger.cxx +++ b/Source/cmMessenger.cxx @@ -16,53 +16,44 @@ #include "cmsys/Terminal.h" +#ifdef CMake_ENABLE_DEBUGGER +# include "cmDebuggerAdapter.h" +#endif + MessageType cmMessenger::ConvertMessageType(MessageType t) const { - bool warningsAsErrors; - if (t == MessageType::AUTHOR_WARNING || t == MessageType::AUTHOR_ERROR) { - warningsAsErrors = this->GetDevWarningsAsErrors(); - if (warningsAsErrors && t == MessageType::AUTHOR_WARNING) { - t = MessageType::AUTHOR_ERROR; - } else if (!warningsAsErrors && t == MessageType::AUTHOR_ERROR) { - t = MessageType::AUTHOR_WARNING; + if (this->GetDevWarningsAsErrors()) { + return MessageType::AUTHOR_ERROR; } - } else if (t == MessageType::DEPRECATION_WARNING || - t == MessageType::DEPRECATION_ERROR) { - warningsAsErrors = this->GetDeprecatedWarningsAsErrors(); - if (warningsAsErrors && t == MessageType::DEPRECATION_WARNING) { - t = MessageType::DEPRECATION_ERROR; - } else if (!warningsAsErrors && t == MessageType::DEPRECATION_ERROR) { - t = MessageType::DEPRECATION_WARNING; + return MessageType::AUTHOR_WARNING; + } + if (t == MessageType::DEPRECATION_WARNING || + t == MessageType::DEPRECATION_ERROR) { + if (this->GetDeprecatedWarningsAsErrors()) { + return MessageType::DEPRECATION_ERROR; } + return MessageType::DEPRECATION_WARNING; } - return t; } bool cmMessenger::IsMessageTypeVisible(MessageType t) const { - bool isVisible = true; - if (t == MessageType::DEPRECATION_ERROR) { - if (!this->GetDeprecatedWarningsAsErrors()) { - isVisible = false; - } - } else if (t == MessageType::DEPRECATION_WARNING) { - if (this->GetSuppressDeprecatedWarnings()) { - isVisible = false; - } - } else if (t == MessageType::AUTHOR_ERROR) { - if (!this->GetDevWarningsAsErrors()) { - isVisible = false; - } - } else if (t == MessageType::AUTHOR_WARNING) { - if (this->GetSuppressDevWarnings()) { - isVisible = false; - } + return this->GetDeprecatedWarningsAsErrors(); + } + if (t == MessageType::DEPRECATION_WARNING) { + return !this->GetSuppressDeprecatedWarnings(); + } + if (t == MessageType::AUTHOR_ERROR) { + return this->GetDevWarningsAsErrors(); + } + if (t == MessageType::AUTHOR_WARNING) { + return !this->GetSuppressDevWarnings(); } - return isVisible; + return true; } static bool printMessagePreamble(MessageType t, std::ostream& msg) @@ -220,6 +211,12 @@ void cmMessenger::DisplayMessage(MessageType t, const std::string& text, PrintCallStack(msg, backtrace, this->TopSource); displayMessage(t, msg); + +#ifdef CMake_ENABLE_DEBUGGER + if (DebuggerAdapter != nullptr) { + DebuggerAdapter->OnMessageOutput(t, msg.str()); + } +#endif } void cmMessenger::PrintBacktraceTitle(std::ostream& out, diff --git a/Source/cmMessenger.h b/Source/cmMessenger.h index 451add0..bdefb00 100644 --- a/Source/cmMessenger.h +++ b/Source/cmMessenger.h @@ -5,6 +5,7 @@ #include "cmConfigure.h" // IWYU pragma: keep #include <iosfwd> +#include <memory> #include <string> #include <cm/optional> @@ -12,6 +13,12 @@ #include "cmListFileCache.h" #include "cmMessageType.h" +#ifdef CMake_ENABLE_DEBUGGER +namespace cmDebugger { +class cmDebuggerAdapter; +} +#endif + class cmMessenger { public: @@ -55,6 +62,13 @@ public: // Print the top of a backtrace. void PrintBacktraceTitle(std::ostream& out, cmListFileBacktrace const& bt) const; +#ifdef CMake_ENABLE_DEBUGGER + void SetDebuggerAdapter( + std::shared_ptr<cmDebugger::cmDebuggerAdapter> const& debuggerAdapter) + { + DebuggerAdapter = debuggerAdapter; + } +#endif private: bool IsMessageTypeVisible(MessageType t) const; @@ -66,4 +80,7 @@ private: bool SuppressDeprecatedWarnings = false; bool DevWarningsAsErrors = false; bool DeprecatedWarningsAsErrors = false; +#ifdef CMake_ENABLE_DEBUGGER + std::shared_ptr<cmDebugger::cmDebuggerAdapter> DebuggerAdapter; +#endif }; diff --git a/Source/cmOutputConverter.cxx b/Source/cmOutputConverter.cxx index 53cb21e..02981ae 100644 --- a/Source/cmOutputConverter.cxx +++ b/Source/cmOutputConverter.cxx @@ -243,11 +243,6 @@ std::string cmOutputConverter::EscapeForShell(cm::string_view str, bool unescapeNinjaConfiguration, bool forResponse) const { - // Do not escape shell operators. - if (cmOutputConverterIsShellOperator(str)) { - return std::string(str); - } - // Compute the flags for the target shell environment. int flags = 0; if (this->GetState()->UseWindowsVSIDE()) { @@ -283,6 +278,16 @@ std::string cmOutputConverter::EscapeForShell(cm::string_view str, flags |= Shell_Flag_IsUnix; } + return cmOutputConverter::EscapeForShell(str, flags); +} + +std::string cmOutputConverter::EscapeForShell(cm::string_view str, int flags) +{ + // Do not escape shell operators. + if (cmOutputConverterIsShellOperator(str)) { + return std::string(str); + } + return Shell_GetArgument(str, flags); } diff --git a/Source/cmOutputConverter.h b/Source/cmOutputConverter.h index 625d897..0ee7afb 100644 --- a/Source/cmOutputConverter.h +++ b/Source/cmOutputConverter.h @@ -107,6 +107,7 @@ public: bool forEcho = false, bool useWatcomQuote = false, bool unescapeNinjaConfiguration = false, bool forResponse = false) const; + static std::string EscapeForShell(cm::string_view str, int flags); enum class WrapQuotes { diff --git a/Source/cmPropertyMap.cxx b/Source/cmPropertyMap.cxx index b15000f..568a3d2 100644 --- a/Source/cmPropertyMap.cxx +++ b/Source/cmPropertyMap.cxx @@ -10,14 +10,9 @@ void cmPropertyMap::Clear() this->Map_.clear(); } -void cmPropertyMap::SetProperty(const std::string& name, const char* value) +void cmPropertyMap::SetProperty(const std::string& name, std::nullptr_t) { - if (!value) { - this->Map_.erase(name); - return; - } - - this->Map_[name] = value; + this->Map_.erase(name); } void cmPropertyMap::SetProperty(const std::string& name, cmValue value) { diff --git a/Source/cmPropertyMap.h b/Source/cmPropertyMap.h index f50b65e..23b50a5 100644 --- a/Source/cmPropertyMap.h +++ b/Source/cmPropertyMap.h @@ -4,6 +4,7 @@ #include "cmConfigure.h" // IWYU pragma: keep +#include <cstddef> #include <string> #include <unordered_map> #include <utility> @@ -25,7 +26,7 @@ public: // -- Properties //! Set the property value - void SetProperty(const std::string& name, const char* value); + void SetProperty(const std::string& name, std::nullptr_t); void SetProperty(const std::string& name, cmValue value); void SetProperty(const std::string& name, const std::string& value) { diff --git a/Source/cmQtAutoMocUic.cxx b/Source/cmQtAutoMocUic.cxx index b7af859..a101a81 100644 --- a/Source/cmQtAutoMocUic.cxx +++ b/Source/cmQtAutoMocUic.cxx @@ -2272,10 +2272,9 @@ cmQtAutoMocUicT::JobDepFilesMergeT::initialDependencies() const void cmQtAutoMocUicT::JobDepFilesMergeT::Process() { if (this->Log().Verbose()) { - this->Log().Info( - GenT::MOC, - cmStrCat("Merging MOC dependencies into ", - this->MessagePath(this->BaseConst().DepFile.c_str()))); + this->Log().Info(GenT::MOC, + cmStrCat("Merging MOC dependencies into ", + this->MessagePath(this->BaseConst().DepFile))); } auto processDepFile = [this](const std::string& mocOutputFile) -> std::vector<std::string> { diff --git a/Source/cmSourceFile.cxx b/Source/cmSourceFile.cxx index 6224d0e..3403745 100644 --- a/Source/cmSourceFile.cxx +++ b/Source/cmSourceFile.cxx @@ -278,8 +278,7 @@ bool cmSourceFile::Matches(cmSourceFileLocation const& loc) return this->Location.Matches(loc); } -template <typename ValueType> -void cmSourceFile::StoreProperty(const std::string& prop, ValueType value) +void cmSourceFile::SetProperty(const std::string& prop, cmValue value) { if (prop == propINCLUDE_DIRECTORIES) { this->IncludeDirectories.clear(); @@ -304,15 +303,6 @@ void cmSourceFile::StoreProperty(const std::string& prop, ValueType value) } } -void cmSourceFile::SetProperty(const std::string& prop, const char* value) -{ - this->StoreProperty(prop, value); -} -void cmSourceFile::SetProperty(const std::string& prop, cmValue value) -{ - this->StoreProperty(prop, value); -} - void cmSourceFile::AppendProperty(const std::string& prop, const std::string& value, bool asString) { diff --git a/Source/cmSourceFile.h b/Source/cmSourceFile.h index 9308af4..3f070a7 100644 --- a/Source/cmSourceFile.h +++ b/Source/cmSourceFile.h @@ -4,6 +4,7 @@ #include "cmConfigure.h" // IWYU pragma: keep +#include <cstddef> #include <memory> #include <string> #include <vector> @@ -41,8 +42,11 @@ public: void SetCustomCommand(std::unique_ptr<cmCustomCommand> cc); //! Set/Get a property of this source file - void SetProperty(const std::string& prop, const char* value); void SetProperty(const std::string& prop, cmValue value); + void SetProperty(const std::string& prop, std::nullptr_t) + { + this->SetProperty(prop, cmValue{ nullptr }); + } void SetProperty(const std::string& prop, const std::string& value) { this->SetProperty(prop, cmValue(value)); diff --git a/Source/cmState.cxx b/Source/cmState.cxx index f12f91f..a72f830 100644 --- a/Source/cmState.cxx +++ b/Source/cmState.cxx @@ -101,11 +101,13 @@ cmStateEnums::CacheEntryType cmState::StringToCacheEntryType( bool cmState::StringToCacheEntryType(const std::string& s, cmStateEnums::CacheEntryType& type) { - for (size_t i = 0; i < cmCacheEntryTypes.size(); ++i) { - if (s == cmCacheEntryTypes[i]) { - type = static_cast<cmStateEnums::CacheEntryType>(i); - return true; - } + // NOLINTNEXTLINE(readability-qualified-auto) + auto const entry = + std::find(cmCacheEntryTypes.begin(), cmCacheEntryTypes.end(), s); + if (entry != cmCacheEntryTypes.end()) { + type = static_cast<cmStateEnums::CacheEntryType>( + entry - cmCacheEntryTypes.begin()); + return true; } return false; } @@ -562,7 +564,8 @@ void cmState::RemoveUserDefinedCommands() this->ScriptedCommands.clear(); } -void cmState::SetGlobalProperty(const std::string& prop, const char* value) +void cmState::SetGlobalProperty(const std::string& prop, + const std::string& value) { this->GlobalProperties.SetProperty(prop, value); } @@ -581,10 +584,10 @@ cmValue cmState::GetGlobalProperty(const std::string& prop) { if (prop == "CACHE_VARIABLES") { std::vector<std::string> cacheKeys = this->GetCacheEntryKeys(); - this->SetGlobalProperty("CACHE_VARIABLES", cmJoin(cacheKeys, ";").c_str()); + this->SetGlobalProperty("CACHE_VARIABLES", cmJoin(cacheKeys, ";")); } else if (prop == "COMMANDS") { std::vector<std::string> commands = this->GetCommandNames(); - this->SetGlobalProperty("COMMANDS", cmJoin(commands, ";").c_str()); + this->SetGlobalProperty("COMMANDS", cmJoin(commands, ";")); } else if (prop == "IN_TRY_COMPILE") { this->SetGlobalProperty( "IN_TRY_COMPILE", @@ -595,10 +598,10 @@ cmValue cmState::GetGlobalProperty(const std::string& prop) } else if (prop == "ENABLED_LANGUAGES") { std::string langs; langs = cmJoin(this->EnabledLanguages, ";"); - this->SetGlobalProperty("ENABLED_LANGUAGES", langs.c_str()); + this->SetGlobalProperty("ENABLED_LANGUAGES", langs); } else if (prop == "CMAKE_ROLE") { std::string mode = this->GetModeString(); - this->SetGlobalProperty("CMAKE_ROLE", mode.c_str()); + this->SetGlobalProperty("CMAKE_ROLE", mode); } #define STRING_LIST_ELEMENT(F) ";" #F if (prop == "CMAKE_C_KNOWN_FEATURES") { diff --git a/Source/cmState.h b/Source/cmState.h index 0a42df0..d9d2c21 100644 --- a/Source/cmState.h +++ b/Source/cmState.h @@ -194,7 +194,7 @@ public: void RemoveUserDefinedCommands(); std::vector<std::string> GetCommandNames() const; - void SetGlobalProperty(const std::string& prop, const char* value); + void SetGlobalProperty(const std::string& prop, const std::string& value); void SetGlobalProperty(const std::string& prop, cmValue value); void AppendGlobalProperty(const std::string& prop, const std::string& value, bool asString = false); diff --git a/Source/cmStateDirectory.cxx b/Source/cmStateDirectory.cxx index 20e4604..6e6fcbd 100644 --- a/Source/cmStateDirectory.cxx +++ b/Source/cmStateDirectory.cxx @@ -271,9 +271,8 @@ void cmStateDirectory::ClearLinkDirectories() this->Snapshot_.Position->LinkDirectoriesPosition); } -template <typename ValueType> -void cmStateDirectory::StoreProperty(const std::string& prop, ValueType value, - cmListFileBacktrace const& lfbt) +void cmStateDirectory::SetProperty(const std::string& prop, cmValue value, + cmListFileBacktrace const& lfbt) { if (prop == "INCLUDE_DIRECTORIES") { if (!value) { @@ -319,17 +318,6 @@ void cmStateDirectory::StoreProperty(const std::string& prop, ValueType value, this->DirectoryState->Properties.SetProperty(prop, value); } -void cmStateDirectory::SetProperty(const std::string& prop, const char* value, - cmListFileBacktrace const& lfbt) -{ - this->StoreProperty(prop, value, lfbt); -} -void cmStateDirectory::SetProperty(const std::string& prop, cmValue value, - cmListFileBacktrace const& lfbt) -{ - this->StoreProperty(prop, value, lfbt); -} - void cmStateDirectory::AppendProperty(const std::string& prop, const std::string& value, bool asString, cmListFileBacktrace const& lfbt) diff --git a/Source/cmStateDirectory.h b/Source/cmStateDirectory.h index 8c6b09d..55cc716 100644 --- a/Source/cmStateDirectory.h +++ b/Source/cmStateDirectory.h @@ -5,6 +5,7 @@ #include "cmConfigure.h" // IWYU pragma: keep +#include <cstddef> #include <string> #include <vector> @@ -57,10 +58,13 @@ public: void SetLinkDirectories(BT<std::string> const& vecs); void ClearLinkDirectories(); - void SetProperty(const std::string& prop, const char* value, - cmListFileBacktrace const& lfbt); void SetProperty(const std::string& prop, cmValue value, cmListFileBacktrace const& lfbt); + void SetProperty(const std::string& prop, std::nullptr_t, + cmListFileBacktrace const& lfbt) + { + this->SetProperty(prop, cmValue{ nullptr }, lfbt); + } void AppendProperty(const std::string& prop, const std::string& value, bool asString, cmListFileBacktrace const& lfbt); cmValue GetProperty(const std::string& prop) const; diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index 0fbe430..b55554d 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -1810,26 +1810,7 @@ MAKE_PROP(INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE); #undef MAKE_PROP } -namespace { -// to workaround bug on GCC/AIX -// Define a template to force conversion to std::string -template <typename ValueType> -std::string ConvertToString(ValueType value); - -template <> -std::string ConvertToString<const char*>(const char* value) -{ - return std::string(value); -} -template <> -std::string ConvertToString<cmValue>(cmValue value) -{ - return std::string(*value); -} -} - -template <typename ValueType> -void cmTarget::StoreProperty(const std::string& prop, ValueType value) +void cmTarget::SetProperty(const std::string& prop, cmValue value) { if (prop == propMANUALLY_ADDED_DEPENDENCIES) { this->impl->Makefile->IssueMessage( @@ -1975,7 +1956,7 @@ void cmTarget::StoreProperty(const std::string& prop, ValueType value) std::string reusedFrom = reusedTarget->GetSafeProperty(prop); if (reusedFrom.empty()) { - reusedFrom = ConvertToString(value); + reusedFrom = *value; } this->impl->Properties.SetProperty(prop, reusedFrom); @@ -2091,15 +2072,6 @@ void cmTarget::AppendProperty(const std::string& prop, } } -void cmTarget::SetProperty(const std::string& prop, const char* value) -{ - this->StoreProperty(prop, value); -} -void cmTarget::SetProperty(const std::string& prop, cmValue value) -{ - this->StoreProperty(prop, value); -} - template <typename ValueType> void cmTargetInternals::AddDirectoryToFileSet(cmTarget* self, std::string const& fileSetName, diff --git a/Source/cmTarget.h b/Source/cmTarget.h index 24f6fcd..5fe5a28 100644 --- a/Source/cmTarget.h +++ b/Source/cmTarget.h @@ -180,8 +180,11 @@ public: std::set<BT<std::pair<std::string, bool>>> const& GetUtilities() const; //! Set/Get a property of this target file - void SetProperty(const std::string& prop, const char* value); void SetProperty(const std::string& prop, cmValue value); + void SetProperty(const std::string& prop, std::nullptr_t) + { + this->SetProperty(prop, cmValue{ nullptr }); + } void SetProperty(const std::string& prop, const std::string& value) { this->SetProperty(prop, cmValue(value)); diff --git a/Source/cmTest.cxx b/Source/cmTest.cxx index e6ed01b..b0d9c2d 100644 --- a/Source/cmTest.cxx +++ b/Source/cmTest.cxx @@ -52,10 +52,6 @@ bool cmTest::GetPropertyAsBool(const std::string& prop) const return cmIsOn(this->GetProperty(prop)); } -void cmTest::SetProperty(const std::string& prop, const char* value) -{ - this->Properties.SetProperty(prop, value); -} void cmTest::SetProperty(const std::string& prop, cmValue value) { this->Properties.SetProperty(prop, value); diff --git a/Source/cmTest.h b/Source/cmTest.h index 1c14310..8b50b87 100644 --- a/Source/cmTest.h +++ b/Source/cmTest.h @@ -4,6 +4,7 @@ #include "cmConfigure.h" // IWYU pragma: keep +#include <cstddef> #include <string> #include <vector> @@ -34,8 +35,11 @@ public: std::vector<std::string> const& GetCommand() const { return this->Command; } //! Set/Get a property of this source file - void SetProperty(const std::string& prop, const char* value); void SetProperty(const std::string& prop, cmValue value); + void SetProperty(const std::string& prop, std::nullptr_t) + { + this->SetProperty(prop, cmValue{ nullptr }); + } void SetProperty(const std::string& prop, const std::string& value) { this->SetProperty(prop, cmValue(value)); diff --git a/Source/cmUVProcessChain.cxx b/Source/cmUVProcessChain.cxx index 3faf2f6..257c054 100644 --- a/Source/cmUVProcessChain.cxx +++ b/Source/cmUVProcessChain.cxx @@ -140,6 +140,19 @@ cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetExternalStream( return *this; } +cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetMergedBuiltinStreams() +{ + this->MergedBuiltinStreams = true; + return this->SetBuiltinStream(Stream_OUTPUT).SetBuiltinStream(Stream_ERROR); +} + +cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetWorkingDirectory( + std::string dir) +{ + this->WorkingDirectory = std::move(dir); + return *this; +} + cmUVProcessChain cmUVProcessChainBuilder::Start() const { cmUVProcessChain chain; @@ -174,27 +187,6 @@ bool cmUVProcessChain::InternalData::Prepare( { this->Builder = builder; - auto const& output = - this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT]; - auto& outputData = this->OutputStreamData; - switch (output.Type) { - case cmUVProcessChainBuilder::None: - outputData.Stdio.flags = UV_IGNORE; - break; - - case cmUVProcessChainBuilder::Builtin: - outputData.BuiltinStream.init(*this->Loop, 0); - outputData.Stdio.flags = - static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE); - outputData.Stdio.data.stream = outputData.BuiltinStream; - break; - - case cmUVProcessChainBuilder::External: - outputData.Stdio.flags = UV_INHERIT_FD; - outputData.Stdio.data.fd = output.FileDescriptor; - break; - } - auto const& error = this->Builder->Stdio[cmUVProcessChainBuilder::Stream_ERROR]; auto& errorData = this->ErrorStreamData; @@ -224,6 +216,32 @@ bool cmUVProcessChain::InternalData::Prepare( break; } + auto const& output = + this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT]; + auto& outputData = this->OutputStreamData; + switch (output.Type) { + case cmUVProcessChainBuilder::None: + outputData.Stdio.flags = UV_IGNORE; + break; + + case cmUVProcessChainBuilder::Builtin: + if (this->Builder->MergedBuiltinStreams) { + outputData.Stdio.flags = UV_INHERIT_FD; + outputData.Stdio.data.fd = errorData.Stdio.data.fd; + } else { + outputData.BuiltinStream.init(*this->Loop, 0); + outputData.Stdio.flags = + static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE); + outputData.Stdio.data.stream = outputData.BuiltinStream; + } + break; + + case cmUVProcessChainBuilder::External: + outputData.Stdio.flags = UV_INHERIT_FD; + outputData.Stdio.data.fd = output.FileDescriptor; + break; + } + return true; } @@ -248,6 +266,9 @@ bool cmUVProcessChain::InternalData::AddCommand( arguments.push_back(nullptr); options.args = const_cast<char**>(arguments.data()); options.flags = UV_PROCESS_WINDOWS_HIDE; + if (!this->Builder->WorkingDirectory.empty()) { + options.cwd = this->Builder->WorkingDirectory.c_str(); + } std::array<uv_stdio_container_t, 3> stdio; stdio[0] = uv_stdio_container_t(); @@ -289,7 +310,8 @@ bool cmUVProcessChain::InternalData::AddCommand( bool cmUVProcessChain::InternalData::Finish() { if (this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT].Type == - cmUVProcessChainBuilder::Builtin) { + cmUVProcessChainBuilder::Builtin && + !this->Builder->MergedBuiltinStreams) { this->OutputStreamData.Streambuf.open( this->OutputStreamData.BuiltinStream); } @@ -339,6 +361,9 @@ uv_loop_t& cmUVProcessChain::GetLoop() std::istream* cmUVProcessChain::OutputStream() { + if (this->Data->Builder->MergedBuiltinStreams) { + return this->Data->ErrorStreamData.GetBuiltinStream(); + } return this->Data->OutputStreamData.GetBuiltinStream(); } diff --git a/Source/cmUVProcessChain.h b/Source/cmUVProcessChain.h index 5e8e7e6..3ade3fd 100644 --- a/Source/cmUVProcessChain.h +++ b/Source/cmUVProcessChain.h @@ -30,7 +30,9 @@ public: const std::vector<std::string>& arguments); cmUVProcessChainBuilder& SetNoStream(Stream stdio); cmUVProcessChainBuilder& SetBuiltinStream(Stream stdio); + cmUVProcessChainBuilder& SetMergedBuiltinStreams(); cmUVProcessChainBuilder& SetExternalStream(Stream stdio, int fd); + cmUVProcessChainBuilder& SetWorkingDirectory(std::string dir); cmUVProcessChain Start() const; @@ -57,6 +59,8 @@ private: std::array<StdioConfiguration, 3> Stdio; std::vector<ProcessConfiguration> Processes; + std::string WorkingDirectory; + bool MergedBuiltinStreams = false; }; class cmUVProcessChain diff --git a/Source/cmake.cxx b/Source/cmake.cxx index 0fd7461..3792791 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -38,6 +38,10 @@ #include "cmCMakePresetsGraph.h" #include "cmCommandLineArgument.h" #include "cmCommands.h" +#ifdef CMake_ENABLE_DEBUGGER +# include "cmDebuggerAdapter.h" +# include "cmDebuggerPipeConnection.h" +#endif #include "cmDocumentation.h" #include "cmDocumentationEntry.h" #include "cmDuration.h" @@ -411,6 +415,11 @@ Json::Value cmake::ReportCapabilitiesJson() const obj["fileApi"] = cmFileAPI::ReportCapabilities(); obj["serverMode"] = false; obj["tls"] = static_cast<bool>(curlVersion->features & CURL_VERSION_SSL); +# ifdef CMake_ENABLE_DEBUGGER + obj["debugger"] = true; +# else + obj["debugger"] = false; +# endif return obj; } @@ -617,6 +626,13 @@ bool cmake::SetCacheArgs(const std::vector<std::string>& args) }; auto ScriptLambda = [&](std::string const& path, cmake* state) -> bool { +#ifdef CMake_ENABLE_DEBUGGER + // Script mode doesn't hit the usual code path in cmake::Run() that starts + // the debugger, so start it manually here instead. + if (!this->StartDebuggerIfEnabled()) { + return false; + } +#endif // Register fake project commands that hint misuse in script mode. GetProjectCommandsInScriptMode(state->GetState()); // Documented behavior of CMAKE{,_CURRENT}_{SOURCE,BINARY}_DIR is to be @@ -1233,7 +1249,52 @@ void cmake::SetArgs(const std::vector<std::string>& args) "CMAKE_COMPILE_WARNING_AS_ERROR variable.\n"; state->SetIgnoreWarningAsError(true); return true; - } } + } }, + CommandArgument{ "--debugger", CommandArgument::Values::Zero, + [](std::string const&, cmake* state) -> bool { +#ifdef CMake_ENABLE_DEBUGGER + std::cout << "Running with debugger on.\n"; + state->SetDebuggerOn(true); + return true; +#else + static_cast<void>(state); + cmSystemTools::Error( + "CMake was not built with support for --debugger"); + return false; +#endif + } }, + CommandArgument{ "--debugger-pipe", + "No path specified for --debugger-pipe", + CommandArgument::Values::One, + [](std::string const& value, cmake* state) -> bool { +#ifdef CMake_ENABLE_DEBUGGER + state->DebuggerPipe = value; + return true; +#else + static_cast<void>(value); + static_cast<void>(state); + cmSystemTools::Error("CMake was not built with support " + "for --debugger-pipe"); + return false; +#endif + } }, + CommandArgument{ + "--debugger-dap-log", "No file specified for --debugger-dap-log", + CommandArgument::Values::One, + [](std::string const& value, cmake* state) -> bool { +#ifdef CMake_ENABLE_DEBUGGER + std::string path = cmSystemTools::CollapseFullPath(value); + cmSystemTools::ConvertToUnixSlashes(path); + state->DebuggerDapLogFile = path; + return true; +#else + static_cast<void>(value); + static_cast<void>(state); + cmSystemTools::Error( + "CMake was not built with support for --debugger-dap-log"); + return false; +#endif + } }, }; #if defined(CMAKE_HAVE_VS_GENERATORS) @@ -2138,12 +2199,10 @@ int cmake::DoPreConfigureChecks() std::string cacheStart = cmStrCat(*this->State->GetInitializedCacheValue("CMAKE_HOME_DIRECTORY"), "/CMakeLists.txt"); - std::string currentStart = - cmStrCat(this->GetHomeDirectory(), "/CMakeLists.txt"); - if (!cmSystemTools::SameFile(cacheStart, currentStart)) { + if (!cmSystemTools::SameFile(cacheStart, srcList)) { std::string message = - cmStrCat("The source \"", currentStart, - "\" does not match the source \"", cacheStart, + cmStrCat("The source \"", srcList, "\" does not match the source \"", + cacheStart, "\" used to generate cache. Re-run cmake with a different " "source directory."); cmSystemTools::Error(message); @@ -2371,16 +2430,16 @@ int cmake::ActualConfigure() cmValue genName = this->State->GetInitializedCacheValue("CMAKE_GENERATOR"); if (genName) { if (!this->GlobalGenerator->MatchesGeneratorName(*genName)) { - std::string message = - cmStrCat("Error: generator : ", this->GlobalGenerator->GetName(), - "\nDoes not match the generator used previously: ", *genName, - "\nEither remove the CMakeCache.txt file and CMakeFiles " - "directory or choose a different binary directory."); + std::string message = cmStrCat( + "Error: generator : ", this->GlobalGenerator->GetName(), '\n', + "Does not match the generator used previously: ", *genName, '\n', + "Either remove the CMakeCache.txt file and CMakeFiles " + "directory or choose a different binary directory."); cmSystemTools::Error(message); return -2; } } - if (!this->State->GetInitializedCacheValue("CMAKE_GENERATOR")) { + if (!genName) { this->AddCacheEntry("CMAKE_GENERATOR", this->GlobalGenerator->GetName(), "Name of generator.", cmStateEnums::INTERNAL); this->AddCacheEntry( @@ -2401,11 +2460,11 @@ int cmake::ActualConfigure() if (cmValue instance = this->State->GetInitializedCacheValue("CMAKE_GENERATOR_INSTANCE")) { if (this->GeneratorInstanceSet && this->GeneratorInstance != *instance) { - std::string message = - cmStrCat("Error: generator instance: ", this->GeneratorInstance, - "\nDoes not match the instance used previously: ", *instance, - "\nEither remove the CMakeCache.txt file and CMakeFiles " - "directory or choose a different binary directory."); + std::string message = cmStrCat( + "Error: generator instance: ", this->GeneratorInstance, '\n', + "Does not match the instance used previously: ", *instance, '\n', + "Either remove the CMakeCache.txt file and CMakeFiles " + "directory or choose a different binary directory."); cmSystemTools::Error(message); return -2; } @@ -2420,9 +2479,9 @@ int cmake::ActualConfigure() if (this->GeneratorPlatformSet && this->GeneratorPlatform != *platformName) { std::string message = cmStrCat( - "Error: generator platform: ", this->GeneratorPlatform, - "\nDoes not match the platform used previously: ", *platformName, - "\nEither remove the CMakeCache.txt file and CMakeFiles " + "Error: generator platform: ", this->GeneratorPlatform, '\n', + "Does not match the platform used previously: ", *platformName, '\n', + "Either remove the CMakeCache.txt file and CMakeFiles " "directory or choose a different binary directory."); cmSystemTools::Error(message); return -2; @@ -2436,9 +2495,9 @@ int cmake::ActualConfigure() this->State->GetInitializedCacheValue("CMAKE_GENERATOR_TOOLSET")) { if (this->GeneratorToolsetSet && this->GeneratorToolset != *tsName) { std::string message = - cmStrCat("Error: generator toolset: ", this->GeneratorToolset, - "\nDoes not match the toolset used previously: ", *tsName, - "\nEither remove the CMakeCache.txt file and CMakeFiles " + cmStrCat("Error: generator toolset: ", this->GeneratorToolset, '\n', + "Does not match the toolset used previously: ", *tsName, '\n', + "Either remove the CMakeCache.txt file and CMakeFiles " "directory or choose a different binary directory."); cmSystemTools::Error(message); return -2; @@ -2620,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) { @@ -2709,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) @@ -3263,10 +3374,6 @@ void cmake::GenerateGraphViz(const std::string& fileName) const #endif } -void cmake::SetProperty(const std::string& prop, const char* value) -{ - this->State->SetGlobalProperty(prop, value); -} void cmake::SetProperty(const std::string& prop, cmValue value) { this->State->SetGlobalProperty(prop, value); @@ -3625,7 +3732,6 @@ int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets, return 1; } } - std::string output; std::string projName; cmValue cachedProjectName = this->State->GetCacheEntryValue("CMAKE_PROJECT_NAME"); @@ -3699,10 +3805,17 @@ int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets, } this->GlobalGenerator->PrintBuildCommandAdvice(std::cerr, jobs); - return this->GlobalGenerator->Build( - jobs, "", dir, projName, targets, output, "", config, buildOptions, + std::stringstream ostr; + // `cmGlobalGenerator::Build` logs metadata about what directory and commands + // are being executed to the `output` parameter. If CMake is verbose, print + // this out. + std::ostream& verbose_ostr = verbose ? std::cout : ostr; + int buildresult = this->GlobalGenerator->Build( + jobs, "", dir, projName, targets, verbose_ostr, "", config, buildOptions, verbose, cmDuration::zero(), cmSystemTools::OUTPUT_PASSTHROUGH, nativeOptions); + + return buildresult; } bool cmake::Open(const std::string& dir, bool dryRun) diff --git a/Source/cmake.h b/Source/cmake.h index 0f8f642..d394a3e 100644 --- a/Source/cmake.h +++ b/Source/cmake.h @@ -37,6 +37,13 @@ #endif class cmConfigureLog; + +#ifdef CMake_ENABLE_DEBUGGER +namespace cmDebugger { +class cmDebuggerAdapter; +} +#endif + class cmExternalMakefileProjectGeneratorFactory; class cmFileAPI; class cmFileTimeCache; @@ -404,8 +411,11 @@ public: std::vector<cmDocumentationEntry> GetGeneratorsDocumentation(); //! Set/Get a property of this target file - void SetProperty(const std::string& prop, const char* value); void SetProperty(const std::string& prop, cmValue value); + void SetProperty(const std::string& prop, std::nullptr_t) + { + this->SetProperty(prop, cmValue{ nullptr }); + } void SetProperty(const std::string& prop, const std::string& value) { this->SetProperty(prop, cmValue(value)); @@ -659,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); @@ -799,6 +826,13 @@ private: std::unique_ptr<cmMakefileProfilingData> ProfilingOutput; #endif +#ifdef CMake_ENABLE_DEBUGGER + std::shared_ptr<cmDebugger::cmDebuggerAdapter> DebugAdapter; + bool DebuggerOn = false; + std::string DebuggerPipe; + std::string DebuggerDapLogFile; +#endif + public: static cmDocumentationEntry CMAKE_STANDARD_OPTIONS_TABLE[18]; }; diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx index ad27443..ced83dc 100644 --- a/Source/cmakemain.cxx +++ b/Source/cmakemain.cxx @@ -392,8 +392,14 @@ int do_cmake(int ac, char const* const* av) // Always return a non-negative value. Windows tools do not always // interpret negative return values as errors. if (res != 0) { +#ifdef CMake_ENABLE_DEBUGGER + cm.StopDebuggerIfNeeded(1); +#endif return 1; } +#ifdef CMake_ENABLE_DEBUGGER + cm.StopDebuggerIfNeeded(0); +#endif return 0; } diff --git a/Source/kwsys/RegularExpression.cxx b/Source/kwsys/RegularExpression.cxx index f2f5143..b51e16d 100644 --- a/Source/kwsys/RegularExpression.cxx +++ b/Source/kwsys/RegularExpression.cxx @@ -378,6 +378,10 @@ bool RegularExpression::compile(const char* exp) return false; } +#ifdef __clang_analyzer__ /* Convince it that the program is initialized. */ + memset(this->program, 0, comp.regsize); +#endif + // Second pass: emit code. comp.regparse = exp; comp.regnpar = 1; diff --git a/Source/kwsys/SystemInformation.cxx b/Source/kwsys/SystemInformation.cxx index 20e2edb..7f8485e 100644 --- a/Source/kwsys/SystemInformation.cxx +++ b/Source/kwsys/SystemInformation.cxx @@ -3453,6 +3453,10 @@ bool SystemInformationImplementation::RetrieveInformationFromCpuInfoFile() fileSize++; } fclose(fd); + if (fileSize < 2) { + std::cout << "No data in /proc/cpuinfo" << std::endl; + return false; + } buffer.resize(fileSize - 2); // Number of logical CPUs (combination of multiple processors, multi-core // and SMT) |