--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+enum { F_FILES, F_DIRS };
+
+typedef struct Target {
+ struct Target* next;
+ char** outputs;
+ char** inputs;
+ void (*build)(struct Target*);
+} Target;
+
+int MaxJobs = 4;
+
+char* CCCMD[] = {
+ "cc",
+ "-Wall", "-Wextra", "-Werror",
+ "-Iinc/",
+ "-I/usr/X11/include/",
+ "-I/usr/X11/include/freetype2",
+ "-c", "-o",
+ /* output */
+ /* inputs */
+ NULL
+};
+
+char* ARCMD[] = {
+ "ar", "rcs",
+ NULL
+};
+
+char* LDCMD[] = {
+ "cc",
+ "-L./build/lib",
+ "-L/usr/X11/lib/",
+ "-o",
+ /* output */
+ /* inputs */
+ /* LIBS */
+ NULL
+};
+
+char* LIBS[] = {
+ "-lX11",
+ "-lfontconfig",
+ NULL
+};
+
+Target* Srcs = NULL;
+Target* Libs = NULL;
+Target* Bins = NULL;
+
+/*
+ Helper Routines
+*/
+
+static char* strmcat(char* first, ...)
+{
+ va_list args;
+ /* calculate the length of the final string */
+ size_t len = strlen(first);
+ va_start(args, first);
+ for (char* s = NULL; (s = va_arg(args, char*));)
+ {
+ len += strlen(s);
+ }
+ va_end(args);
+ /* allocate the final string and copy the args into it */
+ char *str = malloc(len+1), *curr = str;
+ while (first && *first)
+ {
+ *(curr++) = *(first++);
+ }
+ va_start(args, first);
+ for (char* s = NULL; (s = va_arg(args, char*));)
+ {
+ while (s && *s)
+ {
+ *(curr++) = *(s++);
+ }
+ }
+ va_end(args);
+ /* null terminate and return */
+ *curr = '\0';
+ return str;
+}
+
+static char** dir_entries(char* path, int flags)
+{
+ size_t nentries = 0;
+ char** entries = calloc(1, sizeof(char*));
+ struct dirent* entry = NULL;
+ struct stat attrs = {0};
+
+ DIR* dir = opendir(path);
+ while ( (entry = readdir(dir)) )
+ {
+ if (entry->d_name[0] == '.') continue;
+ char* epath = strmcat(path, "/", entry->d_name, 0);
+ memset(&attrs, 0, sizeof(attrs));
+ stat(epath, &attrs);
+ if ( ((flags == F_FILES) && !S_ISDIR(attrs.st_mode)) ||
+ ((flags == F_DIRS) && S_ISDIR(attrs.st_mode)) )
+ {
+ entries = realloc(entries, (nentries+2)*sizeof(char*));
+ entries[nentries++] = epath;
+ entries[nentries] = NULL;
+ }
+ }
+
+ return entries;
+}
+
+char** make_cmd(char*** cmd_parts)
+{
+ size_t count = 0;
+ char** cmd = NULL;
+ for (; *cmd_parts; cmd_parts++)
+ {
+ char** subpart = *cmd_parts;
+ for (; *subpart; subpart++)
+ {
+ cmd = realloc(cmd, ++count * sizeof(char*));
+ cmd[count-1] = *subpart;
+ }
+ }
+ cmd = realloc(cmd, ++count * sizeof(char*));
+ cmd[count-1] = NULL;
+ return cmd;
+}
+
+void print_cmd(char** cmd)
+{
+ for (; *cmd; cmd++)
+ {
+ printf("%s ", *cmd);
+ }
+ puts("");
+}
+
+int wait_for_jobs(int count)
+{
+ int ret_status = 0;
+ do
+ {
+ int status = 0;
+ waitpid(-1, &status, 0);
+ if (WIFEXITED(status))
+ {
+ count--;
+ }
+ int exitcode = WEXITSTATUS(status);
+ ret_status = (!ret_status ? exitcode : ret_status);
+ }
+ while (count > 0);
+ return ret_status;
+}
+
+void make_dirs(char** outputs)
+{
+ for (; *outputs; outputs++)
+ {
+ char* out = strdup(*outputs);
+ char* slash = strrchr(out, '/');
+ if (slash)
+ {
+ *slash = '\0';
+ mkdir(out, 0755);
+ }
+ }
+}
+
+time_t timestamp(char* path)
+{
+ struct stat s;
+ time_t mtime = 0;
+ if (!stat(path, &s))
+ {
+ mtime = s.st_mtime;
+ }
+ return mtime;
+}
+
+int should_rebuild(char** outputs, char** inputs)
+{
+ time_t mtime = 0;
+ for (; *outputs; outputs++)
+ {
+ time_t mt = timestamp(*outputs);
+ mtime = (mt > mtime ? mt : mtime);
+ }
+ for (; *inputs; inputs++)
+ {
+ time_t mt = timestamp(*inputs);
+ if (mt > mtime)
+ {
+ return 1; /* rebuild! */
+ }
+ }
+ return 0; /* no need to rebuild */
+}
+
+/*
+ Build Routines
+*/
+
+void object(Target* tgt)
+{
+ printf("Object %s\n", tgt->outputs[0]);
+ char** cmd = make_cmd((char**[]){ CCCMD, tgt->outputs, tgt->inputs, NULL });
+ make_dirs(tgt->outputs);
+ exit(execvp(cmd[0], cmd));
+}
+
+void library(Target* tgt)
+{
+ printf("Library %s\n", tgt->outputs[0]);
+ char** cmd = make_cmd((char**[]){ ARCMD, tgt->outputs, tgt->inputs, NULL });
+ exit(execvp(cmd[0], cmd));
+}
+
+void binary(Target* tgt)
+{
+ printf("Binary %s\n", tgt->outputs[0]);
+ char** cmd = make_cmd((char**[]){
+ LDCMD, tgt->outputs, tgt->inputs, LIBS, NULL });
+ exit(execvp(cmd[0], cmd));
+}
+
+/*
+ Target List Routines
+*/
+
+void add_target(Target** list, char** outputs, char** inputs, void (*build)(Target*))
+{
+ Target* tgt = calloc(1, sizeof(Target));
+ tgt->next = *list;
+ tgt->outputs = outputs;
+ tgt->inputs = inputs;
+ tgt->build = build;
+ *list = tgt;
+}
+
+/*
+ Target Routines
+*/
+
+char* add_src(char* src)
+{
+ char* obj = strmcat("build/obj/", src, 0);
+ obj[strlen(obj)-1] = 'o';
+ char** outputs = calloc(2, sizeof(char*));
+ outputs[0] = obj;
+ char** inputs = calloc(2, sizeof(char*));
+ inputs[0] = src;
+ add_target(&Srcs, outputs, inputs, object);
+ return obj;
+}
+
+char* add_lib(char* path, char** srcs)
+{
+ char* libname = strrchr(path, '/') + 1;
+ char* lib = strmcat("build/lib/lib", libname, ".a", 0);
+ char** outputs = calloc(2, sizeof(char*));
+ outputs[0] = lib;
+ add_target(&Libs, outputs, srcs, library);
+ return lib;
+}
+
+char* add_bin(char* path, char** srcs)
+{
+ char* binname = strdup(strrchr(path, '/') + 1);
+ char* ext = strrchr(binname, '.');
+
+ if (ext)
+ {
+ *ext = '\0';
+ }
+ char* bin = strmcat("build/bin/", binname, 0);
+ char** outputs = calloc(2, sizeof(char*));
+ outputs[0] = bin;
+ add_target(&Bins, outputs, srcs, binary);
+
+ return bin;
+}
+
+char** add_srcs(char* path)
+{
+ char** srcs = dir_entries(path, F_FILES);
+ for (char** s = srcs; *s; s++)
+ {
+ char* obj = add_src(*s);
+ *s = obj;
+ }
+
+ return srcs;
+}
+
+/*
+ Build Routines
+*/
+
+void build_targets(Target* targets)
+{
+ int exitcode = 0;
+ int count = 0;
+
+ for (; !exitcode && targets; targets = targets->next)
+ {
+ count++;
+ int pid = fork();
+ if (pid == 0)
+ {
+ if (should_rebuild(targets->outputs, targets->inputs))
+ {
+ targets->build(targets);
+ }
+ exit(0);
+ }
+ else if (count >= MaxJobs)
+ {
+ int code = wait_for_jobs(1);
+ exitcode = (!exitcode ? code : exitcode);
+ count--;
+ }
+ }
+ int code = wait_for_jobs(count);
+ exitcode = (!exitcode ? code : exitcode);
+
+ if (exitcode)
+ {
+ printf("Job Failed!\n");
+ exit(exitcode);
+ }
+}
+
+/*
+ Main Routine
+*/
+
+int main(int argc, char** argv)
+{
+ (void)argc, (void)argv;
+
+ /* register all of the libraries */
+ char** libs = dir_entries("lib", F_DIRS);
+ for (; *libs; libs++)
+ {
+ char** srcs = add_srcs(*libs);
+ add_lib(*libs, srcs);
+ }
+
+ /* register all the complex binaries */
+ char** cbins = dir_entries("bin", F_DIRS);
+ for (; *cbins; cbins++)
+ {
+ char** srcs = add_srcs(*cbins);
+ add_bin(*cbins, srcs);
+ }
+
+ /* register all the simple binaries */
+ char** sbins = dir_entries("bin", F_FILES);
+ for (; *sbins; sbins++)
+ {
+ add_bin(*sbins, (char*[]){ *sbins, NULL });
+ }
+
+ /* create output directories */
+ mkdir("build/", 0755);
+ mkdir("build/obj/", 0755);
+ mkdir("build/obj/bin/", 0755);
+ mkdir("build/obj/lib/", 0755);
+ mkdir("build/bin/", 0755);
+ mkdir("build/lib/", 0755);
+
+ /* now build all of the things */
+ build_targets(Srcs);
+ build_targets(Libs);
+ build_targets(Bins);
+
+ return 0;
+}
\ No newline at end of file