From 96b1297c62255abd73dc34f868c3ec33918920e8 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Tue, 7 May 2024 14:25:15 -0400 Subject: [PATCH] Working prototype of an auto builder that watches for changes and runs the build after getting the git status. It's missing the necessary next step of matching up changes to git files, but it does the most of the build already. --- PPP3/Makefile | 2 +- PPP3/efsw.wrap | 10 +++ PPP3/ex01.cpp | 1 - PPP3/goc.cpp | 1 - PPP3/libgit2.wrap | 12 +++ PPP3/meson.build | 22 ++++- PPP3/reset_build.ps1 | 3 +- PPP3/reset_build.sh | 3 +- PPP3/setup.sh | 1 + PPP3/watchgit.cpp | 206 +++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 254 insertions(+), 7 deletions(-) create mode 100644 PPP3/efsw.wrap create mode 100644 PPP3/libgit2.wrap create mode 100644 PPP3/watchgit.cpp diff --git a/PPP3/Makefile b/PPP3/Makefile index f7542fc..109600f 100644 --- a/PPP3/Makefile +++ b/PPP3/Makefile @@ -4,7 +4,7 @@ all: meson compile -C . 2>&1 | ${ROOT_DIR}/builddir/goc build: - meson compile -C . + meson compile -C ${ROOT_DIR}/builddir test: @echo "../ex08.cpp:7:10: error: use of undeclared identifier \'oops\'" | ${ROOT_DIR}/builddir/goc diff --git a/PPP3/efsw.wrap b/PPP3/efsw.wrap new file mode 100644 index 0000000..4de1f1f --- /dev/null +++ b/PPP3/efsw.wrap @@ -0,0 +1,10 @@ +[wrap-git] +url = https://github.com/SpartanJ/efsw.git +revision = 1.3.1 +depth = 1 +# patch_filename = +# patch_hash = + + +[provide] +efsw = efsw_dep diff --git a/PPP3/ex01.cpp b/PPP3/ex01.cpp index e981151..7160fbe 100644 --- a/PPP3/ex01.cpp +++ b/PPP3/ex01.cpp @@ -4,7 +4,6 @@ using namespace std; int main() { - constexpr double pi = 3.14159; double d = 0; while(cin >> d) { int i = d; diff --git a/PPP3/goc.cpp b/PPP3/goc.cpp index 3febe5e..f2053f2 100644 --- a/PPP3/goc.cpp +++ b/PPP3/goc.cpp @@ -22,7 +22,6 @@ int main() auto t = time(nullptr); auto tm = *std::gmtime(&t); - dbc::log("TEST 1 of the logging thing"); dbc::check(stats_out.good(), "Error opening stats.csv file."); dbc::pre("simple test", [&]() { return stats_out.good(); }); diff --git a/PPP3/libgit2.wrap b/PPP3/libgit2.wrap new file mode 100644 index 0000000..5f70604 --- /dev/null +++ b/PPP3/libgit2.wrap @@ -0,0 +1,12 @@ +[wrap-file] +directory = libgit2-1.8.0 +source_url = https://github.com/libgit2/libgit2/archive/refs/tags/v1.8.0.tar.gz +source_filename = v1.8.0.tar.gz +source_hash = 9e1d6a880d59026b675456fbb1593c724c68d73c34c0d214d6eb848e9bbd8ae4 +wrapdb_version = 2.4.1-3 +# patch_filename = +# patch_hash = + + +[provide] +libgit2 = libgit2_dep diff --git a/PPP3/meson.build b/PPP3/meson.build index 6527ae5..d36bbd1 100644 --- a/PPP3/meson.build +++ b/PPP3/meson.build @@ -1,13 +1,31 @@ project('lcppthw', 'cpp', default_options: ['cpp_std=c++20']) +cmake = import('cmake') +opts = cmake.subproject_options() +opts.add_cmake_defines({ + 'USE_ICONV': false, + 'USE_SSH': 'exec', + 'BUILD_SHARED_LIBS': true, + 'BUILD_TESTS': false, + }) +libgit2_proj = cmake.subproject('libgit2', options: opts) +libgit2package_dep = libgit2_proj.dependency('libgit2package') + +efsw_proj = cmake.subproject('efsw') +efsw_dep = efsw_proj.dependency('efsw') + dependencies = [ dependency('fmt'), - dependency('sqlite3'), - dependency('sqlitecpp') + libgit2package_dep, + efsw_dep, ] executable('goc', 'goc.cpp', + cpp_args: '-DFMT_HEADER_ONLY', + dependencies: [dependency('fmt')]) + +executable('watchgit', 'watchgit.cpp', cpp_args: '-DFMT_HEADER_ONLY', dependencies: dependencies) diff --git a/PPP3/reset_build.ps1 b/PPP3/reset_build.ps1 index e8c5d8b..ad84f0b 100644 --- a/PPP3/reset_build.ps1 +++ b/PPP3/reset_build.ps1 @@ -7,4 +7,5 @@ meson wrap install fmt meson wrap install sqlite3 meson wrap install sqlitecpp meson wrap install ftxui -meson setup -Ddefault_library=static builddir +# meson setup -Ddefault_library=static builddir +meson setup builddir diff --git a/PPP3/reset_build.sh b/PPP3/reset_build.sh index 402a505..4f11bb5 100755 --- a/PPP3/reset_build.sh +++ b/PPP3/reset_build.sh @@ -10,4 +10,5 @@ meson wrap install fmt meson wrap install sqlite3 meson wrap install sqlitecpp meson wrap install ftxui -meson setup builddir +cp *.wrap subprojects +meson setup -Ddefault_library=static builddir diff --git a/PPP3/setup.sh b/PPP3/setup.sh index 52666e8..4ca5cf0 100755 --- a/PPP3/setup.sh +++ b/PPP3/setup.sh @@ -7,4 +7,5 @@ meson wrap install fmt meson wrap install sqlite3 meson wrap install sqlitecpp meson wrap install ftxui +cp *.wrap subprojects meson setup builddir diff --git a/PPP3/watchgit.cpp b/PPP3/watchgit.cpp new file mode 100644 index 0000000..f6b115b --- /dev/null +++ b/PPP3/watchgit.cpp @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "dbc.h" +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace fmt; + +/* + * No idea what the semantics of this are. Will need + * to research git's dumb terminology to figure out why + * they have 4 different versions of the path for status. + */ +const char *unfuck_path(const git_status_entry *entry) { + if(entry->head_to_index != nullptr) { + if(entry->head_to_index->new_file.path) { + return entry->head_to_index->new_file.path; + } else { + return entry->head_to_index->old_file.path; + } + } + + if(entry->index_to_workdir != nullptr) { + if(entry->index_to_workdir->new_file.path) { + return entry->index_to_workdir->new_file.path; + } else { + return entry->index_to_workdir->old_file.path; + } + } + + return nullptr; +} + +void add_status(const git_status_entry *entry, unsigned int status_flags, vector &updates) { + const char *path = unfuck_path(entry); + + if(status_flags & GIT_STATUS_WT_NEW + || status_flags & GIT_STATUS_INDEX_NEW) + { + updates.push_back(string{path}); + } + + if(status_flags & GIT_STATUS_WT_MODIFIED + || status_flags & GIT_STATUS_INDEX_MODIFIED) + { + updates.push_back(string{path}); + } + + // need to confirm this gets the new name + if(status_flags & GIT_STATUS_WT_RENAMED + || status_flags & GIT_STATUS_INDEX_RENAMED) + { + updates.push_back(string{path}); + } +} + +class UpdateListener : public efsw::FileWatchListener { + public: + bool changes = false; + + void handleFileAction(efsw::WatchID watchid, + const std::string& dir, + const std::string& filename, + efsw::Action action, + std::string oldFilename) override + { + changes = true; + + switch(action) { + case efsw::Actions::Add: + print("ADD {} {} {}\n", dir, filename, oldFilename); + break; + case efsw::Actions::Delete: + print("DEL {} {} {}\n", dir, filename, oldFilename); + break; + case efsw::Actions::Modified: + print("MOD {} {} {}\n", dir, filename, oldFilename); + break; + case efsw::Actions::Moved: + print("MOV {} {} {}\n", dir, filename, oldFilename); + break; + default: + dbc::sentinel("Unknown efsw action."); + } + + } + + void reset_state() { + changes = false; + } +}; + +void list_git_changes(git_repository* repo) { + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + git_status_list *statuses = nullptr; + + //TODO: does this leak? + int err = git_status_list_new(&statuses, repo, &opts); + dbc::check(err == 0, git_error_last()->message); + size_t count = git_status_list_entrycount(statuses); + + vector updates; + + for(size_t i=0; i < count; i++) { + const git_status_entry *entry = git_status_byindex(statuses, i); + add_status(entry, entry->status, updates); + } + + for(string path : updates) { + print("PATH {}\n", path); + } +} + +void run_build(const char* command) { + regex err_re("(.*?):([0-9]+):([0-9]+):\\s*(.*?):\\s*(.*)"); + char buffer[LINE_MAX]; // LINE_MAX is a define already? + smatch err; + ofstream stats_out; + stats_out.open("stats.csv", ios::out | ios::app); + auto t = time(nullptr); + auto tm = *std::gmtime(&t); + + dbc::check(stats_out.good(), "Error opening stats.csv file."); + dbc::pre("simple test", [&]() { return stats_out.good(); }); + + FILE *build_out = popen(command, "r"); + dbc::check(build_out != nullptr, "Failed to run command."); + + while(fgets(buffer, LINE_MAX, build_out) != nullptr) { + string line(buffer); // yeah, that's probably a problem + + print("{}\n", line); + if(regex_match(line, err, err_re)) { + string file_name = err[1].str(); + string line = err[2].str(); + string col = err[3].str(); + string type = err[4].str(); + string message = err[5].str(); + + stats_out << put_time(&tm, "%FT%TZ"); + stats_out << format(",{},{},{},{},{}\n", file_name, line, col, type, message); + } + } + + stats_out.close(); + dbc::post("a post test", [&]() { return !stats_out.is_open(); }); +} + + +int main(int argc, char *argv[]) +{ + git_repository* repo = nullptr; + + try { + dbc::check(argc == 3, "USAGE: watchgit PATH BUILD_CMD"); + const char *git_path = argv[1]; + const char *build_cmd = argv[2]; + + print("Using build command: {}", build_cmd); + efsw::FileWatcher* fileWatcher = new efsw::FileWatcher(); + dbc::check(fileWatcher != nullptr, "Failed to create filewatcher."); + + git_libgit2_init(); + + int err = git_repository_open(&repo, git_path); + dbc::check(err == 0, git_error_last()->message); + + UpdateListener* listener = new UpdateListener(); + dbc::check(listener != nullptr, "Failed to create listener."); + + print("Watching directory {} for changes...\n", git_path); + efsw::WatchID wid = fileWatcher->addWatch(git_path, listener, true); + + while(true) { + fileWatcher->watch(); + + if(listener->changes) { + sleep(1); + list_git_changes(repo); + listener->reset_state(); + run_build(build_cmd); + } + + sleep(1); + } + + git_libgit2_shutdown(); + } catch(dbc::Error &err) { + print("ERROR: {}\n", err.message); + if(repo != nullptr) git_repository_free(repo); + return 1; + } + + git_libgit2_shutdown(); +}