|
|
|
#include "builder.hpp"
|
|
|
|
#include <chrono> // for milliseconds
|
|
|
|
#include <fmt/core.h>
|
|
|
|
#include <fstream>
|
|
|
|
#include <git2.h>
|
|
|
|
#include <iostream>
|
|
|
|
#include <memory> // for allocator, shared_ptr
|
|
|
|
#include <regex>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h> // for EXIT_SUCCESS
|
|
|
|
#include <string> // for operator+, to_string
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include <fstream>
|
|
|
|
#include <future>
|
|
|
|
#include <mutex>
|
|
|
|
#include "dbc.hpp"
|
|
|
|
|
|
|
|
using std::string;
|
|
|
|
using namespace fmt;
|
|
|
|
using namespace nlohmann;
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
|
|
|
|
#define BUF_MAX 1024
|
|
|
|
|
|
|
|
Builder::Builder(GUI &g, GameEngine &engine)
|
|
|
|
: gui(g), game(engine)
|
|
|
|
{
|
|
|
|
std::ifstream infile(".tarpit.json");
|
|
|
|
json config = json::parse(infile);
|
|
|
|
|
|
|
|
config["git_path"].template get_to<string>(git_path);
|
|
|
|
config["build_cmd"].template get_to<string>(build_cmd);
|
|
|
|
}
|
|
|
|
|
|
|
|
FILE *Builder::start_command(string &build_cmd) {
|
|
|
|
std::lock_guard<std::mutex> lock(fsm_mutex);
|
|
|
|
FILE *build_out = popen(build_cmd.c_str(), "r");
|
|
|
|
dbc::check(build_out != nullptr, "Failed to run command.");
|
|
|
|
return build_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
string Builder::read_line(FILE *build_out, bool &done_out) {
|
|
|
|
std::lock_guard<std::mutex> lock(fsm_mutex);
|
|
|
|
char buffer[BUF_MAX];
|
|
|
|
char *res = fgets(buffer, BUF_MAX, build_out);
|
|
|
|
done_out = res == nullptr;
|
|
|
|
|
|
|
|
if(!done_out) {
|
|
|
|
return string{buffer}; // yeah, that's probably a problem
|
|
|
|
} else {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MatchResult Builder::parse_line(const string &line) {
|
|
|
|
std::regex err_re("(.*?):([0-9]+):([0-9]+):\\s*(.*?):\\s*(.*)\n*");
|
|
|
|
|
|
|
|
std::smatch err;
|
|
|
|
bool match = std::regex_match(line, err, err_re);
|
|
|
|
|
|
|
|
if(match) {
|
|
|
|
return {
|
|
|
|
.match = true,
|
|
|
|
.file_name = err[1].str(),
|
|
|
|
.lnumber = err[2].str(),
|
|
|
|
.col = err[3].str(),
|
|
|
|
.type = err[4].str(),
|
|
|
|
.message = err[5].str(),
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return { .match = false };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Builder::BUILDING(BuildEvent ev) {
|
|
|
|
// check if there's output
|
|
|
|
if(build_done) {
|
|
|
|
int rc = pclose(build_out);
|
|
|
|
|
|
|
|
if(rc == 0) {
|
|
|
|
game.event(GameEvent::BUILD_SUCCESS);
|
|
|
|
gui.build_success();
|
|
|
|
} else {
|
|
|
|
game.event(GameEvent::BUILD_FAILED);
|
|
|
|
gui.build_failed(!game.is_dead(), build_cmd);
|
|
|
|
}
|
|
|
|
|
|
|
|
build_out = NULL;
|
|
|
|
state(BuildState::DONE);
|
|
|
|
} else {
|
|
|
|
auto m = parse_line(line);
|
|
|
|
|
|
|
|
if(m.match) {
|
|
|
|
gui.output(format("HIT WITH {} @ {}:{}:{} {}", m.type, m.file_name, m.lnumber, m.col, m.message));
|
|
|
|
game.event(GameEvent::HIT, m.type);
|
|
|
|
}
|
|
|
|
state(BuildState::READING);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Builder::START(BuildEvent ev) {
|
|
|
|
gui.output(format("Using build command: {}", build_cmd));
|
|
|
|
fileWatcher = new efsw::FileWatcher();
|
|
|
|
dbc::check(fileWatcher != nullptr, "Failed to create filewatcher.");
|
|
|
|
|
|
|
|
git_libgit2_init();
|
|
|
|
|
|
|
|
int err = git_repository_open(&repo, git_path.c_str());
|
|
|
|
dbc::check(err == 0, git_error_last()->message);
|
|
|
|
|
|
|
|
listener = new UpdateListener(repo);
|
|
|
|
dbc::check(listener != nullptr, "Failed to create listener.");
|
|
|
|
|
|
|
|
gui.output(format("Watching directory {} for changes...", git_path));
|
|
|
|
wid = fileWatcher->addWatch(git_path, listener, true);
|
|
|
|
fileWatcher->watch();
|
|
|
|
|
|
|
|
state(BuildState::WAITING);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Builder::WAITING(BuildEvent ev) {
|
|
|
|
if(listener->changes) {
|
|
|
|
game.event(GameEvent::BUILD_START);
|
|
|
|
gui.building();
|
|
|
|
gui.output(format("CHANGES! Running build {}", build_cmd));
|
|
|
|
state(BuildState::FORKING);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Builder::FORKING(BuildEvent ev) {
|
|
|
|
if(build_fut.valid()) {
|
|
|
|
std::future_status status = build_fut.wait_for(0ms);
|
|
|
|
|
|
|
|
if(status == std::future_status::ready) {
|
|
|
|
build_out = build_fut.get();
|
|
|
|
state(BuildState::READING);
|
|
|
|
} else {
|
|
|
|
state(BuildState::FORKING);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
build_fut = std::async([&]() {
|
|
|
|
return start_command(build_cmd);
|
|
|
|
});
|
|
|
|
|
|
|
|
state(BuildState::FORKING);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Builder::READING(BuildEvent ev) {
|
|
|
|
// BUG: too much copy-pasta so turn this into a class?
|
|
|
|
if(read_fut.valid()) {
|
|
|
|
std::future_status status = read_fut.wait_for(0ms);
|
|
|
|
|
|
|
|
if(status == std::future_status::ready) {
|
|
|
|
line = read_fut.get();
|
|
|
|
state(BuildState::BUILDING);
|
|
|
|
} else {
|
|
|
|
state(BuildState::READING);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
read_fut = std::async([&]() {
|
|
|
|
return read_line(build_out, build_done);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Builder::DONE(BuildEvent ev) {
|
|
|
|
if(game.is_dead()) {
|
|
|
|
gui.you_died();
|
|
|
|
}
|
|
|
|
|
|
|
|
game.event(GameEvent::BUILD_DONE);
|
|
|
|
listener->reset_state();
|
|
|
|
gui.output("^^^^^^^^^^^ END ^^^^^^^^^^^");
|
|
|
|
state(BuildState::WAITING);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Builder::EXIT(BuildEvent ev) {
|
|
|
|
if(ev == QUIT) {
|
|
|
|
fileWatcher->removeWatch(wid);
|
|
|
|
git_libgit2_shutdown();
|
|
|
|
state(BuildState::EXIT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Builder::ERROR(BuildEvent ev) {
|
|
|
|
// how to avoid doing this more than once?
|
|
|
|
if(ev == CRASH) {
|
|
|
|
if(repo != nullptr) git_repository_free(repo);
|
|
|
|
git_libgit2_shutdown();
|
|
|
|
}
|
|
|
|
}
|