#include "pstream.h"
#include <cstdlib>
#include <fstream>
#include <functional>
#include <hash_map>
#include <ios>
#include <iostream>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <thread>
#include <unistd.h>
#include <vector>
#define POPT_IMPL 1
#include "popt.h"
#undef POPT_IMPL
#include "json.hpp"
#include <libgen.h>
struct catchall_definition : public popt::definition {
public:
catchall_definition() {}
virtual bool execute(popt::processor &, char) { return true; }
virtual bool execute(popt::processor &, char, const char *) { return true; }
virtual bool execute(popt::processor &, const char *) { return true; }
virtual ~catchall_definition() {}
};
struct special_string_definition : public popt::string_definition {
public:
special_string_definition(char s, const char *l, const char *a, const char *d,
const char *&v)
: string_definition(s, l, a, d, v) {}
virtual bool execute(popt::processor &, char) { return false; }
virtual bool execute(popt::processor &, char, const char *) { return false; }
virtual bool execute(popt::processor &, const char *s) {
if (const char *long_name = query_long_name()) {
const std::size_t len(std::strlen(long_name));
if (0 == std::memcmp(long_name, s, len) && '=' == s[len]) {
value = s + len + 1;
return true;
}
}
return false;
}
};
bool has_cache(std::string x);
static inline std::list<std::string> p_split(const char *s, bool prefixdash) {
std::list<std::string> l;
if (prefixdash && *s && std::isspace(*s))
prefixdash = false;
for (;;) {
while (!prefixdash && *s && std::isspace(*s))
++s;
if (!*s)
break;
std::string r(prefixdash ? "-" : "");
prefixdash = false;
bool quoted(false);
while (char c = *s) {
if ('\"' == c)
quoted = !quoted;
else if ('\\' == c) {
c = *++s;
if (c)
r += c;
else {
r += '\\';
break;
}
} else if (!quoted && std::isspace(c))
break;
else
r += c;
++s;
}
l.push_back(r);
}
return l;
}
static std::vector<const char *> p_convert(const std::list<std::string> &s) {
std::vector<const char *> v;
for (std::list<std::string>::const_iterator i(s.begin()); s.end() != i; ++i)
v.push_back(i->c_str());
return v;
}
struct _JT {
std::thread x;
operator std::thread &() { return x; };
_JT(std::thread y) : x(std::move(y)){};
~_JT() {
if (x.joinable())
x.join();
}
};
struct _Thread {
std::shared_ptr<_JT> target;
operator _JT &() { return *target; };
_Thread(std::thread y) : target(std::make_shared<_JT>(std::move(y))){};
_Thread() : _Thread(std::thread([]() {})){};
};
template <typename... Args> std::vector<std::string> cmd(Args... a) {
return {a...};
}
template <typename... Args> std::vector<std::string> deps(Args... a) {
return {a...};
}
struct makeflags {};
static inline bool procure_job_slot(const char *prog, int *jobserver_fds) {
if (-1 != jobserver_fds[0]) {
char c;
const int r(read(jobserver_fds[0], &c, sizeof c));
return 0 < r;
}
return false;
}
static inline void vacate_job_slot(const char *prog, int *jobserver_fds) {
if (-1 != jobserver_fds[1]) {
const char c('\0');
write(jobserver_fds[1], &c, sizeof c);
}
}
struct pv;
struct rules;
std::shared_ptr<pv> procure(rules &);
struct ruleData {
std::string realPath;
};
using rule = std::function<std::string(rules &, ruleData)>;
struct rules : __gnu_cxx::hash_map<std::string, rule> {
int fds[2];
nlohmann::json cfg;
std::string bdir;
rules(int f[2], std::string r, std::string b)
: bdir(b), fds{f[0], f[1]}, cfg(r) {}
bool procure() { return procure_job_slot("", fds); }
void vacate() { vacate_job_slot("", fds); }
__gnu_cxx::hash_map<std::string, bool> _nocache;
bool nocache = false;
bool cl = false;
long t = 0, tt = 0;
std::string context;
std::string toBuildPath(std::string x) {
if (x.rfind("//", 0) == 0) {
return bdir + x.substr(1);
};
x = context + x;
if (x.rfind("//", 0) == 0) {
return bdir + x.substr(1);
};
return x;
};
std::string operator()(std::string x) {
auto p = toBuildPath(x);
auto rp = p;
start:
if (tc[p].joinable())
tc[p].join();
if (!_nocache[x]) {
if (has_cache(x))
return x;
t++;
};
try {
auto c = context;
auto d = c + x;
context = dirname((char *)d.c_str());
auto r = (*this)[p](*this, ruleData{rp});
context = c;
return r;
} catch (std::bad_function_call _) {
p = dirname((char *)p.c_str());
goto start;
}
}
__gnu_cxx::hash_map<std::string, _Thread> tc;
std::vector<std::function<void(rules &, std::string)>> trackers;
std::map<std::string, std::function<rule(std::string, rules &)>> extra;
template <typename... Args>
void operator()(std::string x, std::vector<std::string> rr,
std::function<rule(std::string, rules &, Args...)> y,
Args... z) {
auto o = tc[x];
std::vector<_Thread> r;
for (auto rr2 : rr)
r.push_back(tc[rr2]);
auto p = toBuildPath(x);
tc[p] = std::thread([&]() {
if (o.joinable())
o.join();
for (auto l : r)
if (l.joinable())
l.join();
while (!cl)
;
cl = true;
nocache = false;
extra = {};
while (!procure_job_slot("[dissolve]", fds))
;
auto p = toBuildPath(x);
auto c = context;
auto d = c + x;
context = dirname((char *)d.c_str());
(*this)[p] = y(x, *this, z...);
for (auto t : trackers) {
t(*this, x);
};
for (auto e : extra) {
(*this)[toBuildPath(e.first)] = e.second(x, *this);
for (auto t : trackers) {
t(*this, e.first);
};
};
context = c;
vacate_job_slot("[dissolve]", fds);
_nocache[x] = nocache;
if (!_nocache[x])
tt++;
cl = false;
std::cout << "Configured " << x << std::endl;
});
}
template <typename... Args>
void operator()(std::string x,
std::function<rule(std::string, rules &, Args...)> y,
Args... z) {
(*this)(x, {}, y, z...);
}
template <typename... Args>
void operator()(std::string x, std::function<rule(std::string, Args...)> y,
Args... z) {
(*this)(
x,
[&](std::string a, rules &b, Args... c) -> rule { return y(a, c...); },
z...);
}
};
template <typename... Args>
rule defer(std::string y, std::function<rule(std::string, rules &, Args...)> fn,
Args... args) {
return [&](rules &x, ruleData _) -> std::string {
x(y, fn, args...);
return x(_.realPath);
};
}
template <typename... Args>
rule defer(std::string y, std::function<rule(std::string, Args...)> fn,
Args... args) {
return [&](rules &x, ruleData _) -> std::string {
x(y, fn, args...);
return x(_.realPath);
};
}
template <typename... Args>
std::function<rule(std::string, rules &)>
bind(std::function<rule(std::string, rules &, Args...)> x, Args... y) {
return std::bind(x, std::placeholders::_1, std::placeholders::_2, y...);
}
template <typename... Args>
std::function<rule(std::string)>
bind(std::function<rule(std::string, Args...)> x, Args... y) {
return std::bind(x, std::placeholders::_1, y...);
}
std::function<rule(std::string, rules &)>
both(std::function<rule(std::string, rules &)> a,
std::function<rule(std::string, rules &)> b) {
return [&](std::string a2, rules &b2) -> rule {
auto c = a(a2, b2);
auto d = b(a2, b2);
return [&](rules &e, ruleData f) -> std::string {
c(e, f);
d(e, f);
return a2;
};
;
};
}
std::function<rule(std::string)> both(std::function<rule(std::string)> a,
std::function<rule(std::string)> b) {
return [&](std::string a2) -> rule {
auto c = a(a2);
auto d = b(a2);
return [&](rules &e, ruleData f) -> std::string {
c(e, f);
d(e, f);
return a2;
};
;
};
}
struct pv {
rules &r;
pv(rules &r) : r(r) {
while (!r.procure())
;
}
pv(pv &x) = delete;
~pv() { r.vacate(); }
};
std::shared_ptr<pv> procure(rules &x) { return std::make_shared<pv>(x); }
static bool parse_fds(const char *prog, const char *str, int fds[],
size_t count) {
char *s(const_cast<char *>(str));
for (size_t j(0); j < count; ++j) {
if (j > 0) {
if (!*s) {
fds[j] = fds[j - 1];
continue;
} else if (',' != *s) {
std::cerr << str << ": " << s << ": Missing comma.\n";
return false;
} else
++s;
}
const char *old(s);
long i(std::strtol(old, &s, 0));
if (old == s) {
std::cerr << str << ": " << s
<< ": File descriptor number not supplied.\n";
return false;
}
fds[j] = i;
}
if (*s) {
std::cerr << str << ": " << s
<< ": Trailing junk after file descriptor(s).\n";
return false;
}
return true;
}
int _makeflags(int &a, int &b, int argc, char **argv) {
const char *prog(basename(argv[0]));
std::vector<const char *> filev;
int jobserver_fds[2];
try {
std::string jobserver_fds_string;
std::string redoparent_fd_string;
const char *jobserver_fds_c_str = 0;
const char *redoparent_fd_c_str = 0;
const char *directory = 0;
unsigned long max_jobs = 0;
popt::bool_definition silent_option('s', "silent", "Operate quietly.",
silent);
popt::bool_definition quiet_option('\0', "quiet", "alias for --silent",
silent);
popt::bool_definition keep_going_option(
'k', "keep-going",
"Continue with the next target if a .do script fails.", keep_going);
popt::bool_definition debug_option('d', "debug",
"Output debugging information.", debug);
popt::bool_definition verbose_option(
'\0', "verbose", "Display information about the database.", verbose);
popt::bool_definition print_option('p', "print", "alias for --verbose",
verbose);
popt::unsigned_number_definition jobs_option(
'j', "jobs", "number", "Allow multiple jobs to run in parallel.",
max_jobs, 0);
popt::string_definition directory_option(
'C', "directory", "directory",
"Change to directory before doing anything.", directory);
popt::definition *top_table[] = {
&silent_option, &quiet_option, &debug_option, &keep_going_option,
&verbose_option, &print_option, &jobs_option, &directory_option};
popt::top_table_definition main_option(sizeof top_table / sizeof *top_table,
top_table, "Main options",
"filename(s)");
catchall_definition ignore;
special_string_definition jobserver_option(
'\0', "jobserver-fds", "fd-list",
"Provide the file descriptor numbers of the jobserver pipe.",
jobserver_fds_c_str);
special_string_definition redoparent_option(
'\0', "redoparent-fd", "fd",
"Provide the file descriptor number of the redo database current "
"parent file.",
redoparent_fd_c_str);
popt::definition *make_env_top_table[] = {
&silent_option, &quiet_option, &debug_option,
&keep_going_option, &verbose_option, &print_option,
&jobs_option, &jobserver_option, &ignore};
popt::table_definition make_env_main_option(
sizeof make_env_top_table / sizeof *make_env_top_table,
make_env_top_table, "Main options (environment variable arguments)");
popt::definition *redo_env_top_table[] = {
&silent_option, &quiet_option, &debug_option,
&keep_going_option, &verbose_option, &print_option,
&jobs_option, &jobserver_option, &redoparent_option,
};
popt::table_definition redo_env_main_option(
sizeof redo_env_top_table / sizeof *redo_env_top_table,
redo_env_top_table, "Main options (environment variable arguments)");
static const char *vars[] = {"REDOFLAGS", "MAKEFLAGS", "MFLAGS"};
for (unsigned i = 0; i < sizeof vars / sizeof *vars; ++i) {
const char *var(vars[i]);
if (const char *s = std::getenv(var)) {
std::list<std::string> args(
p_split(s, 1 == i)); std::vector<const char *> argv(p_convert(args));
std::vector<const char *> filev;
popt::arg_processor<std::vector<const char *>::const_iterator> p(
argv.begin(), argv.end(), prog,
i ? make_env_main_option : redo_env_main_option, filev);
p.process(false );
if (p.stopped())
std::cerr << var << ": Ignoring halt of option processing.\n";
if (1 == i) { for (std::vector<const char *>::iterator j = filev.begin();
j != filev.end();) {
if (std::strchr(*j, '='))
j = filev.erase(j);
else
++j;
}
}
if (!filev.empty())
std::cerr << var << ": Ignoring filenames.\n";
if (jobserver_fds_c_str) {
jobserver_fds_string = jobserver_fds_c_str;
jobserver_fds_c_str = 0;
}
if (redoparent_fd_c_str) {
redoparent_fd_string = redoparent_fd_c_str;
redoparent_fd_c_str = 0;
}
break;
}
}
popt::arg_processor<const char **> p(argv + 1, argv + argc, prog,
main_option, filev);
p.process(false );
if (p.stopped())
return EXIT_SUCCESS;
if (directory) {
const int r(chdir(directory));
if (r < 0) {
const int error(errno);
std::cerr << directory << ": " << std::strerror(error) << "\n";
return EXIT_FAILURE;
}
}
if (jobserver_fds_c_str) {
jobserver_fds_string = jobserver_fds_c_str;
jobserver_fds_c_str = 0;
}
if (redoparent_fd_c_str) {
redoparent_fd_string = redoparent_fd_c_str;
redoparent_fd_c_str = 0;
}
if (!jobserver_fds_string.empty()) {
if (!parse_fds(prog, jobserver_fds_string.c_str(), jobserver_fds,
sizeof jobserver_fds / sizeof *jobserver_fds))
return EXIT_FAILURE;
if (jobs_option.is_set())
std::cerr
<< "Ignoring jobs option when there's already a job server.\n";
} else {
if (jobs_option.is_set()) {
if (max_jobs < 1U) {
std::cerr << "Invalid job limit.\n";
return EXIT_FAILURE;
}
if (0 > pipe(jobserver_fds)) {
const int error(errno);
std::cerr << "pipe: " << std::strerror(error) << "\n";
return EXIT_FAILURE;
}
for (unsigned m(max_jobs); m > 1U; --m)
vacate_job_slot(prog, jobserver_fds);
}
}
if (!redoparent_fd_string.empty()) {
}
} catch (const popt::error &e) {
std::cerr << e.arg << ": " << e.msg << "\n";
return EXIT_FAILURE;
}
a = jobserver_fds[0];
b = jobserver_fds[1];
std::ostringstream s;
s << "--jobserver-fds=" << a << "," << b;
if (!getenv("MAKEFLAGS"))
setenv("MAKEFLAGS", s.str().c_str(), 0);
}
template <typename... Args> std::vector<rule> dyndeps(Args... a) {
return {a...};
}
template <typename T> void append(std::vector<T> &b, std::vector<T> a) {
b.insert(b.end(), a.begin(), a.end());
}
bool has_cache(std::string file) {
std::ifstream i("cache.txt");
redi::pstream p({"sha256sum", file});
std::string h;
std::getline(p, h);
h = h + " | " + file;
std::string t;
while (std::getline(i, t))
if (t == h)
return true;
return false;
}
rule run(std::string makes, std::vector<std::string> x,
std::vector<std::string> deps, std::vector<rule> dyndeps = deps()) {
return [&](rules &x, ruleData p) -> std::string {
if (has_cache(makes))
return makes;
auto fd = procure(x);
std::vector<_Thread> d2;
for (auto d : deps)
d2.push_back(_Thread(std::thread([&]() { x(d); })));
for (auto dd : dyndeps) {
std::ifstream d(dd(x));
std::string l;
while (std::getline(d, l))
d2.push_back(_Thread(std::thread([&]() { x[l](x); })));
};
for (auto e : d2)
e.join();
auto t =
cmd("sh", "-c", "target=\"$1\";shift 1;. \"$target.env\";exec \"$@\"",
makes, "env", std::string("REALTARGET=") + p.realPath,
std::string("SUBTARGET=") + p.realPath.substr(makes.length()),
std::string("SELFEXE=/proc/") + std::to_string(getpid()) + "/exe");
for (auto c : x.cfg[makes]) {
t.push_back(std::string(c[0]) + "=" + std::string(c[1]));
};
append(t, x);
redi::pstream p(t);
std::string l;
while (std::getline(p, l))
std::cout << makes << " | " << l;
std::ofstream o("cache.txt", std::ios_base::app);
redi::pstream p({"sha256sum", makes});
std::string h;
std::getline(p, h);
o << h << " | " << makes << std::endl;
return makes;
};
};
rule make(std::string makes, std::vector<std::string> scriptTargets) {
return run(makes, cmd("sh", "-c", "make -C \"$1\" $SUBTARGET", "--", makes),
scriptTargets);
}
rule ninja(std::string makes, std::vector<std::string> scriptTargets) {
return run(makes, cmd("sh", "-c", "ninja -C \"$1\" $SUBTARGET", "--", makes),
scriptTargets);
}
rule configure_make(std::string makes, std::vector<std::string> scriptTargets) {
return run(makes,
cmd("sh", "-c",
"(cd \"$1\";mkdir build;cd build;../configure);make -C "
"\"$1\"/build $SUBTARGET",
"--", makes),
scriptTargets);
}
rule redo(std::string makes, std::vector<std::string> scriptTargets) {
return run(makes,
cmd("sh", "-c", "redo-ifchange -C \"$1\" $SUBTARGET", "--", makes),
scriptTargets);
}
rule pijul_clone(std::string makes, std::string patchRemote) {
return run(makes, cmd("pijul", "clone", patchRemote, makes));
}
rule subrepo_internal(std::string makes) {
return run(makes, cmd("sh", "-c",
"cd \"$1\";cc -I$CXXBUILDPATH BUILD.cpp -o "
"BUILD;BDIR=\"$(pwd)\" ./BUILD //$SUBTARGET",
"--", makes));
}
rule pijul_repo(std::string makes, std::string patchRemote) {
return both(bind(pijul_clone, patchRemote), subrepo_internal)(makes);
}
rule pijul_external_repo(
std::string makes, std::string patchRemote,
std::function<rule(std::string, std::vector<std::string>)> r,
std::vector<std::string> targets) {
return both(bind(pijul_clone, patchRemote), bind(r, targets))(makes);
}
rule git_clone(std::string makes, std::string remote) {
return run(makes, cmd("git", "clone", remote, makes));
}
rule git_repo(std::string makes, std::string remote) {
return both(bind(git_clone, remote), subrepo_internal)(makes);
}
rule git_external_repo(
std::string makes, std::string remote,
std::function<rule(std::string, std::vector<std::string>)> r,
std::vector<std::string> targets) {
return both(bind(git_clone, remote), bind(r, targets))(makes);
}
nlohmann::json readManifest(rules &r, std::string target) {
r(target);
redi::pstream p({"ar", "p", target, "manifest.json"});
std::string x;
char y;
while (p >> y)
x += y;
return nlohmann::json(x);
}
std::map<std::string, std::shared_ptr<std::ifstream>>
getFilesOf(rules &r, std::string target, std::string type) {
auto m = readManifest(r, target);
std::map<std::string, std::shared_ptr<std::ifstream>> n;
for (auto x : m[type]) {
r(x["target"]);
n[x["result"]] = std::make_shared<std::ifstream>(x["target"]);
};
return n;
}
rule file(std::string makes, std::shared_ptr<std::istream> t) {
return [&](rules &x) -> std::string {
if (has_cache(makes))
return makes;
auto fd = procure(x);
std::ofstream u(makes);
char v;
while (*t >> v)
u << v;
return makes;
};
}
rule files(std::string prefix, std::string target, std::string type) {
return [&](rules &r) -> std::string {
for (auto f : getFilesOf(r, target, type)) {
r(prefix + f.first, file, f.second);
}
return prefix;
};
}
std::vector<std::string> listdir(std::string x) {
redi::pstream p({"ls", x});
std::vector<std::string> a;
std::string b;
while (std::getline(p, b))
a.push_back(b);
return a;
}
rule concat(std::string target, std::vector<std::string> srcs) {
auto v = cmd("sh", "-c", "t=\"$1\";shift 1;cat \"$@\" > \"$t\"", target);
for (auto s : srcs)
v.push_back(s);
return run(target, v, srcs);
}
rule cc_compile(std::string target, std::vector<std::string> hdrs,
std::string src) {
auto v = cmd("cxx", src, "-c", "-o", target);
auto vv = hdrs;
vv.push_back(src);
return run(target, v, vv);
}
rule cc_library(std::string name, std::vector<std::string> hdrs,
std::vector<std::string> srcs, std::vector<std::string> deps) {
return [&](rules &r) -> std::string {
for (auto s : srcs) {
r(s + ".o", cc_compile, hdrs, s);
};
std::vector<std::string> a{"ar", "crus"}, d;
for (auto s : srcs) {
a.push_back(s + ".o");
d.push_back(s + ".o");
}
auto c = a;
for (auto hd : hdrs) {
a.push_back(hd);
d.push_back(hd);
}
a.push_back(name + ".d/manifest.json");
d.push_back(name + ".d/manifest.json");
for (auto dep : deps) {
d.push_back(name + ".d/_" + dep);
r(name + ".d/_", run, {"ar", "x", dep})
};
r(name, run, a, d);
r(name + ".d/manifest.json", file,
std::make_shared<std::istringstream>(
nlohmann::json::object({{"hdrs", hdrs}, {"objs", c}}).to_ubjson()));
r(name);
return name;
};
}
void build_rules(rules &);
int main(int argc, char **argv) {
int a, b;
_makeflags(a, b, argc, argv);
int f[2]{a, b};
rules r(f, getenv("CONFIG"), getenv("BDIR"));
build_rules(r);
r("default.do", file,
std::make_shared<std::istringstream>(std::string("exec '") + argv[0] +
"' \"$1\""));
std::string o(argv[1]);
if (!has_cache(argv[0]) && r.find(argv[0]) != r.end()) {
r[argv[0]](r);
std::vector<char *> a(argv, argv + argc);
a.push_back(nullptr);
execv(argv[0], &a[0]);
};
if (o == "--list") {
for (auto e : r)
std::cout << e.first << std::endl;
} else if (o == "--all" || o == "-A") {
for (auto e : r)
e.second(r);
} else {
r[argv[1]](r);
}
};