aboutsummaryrefslogblamecommitdiff
path: root/cbs.h
blob: 8bcada81d4f54ee10a720131a2a2692bb0dbe55a (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16














                                                                                


                        
                   

                     
                   
                  
                   
                      
                           
                    




                    
                                             
 
                                                 
 
             
                   
                        
  




                           
 
                                  









                                                                                   
 




























                                                                                
 















                                                        
 
                    
 


                                                         
 

                                   
 









                                                        
 





                                                  
 


                           
 


                                               
 


                                                     
 
                                  
 









                                            
 

                                             
 
    
                         
 

                              
 
                         
 

                                  

    
                                              
 
                       
 


                                                                           
         



                                                          

    
                                                                 
 




                                                                 
 
                                                 
 



                                                  
         
                      
 
                      
 
                                
 
                                        
 
                             
 
                                                   
 
                                                
 




                                                 
 

                                                        
 
                                          
 
                                    
 


                                          
 
                                                 
 



                                           

   
                       
 


                                             

     
                             
 



                                                     
         


                   
                                                 
              
                  
          
                   
 









                                                          
         
                      
 




                                          
 
                  
                           
                                                                 
                              





                                              
         

                      




                            



                                                     
 








                                                                                

                                                                                  
                      
 
                            

    
                                 
 
                                             
                                 
                                          
 
                                                       




                                             
                                     
                      
                                                          
                                               
                                                             
                                    
                                                     
                         
                                       
                 
                                                       
         
                        
 
                                                    
 










                                                         
 




                                                
 
                                     
 
                                                   
 
                                                            
 

                                                                    
 



                                         

    
                        
 
                                          
 
                                    
 




                                                                       
                                    
                 
         
 
                     











                                                  
                                         
 



                                           
         




                                                        
 



                                     
 


                                          
         
                 
 

                  
                        
 
                                  
 



                                                              

                              
                                               
 


                                        
 


                                                 




                    
                           
 






                                                  
                                      
                                                                              

    
                 
 
                        
 

                                         
 
                                                 
 




                                               
         
                                        
 
                 
 


                                                      

    
                                                      
 



                                               

                             







                                       
 
                            
 






                                 
                              
/*
 * 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");
#if __STDC_VERSION__ >= 202000L
	strspushl(&xs, "-std=c23");
#endif
#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)
{
	flockfile(stderr);
	int ret = cmdwait(cmdexec_async(xs));
	funlockfile(stderr);
	return ret;
}

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 */