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.

This commit is contained in:
Zed A. Shaw 2024-05-07 14:25:15 -04:00
parent 39f89ecbf2
commit 96b1297c62
10 changed files with 254 additions and 7 deletions

View file

@ -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

10
PPP3/efsw.wrap Normal file
View file

@ -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

View file

@ -4,7 +4,6 @@ using namespace std;
int main()
{
constexpr double pi = 3.14159;
double d = 0;
while(cin >> d) {
int i = d;

View file

@ -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(); });

12
PPP3/libgit2.wrap Normal file
View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

206
PPP3/watchgit.cpp Normal file
View file

@ -0,0 +1,206 @@
#include <iostream>
#include <iomanip>
#include <fstream>
#include <fmt/core.h>
#include <regex>
#include <string>
#include <iterator>
#include <ctime>
#include "dbc.h"
#include <unistd.h>
#include <stdio.h>
#include <git2/sys/errors.h>
#include <git2.h>
#include <efsw/efsw.hpp>
#include <regex>
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<string> &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<string> 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();
}