aboutsummaryrefslogtreecommitdiff
path: root/cbs.h
diff options
context:
space:
mode:
Diffstat (limited to 'cbs.h')
-rw-r--r--cbs.h638
1 files changed, 638 insertions, 0 deletions
diff --git a/cbs.h b/cbs.h
new file mode 100644
index 0000000..0f8551c
--- /dev/null
+++ b/cbs.h
@@ -0,0 +1,638 @@
+/*
+ * BSD Zero Clause License
+ *
+ * Copyright © 2023–2024 Thomas Voss
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef C_BUILD_SYSTEM_H
+#define C_BUILD_SYSTEM_H
+
+#define _GNU_SOURCE
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#ifndef CBS_NO_THREADS
+# include <pthread.h>
+#endif
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wordexp.h>
+
+#define _vtoxs(...) ((char *[]){__VA_ARGS__})
+
+#define lengthof(xs) (sizeof(xs) / sizeof(*(xs)))
+
+struct strs {
+ char **buf;
+ size_t len, cap;
+};
+
+enum pkg_config_flags {
+ PC_CFLAGS = 1 << 0,
+ PC_LIBS = 1 << 1,
+ PC_SHARED = 1 << 2,
+ PC_STATIC = 1 << 3,
+};
+
+static void cbsinit(int, char **);
+static void rebuild(const char *); /* Always call via macro wrapper */
+#define rebuild() rebuild(__FILE__)
+
+static void strsfree(struct strs *);
+static void strszero(struct strs *);
+static void strspush(struct strs *, char **, size_t);
+static void strspushenv(struct strs *, const char *, char **, size_t);
+#define strspushl(xs, ...) \
+ strspush((xs), _vtoxs(__VA_ARGS__), lengthof(_vtoxs(__VA_ARGS__)))
+#define strspushenvl(xs, ev, ...) \
+ strspushenv((xs), (ev), _vtoxs(__VA_ARGS__), lengthof(_vtoxs(__VA_ARGS__)))
+
+static bool fexists(const char *);
+static int fmdcmp(const char *, const char *);
+static bool fmdolder(const char *, const char *);
+static bool fmdnewer(const char *, const char *);
+static bool foutdated(const char *, char **, size_t);
+#define foutdatedl(s, ...) \
+ foutdated((s), _vtoxs(__VA_ARGS__), lengthof(_vtoxs(__VA_ARGS__)))
+
+static int cmdexec(struct strs);
+static pid_t cmdexec_async(struct strs);
+static int cmdexec_read(struct strs, char **, size_t *);
+static int cmdwait(pid_t);
+static void cmdput(struct strs);
+static void cmdfput(FILE *, struct strs);
+
+static char *swpext(const char *, const char *);
+static bool pcquery(struct strs *, const char *, int);
+static bool binexists(const char *);
+static int nproc(void);
+
+#ifndef CBS_NO_THREADS
+typedef void tjob(void *);
+typedef void tjob_free(void *);
+
+struct _tqueue {
+ void *arg;
+ tjob *fn;
+ tjob_free *free;
+ struct _tqueue *next;
+};
+
+typedef struct {
+ bool stop;
+ size_t tcnt, left;
+ pthread_t *thrds;
+ pthread_cond_t cnd;
+ pthread_mutex_t mtx;
+ struct _tqueue *head, *tail;
+} tpool;
+
+static void tpinit(tpool *, size_t);
+static void tpfree(tpool *);
+static void tpwait(tpool *);
+static void tpenq(tpool *, tjob *, void *, tjob_free *);
+#endif /* !CBS_NO_THREADS */
+
+static int _cbs_argc;
+static char **_cbs_argv;
+
+/* Implementation */
+
+#ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wunused-function"
+#endif
+
+#ifdef __APPLE__
+# define st_mtim st_mtimespec
+#endif
+
+void
+cbsinit(int argc, char **argv)
+{
+ _cbs_argc = argc;
+ _cbs_argv = malloc(sizeof(char *) * (argc + 1));
+ assert(_cbs_argv != NULL);
+ for (int i = 0; i < argc; i++) {
+ _cbs_argv[i] = strdup(argv[i]);
+ assert(_cbs_argv[i] != NULL);
+ }
+ _cbs_argv[argc] = NULL;
+
+ char *s = strrchr(_cbs_argv[0], '/');
+ if (s != NULL) {
+ s[0] = 0;
+ assert(chdir(_cbs_argv[0]) != -1);
+ s[0] = '/';
+ }
+}
+
+void
+(rebuild)(const char *path)
+{
+ char *src, *dst;
+
+ if ((src = strrchr(path, '/')) != NULL)
+ src++;
+ else
+ src = (char *)path;
+
+ if ((dst = strrchr(*_cbs_argv, '/')) != NULL)
+ dst++;
+ else
+ dst = *_cbs_argv;
+
+ if (!foutdatedl(dst, src))
+ return;
+
+ struct strs xs = {0};
+ strspushenvl(&xs, "CC", "cc");
+#ifndef CBS_NO_THREADS
+ strspushl(&xs, "-lpthread");
+#endif
+ strspushl(&xs, "-o", dst, src);
+ cmdput(xs);
+ assert(cmdexec(xs) == EXIT_SUCCESS);
+
+ execvp(*_cbs_argv, _cbs_argv);
+ assert(!"failed to execute process");
+}
+
+void
+strsfree(struct strs *xs)
+{
+ free(xs->buf);
+ xs->buf = NULL;
+ xs->len = xs->cap = 0;
+}
+
+void
+strszero(struct strs *xs)
+{
+ xs->len = 0;
+ if (xs->cap > 0)
+ xs->buf[0] = NULL;
+}
+
+void
+strspush(struct strs *xs, char **ys, size_t n)
+{
+ if (n == 0)
+ return;
+
+ if (xs->len + n >= xs->cap) {
+ xs->cap = (xs->len + n) * 2;
+ xs->buf = realloc(xs->buf, sizeof(char *) * (xs->cap + 1));
+ assert(xs->buf != NULL);
+ }
+ memcpy(xs->buf + xs->len, ys, n * sizeof(char *));
+ xs->len += n;
+
+ assert(xs->len <= xs->cap);
+ xs->buf[xs->len] = NULL;
+}
+
+void
+strspushenv(struct strs *xs, const char *ev, char **ys, size_t n)
+{
+ /* NOTE: Do your best to NOT modify any pushed envvar! */
+ char *p = getenv(ev);
+ if (p == NULL || *p == 0) {
+ strspush(xs, ys, n);
+ return;
+ }
+
+ wordexp_t we;
+ assert(wordexp(p, &we, WRDE_NOCMD) == 0);
+
+ /* TODO: Memory leak! */
+ for (size_t i = 0; i < we.we_wordc; i++) {
+ char *w = strdup(we.we_wordv[i]);
+ assert(w != NULL);
+ strspushl(xs, w);
+ }
+
+ wordfree(&we);
+}
+
+bool
+fexists(const char *f)
+{
+ return !access(f, F_OK);
+}
+
+int
+fmdcmp(const char *lhs, const char *rhs)
+{
+ int errnol, errnor;
+ struct stat sbl, sbr;
+
+ stat(lhs, &sbl); errnol = errno; errno = 0;
+ stat(rhs, &sbr); errnor = errno;
+
+ assert(errnol == 0 || errnol == ENOENT);
+ assert(errnor == 0 || errnor == ENOENT);
+
+ if (errnol == ENOENT && errnor == ENOENT)
+ return 0;
+ if (errnol == ENOENT)
+ return -1;
+ if (errnor == ENOENT)
+ return +1;
+
+ return sbl.st_mtim.tv_sec == sbr.st_mtim.tv_sec
+ ? sbl.st_mtim.tv_nsec - sbr.st_mtim.tv_nsec
+ : sbl.st_mtim.tv_sec - sbr.st_mtim.tv_sec;
+}
+
+bool
+fmdnewer(const char *lhs, const char *rhs)
+{
+ return fmdcmp(lhs, rhs) > 0;
+}
+
+bool
+fmdolder(const char *lhs, const char *rhs)
+{
+ return fmdcmp(lhs, rhs) < 0;
+}
+
+bool
+foutdated(const char *src, char **deps, size_t n)
+{
+ for (size_t i = 0; i < n; i++) {
+ if (fmdolder(src, deps[i]))
+ return true;
+ }
+ return false;
+}
+
+int
+cmdexec(struct strs xs)
+{
+ return cmdwait(cmdexec_async(xs));
+}
+
+pid_t
+cmdexec_async(struct strs xs)
+{
+ pid_t pid = fork();
+ assert(pid != -1);
+ if (pid == 0) {
+ execvp(xs.buf[0], xs.buf);
+ assert(!"failed to execute process");
+ }
+ return pid;
+}
+
+int
+cmdexec_read(struct strs xs, char **p, size_t *n)
+{
+ enum {
+ R,
+ W,
+ };
+ int fds[2];
+
+ assert(pipe(fds) != -1);
+
+ pid_t pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ close(fds[R]);
+ close(STDOUT_FILENO);
+ assert(dup2(fds[W], STDOUT_FILENO) != -1);
+ execvp(xs.buf[0], xs.buf);
+ assert(!"failed to execute process");
+ }
+
+ close(fds[W]);
+
+ struct stat sb;
+ assert(fstat(fds[R], &sb) != -1);
+
+ *p = NULL, *n = 0;
+ char *buf = malloc(sb.st_blksize);
+ assert(buf != NULL);
+
+ for (;;) {
+ ssize_t nr;
+ if ((nr = read(fds[R], buf, sb.st_blksize)) == 0)
+ break;
+ assert(nr != -1);
+
+ *p = realloc(*p, *n + nr + 1);
+ assert(*p != NULL);
+
+ memcpy(*p + *n, buf, nr);
+ *n += nr;
+ }
+
+ close(fds[R]);
+ free(buf);
+
+ return cmdwait(pid);
+}
+
+int
+cmdwait(pid_t pid)
+{
+ int ws;
+ assert(waitpid(pid, &ws, 0) != -1);
+ if (WIFEXITED(ws))
+ return WEXITSTATUS(ws);
+ return WIFEXITED(ws) ? WEXITSTATUS(ws) : 256;
+}
+
+/*
+ * import shlex
+ *
+ * s = '#define _SHELL_SAFE "'
+ * for c in map(chr, range(128)):
+ * if not shlex._find_unsafe(c):
+ * s += c
+ * print(s + '"')
+ */
+#define _SHELL_SAFE \
+ "%+,-./0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"
+
+void
+cmdput(struct strs xs)
+{
+ cmdfput(stdout, xs);
+}
+
+void
+cmdfput(FILE *fp, struct strs xs)
+{
+ flockfile(fp);
+ for (size_t i = 0; i < xs.len; i++) {
+ bool safe = true;
+ const char *p = xs.buf[i];
+
+ for (const char *q = p; *q; q++) {
+ if (!strchr(_SHELL_SAFE, *q)) {
+ safe = false;
+ break;
+ }
+ }
+
+ if (safe)
+ fputs(p, fp);
+ else {
+ putc('\'', fp);
+ for (const char *q = p; *q; q++) {
+ if (*q == '\'')
+ fputs("'\"'\"'", fp);
+ else
+ putc(*q, fp);
+ }
+ putc('\'', fp);
+ }
+
+ putc(i == xs.len - 1 ? '\n' : ' ', fp);
+ }
+ funlockfile(fp);
+}
+
+bool
+pcquery(struct strs *xs, const char *lib, int flags)
+{
+ struct strs ys = {0};
+
+ strspushl(&ys, "pkg-config", "--silence-errors");
+ if (flags & PC_CFLAGS)
+ strspushl(&ys, "--cflags");
+ if (flags & PC_LIBS)
+ strspushl(&ys, "--libs");
+ if (flags & PC_SHARED)
+ strspushl(&ys, "--shared");
+ if (flags & PC_STATIC)
+ strspushl(&ys, "--static");
+ strspushl(&ys, (char *)lib);
+
+ char *buf;
+ size_t bufsz;
+ int ec = cmdexec_read(ys, &buf, &bufsz);
+ strsfree(&ys);
+ if (ec != EXIT_SUCCESS)
+ return false;
+
+ /* Remove trailing newline */
+ buf[bufsz - 1] = 0;
+
+ wordexp_t we;
+ assert(wordexp(buf, &we, WRDE_NOCMD) == 0);
+
+ char **words = malloc(sizeof(char *) * we.we_wordc);
+ assert(words != NULL);
+
+ /* TODO: Memory leak! */
+ for (size_t i = 0; i < we.we_wordc; i++)
+ assert((words[i] = strdup(we.we_wordv[i])) != NULL);
+
+ strspush(xs, words, we.we_wordc);
+ wordfree(&we);
+ free(words);
+ free(buf);
+ return true;
+}
+
+bool
+binexists(const char *s)
+{
+ const char *path = getenv("PATH");
+ assert(path != NULL);
+
+ char *p = strdup(path), *it;
+ assert(p != NULL);
+
+ for (it = strtok(p, ":"); it != NULL; it = strtok(NULL, ":")) {
+ static char buf[PATH_MAX];
+ memset(buf, 0, sizeof(buf));
+ snprintf(buf, sizeof(buf), "%s/%s", it, s);
+ if (fexists(buf)) {
+ free(p);
+ return true;
+ }
+ }
+
+ free(p);
+ return false;
+}
+
+int
+nproc(void)
+{
+#ifdef _SC_NPROCESSORS_ONLN
+ return (int)sysconf(_SC_NPROCESSORS_ONLN);
+#else
+ errno = 0;
+ return -1;
+#endif
+}
+
+char *
+swpext(const char *file, const char *ext)
+{
+ const char *p = strrchr(file, '.');
+ if (p == NULL) {
+ p = strdup(file);
+ assert(p != NULL);
+ return (char *)p;
+ }
+
+ size_t noextlen = p - file;
+ char *s = malloc(noextlen + strlen(ext) + 2);
+ assert(s != NULL);
+ sprintf(s, "%.*s.%s", (int)noextlen, file, ext);
+ return s;
+}
+
+#ifndef CBS_NO_THREADS
+static struct _tqueue *
+_tpdeq(tpool *tp)
+{
+ struct _tqueue *q = tp->head;
+
+ if (q != NULL) {
+ tp->head = tp->head->next;
+ if (!tp->head)
+ tp->tail = NULL;
+ }
+
+ return q;
+}
+
+static void *
+_tpwork(void *arg)
+{
+ tpool *tp = arg;
+
+ while (!tp->stop) {
+ struct _tqueue *q;
+
+ pthread_mutex_lock(&tp->mtx);
+ while (!tp->stop && !tp->head)
+ pthread_cond_wait(&tp->cnd, &tp->mtx);
+ if (tp->stop) {
+ pthread_mutex_unlock(&tp->mtx);
+ break;
+ }
+
+ q = _tpdeq(tp);
+ pthread_mutex_unlock(&tp->mtx);
+
+ q->fn(q->arg);
+ if (q->free)
+ q->free(q->arg);
+ free(q);
+
+ pthread_mutex_lock(&tp->mtx);
+ tp->left--;
+ pthread_cond_broadcast(&tp->cnd);
+ pthread_mutex_unlock(&tp->mtx);
+ }
+
+ return NULL;
+}
+
+void
+tpinit(tpool *tp, size_t n)
+{
+ tp->tcnt = n;
+ tp->stop = false;
+ tp->left = 0;
+ tp->head = tp->tail = NULL;
+ tp->thrds = malloc(sizeof(pthread_t) * n);
+ assert(tp->thrds != NULL);
+ pthread_cond_init(&tp->cnd, NULL);
+ pthread_mutex_init(&tp->mtx, NULL);
+ for (size_t i = 0; i < n; i++)
+ assert(pthread_create(tp->thrds + i, NULL, _tpwork, tp) == 0);
+}
+
+void
+tpfree(tpool *tp)
+{
+ tp->stop = true;
+
+ pthread_mutex_lock(&tp->mtx);
+ pthread_cond_broadcast(&tp->cnd);
+ pthread_mutex_unlock(&tp->mtx);
+
+ for (size_t i = 0; i < tp->tcnt; i++)
+ pthread_join(tp->thrds[i], NULL);
+
+ free(tp->thrds);
+ while (tp->head != NULL) {
+ struct _tqueue *q = _tpdeq(tp);
+ if (q->free)
+ q->free(q->arg);
+ free(q);
+ }
+
+ pthread_cond_destroy(&tp->cnd);
+ pthread_mutex_destroy(&tp->mtx);
+}
+
+void
+tpwait(tpool *tp)
+{
+ pthread_mutex_lock(&tp->mtx);
+ while (!tp->stop && tp->left)
+ pthread_cond_wait(&tp->cnd, &tp->mtx);
+ pthread_mutex_unlock(&tp->mtx);
+}
+
+void
+tpenq(tpool *tp, tjob *fn, void *arg, tjob_free *free)
+{
+ struct _tqueue *q = malloc(sizeof(*q));
+ assert(q != NULL);
+ *q = (struct _tqueue){
+ .fn = fn,
+ .arg = arg,
+ .free = free,
+ };
+
+ pthread_mutex_lock(&tp->mtx);
+ if (tp->tail)
+ tp->tail->next = q;
+ if (!tp->head)
+ tp->head = q;
+ tp->tail = q;
+ tp->left++;
+ pthread_cond_signal(&tp->cnd);
+ pthread_mutex_unlock(&tp->mtx);
+}
+#endif /* !CBS_NO_THREADS */
+
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+
+#ifdef __APPLE__
+# undef st_mtim
+#endif
+
+#endif /* !C_BUILD_SYSTEM_H */