diff options
| author | KonAki <konaki@anokonaki.org> | 2026-10-17 13:47:35 +0800 |
|---|---|---|
| committer | KonAki <konaki@anokonaki.org> | 2026-10-17 13:47:35 +0800 |
| commit | 6a133b95c137dd1decfea4af9f66ecc7073baeb4 (patch) | |
| tree | 04a8c127325626877227de578adb8bbac98b8e9e | |
| parent | 1998534d1b1a59d4d453abe010f44dc8af93b24b (diff) | |
konbuild: 64 bit times, windows quoting, log capture, file listing
| -rw-r--r-- | kon/konbuild.h | 687 |
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 */ |
