From b23a2deec17d99a8f8a94498e58c21166d982ecc Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 28 Dec 2015 13:00:18 +0100 Subject: [PATCH] Work in progress: debug integration --- src/CMakeLists.txt | 4 ++ src/cmake.cc | 94 +++++++++++++++++++++++-- src/cmake.h | 18 +++-- src/config.cc | 1 + src/config.h | 1 + src/debug.cc | 86 +++++++++++++++++++++++ src/debug.h | 25 +++++++ src/files.h | 7 +- src/menu.cc | 16 +++++ src/notebook.cc | 15 ++++ src/source.cc | 9 ++- src/window.cc | 168 +++++++++++++++++++++++++++++++-------------- 12 files changed, 376 insertions(+), 68 deletions(-) create mode 100644 src/debug.cc create mode 100644 src/debug.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 86bb96a..25ea8dd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,7 @@ endif() INCLUDE(FindPkgConfig) find_package(LibClang REQUIRED) +string(REPLACE libclang liblldb LLDB_LIBRARIES "${LIBCLANG_LIBRARIES}") #find_package(PythonLibs 2.7) @@ -72,6 +73,8 @@ set(source_files juci.h cmake.h cmake.cc dialogs.cc + debug.h + debug.cc ../libclangmm/src/CodeCompleteResults.cc ../libclangmm/src/CompilationDatabase.cc @@ -136,6 +139,7 @@ target_link_libraries(${project_name} ${GTKSVMM_LIBRARIES} ${Boost_LIBRARIES} ${ASPELL_LIBRARIES} + ${LLDB_LIBRARIES} #${PYTHON_LIBRARIES} ) diff --git a/src/cmake.cc b/src/cmake.cc index 0d8ab02..21d6960 100644 --- a/src/cmake.cc +++ b/src/cmake.cc @@ -39,13 +39,13 @@ CMake::CMake(const boost::filesystem::path &path) { } } -boost::filesystem::path CMake::get_default_build_path(const boost::filesystem::path &path) { +boost::filesystem::path CMake::get_default_build_path(const boost::filesystem::path &project_path) { boost::filesystem::path default_build_path=Config::get().terminal.default_build_path; const std::string path_variable_project_directory_name=""; size_t pos=0; auto default_build_path_string=default_build_path.string(); - auto path_filename_string=path.filename().string(); + auto path_filename_string=project_path.filename().string(); while((pos=default_build_path_string.find(path_variable_project_directory_name, pos))!=std::string::npos) { default_build_path_string.replace(pos, path_variable_project_directory_name.size(), path_filename_string); pos+=path_filename_string.size(); @@ -54,7 +54,7 @@ boost::filesystem::path CMake::get_default_build_path(const boost::filesystem::p default_build_path=default_build_path_string; if(default_build_path.is_relative()) - default_build_path=path/default_build_path; + default_build_path=project_path/default_build_path; if(!boost::filesystem::exists(default_build_path)) { boost::system::error_code ec; @@ -68,14 +68,55 @@ boost::filesystem::path CMake::get_default_build_path(const boost::filesystem::p return default_build_path; } -bool CMake::create_compile_commands(const boost::filesystem::path &path) { - auto default_build_path=get_default_build_path(path); +boost::filesystem::path CMake::get_debug_build_path(const boost::filesystem::path &project_path) { + boost::filesystem::path debug_build_path=Config::get().terminal.debug_build_path; + + const std::string path_variable_project_directory_name=""; + size_t pos=0; + auto debug_build_path_string=debug_build_path.string(); + auto path_filename_string=project_path.filename().string(); + while((pos=debug_build_path_string.find(path_variable_project_directory_name, pos))!=std::string::npos) { + debug_build_path_string.replace(pos, path_variable_project_directory_name.size(), path_filename_string); + pos+=path_filename_string.size(); + } + if(pos!=0) + debug_build_path=debug_build_path_string; + + const std::string path_variable_default_build_path=""; + pos=0; + debug_build_path_string=debug_build_path.string(); + auto default_build_path=Config::get().terminal.default_build_path; + while((pos=debug_build_path_string.find(path_variable_default_build_path, pos))!=std::string::npos) { + debug_build_path_string.replace(pos, path_variable_default_build_path.size(), default_build_path); + pos+=default_build_path.size(); + } + if(pos!=0) + debug_build_path=debug_build_path_string; + + + if(debug_build_path.is_relative()) + debug_build_path=project_path/debug_build_path; + + if(!boost::filesystem::exists(debug_build_path)) { + boost::system::error_code ec; + boost::filesystem::create_directories(debug_build_path, ec); + if(ec) { + Terminal::get().print("Error: could not create "+debug_build_path.string()+": "+ec.message()+"\n", true); + return boost::filesystem::path(); + } + } + + return debug_build_path; +} + +bool CMake::create_compile_commands(const boost::filesystem::path &project_path) { + auto default_build_path=get_default_build_path(project_path); if(default_build_path.empty()) return false; auto compile_commands_path=default_build_path/"compile_commands.json"; Dialog::Message message("Creating "+compile_commands_path.string()); auto exit_status=Terminal::get().process(Config::get().terminal.cmake_command+" "+ - path.string()+" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", default_build_path); + project_path.string()+" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", default_build_path); message.hide(); if(exit_status==EXIT_SUCCESS) { #ifdef _WIN32 //Temporary fix to MSYS2's libclang @@ -97,6 +138,47 @@ bool CMake::create_compile_commands(const boost::filesystem::path &path) { return false; } +bool CMake::create_debug_build(const boost::filesystem::path &project_path) { + auto debug_build_path=get_debug_build_path(project_path); + if(debug_build_path.empty()) + return false; + std::unique_ptr message; + if(!boost::filesystem::exists(debug_build_path/"CMakeCache.txt")) + message=std::unique_ptr(new Dialog::Message("Creating debug build")); + auto exit_status=Terminal::get().process(Config::get().terminal.cmake_command+" "+ + project_path.string()+" -DCMAKE_BUILD_TYPE=Debug", debug_build_path); + if(message) + message->hide(); + if(exit_status==EXIT_SUCCESS) + return true; + return false; +} + +boost::filesystem::path CMake::get_executable(const boost::filesystem::path &file_path) { + auto executables = get_functions_parameters("add_executable"); + + //Attempt to find executable based add_executable files and opened tab + boost::filesystem::path executable_path; + if(!file_path.empty()) { + for(auto &executable: executables) { + if(executable.second.size()>1) { + for(size_t c=1;c0 && executables[0].second.size()>0) + executable_path=executables[0].first.parent_path()/executables[0].second[0]; + + return executable_path; +} + void CMake::read_files() { for(auto &path: paths) files.emplace_back(filesystem::read(path)); diff --git a/src/cmake.h b/src/cmake.h index e995b23..e26c4cc 100644 --- a/src/cmake.h +++ b/src/cmake.h @@ -7,16 +7,20 @@ class CMake { public: CMake(const boost::filesystem::path &path); - std::vector > > get_functions_parameters(const std::string &name); - static boost::filesystem::path get_default_build_path(const boost::filesystem::path &path); - static bool create_compile_commands(const boost::filesystem::path &path); - + boost::filesystem::path project_path; std::vector paths; + + static boost::filesystem::path get_default_build_path(const boost::filesystem::path &project_path); + static boost::filesystem::path get_debug_build_path(const boost::filesystem::path &project_path); + static bool create_compile_commands(const boost::filesystem::path &project_path); + static bool create_debug_build(const boost::filesystem::path &project_path); + + boost::filesystem::path get_executable(const boost::filesystem::path &file_path); + + std::vector > > get_functions_parameters(const std::string &name); +private: std::vector files; - boost::filesystem::path project_path; std::unordered_map variables; -private: - void read_files(); void remove_tabs(); void remove_comments(); diff --git a/src/config.cc b/src/config.cc index 732344f..b04ec8a 100644 --- a/src/config.cc +++ b/src/config.cc @@ -86,6 +86,7 @@ void Config::retrieve_config() { window.default_size = {cfg.get("default_window_size.width"), cfg.get("default_window_size.height")}; terminal.default_build_path=cfg.get("project.default_build_path"); + terminal.debug_build_path=cfg.get("project.debug_build_path"); terminal.make_command=cfg.get("project.make_command"); terminal.cmake_command=cfg.get("project.cmake_command"); terminal.history_size=cfg.get("terminal_history_size"); diff --git a/src/config.h b/src/config.h index 6599c3a..8ebba6f 100644 --- a/src/config.h +++ b/src/config.h @@ -25,6 +25,7 @@ public: class Terminal { public: std::string default_build_path; + std::string debug_build_path; std::string cmake_command; std::string make_command; std::string clang_format_command; diff --git a/src/debug.cc b/src/debug.cc new file mode 100644 index 0000000..7dacc30 --- /dev/null +++ b/src/debug.cc @@ -0,0 +1,86 @@ +#include "debug.h" + +#include + +#include "lldb/API/SBTarget.h" +#include "lldb/API/SBProcess.h" +#include "lldb/API/SBListener.h" +#include "lldb/API/SBEvent.h" +#include "lldb/API/SBBreakpoint.h" +#include "lldb/API/SBThread.h" +#include "lldb/API/SBStream.h" + +#include //TODO: remove +using namespace std; + +void log(const char *msg, void *) { + cout << "debugger log: " << msg << endl; +} + +Debug::Debug() { + lldb::SBDebugger::Initialize(); + + debugger=lldb::SBDebugger::Create(true, log, nullptr); +} + +void Debug::start(const boost::filesystem::path &project_path, const boost::filesystem::path &executable, + const boost::filesystem::path &path, std::function callback) { + std::thread debug_thread([this, project_path, executable, path, callback]() { + auto target=debugger.CreateTarget(executable.string().c_str()); + auto listener=lldb::SBListener("juCi++ lldb listener"); + + if(!target.IsValid()) { + cerr << "Error: Could not create debug target to: " << executable << endl; //TODO: output to terminal instead + return; + } + + for(auto &breakpoint: breakpoints[project_path.string()]) { + if(!(target.BreakpointCreateByLocation(breakpoint.first.c_str(), breakpoint.second)).IsValid()) { + cerr << "Error: Could not create breakpoint at: " << breakpoint.first << ":" << breakpoint.second << endl; //TODO: fix output to terminal instead + return; + } + else + cerr << "Created breakpoint at: " << breakpoint.first << ":" << breakpoint.second << endl; + } + + lldb::SBError error; + auto process = target.Launch(listener, nullptr, nullptr, nullptr, nullptr, nullptr, path.string().c_str(), lldb::eLaunchFlagNone, false, error); + + if(error.Fail()) { + cerr << "Error (debug): " << error.GetCString() << endl; //TODO: output to terminal instead + return; + } + + //Wait till stopped. TODO: add check if process.GetStateFromEvent(event)==lldb::StateType::eStateStopped after + lldb::SBEvent event; + while(listener.WaitForEvent(3, event) && process.GetStateFromEvent(event)!=lldb::StateType::eStateStopped) {} + + cout << "NumThreads: " << process.GetNumThreads() << endl; + for(uint32_t thread_index_id=0;thread_index_id +#include +#include "lldb/API/SBDebugger.h" + +class Debug { +private: + Debug(); +public: + static Debug &get() { + static Debug singleton; + return singleton; + } + + void start(const boost::filesystem::path &project_path, const boost::filesystem::path &executable, const boost::filesystem::path &path="", std::function callback=nullptr); + + std::unordered_map > > breakpoints; + +private: + lldb::SBDebugger debugger; +}; + +#endif diff --git a/src/files.h b/src/files.h index 1400e0f..945a2ba 100644 --- a/src/files.h +++ b/src/files.h @@ -2,7 +2,7 @@ #define JUCI_FILES_H_ #include -#define JUCI_VERSION "1.0.1" +#define JUCI_VERSION "1.0.2" const std::string configjson = "{\n" @@ -94,9 +94,12 @@ const std::string configjson = " \"source_apply_fix_its\": \"space\",\n" " \"compile_and_run\": \"Return\",\n" " \"compile\": \"Return\",\n" +" \"compile_and_run\": \"Return\",\n" " \"run_command\": \"Return\",\n" " \"kill_last_running\": \"Escape\",\n" " \"force_kill_last_running\": \"Escape\",\n" +" \"debug_start\": \"y\",\n" +" \"debug_toggle_breakpoint\": \"b\",\n" #ifdef __linux " \"next_tab\": \"Tab\",\n" " \"previous_tab\": \"Tab\",\n" @@ -109,6 +112,8 @@ const std::string configjson = " \"project\": {\n" " \"default_build_path_comment\": \"Use to insert the project top level directory name\",\n" " \"default_build_path\": \"./build\",\n" +" \"debug_build_path_comment\": \"Use to insert the project top level directory name, and to insert your default_build_path setting.\",\n" +" \"debug_build_path\": \"/debug\",\n" #ifdef _WIN32 " \"cmake_command\": \"cmake -G\\\"MSYS Makefiles\\\" -DCMAKE_INSTALL_PREFIX="+JUCI_CMAKE_INSTALL_PREFIX+"\",\n" #else diff --git a/src/menu.cc b/src/menu.cc index 36e23a7..ddc6f45 100644 --- a/src/menu.cc +++ b/src/menu.cc @@ -269,6 +269,22 @@ Menu::Menu() { " " "" " " + " _Debug" + "
" + " " + " _Start" + " app.debug_start" + +accels["debug_start"]+ //For Ubuntu... + " " + " " + " _Toggle _Breakpoint" + " app.debug_toggle_breakpoint" + +accels["debug_toggle_breakpoint"]+ //For Ubuntu... + " " + "
" + "
" + "" + " " " _Window" "
" " " diff --git a/src/notebook.cc b/src/notebook.cc index f42f022..88ee923 100644 --- a/src/notebook.cc +++ b/src/notebook.cc @@ -6,6 +6,7 @@ #include #include "cmake.h" #include "filesystem.h" +#include "debug.h" #if GTKSOURCEVIEWMM_MAJOR_VERSION > 2 & GTKSOURCEVIEWMM_MINOR_VERSION > 17 #include "gtksourceview-3.0/gtksourceview/gtksourcemap.h" @@ -307,7 +308,21 @@ bool Notebook::close(int page) { #if GTKSOURCEVIEWMM_MAJOR_VERSION > 2 & GTKSOURCEVIEWMM_MINOR_VERSION > 17 source_maps.erase(source_maps.begin()+index); #endif + auto source_view=source_views.at(index); + + //Remove breakpoints + auto it=Debug::get().breakpoints.find(source_view->project_path.string()); + if(it!=Debug::get().breakpoints.end()) { + auto filename=source_view->file_path.filename().string(); + for(auto it_bp=it->second.begin();it_bp!=it->second.end();) { + if(it_bp->first==filename) + it_bp=it->second.erase(it_bp); + else + it_bp++; + } + } + if(auto source_clang_view=dynamic_cast(source_view)) source_clang_view->async_delete(); else diff --git a/src/source.cc b/src/source.cc index 97413e7..88e5d30 100644 --- a/src/source.cc +++ b/src/source.cc @@ -121,7 +121,14 @@ Source::View::View(const boost::filesystem::path &file_path, const boost::filesy spellcheck_checker=NULL; auto tag=get_buffer()->create_tag("spellcheck_error"); tag->property_underline()=Pango::Underline::UNDERLINE_ERROR; - + + auto mark_attr=Gsv::MarkAttributes::create(); + Gdk::RGBA rgba; + rgba.set_red(1.0); + rgba.set_alpha(0.1); + mark_attr->set_background(rgba); + set_mark_attributes("breakpoint", mark_attr, 100); + get_buffer()->signal_changed().connect([this](){ if(spellcheck_checker==NULL) return; diff --git a/src/window.cc b/src/window.cc index 38896f4..761a515 100644 --- a/src/window.cc +++ b/src/window.cc @@ -6,6 +6,7 @@ //#include "api.h" #include "dialogs.h" #include "filesystem.h" +#include "debug.h" //TODO: remove namespace sigc { #ifndef SIGC_FUNCTORS_DEDUCE_RESULT_TYPE_WITH_DECLTYPE @@ -516,64 +517,45 @@ void Window::set_menu_actions() { if(cmake_path.empty()) return; CMake cmake(cmake_path); - auto executables = cmake.get_functions_parameters("add_executable"); + if(cmake.project_path.empty()) + return; + auto executable_path=cmake.get_executable(notebook.get_current_page()!=-1?notebook.get_current_view()->file_path:""); - //Attempt to find executable based add_executable files and opened tab - boost::filesystem::path executable_path; - if(notebook.get_current_page()!=-1) { - for(auto &executable: executables) { - if(executable.second.size()>1) { - for(size_t c=1;cfile_path.filename()) { - executable_path=executable.first.parent_path()/executable.second[0]; - break; + if(executable_path!="") { + auto project_path=cmake.project_path; + auto default_build_path=CMake::get_default_build_path(project_path); + if(default_build_path.empty()) + return; + compiling=true; + auto executable_path_string=executable_path.string(); + size_t pos=executable_path_string.find(project_path.string()); + if(pos!=std::string::npos) { + executable_path_string.replace(pos, project_path.string().size(), default_build_path.string()); + executable_path=executable_path_string; + } + Terminal::get().print("Compiling and running "+executable_path.string()+"\n"); + Terminal::get().async_process(Config::get().terminal.make_command, default_build_path, [this, executable_path, default_build_path](int exit_status){ + compiling=false; + if(exit_status==EXIT_SUCCESS) { + auto executable_path_spaces_fixed=executable_path.string(); + char last_char=0; + for(size_t c=0;c0 && executables[0].second.size()>0) - executable_path=executables[0].first.parent_path()/executables[0].second[0]; - - if(cmake.project_path!="") { - if(executable_path!="") { - auto project_path=cmake.project_path; - auto default_build_path=CMake::get_default_build_path(project_path); - if(default_build_path.empty()) - return; - compiling=true; - auto executable_path_string=executable_path.string(); - size_t pos=executable_path_string.find(project_path.string()); - if(pos!=std::string::npos) { - executable_path_string.replace(pos, project_path.string().size(), default_build_path.string()); - executable_path=executable_path_string; - } - Terminal::get().print("Compiling and running "+executable_path.string()+"\n"); - Terminal::get().async_process(Config::get().terminal.make_command, default_build_path, [this, executable_path, default_build_path](int exit_status){ - compiling=false; - if(exit_status==EXIT_SUCCESS) { - auto executable_path_spaces_fixed=executable_path.string(); - char last_char=0; - for(size_t c=0;cfile_path.parent_path(); + else + cmake_path=Directories::get().current_path; + if(cmake_path.empty()) + return; + CMake cmake(cmake_path); + if(cmake.project_path.empty()) + return; + auto executable_path=cmake.get_executable(notebook.get_current_page()!=-1?notebook.get_current_view()->file_path:""); + + if(executable_path!="") { + auto project_path=cmake.project_path; + auto debug_build_path=CMake::get_debug_build_path(project_path); + if(debug_build_path.empty()) + return; + if(!CMake::create_debug_build(project_path)) + return; + compiling=true; + auto executable_path_string=executable_path.string(); + size_t pos=executable_path_string.find(project_path.string()); + if(pos!=std::string::npos) { + executable_path_string.replace(pos, project_path.string().size(), debug_build_path.string()); + executable_path=executable_path_string; + } + Terminal::get().print("Compiling and running "+executable_path.string()+"\n"); + Terminal::get().async_process(Config::get().terminal.make_command, debug_build_path, [this, project_path, executable_path, debug_build_path](int exit_status){ + compiling=false; + if(exit_status==EXIT_SUCCESS) { + auto executable_path_spaces_fixed=executable_path.string(); + char last_char=0; + for(size_t c=0;cproject_path.string(); + auto filename=view->file_path.filename().string(); + auto line_nr=view->get_buffer()->get_insert()->get_iter().get_line()+1; + + auto it=Debug::get().breakpoints.find(project_path_string); + if(it!=Debug::get().breakpoints.end()) { + for(auto it_bp=it->second.begin();it_bp!=it->second.end();it_bp++) { + if(it_bp->first==filename && it_bp->second==line_nr) { + //Remove breakpoint + it->second.erase(it_bp); + auto start_iter=view->get_buffer()->get_iter_at_line(line_nr-1); + auto end_iter=start_iter; + while(!end_iter.ends_line() && end_iter.forward_char()) {} + view->get_source_buffer()->remove_source_marks(start_iter, end_iter, "breakpoint"); + return; + } + } + } + Debug::get().breakpoints[project_path_string].emplace_back(filename, line_nr); + auto mark=view->get_source_buffer()->create_source_mark("breakpoint", view->get_buffer()->get_insert()->get_iter()); + } + }); + menu.add_action("next_tab", [this]() { if(notebook.get_current_page()!=-1) { notebook.open(notebook.get_view((notebook.get_current_page()+1)%notebook.size())->file_path);