diff options
-rw-r--r-- | cbs.h | 1100 |
1 files changed, 380 insertions, 720 deletions
@@ -1,758 +1,458 @@ -/* Single-header library to help write build scripts in C. This library is - POSIX compliant, so it should work on all respectible UNIX-like systems. - - You can find the CBS git repository at https://git.sr.ht/~mango/cbs and you - can include this in your project with the following command: - - $ wget 'https://git.sr.ht/~mango/cbs/blob/master/cbs.h' - - This library is licensed under the 0-Clause BSD license, and as such you may - do whatever you want to it, however you want to it, whenever you want. You - are encouraged in fact to modify this library to suit your specific usecase. - - All functions and macros are documented. You can figure out the API pretty - easily by just reading the comments in this file. - - In many cases you may want to be able to execute commands on multiple threads - to speed up compilation, such as the -j option when using Make. Functions - for creating and using thread pools will be made available if the CBS_PTHREAD - macro is defined before including this file. Do note that on POSIX platforms - it will require linking to the pthreads library when bootstrapping the build - script. - - This file does not support C89. Fuck C89, that shit is ancient. Move on. - - IMPORTANT NOTE: Any identifiers prefixed with an underscore (e.g. ‘_rebuild’) - are meant for internal use only and you should not touch them unless you know - what you’re doing. - - IMPORTANT NOTE: All the functions and macros in this library will terminate - the program on error. If this is undesired behavior, feel free to edit the - functions to return errors. - - IMPORTANT NOTE: This library is built with the assumption that the source - file for your build script and your build script are in the SAME DIRECTORY. - - There are a few exceptions to the above rule, and they are documented. - - This library does not aim to ever support Windows */ +/* + * 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 -#ifdef __GNUC__ -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wunused-function" -# pragma GCC diagnostic ignored "-Wparentheses" -#endif - -/* Assert that the user is building for a supported platform. The only portable - way to check for POSIX is to validate that unistd.h exists. This is only - possible without compiler extensions in C23 (although some compilers support - it as an extension in earlier editions), so people compiling for pre-C23 - might not get this error if on a bad platform, and may end up being a bit - confused. - - It’s just a maybe though, this is nothing more than a sanity check for the - users sake. */ -#if defined(__has_include) && !__has_include(<unistd.h>) -# error "Non-POSIX platform detected" -#endif - -#ifdef __APPLE__ -# define st_mtim st_mtimespec -#endif - +#define _GNU_SOURCE #include <sys/stat.h> #include <sys/wait.h> +#include <assert.h> #include <errno.h> -#include <libgen.h> #include <limits.h> -#ifdef CBS_PTHREAD +#ifndef CBS_NO_THREADS # include <pthread.h> #endif -#include <stdarg.h> -#include <stdint.h> +#include <stdbool.h> +#include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <wordexp.h> -/* C23 changed a lot so we want to check for it */ -#if __STDC_VERSION__ >= 202000 -# define CBS_IS_C23 1 -#endif +#define _vtoxs(...) ((char *[]){__VA_ARGS__}) -/* Some C23 compat. In C23 booleans are actual keywords, and the noreturn - attribute is different. */ -#if CBS_IS_C23 -# define noreturn [[noreturn]] -#else -# include <stdbool.h> -# include <stddef.h> -# include <stdnoreturn.h> -# define nullptr NULL -#endif +#define lengthof(xs) (sizeof(xs) / sizeof(*(xs))) -/* Give helpful diagnostics when people use die() incorrectly on GCC. C23 - introduced C++ attribute syntax, so we need a check for that too. */ -#ifdef __GNUC__ -# if CBS_IS_C23 -# define ATTR_FMT [[gnu::format(printf, 1, 2)]] -# else -# define ATTR_FMT __attribute__((format(printf, 1, 2))) -# endif -#else -# define ATTR_FMT -#endif - -/* Clang defines this attribute, and while it does nothing it does serve as - good documentation. */ -#ifndef _Nullable -# define _Nullable -#endif - -/* Convert the given variadic arguments to a string array */ -#define _vtoa(...) ((char *[]){__VA_ARGS__}) - -/* Internal global versions of argc and argv, so our functions and macros can - access them from anywhere. */ -static int _cbs_argc; -static char **_cbs_argv; - -/* A vector of strings used to accumulate output of functions such as pcquery(). - The array of strings buf has length len, and is not null-terminated. You - should always zero-initialize variables of this type. */ -struct strv { +struct strs { char **buf; - size_t len; + size_t len, cap; }; -/* Free and zero a string vector. Because this function zeros the structure, it - is safe to reuse the vector after this function is called. */ -static void strvfree(struct strv *); - -/* A wrapper function around realloc(). It behaves exactly the same except - instead of taking a buffer size as an argument, it takes a count n of - elements, and a size m of each element. This allows it to properly check for - overflow, and errors if overflow would occur. */ -static void *bufalloc(void *_Nullable, size_t n, size_t m); - -/* Error reporting functions. The die() function takes the same arguments as - printf() and prints the corresponding string to stderr. It also prefixes the - string with the command name followed by a colon, and suffixes the string - with a colon and the error string returned from strerror(). - - If you want to print just the error message and no custom text, NULL may be - passed to die(). NULL should not be passed to diex(). - - diex() is the same as die() but does not print a strerror() error string. */ -ATTR_FMT noreturn static void die(const char *_Nullable, ...); -ATTR_FMT noreturn static void diex(const char *, ...); - -/* Initializes some data required for this header to work properly. This should - be the first thing called in main() with argc and argv passed. It also - chdir()s into the directory where the build script is located. */ -static void cbsinit(int, char **); - -/* Get the number of items in the array a */ -#define lengthof(a) (sizeof(a) / sizeof(*(a))) - -/* Struct representing a CLI command that various functions act on. You should - always zero-initialize variables of this type before use. - - After executing a command, you can reuse the already allocated buffer this - command holds by calling cmdclr(). When you’re really done with an object of - this type, remember to call free() on ._argv. - - The ._argv field is a NULL-terminated list of command arguments of length - ._len. You may safely read from both of these fields but they should NOT be - modified without use of cmdadd() and cmdaddv(). */ -typedef struct { - char **_argv; - size_t _len, _cap; -} cmd_t; +enum pkg_config_flags { + PC_CFLAGS = 1 << 0, + PC_LIBS = 1 << 1, + PC_SHARED = 1 << 2, + PC_STATIC = 1 << 3, +}; -/* Returns whether or not a binary of the given name exists in the users - environment as defined by $PATH. */ -static bool binexists(const char *); +void cbsinit(int, char **); +static void rebuild(const char *); /* Always call via macro wrapper */ +#define rebuild() rebuild(__FILE__) -/* cmdadd() adds the variadic string arguments to the given command. - Alternatively, the cmdaddv() function adds the n strings pointed to by p to - the given command. */ -static void cmdaddv(cmd_t *, char **p, size_t n); -#define cmdadd(cmd, ...) \ - cmdaddv(cmd, _vtoa(__VA_ARGS__), lengthof(_vtoa(__VA_ARGS__))) - -/* Clear (but not free) the command c. Useful for reusing the same command - struct to minimize allocations. */ -static void cmdclr(cmd_t *c); - -/* The cmdexec() function executes the given command and waits for it to - terminate, returning its exit code. The cmdexeca() function executes the - given command and returns immediately, returning its process ID. - - The cmdexecb() function is like cmdexec() except it writes the given commands - standard output to the character buffer pointed to by p. It also stores the - size of the output in *n. The character buffer p is null-terminated. If the - given command produces no output to the standard output, p will be set to - NULL. - - cmdexec() and cmdexecb() have the same return values as cmdwait(). */ -static int cmdexec(cmd_t); -static pid_t cmdexeca(cmd_t); -static int cmdexecb(cmd_t, char **p, size_t *n); - -/* Wait for the process with the given PID to terminate, and return its exit - status. If the process was terminated by a signal 256 is returned. */ -static int cmdwait(pid_t); +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__))) -/* Write a representation of the given command to the given file stream. This - can be used to mimick the echoing behavior of make(1). The cmdput() function - is a nice convenience function so you can avoid writing ‘stdout’ all the - time. */ -static void cmdput(cmd_t); -static void cmdputf(FILE *, cmd_t); - -/* Expand the environment variable s using /bin/sh expansion rules and append - the results in the given string vector. If the environment variable is NULL - or empty, then store the strings specified by the array p of length n. - - env_or_default() is the same as env_or_defaultv() but you provide p as - variadic arguments. - - If the pointer p is null, then no default value is appended to the given - string vector when the environment variable is not present. */ -static void env_or_defaultv(struct strv *, const char *s, char *_Nullable *p, - size_t n); -#define env_or_default(sv, s, ...) \ - env_or_defaultv((sv), (s), _vtoa(__VA_ARGS__), lengthof(_vtoa(__VA_ARGS__))) - -/* Returns if a file exists at the given path. A return value of false may also - mean you don’t have the proper file access permissions, which will also set - errno. */ static bool fexists(const char *); - -/* Compare the modification dates of the two named files. - - A return value >0 means the LHS is newer than the RHS. - A return value <0 means the LHS is older than the RHS. - A return value of 0 means the LHS and RHS have the same modification date. - - The fmdnewer() and fmdolder() functions are wrappers around fmdcmp() that - return true when the LHS is newer or older than the RHS respectively. */ 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__))) -/* Report if any of n files specified by p render the file base outdated. If - the file base does not exist, this function returns true. You will typically - call this with a compiled program as base, and C source files as p. The - macro foutdated() is a wrapper around foutdatedv() that allows you to specify - the sources to base as variadic arguments instead of as an array. */ -static bool foutdatedv(const char *base, const char **p, size_t n); -#define foutdated(s, ...) \ - foutdatedv(s, (const char **)_vtoa(__VA_ARGS__), \ - lengthof(_vtoa(__VA_ARGS__))) - -/* Rebuild the build script if it has been modified, and execute the newly built - script. You should call the rebuild() macro at the very beginning of main(), - but right after cbsinit(). You probably don’t want to call _rebuild() - directly. - - NOTE: This function/macro REQUIRES that the source for the build script and - the compiled build script are in the SAME DIRECTORY. */ -static void _rebuild(char *); -#define rebuild() _rebuild(__FILE__) - -/* Get the number of available CPUs, or -1 on error. This function also returns - -1 if the _SC_NPROCESSORS_ONLN flag to sysconf(3) is not available. In that - case, errno will not be set. */ -static int nproc(void); +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); -/* Append the arguments returned by an invokation of pkg-config for the library - lib to the given string vector. The flags argument is one-or-more of the - flags in the pkg_config_flags enum bitwise-ORed together. +static char *swpext(const char *, const char *); +static bool pcquery(struct strs *, const char *, int); +static bool binexists(const char *); +static int nproc(void); - If PKGC_CFLAGS is specified, call pkg-config with ‘--cflags’. - If PKGC_LIBS is specified, call pkg-config with ‘--libs’. +#ifndef CBS_NO_THREADS +typedef void tjob(void *); +typedef void tjob_free(void *); - This function returns true on success and false if pkg-config is not found on - the system. To check for pkg-configs existance, you can use the binexists() - function. */ -static bool pcquery(struct strv *, char *lib, int flags); -enum pkg_config_flags { - PKGC_LIBS = 1 << 0, - PKGC_CFLAGS = 1 << 1, +struct _tqueue { + void *arg; + tjob *fn; + tjob_free *free; + struct _tqueue *next; }; -#ifdef CBS_PTHREAD +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 */ -/* A tfunc_t represents a function to be executed by a threads in a thread pool. - It takes an argument in the form of a void * and returns nothing. */ -typedef void (*tfunc_t)(void *); +static int _cbs_argc; +static char **_cbs_argv; -/* A tfunc_free_t represents a function which frees the argument passed to a - tfunc_t function. */ -typedef void (*tfree_func_t)(void *); +/* Implementation */ -/* A thread pool job queue. Meant for internal-use only. */ -struct _tjob { - void *arg; - tfunc_t fn; - tfree_func_t free; - struct _tjob *next; -}; +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-function" +#endif -/* A basic thread pool. None of its fields should really be touched. */ -typedef struct { - bool _stop; - size_t _tcnt, _left; - pthread_t *_thrds; - pthread_cond_t _cnd; - pthread_mutex_t _mtx; - struct _tjob *_head, *_tail; -} tpool_t; - -/* Initialize and destroy a thread pool. The tpinit() function initializes the - given thread pool and creates n threads ready to execute work. The tpfree() - function should be called after a thread pool has been used to release all - resources used by the thread pool. */ -static void tpinit(tpool_t *, size_t n); -static void tpfree(tpool_t *); - -/* Wait for all jobs on the given thread pool to be executed. Note that this - function does not destroy the threads or free any resources — those are tasks - for the tpfree() function. */ -static void tpwait(tpool_t *); - -/* Enqueue and dequeue jobs to the thread pools job queue. The tpenq() function - is threadsafe while the _tpdeq() function is not (so don’t use it). When - calling the tpenq() function, the function fn will be queued to be executed - by a thread in the thread pool with the argument arg. If the given argument - needs to be deallocated after the job completes, you can pass the free - argument which will be called with the given argument after use. If free is - NULL, it will be ignored. - - The free() function is a valid argument to the free parameter. */ -static void tpenq(tpool_t *, tfunc_t fn, void *arg, - tfree_func_t _Nullable free); -static struct _tjob *_tpdeq(tpool_t *); - -#endif /* CBS_PTHREAD */ - -/* BEGIN DEFINITIONS */ +#ifdef __APPLE__ +# define st_mtim st_mtimespec +#endif void -strvfree(struct strv *v) +cbsinit(int argc, char **argv) { - free(v->buf); - memset(v, 0, sizeof(*v)); + _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 * -bufalloc(void *p, size_t n, size_t m) +void +(rebuild)(const char *path) { - if (n && SIZE_MAX / n < m) { - errno = EOVERFLOW; - die(__func__); - } + char *src, *dst; + + if ((src = strrchr(path, '/')) != NULL) + src++; + else + src = (char *)path; - if (!(p = realloc(p, n * m))) - die(__func__); - return p; + 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 -die(const char *fmt, ...) +strsfree(struct strs *xs) { - int e = errno; - va_list ap; - - va_start(ap, fmt); - flockfile(stderr); - fprintf(stderr, "%s: ", *_cbs_argv); - if (fmt) { - vfprintf(stderr, fmt, ap); - fprintf(stderr, ": "); - } - fprintf(stderr, "%s\n", strerror(e)); - exit(EXIT_FAILURE); + free(xs->buf); + xs->buf = NULL; + xs->len = xs->cap = 0; } void -diex(const char *fmt, ...) +strszero(struct strs *xs) { - va_list ap; - - va_start(ap, fmt); - flockfile(stderr); - fprintf(stderr, "%s: ", *_cbs_argv); - vfprintf(stderr, fmt, ap); - fputc('\n', stderr); - exit(EXIT_FAILURE); + xs->len = 0; } void -cbsinit(int argc, char **argv) +strspush(struct strs *xs, char **ys, size_t n) { - char *s; + if (n == 0) + return; - _cbs_argc = argc; - _cbs_argv = bufalloc(nullptr, argc, sizeof(char *)); - for (int i = 0; i < argc; i++) { - if (!(_cbs_argv[i] = strdup(argv[i]))) { - /* We might not have set _cbs_argv[0] yet, so we can’t use die() */ - fprintf(stderr, "%s: strdup: %s\n", *argv, strerror(errno)); - exit(EXIT_FAILURE); - } + 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; - /* Cd to dirname(argv[0]). We can’t use dirname(3) because it may modify - the source string. */ - if (s = strrchr(_cbs_argv[0], '/')) { - s[0] = '\0'; - if (chdir(_cbs_argv[0]) == -1) - die("chdir: %s", s); - s[0] = '/'; - } + assert(xs->len <= xs->cap); + xs->buf[xs->len] = NULL; } -static size_t -_next_powerof2(size_t x) +void +strspushenv(struct strs *xs, const char *ev, char **ys, size_t n) { -#if defined(__has_builtin) && __has_builtin(__builtin_clzl) - x = x <= 1 ? 1 : 1 << (64 - __builtin_clzl(x - 1)); -#else - if (x) { - x--; - x |= x >> 1; - x |= x >> 2; - x |= x >> 4; - x |= x >> 8; - if (sizeof(size_t) >= 4) - x |= x >> 16; - if (sizeof(size_t) >= 8) - x |= x >> 32; - } - x++; -#endif - - return x; + /* NOTE: Do your best to NOT modify any pushed envvar! */ + char *p = getenv(ev); + if (p == NULL || *p == 0) + strspush(xs, ys, n); + else + strspush(xs, &p, 1); } bool -binexists(const char *name) +fexists(const char *f) { - char *p, *path; - - if (!(path = getenv("PATH"))) - diex("PATH environment variable not found"); + return !access(f, F_OK); +} - if (!(path = strdup(path))) - die("strdup"); - p = strtok(path, ":"); - while (p) { - char buf[PATH_MAX]; - snprintf(buf, sizeof(buf), "%s/%s", p, name); - if (fexists(buf)) { - free(path); - return true; - } +int +fmdcmp(const char *lhs, const char *rhs) +{ + struct stat sbl, sbr; - p = strtok(nullptr, ":"); - } + assert(stat(lhs, &sbl) != -1); + assert(stat(rhs, &sbr) != -1); - free(path); - return false; + 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; } -void -cmdaddv(cmd_t *cmd, char **xs, size_t n) +bool +fmdnewer(const char *lhs, const char *rhs) { - if (cmd->_len + n >= cmd->_cap) { - cmd->_cap = _next_powerof2(cmd->_len + n) + 2; - cmd->_argv = bufalloc(cmd->_argv, cmd->_cap, sizeof(char *)); - } + return fmdcmp(lhs, rhs) > 0; +} - memcpy(cmd->_argv + cmd->_len, xs, n * sizeof(*xs)); - cmd->_len += n; - cmd->_argv[cmd->_len] = nullptr; +bool +fmdolder(const char *lhs, const char *rhs) +{ + return fmdcmp(lhs, rhs) < 0; } -void -cmdclr(cmd_t *c) +bool +foutdated(const char *src, char **deps, size_t n) { - c->_len = 0; - *c->_argv = nullptr; + if (!fexists(src)) + return true; + for (size_t i = 0; i < n; i++) { + if (fmdolder(src, deps[i])) + return true; + } + return false; } int -cmdexec(cmd_t c) +cmdexec(struct strs xs) { - return cmdwait(cmdexeca(c)); + return cmdwait(cmdexec_async(xs)); } pid_t -cmdexeca(cmd_t c) +cmdexec_async(struct strs xs) { - pid_t pid; - - switch (pid = fork()) { - case -1: - die("fork"); - case 0: - execvp(*c._argv, c._argv); - die("execvp: %s", *c._argv); + pid_t pid = fork(); + assert(pid != -1); + if (pid == 0) { + execvp(xs.buf[0], xs.buf); + assert(!"failed to execute process"); } - return pid; } int -cmdexecb(cmd_t c, char **p, size_t *n) +cmdexec_read(struct strs xs, char **p, size_t *n) { enum { - FD_R, - FD_W, + R, + W, }; - pid_t pid; int fds[2]; - char *buf; - size_t len, blksize; - struct stat sb; - if (pipe(fds) == -1) - die("pipe"); - - switch (pid = fork()) { - case -1: - die("fork"); - case 0: - close(fds[FD_R]); - if (dup2(fds[FD_W], STDOUT_FILENO) == -1) - die("dup2"); - execvp(*c._argv, c._argv); - die("execvp: %s", *c._argv); + 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[FD_W]); + close(fds[W]); + + struct stat sb; + assert(fstat(fds[R], &sb) != -1); - buf = nullptr; - len = 0; + *p = NULL, *n = 0; + char *buf = malloc(sb.st_blksize); + assert(buf != NULL); - blksize = fstat(fds[FD_R], &sb) == -1 ? BUFSIZ : sb.st_blksize; for (;;) { - /* This can maybe somewhere somehow break some system. I do not care */ - char tmp[blksize]; ssize_t nr; - - if ((nr = read(fds[FD_R], tmp, blksize)) == -1) - die("read"); - if (!nr) + if ((nr = read(fds[R], buf, sb.st_blksize)) == 0) break; - buf = bufalloc(buf, len + nr + 1, sizeof(char)); - memcpy(buf + len, tmp, nr); - len += nr; + assert(nr != -1); + + *p = realloc(*p, *n + nr + 1); + assert(*p != NULL); + + memcpy(*p + *n, buf, nr); + *n += nr; } - close(fds[FD_R]); - if (buf) - buf[len] = 0; - *p = buf; - *n = len; + close(fds[R]); + if (buf != NULL) + buf[*n] = 0; + free(buf); + return cmdwait(pid); } int cmdwait(pid_t pid) { - for (;;) { - int ws; - if (waitpid(pid, &ws, 0) == -1) - die("waitpid"); - - if (WIFEXITED(ws)) - return WEXITSTATUS(ws); - - if (WIFSIGNALED(ws)) - return 256; - } + 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 "' + s = '#define _SHELL_SAFE "' for c in map(chr, range(128)): if not shlex._find_unsafe(c): s += c print(s + '"') */ -#define SHELL_SAFE \ +#define _SHELL_SAFE \ "%+,-./0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" void -cmdput(cmd_t c) +cmdput(struct strs xs) { - cmdputf(stdout, c); + cmdfput(stdout, xs); } void -cmdputf(FILE *stream, cmd_t cmd) +cmdfput(FILE *fp, struct strs xs) { - flockfile(stream); - for (size_t i = 0; i < cmd._len; i++) { + flockfile(fp); + for (size_t i = 0; i < xs.len; i++) { bool safe = true; char *p, *q; - p = q = cmd._argv[i]; + p = q = xs.buf[i]; for (; *q; q++) { - if (!strchr(SHELL_SAFE, *q)) { + if (!strchr(_SHELL_SAFE, *q)) { safe = false; break; } } if (safe) - fputs(p, stream); + fputs(p, fp); else { - putc('\'', stream); + putc('\'', fp); for (q = p; *q; q++) { if (*q == '\'') - fputs("'\"'\"'", stream); + fputs("'\"'\"'", fp); else - putc(*q, stream); + putc(*q, fp); } - putc('\'', stream); + putc('\'', fp); } - putc(i == cmd._len - 1 ? '\n' : ' ', stream); + putc(i == xs.len - 1 ? '\n' : ' ', fp); } - funlockfile(stream); + funlockfile(fp); } -void -env_or_defaultv(struct strv *sv, const char *s, char **p, size_t n) +bool +pcquery(struct strs *xs, const char *lib, int flags) { - wordexp_t we; - const char *ev; - - if ((ev = getenv(s)) && *ev) { - switch (wordexp(ev, &we, WRDE_NOCMD)) { - case WRDE_BADCHAR: - case WRDE_BADVAL: - case WRDE_SYNTAX: - errno = EINVAL; - die("wordexp"); - case WRDE_NOSPACE: - errno = ENOMEM; - die("wordexp"); - } + 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); - p = we.we_wordv; - n = we.we_wordc; - } else if (!n || !*p) - return; - - sv->buf = bufalloc(sv->buf, sv->len + n, sizeof(*sv->buf)); - for (size_t i = 0; i < n; i++) { - if (!(sv->buf[sv->len + i] = strdup(p[i]))) - die("strdup"); - } - sv->len += n; - - if (ev && *ev) - wordfree(&we); -} + char *buf; + size_t bufsz; + int ec = cmdexec_read(ys, &buf, &bufsz); + strsfree(&ys); + if (ec != EXIT_SUCCESS) + return false; -bool -fexists(const char *f) -{ - return !access(f, F_OK); -} + /* Remove trailing newline */ + buf[bufsz - 1] = 0; -int -fmdcmp(const char *lhs, const char *rhs) -{ - struct stat sbl, sbr; + wordexp_t we; + assert(wordexp(buf, &we, WRDE_NOCMD) == 0); - if (stat(lhs, &sbl) == -1) - die("%s", lhs); - if (stat(rhs, &sbr) == -1) - die("%s", rhs); + char **words = malloc(sizeof(char *) * we.we_wordc); + assert(words != NULL); + for (size_t i = 0; i < we.we_wordc; i++) + assert((words[i] = strdup(we.we_wordv[i])) != NULL); - 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; + strspush(xs, words, we.we_wordc); + wordfree(&we); + free(buf); + return true; } bool -fmdnewer(const char *lhs, const char *rhs) +binexists(const char *s) { - return fmdcmp(lhs, rhs) > 0; -} + const char *path = getenv("PATH"); + assert(path != NULL); -bool -fmdolder(const char *lhs, const char *rhs) -{ - return fmdcmp(lhs, rhs) < 0; -} + char *p = strdup(path), *it; + assert(p != NULL); -bool -foutdatedv(const char *src, const char **deps, size_t n) -{ - if (!fexists(src)) - return true; - for (size_t i = 0; i < n; i++) { - if (fmdolder(src, deps[i])) + 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; + } } - return false; -} - -void -_rebuild(char *src) -{ - char *bbn, *sbn, *argv0; - cmd_t cmd = {0}; - size_t bufsiz; - - /* We assume that the compiled binary and the source file are in the same - directory. */ - if (sbn = strrchr(src, '/')) - sbn++; - else - sbn = src; - if (bbn = strrchr(*_cbs_argv, '/')) - bbn++; - else - bbn = src; - - if (!foutdated(bbn, sbn)) - return; - - cmdadd(&cmd, "cc"); -#ifdef CBS_PTHREAD - cmdadd(&cmd, "-lpthread"); -#endif - cmdadd(&cmd, "-o", bbn, sbn); - cmdput(cmd); - if (cmdexec(cmd)) - diex("Compilation of build script failed"); - - cmdclr(&cmd); - argv0 = bufalloc(nullptr, strlen(bbn) + 3, 1); - *_cbs_argv = argv0; - - *argv0++ = '.'; - *argv0++ = '/'; - strcpy(argv0, bbn); - - cmdaddv(&cmd, _cbs_argv, _cbs_argc); - execvp(*cmd._argv, cmd._argv); - die("execvp: %s", *cmd._argv); + free(p); + return false; } int @@ -766,181 +466,141 @@ nproc(void) #endif } -bool -pcquery(struct strv *vec, char *lib, int flags) +char * +swpext(const char *file, const char *ext) { - int ec; - char *p; - size_t n; - cmd_t c = {0}; - wordexp_t we; - - p = nullptr; - - cmdadd(&c, "pkg-config"); - if (flags & PKGC_LIBS) - cmdadd(&c, "--libs"); - if (flags & PKGC_CFLAGS) - cmdadd(&c, "--cflags"); - cmdadd(&c, lib); - - if ((ec = cmdexecb(c, &p, &n))) { - if (errno == ENOENT) { - free(c._argv); - return false; - } - diex("pkg-config terminated with exit-code %d", ec); + const char *p = strrchr(file, '.'); + if (p == NULL) { + p = strdup(file); + assert(p != NULL); + return (char *)p; } - if (!p) - return true; + 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; +} - /* Remove trailing newline */ - p[n - 1] = 0; - - switch (wordexp(p, &we, WRDE_NOCMD)) { - case WRDE_BADCHAR: - case WRDE_BADVAL: - case WRDE_SYNTAX: - errno = EINVAL; - die("wordexp"); - case WRDE_NOSPACE: - errno = ENOMEM; - die("wordexp"); - } +#ifndef CBS_NO_THREADS +static struct _tqueue * +_tpdeq(tpool *tp) +{ + struct _tqueue *q = tp->head; - vec->buf = bufalloc(vec->buf, vec->len + we.we_wordc, sizeof(char *)); - for (size_t i = 0; i < we.we_wordc; i++) { - char *p = strdup(we.we_wordv[i]); - if (!p) - die(__func__); - vec->buf[vec->len++] = p; + if (q != NULL) { + tp->head = tp->head->next; + if (!tp->head) + tp->tail = NULL; } - wordfree(&we); - free(p); - return true; + return q; } -#ifdef CBS_PTHREAD - static void * _tpwork(void *arg) { - tpool_t *tp = arg; + tpool *tp = arg; - while (!tp->_stop) { - struct _tjob *j; + 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); + 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; } - j = _tpdeq(tp); - pthread_mutex_unlock(&tp->_mtx); + q = _tpdeq(tp); + pthread_mutex_unlock(&tp->mtx); - j->fn(j->arg); - if (j->free) - j->free(j->arg); - free(j); + 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); + pthread_mutex_lock(&tp->mtx); + tp->left--; + pthread_cond_broadcast(&tp->cnd); + pthread_mutex_unlock(&tp->mtx); } - return nullptr; + return NULL; } void -tpinit(tpool_t *tp, size_t n) +tpinit(tpool *tp, size_t n) { - tp->_tcnt = n; - tp->_stop = false; - tp->_left = 0; - tp->_head = tp->_tail = nullptr; - tp->_thrds = bufalloc(nullptr, n, sizeof(pthread_t)); - pthread_cond_init(&tp->_cnd, nullptr); - pthread_mutex_init(&tp->_mtx, nullptr); - + 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++) - pthread_create(tp->_thrds + i, nullptr, _tpwork, tp); + assert(pthread_create(tp->thrds + i, NULL, _tpwork, tp) == 0); } void -tpfree(tpool_t *tp) +tpfree(tpool *tp) { - tp->_stop = true; + tp->stop = true; - pthread_mutex_lock(&tp->_mtx); - pthread_cond_broadcast(&tp->_cnd); - pthread_mutex_unlock(&tp->_mtx); + 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], nullptr); + for (size_t i = 0; i < tp->tcnt; i++) + pthread_join(tp->thrds[i], NULL); - free(tp->_thrds); - while (tp->_head) { - struct _tjob *j = _tpdeq(tp); - if (j->free) - j->free(j->arg); - free(j); + 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); + pthread_cond_destroy(&tp->cnd); + pthread_mutex_destroy(&tp->mtx); } -struct _tjob * -_tpdeq(tpool_t *tp) +void +tpwait(tpool *tp) { - struct _tjob *j = tp->_head; - - if (j) { - tp->_head = tp->_head->next; - if (!tp->_head) - tp->_tail = nullptr; - } - - return j; + 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_t *tp, tfunc_t fn, void *arg, tfree_func_t free) +tpenq(tpool *tp, tjob *fn, void *arg, tjob_free *free) { - struct _tjob *j = bufalloc(nullptr, 1, sizeof(struct _tjob)); - *j = (struct _tjob){ + 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 = j; - if (!tp->_head) - tp->_head = j; - tp->_tail = j; - tp->_left++; - pthread_cond_signal(&tp->_cnd); - pthread_mutex_unlock(&tp->_mtx); + 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); } - -void -tpwait(tpool_t *tp) -{ - pthread_mutex_lock(&tp->_mtx); - while (!tp->_stop && tp->_left) - pthread_cond_wait(&tp->_cnd, &tp->_mtx); - pthread_mutex_unlock(&tp->_mtx); -} - -#endif /* CBS_PTHREAD */ +#endif /* !CBS_NO_THREADS */ #ifdef __GNUC__ # pragma GCC diagnostic pop |