summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonAki <konaki@anokonaki.org>2026-10-17 13:47:35 +0800
committerKonAki <konaki@anokonaki.org>2026-10-17 13:47:35 +0800
commit6a133b95c137dd1decfea4af9f66ecc7073baeb4 (patch)
tree04a8c127325626877227de578adb8bbac98b8e9e
parent1998534d1b1a59d4d453abe010f44dc8af93b24b (diff)
konbuild: 64 bit times, windows quoting, log capture, file listing
-rw-r--r--kon/konbuild.h687
1 files changed, 687 insertions, 0 deletions
diff --git a/kon/konbuild.h b/kon/konbuild.h
new file mode 100644
index 0000000..d687cbb
--- /dev/null
+++ b/kon/konbuild.h
@@ -0,0 +1,687 @@
+
+/* SPDX-License-Identifier: MIT */
+/*
+ * konbuild.h
+ *
+ * Copyright (c) 2026 KonAki
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef KONBUILD_H
+#define KONBUILD_H
+
+/*** includes ***/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <direct.h>
+
+#else
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/wait.h>
+
+#endif
+
+/*** definitions ***/
+
+#if defined(__GNUC__) || defined(__clang__)
+#define KON_MAYBE_UNUSED __attribute__((unused))
+#else
+#define KON_MAYBE_UNUSED
+#endif
+
+#ifdef _MSC_VER
+#define KON_TOOLCHAIN_MSVC 1
+#else
+#define KON_TOOLCHAIN_MSVC 0
+#endif
+
+static const char *kon_cc(void) KON_MAYBE_UNUSED;
+static const char *kon_ar(void) KON_MAYBE_UNUSED;
+
+typedef struct kon_cmd {
+ char **items;
+ size_t count;
+ size_t cap;
+} KonCmd;
+
+/* a list of paths, the strings belong to the list */
+typedef struct kon_list {
+ char **items;
+ size_t count;
+ size_t cap;
+} KonList;
+
+void kon_pushCmd_(KonCmd *c, ...);
+#define kon_pushCmd(c, ...) kon_pushCmd_((c), __VA_ARGS__, (char *)NULL)
+bool kon_runCmdSync(KonCmd *c);
+/* the same, with what the command prints appended to a file. NULL is the same as kon_runCmdSync */
+bool kon_runCmdLog(KonCmd *c, const char *logPath);
+void kon_resetCmd(KonCmd *c);
+void kon_freeCmd(KonCmd *c);
+
+/* a string that lives until the program ends (or kon_freeTemp), so a command can be filled with built up text without keeping track */
+char *kon_fmt(const char *format, ...);
+void kon_freeTemp(void);
+
+/* seconds, -1 when the path is not there */
+long long kon_mtime(const char *path);
+bool kon_needsRebuild(const char *output, const char **sources, size_t nSources);
+/* the newest file in a folder (not the folders in it) whose name ends in ext, or -1 */
+long long kon_newestIn(const char *dir, const char *ext);
+
+bool kon_exists(const char *path);
+bool kon_isDir(const char *path);
+bool kon_mkdir(const char *path);
+/* every folder on the way, like mkdir -p */
+bool kon_mkdirs(const char *path);
+bool kon_remove(const char *path);
+
+/* every file under a folder, in every folder inside it, sorted. the paths are the folder, a slash and the rest */
+bool kon_listFiles(const char *dir, KonList *out);
+void kon_freeList(KonList *l);
+
+#define KON_REBUILDSELF(argc, argv) kon_rebuildSelf_(__FILE__, (argc), (argv))
+void kon_rebuildSelf_(const char *src_path, int argc, char **argv);
+
+#endif /* end of KONBUILD_H */
+
+#ifdef KONBUILD_IMPLEMENTATION
+
+#include <errno.h>
+
+static const char *kon_cc(void) {
+#if KON_TOOLCHAIN_MSVC
+ return "cl";
+#else
+ const char *cc = getenv("CC");
+ return cc ? cc : "cc";
+#endif
+}
+
+static const char *kon_ar(void) {
+#if KON_TOOLCHAIN_MSVC
+ return "lib";
+#else
+ const char *ar = getenv("AR");
+ return ar ? ar : "ar";
+#endif
+}
+
+/*** files ***/
+
+long long kon_mtime(const char *path) {
+#ifdef _WIN32
+ struct _stat64 st;
+ if (_stat64(path, &st) != 0) return -1;
+#else
+ struct stat st;
+ if (stat(path, &st) != 0) return -1;
+#endif
+ return (long long)st.st_mtime;
+}
+
+bool kon_exists(const char *path) {
+ return kon_mtime(path) >= 0;
+}
+
+bool kon_isDir(const char *path) {
+ struct stat st;
+ if (stat(path, &st) != 0) return false;
+ return (st.st_mode & S_IFMT) == S_IFDIR;
+}
+
+bool kon_mkdir(const char *path) {
+#ifdef _WIN32
+ if (_mkdir(path) == 0) return true;
+#else
+ if (mkdir(path, 0755) == 0) return true;
+#endif
+ return errno == EEXIST;
+}
+
+bool kon_mkdirs(const char *path) {
+ size_t n = strlen(path);
+ char *copy = (char *)malloc(n + 1);
+ size_t i;
+ bool ok;
+
+ if (!copy) return false;
+ memcpy(copy, path, n + 1);
+ for (i = 1; i < n; i++) {
+ if (copy[i] != '/' && copy[i] != '\\') continue;
+ copy[i] = '\0';
+ /* a drive like C: cannot be made, and one that is there already is fine, only the last one has to work */
+ kon_mkdir(copy);
+ copy[i] = path[i];
+ }
+ ok = kon_mkdir(copy) || kon_isDir(copy);
+ free(copy);
+ return ok;
+}
+
+bool kon_remove(const char *path) {
+ return remove(path) == 0;
+}
+
+bool kon_needsRebuild(const char *output, const char **sources, size_t nSources) {
+ long long outM = kon_mtime(output);
+ size_t i;
+
+ if (outM < 0) return true;
+ for (i = 0; i < nSources; i++)
+ if (kon_mtime(sources[i]) > outM) return true;
+
+ return false;
+}
+
+/*** lists ***/
+
+static bool kon_listPush_(KonList *l, const char *s) {
+ size_t n = strlen(s);
+ char *copy;
+
+ if (l->count == l->cap) {
+ size_t cap = l->cap ? l->cap * 2 : 64;
+ char **items = (char **)realloc(l->items, cap * sizeof(char *));
+
+ if (!items) return false;
+ l->items = items;
+ l->cap = cap;
+ }
+ copy = (char *)malloc(n + 1);
+ if (!copy) return false;
+ memcpy(copy, s, n + 1);
+ l->items[l->count++] = copy;
+ return true;
+}
+
+void kon_freeList(KonList *l) {
+ size_t i;
+
+ for (i = 0; i < l->count; i++) free(l->items[i]);
+ free(l->items);
+ *l = (KonList){0};
+}
+
+static char *kon_join_(const char *dir, const char *name) {
+ size_t a = strlen(dir), b = strlen(name);
+ char *s = (char *)malloc(a + b + 2);
+
+ if (!s) return NULL;
+ memcpy(s, dir, a);
+ if (a > 0 && dir[a - 1] != '/' && dir[a - 1] != '\\') s[a++] = '/';
+ memcpy(s + a, name, b + 1);
+ return s;
+}
+
+/* calls back with every name in a folder, not . and .. . returns false if the folder cannot be read or the callback says stop */
+static bool kon_eachName_(const char *dir, bool (*fn)(const char *name, void *user), void *user) {
+#ifdef _WIN32
+ WIN32_FIND_DATAA fd;
+ HANDLE h;
+ char *pattern = kon_join_(dir, "*");
+
+ if (!pattern) return false;
+ h = FindFirstFileA(pattern, &fd);
+ free(pattern);
+ if (h == INVALID_HANDLE_VALUE) return false;
+ do {
+ if (strcmp(fd.cFileName, ".") == 0 || strcmp(fd.cFileName, "..") == 0) continue;
+ if (!fn(fd.cFileName, user)) {
+ FindClose(h);
+ return false;
+ }
+ } while (FindNextFileA(h, &fd));
+ FindClose(h);
+ return true;
+#else
+ DIR *d = opendir(dir);
+ struct dirent *e;
+
+ if (!d) return false;
+ while ((e = readdir(d)) != NULL) {
+ if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue;
+ if (!fn(e->d_name, user)) {
+ closedir(d);
+ return false;
+ }
+ }
+ closedir(d);
+ return true;
+#endif
+}
+
+typedef struct kon_newest_ {
+ const char *dir, *ext;
+ long long newest;
+} kon_newest_;
+
+static bool kon_newestName_(const char *name, void *user) {
+ kon_newest_ *s = (kon_newest_ *)user;
+ size_t n = strlen(name), e = strlen(s->ext);
+
+ if (n >= e && strcmp(name + n - e, s->ext) == 0) {
+ char *path = kon_join_(s->dir, name);
+ long long m = path ? kon_mtime(path) : -1;
+
+ free(path);
+ if (m > s->newest) s->newest = m;
+ }
+ return true;
+}
+
+long long kon_newestIn(const char *dir, const char *ext) {
+ kon_newest_ s;
+
+ s.dir = dir;
+ s.ext = ext;
+ s.newest = -1;
+ kon_eachName_(dir, kon_newestName_, &s);
+ return s.newest;
+}
+
+typedef struct kon_walk_ {
+ const char *dir;
+ KonList *out;
+} kon_walk_;
+
+static bool kon_walkInto_(const char *dir, KonList *out);
+
+static bool kon_walkName_(const char *name, void *user) {
+ kon_walk_ *w = (kon_walk_ *)user;
+ char *path = kon_join_(w->dir, name);
+ bool ok;
+
+ if (!path) return false;
+ if (kon_isDir(path)) ok = kon_walkInto_(path, w->out);
+ else ok = kon_listPush_(w->out, path);
+ free(path);
+ return ok;
+}
+
+static bool kon_walkInto_(const char *dir, KonList *out) {
+ kon_walk_ w;
+
+ w.dir = dir;
+ w.out = out;
+ return kon_eachName_(dir, kon_walkName_, &w);
+}
+
+static int kon_compareNames_(const void *a, const void *b) {
+ return strcmp(*(char *const *)a, *(char *const *)b);
+}
+
+bool kon_listFiles(const char *dir, KonList *out) {
+ *out = (KonList){0};
+ if (!kon_walkInto_(dir, out)) {
+ kon_freeList(out);
+ return false;
+ }
+ if (out->count > 1) qsort(out->items, out->count, sizeof(char *), kon_compareNames_);
+ return true;
+}
+
+/*** commands ***/
+
+static void kon_ensureCmd_(KonCmd *c, size_t extra) {
+ if (c->count + extra <= c->cap) return;
+
+ c->cap = (c->cap == 0) ? 16 : c->cap * 2;
+ while (c->cap < c->count + extra) c->cap *= 2;
+
+ c->items = (char **)realloc(c->items, c->cap * sizeof(char *));
+ if (!c->items) {
+ fprintf(stderr, "konbuild: out of memory\n");
+ exit(1);
+ }
+}
+
+void kon_pushCmd_(KonCmd *c, ...) {
+ va_list ap;
+ va_start(ap, c);
+
+ for (;;) {
+ char *a = va_arg(ap, char *);
+ if (!a) break;
+
+ kon_ensureCmd_(c, 1);
+ c->items[c->count++] = a;
+ }
+
+ va_end(ap);
+}
+
+void kon_resetCmd(KonCmd *c) {
+ c->count = 0;
+}
+
+void kon_freeCmd(KonCmd *c) {
+ free(c->items);
+ *c = (KonCmd){0};
+}
+
+static char **kon_temp_;
+static size_t kon_tempCount_, kon_tempCap_;
+
+char *kon_fmt(const char *format, ...) {
+ va_list ap, again;
+ int n;
+ char *s;
+
+ va_start(ap, format);
+ va_copy(again, ap);
+ n = vsnprintf(NULL, 0, format, ap);
+ va_end(ap);
+ if (n < 0) {
+ va_end(again);
+ fprintf(stderr, "konbuild: bad format\n");
+ exit(1);
+ }
+ s = (char *)malloc((size_t)n + 1);
+ if (!s) {
+ fprintf(stderr, "konbuild: out of memory\n");
+ exit(1);
+ }
+ vsnprintf(s, (size_t)n + 1, format, again);
+ va_end(again);
+ if (kon_tempCount_ == kon_tempCap_) {
+ kon_tempCap_ = kon_tempCap_ ? kon_tempCap_ * 2 : 64;
+ kon_temp_ = (char **)realloc(kon_temp_, kon_tempCap_ * sizeof(char *));
+ if (!kon_temp_) {
+ fprintf(stderr, "konbuild: out of memory\n");
+ exit(1);
+ }
+ }
+ kon_temp_[kon_tempCount_++] = s;
+ return s;
+}
+
+void kon_freeTemp(void) {
+ size_t i;
+
+ for (i = 0; i < kon_tempCount_; i++) free(kon_temp_[i]);
+ free(kon_temp_);
+ kon_temp_ = NULL;
+ kon_tempCount_ = kon_tempCap_ = 0;
+}
+
+static void kon_printCmd_(KonCmd *c) {
+ size_t i;
+
+ for (i = 0; i < c->count; i++) fprintf(stderr, "%s ", c->items[i]);
+ fprintf(stderr, "\n");
+}
+
+#ifdef _WIN32
+
+/* the quoting that CommandLineToArgvW undoes: a quote is escaped with a backslash, and the backslashes right before it or before the closing quote are doubled */
+static size_t kon_quoteArg_(char *out, const char *arg) {
+ size_t n = 0;
+ const char *p;
+
+ if (*arg && !strpbrk(arg, " \t\n\v\"")) {
+ size_t len = strlen(arg);
+
+ if (out) memcpy(out, arg, len);
+ return len;
+ }
+ if (out) out[n] = '"';
+ n++;
+ for (p = arg;; p++) {
+ size_t slashes = 0, i;
+
+ while (*p == '\\') {
+ p++;
+ slashes++;
+ }
+ if (*p == '\0') {
+ for (i = 0; i < slashes * 2; i++) {
+ if (out) out[n] = '\\';
+ n++;
+ }
+ break;
+ }
+ if (*p == '"') {
+ for (i = 0; i < slashes * 2 + 1; i++) {
+ if (out) out[n] = '\\';
+ n++;
+ }
+ } else {
+ for (i = 0; i < slashes; i++) {
+ if (out) out[n] = '\\';
+ n++;
+ }
+ }
+ if (out) out[n] = *p;
+ n++;
+ }
+ if (out) out[n] = '"';
+ n++;
+ return n;
+}
+
+bool kon_runCmdLog(KonCmd *c, const char *logPath) {
+ STARTUPINFOA si;
+ PROCESS_INFORMATION pi;
+ SECURITY_ATTRIBUTES sa;
+ HANDLE log = INVALID_HANDLE_VALUE;
+ size_t total = 1, at = 0, i;
+ char *line;
+ bool ok;
+ DWORD code = 1;
+
+ kon_printCmd_(c);
+ if (c->count == 0) return false;
+
+ for (i = 0; i < c->count; i++) total += kon_quoteArg_(NULL, c->items[i]) + 1;
+ line = (char *)malloc(total);
+ if (!line) return false;
+ for (i = 0; i < c->count; i++) {
+ at += kon_quoteArg_(line + at, c->items[i]);
+ if (i + 1 < c->count) line[at++] = ' ';
+ }
+ line[at] = '\0';
+
+ memset(&si, 0, sizeof(si));
+ memset(&pi, 0, sizeof(pi));
+ si.cb = sizeof(si);
+ if (logPath) {
+ memset(&sa, 0, sizeof(sa));
+ sa.nLength = sizeof(sa);
+ sa.bInheritHandle = TRUE;
+ log = CreateFileA(logPath, FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (log == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "konbuild: cannot open %s (%lu)\n", logPath, (unsigned long)GetLastError());
+ free(line);
+ return false;
+ }
+ si.dwFlags = STARTF_USESTDHANDLES;
+ si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+ si.hStdOutput = log;
+ si.hStdError = log;
+ }
+
+ ok = CreateProcessA(NULL, line, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) != 0;
+ free(line);
+ if (!ok) {
+ fprintf(stderr, "konbuild: could not start %s (%lu)\n", c->items[0], (unsigned long)GetLastError());
+ if (log != INVALID_HANDLE_VALUE) CloseHandle(log);
+ return false;
+ }
+
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ GetExitCodeProcess(pi.hProcess, &code);
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ if (log != INVALID_HANDLE_VALUE) CloseHandle(log);
+
+ return code == 0;
+}
+
+#else /* posix complian systems */
+
+bool kon_runCmdLog(KonCmd *c, const char *logPath) {
+ pid_t pid;
+ int status = 0, fd = -1;
+
+ kon_printCmd_(c);
+ if (c->count == 0) return false;
+
+ kon_ensureCmd_(c, 1);
+ c->items[c->count] = NULL;
+
+ if (logPath) {
+ fd = open(logPath, O_WRONLY | O_CREAT | O_APPEND, 0644);
+ if (fd < 0) {
+ fprintf(stderr, "konbuild: cannot open %s: %s\n", logPath, strerror(errno));
+ return false;
+ }
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ fprintf(stderr, "konbuild: fork failed: %s\n", strerror(errno));
+ if (fd >= 0) close(fd);
+ return false;
+ }
+
+ if (pid == 0) {
+ if (fd >= 0) {
+ dup2(fd, 1);
+ dup2(fd, 2);
+ close(fd);
+ }
+ execvp(c->items[0], c->items);
+ fprintf(stderr, "konbuild: exec failed: %s: %s\n", c->items[0], strerror(errno));
+ _exit(127);
+ }
+
+ if (fd >= 0) close(fd);
+ while (waitpid(pid, &status, 0) < 0) {
+ if (errno == EINTR) continue;
+ fprintf(stderr, "konbuild: waitpid failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ return WIFEXITED(status) && WEXITSTATUS(status) == 0;
+}
+
+#endif
+
+bool kon_runCmdSync(KonCmd *c) {
+ return kon_runCmdLog(c, NULL);
+}
+
+/*** rebuilding the build program ***/
+
+void kon_rebuildSelf_(const char *src_path, int argc, char **argv) {
+#ifndef _WIN32
+ (void)argc;
+#endif
+ const char *self = argv[0];
+ const char *hdr_path = __FILE__;
+ long long srcM, binM, hdrM, newestM;
+ KonCmd cmd = {0};
+
+#ifdef _WIN32
+ /* started as just "build", the file is build.exe. the old copy this leaves behind is cleared on the next start */
+ if (!kon_exists(self)) self = kon_fmt("%s.exe", self);
+ {
+ const char *old = kon_fmt("%s.old", self);
+ if (kon_exists(old)) kon_remove(old);
+ }
+#endif
+
+ srcM = kon_mtime(src_path);
+ binM = kon_mtime(self);
+ if (srcM < 0 || binM < 0) return;
+
+ hdrM = kon_mtime(hdr_path);
+ newestM = (hdrM > srcM) ? hdrM : srcM;
+
+ if (newestM <= binM) return;
+
+ fprintf(stderr, "konbuild: %s or %s changed, rebuilding self...\n", src_path, hdr_path);
+
+#ifdef _WIN32
+ /* a running program cannot be written over, but it can be renamed */
+ {
+ const char *old = kon_fmt("%s.old", self);
+
+ if (!MoveFileExA(self, old, MOVEFILE_REPLACE_EXISTING)) {
+ fprintf(stderr, "konbuild: could not move %s out of the way (%lu)\n", self, (unsigned long)GetLastError());
+ exit(1);
+ }
+ }
+#endif
+
+#if KON_TOOLCHAIN_MSVC
+ {
+ char *feFlag = kon_fmt("/Fe:%s", self);
+ kon_pushCmd(&cmd, (char *)kon_cc(), "/nologo", (char *)src_path, feFlag, NULL);
+ }
+#else
+ kon_pushCmd(&cmd, (char *)kon_cc(), "-o", (char *)self, (char *)src_path, NULL);
+#endif
+
+ if (!kon_runCmdSync(&cmd)) {
+ fprintf(stderr, "konbuild: self-rebuild failed\n");
+ exit(1);
+ }
+
+ kon_freeCmd(&cmd);
+
+#ifdef _WIN32
+ {
+ KonCmd relaunch = {0};
+ bool ok;
+ int i;
+
+ kon_pushCmd(&relaunch, (char *)self, NULL);
+ for (i = 1; i < argc; i++) kon_pushCmd(&relaunch, argv[i], NULL);
+ ok = kon_runCmdSync(&relaunch);
+ kon_freeCmd(&relaunch);
+ exit(ok ? 0 : 1);
+ }
+#else
+ execv(self, argv);
+ fprintf(stderr, "konbuild: exec self failed: %s\n", strerror(errno));
+ exit(1);
+#endif
+}
+
+#endif /* end of KONBUILD_IMPLEMENTATION */