aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2024-06-08 18:51:26 +0200
committerThomas Voss <mail@thomasvoss.com> 2024-06-08 18:52:24 +0200
commit66189c46e9877b5fc65ce49ae64e57250a038c8a (patch)
tree1c7d280acbc41c735f212675c94c3f74f8109845
parent30bb758f4f095812c593294ca7f2ab39b5fb8b39 (diff)
Massive overhaul to cbs.h
-rw-r--r--cbs.h1100
1 files changed, 380 insertions, 720 deletions
diff --git a/cbs.h b/cbs.h
index 72ccef5..64d6372 100644
--- a/cbs.h
+++ b/cbs.h
@@ -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