/* 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 #include #include #include #include #include #include #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #else #include #include #include #include #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 /* what built this program is what it builds with, unless CC says otherwise. the compiler tells us what it is, but not what it is called on this machine, so -DKON_CC='"name"' on the command line that builds it names it by hand, and the program keeps it when it rebuilds itself. gcc and clang (and mingw and llvm-mingw, which are those two) are gcc and clang, the rest are cc */ #if defined(KON_CC) #define KON_CC_DEFAULT KON_CC #elif defined(__TINYC__) #define KON_CC_DEFAULT "tcc" #elif defined(__clang__) #define KON_CC_DEFAULT "clang" #elif defined(__GNUC__) #define KON_CC_DEFAULT "gcc" #else #define KON_CC_DEFAULT "cc" #endif #if defined(__TINYC__) #define KON_CC_FAMILY "tcc" #elif defined(__clang__) #define KON_CC_FAMILY "clang" #elif defined(__GNUC__) #define KON_CC_FAMILY "gcc" #elif defined(_MSC_VER) #define KON_CC_FAMILY "msvc" #else #define KON_CC_FAMILY "unknown" #endif static const char *kon_cc(void) KON_MAYBE_UNUSED; static const char *kon_ccVersion(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); /* a command running on its own, to wait for later. kon_waitAny waits for whichever of procs ends first, gives its number and in ok whether it worked (-1 when none is running). kon_cpuCount is how many programs make sense to run at once */ typedef struct kon_proc { #ifdef _WIN32 HANDLE handle; #else pid_t pid; #endif int active; } KonProc; bool kon_startCmd(KonCmd *c, const char *logPath, KonProc *p); bool kon_waitProc(KonProc *p); int kon_waitAny(KonProc *procs, size_t n, bool *ok); int kon_cpuCount(void); /* 0 stops the commands being written to stderr as they run, they are by default */ extern int kon_verbose; 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); /* a dependency file as -MMD -MF writes it: true when the output is not there, the file is not, or anything it lists is newer than the output or gone */ bool kon_depsNewer(const char *output, const char *depFile); 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 static const char *kon_cc(void) { #if KON_TOOLCHAIN_MSVC return "cl"; #else const char *cc = getenv("CC"); return cc && *cc ? cc : KON_CC_DEFAULT; #endif } /* the version of the compiler this program was built with, as it reports it */ static const char *kon_ccVersion(void) { #if defined(__clang__) return __clang_version__; #elif defined(__TINYC__) return "tcc"; #elif defined(__VERSION__) return __VERSION__; #else return "unknown"; #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; } int kon_verbose = 1; static void kon_printCmd_(KonCmd *c) { size_t i; if (!kon_verbose) return; 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_startCmd(KonCmd *c, const char *logPath, KonProc *p) { STARTUPINFOA si; PROCESS_INFORMATION pi; SECURITY_ATTRIBUTES sa; HANDLE log = INVALID_HANDLE_VALUE; size_t total = 1, at = 0, i; char *line; bool ok; p->active = 0; 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 (log != INVALID_HANDLE_VALUE) CloseHandle(log); if (!ok) { fprintf(stderr, "konbuild: could not start %s (%lu)\n", c->items[0], (unsigned long)GetLastError()); return false; } CloseHandle(pi.hThread); p->handle = pi.hProcess; p->active = 1; return true; } bool kon_waitProc(KonProc *p) { DWORD code = 1; if (!p->active) return false; WaitForSingleObject(p->handle, INFINITE); GetExitCodeProcess(p->handle, &code); CloseHandle(p->handle); p->active = 0; return code == 0; } int kon_waitAny(KonProc *procs, size_t n, bool *ok) { HANDLE handles[MAXIMUM_WAIT_OBJECTS]; size_t map[MAXIMUM_WAIT_OBJECTS], count = 0, i; DWORD r, code = 1; for (i = 0; i < n && count < MAXIMUM_WAIT_OBJECTS; i++) { if (!procs[i].active) continue; handles[count] = procs[i].handle; map[count++] = i; } if (count == 0) return -1; r = WaitForMultipleObjects((DWORD)count, handles, FALSE, INFINITE); if (r >= WAIT_OBJECT_0 + count) return -1; i = map[r - WAIT_OBJECT_0]; GetExitCodeProcess(procs[i].handle, &code); CloseHandle(procs[i].handle); procs[i].active = 0; *ok = code == 0; return (int)i; } int kon_cpuCount(void) { SYSTEM_INFO si; GetSystemInfo(&si); return si.dwNumberOfProcessors < 1 ? 1 : (int)si.dwNumberOfProcessors; } #else /* posix complian systems */ bool kon_startCmd(KonCmd *c, const char *logPath, KonProc *p) { pid_t pid; int fd = -1; p->active = 0; 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; } } fflush(NULL); 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); p->pid = pid; p->active = 1; return true; } bool kon_waitProc(KonProc *p) { int status = 0; if (!p->active) return false; while (waitpid(p->pid, &status, 0) < 0) { if (errno == EINTR) continue; fprintf(stderr, "konbuild: waitpid failed: %s\n", strerror(errno)); p->active = 0; return false; } p->active = 0; return WIFEXITED(status) && WEXITSTATUS(status) == 0; } int kon_waitAny(KonProc *procs, size_t n, bool *ok) { size_t i, active = 0; for (i = 0; i < n; i++) { if (procs[i].active) active++; } if (active == 0) return -1; for (;;) { int status = 0; pid_t pid = waitpid(-1, &status, 0); if (pid < 0) { if (errno == EINTR) continue; return -1; } for (i = 0; i < n; i++) { if (procs[i].active && procs[i].pid == pid) { procs[i].active = 0; *ok = WIFEXITED(status) && WEXITSTATUS(status) == 0; return (int)i; } } } } int kon_cpuCount(void) { #ifdef _SC_NPROCESSORS_ONLN long n = sysconf(_SC_NPROCESSORS_ONLN); return n < 1 ? 1 : (int)n; #else return 1; #endif } #endif bool kon_runCmdLog(KonCmd *c, const char *logPath) { KonProc p; if (!kon_startCmd(c, logPath, &p)) return false; return kon_waitProc(&p); } bool kon_runCmdSync(KonCmd *c) { return kon_runCmdLog(c, NULL); } /*** dependency files ***/ /* what -MMD writes: "output: source header header \" and the rest on the lines after, a space in a name has a backslash before it */ bool kon_depsNewer(const char *output, const char *depFile) { long long outM = kon_mtime(output); FILE *f; char *buf, *tok; long size; size_t n, i = 0; bool newer = false; if (outM < 0) return true; f = fopen(depFile, "rb"); if (!f) return true; if (fseek(f, 0, SEEK_END) != 0 || (size = ftell(f)) < 0 || fseek(f, 0, SEEK_SET) != 0) { fclose(f); return true; } n = (size_t)size; buf = (char *)malloc(n + 1); tok = (char *)malloc(n + 1); if (!buf || !tok || fread(buf, 1, n, f) != n) { fclose(f); free(buf); free(tok); return true; } fclose(f); buf[n] = '\0'; while (i < n && !newer) { size_t len = 0; for (;;) { if (i < n && (buf[i] == ' ' || buf[i] == '\t' || buf[i] == '\n' || buf[i] == '\r')) i++; else if (i + 1 < n && buf[i] == '\\' && buf[i + 1] == '\n') i += 2; else if (i + 2 < n && buf[i] == '\\' && buf[i + 1] == '\r' && buf[i + 2] == '\n') i += 3; else if (i + 1 == n && buf[i] == '\\') i++; else break; } while (i < n) { if (buf[i] == '\\' && i + 1 < n && buf[i + 1] == ' ') { tok[len++] = ' '; i += 2; } else if (buf[i] == '\\' && i + 1 < n && (buf[i + 1] == '\n' || buf[i + 1] == '\r')) { break; } else if (buf[i] == '\\' && i + 1 == n) { break; } else if (buf[i] == ' ' || buf[i] == '\t' || buf[i] == '\n' || buf[i] == '\r') { break; } else { tok[len++] = buf[i++]; } } tok[len] = '\0'; if (len == 0 || tok[len - 1] == ':') continue; if (kon_mtime(tok) > outM || kon_mtime(tok) < 0) newer = true; } free(buf); free(tok); return newer; } /*** 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 #ifdef KON_CC kon_pushCmd(&cmd, (char *)kon_cc(), kon_fmt("-DKON_CC=\"%s\"", KON_CC), "-o", (char *)self, (char *)src_path, NULL); #else kon_pushCmd(&cmd, (char *)kon_cc(), "-o", (char *)self, (char *)src_path, NULL); #endif #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 */