Browse Source

Work in progress: debug integration

merge-requests/365/head
eidheim 10 years ago
parent
commit
b23a2deec1
  1. 4
      src/CMakeLists.txt
  2. 94
      src/cmake.cc
  3. 18
      src/cmake.h
  4. 1
      src/config.cc
  5. 1
      src/config.h
  6. 86
      src/debug.cc
  7. 25
      src/debug.h
  8. 7
      src/files.h
  9. 16
      src/menu.cc
  10. 15
      src/notebook.cc
  11. 7
      src/source.cc
  12. 106
      src/window.cc

4
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}
)

94
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="<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="<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="<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<Dialog::Message> message;
if(!boost::filesystem::exists(debug_build_path/"CMakeCache.txt"))
message=std::unique_ptr<Dialog::Message>(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;c<executable.second.size();c++) {
if(executable.second[c]==file_path.filename()) {
executable_path=executable.first.parent_path()/executable.second[0];
break;
}
}
}
if(!executable_path.empty())
break;
}
}
if(executable_path.empty() && executables.size()>0 && 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));

18
src/cmake.h

@ -7,16 +7,20 @@
class CMake {
public:
CMake(const boost::filesystem::path &path);
std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > 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<boost::filesystem::path> 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<std::pair<boost::filesystem::path, std::vector<std::string> > > get_functions_parameters(const std::string &name);
private:
std::vector<std::string> files;
boost::filesystem::path project_path;
std::unordered_map<std::string, std::string> variables;
private:
void read_files();
void remove_tabs();
void remove_comments();

1
src/config.cc

@ -86,6 +86,7 @@ void Config::retrieve_config() {
window.default_size = {cfg.get<int>("default_window_size.width"), cfg.get<int>("default_window_size.height")};
terminal.default_build_path=cfg.get<std::string>("project.default_build_path");
terminal.debug_build_path=cfg.get<std::string>("project.debug_build_path");
terminal.make_command=cfg.get<std::string>("project.make_command");
terminal.cmake_command=cfg.get<std::string>("project.cmake_command");
terminal.history_size=cfg.get<int>("terminal_history_size");

1
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;

86
src/debug.cc

@ -0,0 +1,86 @@
#include "debug.h"
#include <thread>
#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 <iostream> //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<void(int exit_status)> 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<process.GetNumThreads();thread_index_id++) {
auto thread=process.GetThreadAtIndex(thread_index_id);
cout << "NumFrames: " << thread.GetNumFrames() << endl;
for(uint32_t frame_index=0;frame_index<thread.GetNumFrames();frame_index++) {
auto frame=thread.GetFrameAtIndex(frame_index);
auto values=frame.GetVariables(true, true, true, true);
cout << "variables.GetSize(): " << values.GetSize() << endl;
for(uint32_t value_index=0;value_index<values.GetSize();value_index++) {
auto value=values.GetValueAtIndex(value_index);
//cout << value.GetName() << ": " << value.GetValue() << endl;
lldb::SBStream stream;
value.GetDescription(stream);
cout << stream.GetData();
}
}
}
process.Continue();
//Get exit status. TODO: add check if process.GetStateFromEvent(event)==lldb::StateType::eStateExited after
while(listener.WaitForEvent(3, event) && process.GetStateFromEvent(event)!=lldb::StateType::eStateExited) {}
auto exit_status=process.GetExitStatus();
if(callback)
callback(exit_status);
});
debug_thread.detach();
}

25
src/debug.h

@ -0,0 +1,25 @@
#ifndef JUCI_DEBUG_H_
#define JUCI_DEBUG_H_
#include <boost/filesystem.hpp>
#include <unordered_map>
#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<void(int exit_status)> callback=nullptr);
std::unordered_map<std::string, std::vector<std::pair<std::string, int> > > breakpoints;
private:
lldb::SBDebugger debugger;
};
#endif

7
src/files.h

@ -2,7 +2,7 @@
#define JUCI_FILES_H_
#include <string>
#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\": \"<control>space\",\n"
" \"compile_and_run\": \"<primary>Return\",\n"
" \"compile\": \"<primary><shift>Return\",\n"
" \"compile_and_run\": \"<primary>Return\",\n"
" \"run_command\": \"<alt>Return\",\n"
" \"kill_last_running\": \"<primary>Escape\",\n"
" \"force_kill_last_running\": \"<primary><shift>Escape\",\n"
" \"debug_start\": \"<primary>y\",\n"
" \"debug_toggle_breakpoint\": \"<primary>b\",\n"
#ifdef __linux
" \"next_tab\": \"<primary>Tab\",\n"
" \"previous_tab\": \"<primary><shift>Tab\",\n"
@ -109,6 +112,8 @@ const std::string configjson =
" \"project\": {\n"
" \"default_build_path_comment\": \"Use <project_directory_name> to insert the project top level directory name\",\n"
" \"default_build_path\": \"./build\",\n"
" \"debug_build_path_comment\": \"Use <project_directory_name> to insert the project top level directory name, and <default_build_path> to insert your default_build_path setting.\",\n"
" \"debug_build_path\": \"<default_build_path>/debug\",\n"
#ifdef _WIN32
" \"cmake_command\": \"cmake -G\\\"MSYS Makefiles\\\" -DCMAKE_INSTALL_PREFIX="+JUCI_CMAKE_INSTALL_PREFIX+"\",\n"
#else

16
src/menu.cc

@ -269,6 +269,22 @@ Menu::Menu() {
" </submenu>"
""
" <submenu>"
" <attribute name='label' translatable='yes'>_Debug</attribute>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_Start</attribute>"
" <attribute name='action'>app.debug_start</attribute>"
+accels["debug_start"]+ //For Ubuntu...
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Toggle _Breakpoint</attribute>"
" <attribute name='action'>app.debug_toggle_breakpoint</attribute>"
+accels["debug_toggle_breakpoint"]+ //For Ubuntu...
" </item>"
" </section>"
" </submenu>"
""
" <submenu>"
" <attribute name='label' translatable='yes'>_Window</attribute>"
" <section>"
" <item>"

15
src/notebook.cc

@ -6,6 +6,7 @@
#include <regex>
#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::ClangView*>(source_view))
source_clang_view->async_delete();
else

7
src/source.cc

@ -122,6 +122,13 @@ Source::View::View(const boost::filesystem::path &file_path, const boost::filesy
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;

106
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,28 +517,10 @@ void Window::set_menu_actions() {
if(cmake_path.empty())
return;
CMake cmake(cmake_path);
auto executables = cmake.get_functions_parameters("add_executable");
//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;c<executable.second.size();c++) {
if(executable.second[c]==notebook.get_current_view()->file_path.filename()) {
executable_path=executable.first.parent_path()/executable.second[0];
break;
}
}
}
if(!executable_path.empty())
break;
}
}
if(executable_path.empty() && executables.size()>0 && executables[0].second.size()>0)
executable_path=executables[0].first.parent_path()/executables[0].second[0];
if(cmake.project_path.empty())
return;
auto executable_path=cmake.get_executable(notebook.get_current_page()!=-1?notebook.get_current_view()->file_path:"");
if(cmake.project_path!="") {
if(executable_path!="") {
auto project_path=cmake.project_path;
auto default_build_path=CMake::get_default_build_path(project_path);
@ -574,7 +557,6 @@ void Window::set_menu_actions() {
for(auto &path: cmake.paths)
Terminal::get().print(" "+path.string()+"\n");
}
}
});
menu.add_action("compile", [this]() {
if(compiling)
@ -634,6 +616,86 @@ void Window::set_menu_actions() {
Terminal::get().kill_last_async_process(true);
});
menu.add_action("debug_start", [this](){
if(compiling)
return;
boost::filesystem::path cmake_path;
if(notebook.get_current_page()!=-1)
cmake_path=notebook.get_current_view()->file_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;c<executable_path_spaces_fixed.size();c++) {
if(last_char!='\\' && executable_path_spaces_fixed[c]==' ') {
executable_path_spaces_fixed.insert(c, "\\");
c++;
}
last_char=executable_path_spaces_fixed[c];
}
Debug::get().start(project_path, executable_path_spaces_fixed, debug_build_path, [this, executable_path](int exit_status){
Terminal::get().async_print(executable_path.string()+" returned: "+std::to_string(exit_status)+'\n');
});
}
});
}
else {
Terminal::get().print("Could not find add_executable in the following paths:\n");
for(auto &path: cmake.paths)
Terminal::get().print(" "+path.string()+"\n");
}
});
menu.add_action("debug_toggle_breakpoint", [this](){
if(notebook.get_current_page()!=-1) {
auto view=notebook.get_current_view();
auto &project_path_string=view->project_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);

Loading…
Cancel
Save