// x-run: tcc -Wextra -Wall -run cbt.c #ifndef _CBT_H_ #define _CBT_H_ #include #include #include enum cbt_loglevel { CBT_LOG_FATAL, CBT_LOG_ERROR, CBT_LOG_WARNING, CBT_LOG_INFO, CBT_LOG_DEBUG, CBT_LOG_ALL }; struct cbt_strarr { char **items; size_t size, cap; }; struct cbt_strarr cbt_strarrdup(struct cbt_strarr src); extern long cbt_start_time; extern enum cbt_loglevel cbt_verbosity; extern char *cbt_cc, *cbt_cache_dir; void cbt__init(int argc, char **argv, const char *source_file); void cbt_cleanup(void); #define CBT_INIT(ARGC, ARGV) cbt__init(ARGC, ARGV, __FILE__); extern const char *cbt_log__colors[CBT_LOG_ALL + 1]; extern const char *cbt_log__typecolors[8]; void cbt_log(enum cbt_loglevel lvl, const char *fmt, ...); long cbt_get_time(void); unsigned long cbt_get_modtime(const char *filename); bool cbt_needs_recompilation(const char *input, const char *output); const char *cbt_escape_shell(char *arg); const char *cbt_escape_args(char **args); const char *cbt_escape_argsd(struct cbt_strarr arr); #ifndef CBT_FMT_NBUFS #define CBT_FMT_NBUFS 128 #endif #ifndef CBT_FMT_BUFSIZE #define CBT_FMT_BUFSIZE 1024 #endif const char *cbt_fmt(const char *fmt, ...); struct cbt_proc { FILE *fp_stdin, *fp_stdout, *fp_stderr; int pid; }; struct cbt_procgroup { struct cbt_proc *items; size_t size, cap; bool fixed_size; }; enum cbt_proc_mode { CBT_PROC_AUTO = 0, CBT_PROC_R = 1, CBT_PROC_W = 2, CBT_PROC_RW = 3, }; #define cbt_proc_new(...) _cbt_proc_new(__VA_ARGS__, NULL) #define cbt_shell(...) cbt_proc_wait(_cbt_proc_new(__VA_ARGS__, NULL)) struct cbt_proc _cbt_proc_new(enum cbt_proc_mode mode, ...); struct cbt_proc cbt_proc_newv(enum cbt_proc_mode mode, va_list args); struct cbt_proc cbt_proc_newd(enum cbt_proc_mode mode, struct cbt_strarr args); int cbt_proc_wait(struct cbt_proc proc); enum cbt_libtype { CBT_LIB_STATIC, CBT_LIB_SHARED }; struct cbt_lib { enum cbt_libtype type; const char *libname; struct cbt_strarr sources, cflags, ldflags; }; #define cbt_lib_src(LIB, ...) _cbt_lib_src(LIB, __VA_ARGS__, NULL) #define cbt_lib_cflags(LIB, ...) _cbt_lib_cflags(LIB, __VA_ARGS__, NULL) #define cbt_lib_ldflags(LIB, ...) _cbt_lib_ldflags(LIB, __VA_ARGS__, NULL) struct cbt_lib cbt_lib(const char *libname); void _cbt_lib_src(struct cbt_lib *lib, ...); void _cbt_lib_cflags(struct cbt_lib *lib, ...); void _cbt_lib_ldflags(struct cbt_lib *lib, ...); bool cbt_lib_build(struct cbt_lib lib, const char *cc); void cbt_lib_free(struct cbt_lib lib); struct cbt_binary { const char *out_path; struct cbt_strarr sources, cflags, ldflags; }; #define cbt_binary_add_src(B, ...) _cbt_binary_add_src(B, __VA_ARGS__, NULL) #define cbt_binary_build(B, CC, ...) _cbt_binary_build(B, CC, __VA_ARGS__, NULL) struct cbt_binary cbt_binary(const char *out_path); void _cbt_binary_add_src(struct cbt_binary bin, ...); bool _cbt_binary_build(struct cbt_binary bin, const char *compiler, ...); void cbt_binary_free(struct cbt_binary bin); #define CBT_ARRLEN(ARR) (sizeof(ARR) / sizeof(ARR[0])) #ifndef CBT_REALLOC #define CBT_REALLOC realloc #endif #define CBT_DA_INIT_SIZE 256 #define cbt_da_add(ARR, ITEM) \ { \ if (ARR.size >= ARR.cap) { \ ARR.cap = ARR.size == 0 ? CBT_DA_INIT_SIZE : ARR.size * 2; \ ARR.items = CBT_REALLOC(ARR.items, ARR.cap * sizeof(ARR.items[0])); \ } \ ARR.items[ARR.size] = (ITEM); \ ARR.size++; \ } #define cbt_da_remove(ARR, I) \ if (I >= 0 && I < ARR.size) { \ memmove(&ARR.items[I], &ARR.items[I + 1], \ (ARR.size - 1) * sizeof((ARR).items[0])); \ ARR.size--; \ } #define cbt_da_pop(ARR) cbt_da_remove(ARR, ARR.size - 1) #define cbt_da_merge(OUT, IN) \ { \ OUT.cap = OUT.size + IN.size; \ OUT.items = realloc(OUT.items, sizeof(OUT.items[0]) * OUT.cap); \ size_t offset = OUT.size; \ OUT.size = OUT.size + IN.size; \ memmove(&OUT.items[offset], IN.items, sizeof(OUT.items[0]) * IN.size); \ } #define cbt_da_copy(OUT, IN, T) \ { \ OUT.size = IN.size; \ OUT.cap = IN.size; \ OUT.items = malloc(sizeof(IN.items[0]) * IN.size); \ memcpy(OUT.items, IN.items, sizeof(IN.items[0]) * IN.size); \ } #ifdef CBT_IMPLEMENTATION //-*- begin cbt_impl.c 5 #define __USE_GNU #include #include #include #include #ifdef __APPLE__ #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #define CBT_FAIL(COND) \ if (COND) { \ cbt_log(CBT_LOG_FATAL, "condition %s failed, %s", #COND, strerror(errno)); \ abort(); \ }; #define CBT_TODO(NAME) \ { \ cbt_log(CBT_LOG_FATAL, "%s: Not implemented! %s:%d", NAME, __FILE__, \ __LINE__); \ abort(); \ } #ifndef PATH_MAX #define PATH_MAX 1024 #endif enum cbt_loglevel cbt_verbosity = CBT_LOG_INFO; char *cbt_cc = NULL; char cbt_location[PATH_MAX] = {0}; char *cbt_cache_dir = NULL; long cbt_start_time = 0; bool cbt_running = false; struct _cbt__autoproc_fdset { struct pollfd *items; size_t size, cap; } cbt__autoproc_fdset = {0}; struct _cbt__autoproc_set { struct _cbt__autoproc_set__item { FILE *fp; bool is_err; } *items; size_t size, cap; } cbt__autoproc_set = {0}; struct cbt_procgroup cbt__proc_pool = {0, .cap = 4, .fixed_size = 1}; pthread_t cbt__autoproc_thread; pthread_mutex_t _cbt__autoproc_mut = PTHREAD_MUTEX_INITIALIZER; const char *cbt_log__colors[CBT_LOG_ALL + 1] = { "\x1b[1;31m", "\x1b[31m", "\x1b[33m", "\x1b[32m", "\x1b[34m", "\x1b[35m"}; const char *cbt_log__text[CBT_LOG_ALL + 1] = {"FATAL", "ERROR", "WARN ", "INFO ", "DEBUG", "TRACE"}; void *cbt__line_processor(void *); void cbt__init(int argc, char **argv, const char *source_file) { cbt_start_time = cbt_get_time(); cbt_running = true; (void)argc; cbt_log(CBT_LOG_INFO, "Running CBT build %s %s", __DATE__, __TIME__); if (!cbt_cc) cbt_cc = getenv("CC"); if (!cbt_cc) cbt_cc = "cc"; cbt_log(CBT_LOG_INFO, "Found C Compiler: %s", cbt_cc); #ifdef __APPLE__ CBT_FAIL(proc_pidpath(getpid(), cbt_location, PATH_MAX) == -1); #else CBT_FAIL(readlink("/proc/self/exe", cbt_location, PATH_MAX) == -1); #endif cbt_log(CBT_LOG_INFO, "Location: %s", cbt_location); char *cbt_cache_dir = strdup(cbt_location); cbt_cache_dir = strdup(cbt_fmt("%s/%s", dirname(cbt_cache_dir), "cache")); cbt_log(CBT_LOG_DEBUG, "Args: %s", cbt_escape_args(argv)); cbt_log(CBT_LOG_DEBUG, "%s", cbt_fmt("format test: %s", "ok")); cbt_log(CBT_LOG_INFO, "Cache dir: %s", cbt_cache_dir); cbt__proc_pool.items = calloc(cbt__proc_pool.cap, sizeof(struct cbt_proc)); { cbt_log(CBT_LOG_DEBUG, "Starting line processor thread"); int err; if ((err = pthread_create(&cbt__autoproc_thread, NULL, cbt__line_processor, NULL)) != 0) { cbt_log(CBT_LOG_ERROR, "pthread_create() failed for line processor: %d", err); exit(1); } } if (cbt_needs_recompilation(source_file, argv[0])) { cbt_log(CBT_LOG_INFO, "Recompiling..."); struct cbt_proc cc = cbt_proc_new(CBT_PROC_AUTO, cbt_cc, source_file, "-o", argv[0], "-Wall", "-Wextra", NULL); int status = cbt_proc_wait(cc); cbt_log(CBT_LOG_INFO, "CC returned %d", status); if (status == 0) { cbt_cleanup(); cbt_log(CBT_LOG_INFO, "Restarting CBT", cbt_escape_args(argv)); execv(argv[0], argv); exit(0); } } } void cbt_cleanup(void) { usleep(50000); pthread_mutex_lock(&_cbt__autoproc_mut); cbt_running = false; cbt_log(CBT_LOG_INFO, "cbt shutting down"); pthread_join(cbt__autoproc_thread, NULL); pthread_mutex_unlock(&_cbt__autoproc_mut); cbt_log(CBT_LOG_INFO, "shutdown complete"); } const char *cbt_fmt(const char *fmt, ...) { static char cbt_fmt_buffers[CBT_FMT_NBUFS][CBT_FMT_BUFSIZE]; static int cbt_fmt_buffer = 0; char *buffer = cbt_fmt_buffers[(cbt_fmt_buffer++) % CBT_FMT_NBUFS]; va_list args; va_start(args, fmt); vsnprintf(buffer, CBT_FMT_BUFSIZE - 1, fmt, args); va_end(args); return buffer; } long cbt_get_time(void) { struct timespec now; clock_gettime(CLOCK_REALTIME, &now); return (long)(now.tv_sec * 1000000L) + (long)(now.tv_nsec / 1000L); } void cbt_log(enum cbt_loglevel lvl, const char *fmt, ...) { if (lvl > cbt_verbosity) return; double time_delta = (double)(cbt_get_time() - cbt_start_time) / 1000.0L; printf("[%10.3lf] [%s%s\x1b[0m] ", time_delta, cbt_log__colors[lvl], cbt_log__text[lvl]); va_list args; va_start(args, fmt); vprintf(fmt, args); va_end(args); fputc('\n', stdout); } struct cbt_strarr cbt_strarrdup(struct cbt_strarr src) { char **items = calloc(src.size, sizeof(char *)); for (size_t i = 0; i < src.size; i++) { items[i] = strdup(src.items[i]); } return (struct cbt_strarr) { items, src.size, src.size }; } // TODO: find a better option const char *cbt_escape_shell(char *arg) { int n = strlen(arg); char *esc = (char *)cbt_fmt(""); // just take a buffer esc[0] = '\''; int j = 1, i; for (i = 0; i < n; i++) { if (arg[i] == '\'') { if (j + 4 >= CBT_FMT_BUFSIZE) { return NULL; } esc[j] = '\''; esc[j + 1] = '\\'; esc[j + 2] = '\''; esc[j + 3] = '\''; j += 4; } else { if (j >= CBT_FMT_BUFSIZE) { return NULL; } esc[j++] = arg[i]; } } if (j >= CBT_FMT_BUFSIZE) { return NULL; } esc[j] = '\''; return esc; } const char *cbt_escape_args(char **args) { if (args == NULL || args[0] == NULL) return cbt_fmt(""); char *out = (char *)cbt_escape_shell(args[0]); for (int i = 1; args[i] != NULL; i++) { out = (char *)cbt_fmt("%s %s", out, cbt_escape_shell(args[i])); } return out; } const char *cbt_escape_argsd(struct cbt_strarr arr) { if (arr.size == 0 || arr.items[0] == NULL) return cbt_fmt(""); char *out = (char *)cbt_escape_shell(arr.items[0]); for (size_t i = 1; i < arr.size; i++) { if (arr.items[i]) { out = (char *)cbt_fmt("%s %s", out, cbt_escape_shell(arr.items[i])); } } return out; } unsigned long cbt_get_modtime(const char *filename) { struct stat st; int ret = lstat(filename, &st); if (ret == -1) return -1; cbt_log(CBT_LOG_DEBUG, "stat(%s) -> %ld", filename, st.st_mtime); return st.st_mtime; } bool cbt_needs_recompilation(const char *input, const char *output) { unsigned long mod_in = cbt_get_modtime(input), mod_out = cbt_get_modtime(output); if (mod_in == (unsigned long)-1) { cbt_log(CBT_LOG_WARNING, "cbt_needs_recompilation(): %s does not exist", input); return false; } return mod_out == (unsigned long)-1 || mod_in > mod_out; } void cbt__add_fd(int fd, bool is_err) { cbt_log(CBT_LOG_DEBUG, "cbt__add_fd() adding %d", fd); pthread_mutex_lock(&_cbt__autoproc_mut); cbt_log(CBT_LOG_DEBUG, "cbt__add_fd() locked mutex for fd=%d", fd); struct pollfd pfd; pfd.fd = fd; pfd.events = POLLIN | POLLOUT; struct _cbt__autoproc_set__item info = {.is_err = is_err}; cbt_da_add(cbt__autoproc_fdset, pfd); cbt_da_add(cbt__autoproc_set, info); pthread_mutex_unlock(&_cbt__autoproc_mut); } struct cbt_proc _cbt_proc_new(enum cbt_proc_mode mode, ...) { va_list args; va_start(args, mode); struct cbt_proc process = cbt_proc_newv(mode, args); va_end(args); return process; } struct cbt_proc cbt_proc_newv(enum cbt_proc_mode mode, va_list args) { struct cbt_strarr args_da = {0}; while (true) { const char *arg = va_arg(args, const char *); if (arg == NULL) break; cbt_da_add(args_da, strdup(arg)); } cbt_da_add(args_da, NULL); struct cbt_proc proc = cbt_proc_newd(mode, args_da); cbt_log(CBT_LOG_DEBUG, "freeing up args"); for (size_t i = 0; i < args_da.size; i++) { free(args_da.items[i]); } free(args_da.items); return proc; } struct cbt_proc cbt_proc_newd(enum cbt_proc_mode mode, struct cbt_strarr args) { struct cbt_proc procinfo = {0}; cbt_log(CBT_LOG_DEBUG, "Spawning process with mode %d and args %s", mode, cbt_escape_args(args.items)); int pipe_fds_sout[2] = {0}, pipe_fds_serr[2] = {0}, pipe_fds_sin[2] = {0}; if (mode & CBT_PROC_W) CBT_FAIL(pipe(pipe_fds_sin) == -1); CBT_FAIL(pipe(pipe_fds_sout) == -1); CBT_FAIL(pipe(pipe_fds_serr) == -1); procinfo.pid = fork(); if (procinfo.pid < 0) { cbt_log(CBT_LOG_ERROR, "fork() failed: %s", strerror(errno)); return procinfo; } if (procinfo.pid == 0) { // child if (mode & CBT_PROC_W) CBT_FAIL(dup2(pipe_fds_sin[1], STDIN_FILENO) == -1); CBT_FAIL(dup2(pipe_fds_sout[1], STDOUT_FILENO) == -1); CBT_FAIL(dup2(pipe_fds_serr[1], STDERR_FILENO) == -1); execvp(args.items[0], (char *const *)args.items); __builtin_unreachable(); } else { if (mode & CBT_PROC_W) procinfo.fp_stdin = fdopen(pipe_fds_sin[0], "w"); if (mode & CBT_PROC_R) procinfo.fp_stdout = fdopen(pipe_fds_sout[0], "r"); if (mode & CBT_PROC_R) procinfo.fp_stderr = fdopen(pipe_fds_serr[0], "r"); cbt_log(CBT_LOG_DEBUG, "stdin: %p (fd=%d)", procinfo.fp_stdin, pipe_fds_sin[0]); cbt_log(CBT_LOG_DEBUG, "stdout: %p (fd=%d)", procinfo.fp_stdout, pipe_fds_sout[0]); cbt_log(CBT_LOG_DEBUG, "stderr: %p (fd=%d)", procinfo.fp_stderr, pipe_fds_serr[0]); if (!(mode & CBT_PROC_R)) { cbt__add_fd(pipe_fds_sout[0], false); cbt__add_fd(pipe_fds_serr[0], true); } return procinfo; } } int cbt_proc_wait(struct cbt_proc proc) { int wstatus; do { pid_t w = waitpid(proc.pid, &wstatus, WUNTRACED | WCONTINUED); cbt_log(CBT_LOG_DEBUG, "waitpid(%d) -> %d, status = %d", proc.pid, w, wstatus); if (w == -1) { cbt_log(CBT_LOG_WARNING, "waitpid(%d) -> %d (%s)", proc.pid, w, strerror(errno)); return -2000; } if (WIFEXITED(wstatus)) { return WEXITSTATUS(wstatus); } else if (WIFSIGNALED(wstatus)) { return 1000 + WTERMSIG(wstatus); } } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus)); return -1000; } #if 1 && !defined(__APPLE__) static void cbt__line_processor_sig_handler(int signal) { (void)signal; } #endif void *cbt__line_processor(void *arg) { (void)arg; #if 1 && !defined(__APPLE__) sigset_t sigmask; sigemptyset(&sigmask); sigaddset(&sigmask, SIGUSR1); struct sigaction sa; sa.sa_handler = cbt__line_processor_sig_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; CBT_FAIL(sigaction(SIGUSR1, &sa, NULL) == -1); #endif while (cbt_running) { pthread_mutex_lock(&_cbt__autoproc_mut); #if 1 && !defined(__APPLE__) struct timespec timeout = {.tv_sec = 0, .tv_nsec = 100}; int res = ppoll(cbt__autoproc_fdset.items, cbt__autoproc_fdset.size, &timeout, &sigmask); #else for (size_t i = 0; i < cbt__autoproc_set.size; i++) { cbt_log(CBT_LOG_DEBUG, "fdset[%d] = { %d, %d }", i, cbt__autoproc_fdset.items[i].fd, cbt__autoproc_fdset.items[i].events); } cbt_log(CBT_LOG_DEBUG, "poll(_, %d, _) ...", cbt__autoproc_set.size); int res = poll(cbt__autoproc_fdset.items, cbt__autoproc_set.size, 1000); #endif if (res < 0) { cbt_log(CBT_LOG_ERROR, "(p)poll() -> %d (%s)", res, strerror(errno)); break; } char buf[1024]; for (size_t i = 0; i < cbt__autoproc_fdset.size; i++) { struct pollfd pfd = cbt__autoproc_fdset.items[i]; struct _cbt__autoproc_set__item item = cbt__autoproc_set.items[i]; if (pfd.revents != 0) { if (pfd.revents & POLLIN) { ssize_t s = read(pfd.fd, buf, sizeof(buf)); cbt_log(item.is_err ? CBT_LOG_ERROR : CBT_LOG_INFO, "fd(%d): %.*s", pfd.fd, (int)s - 1, buf); } } } pthread_mutex_unlock(&_cbt__autoproc_mut); usleep(1000); } return NULL; } struct cbt_lib cbt_lib(const char *libname) { return (struct cbt_lib){.type = CBT_LIB_STATIC, .libname = libname, .cflags = {0}, .ldflags = {0}, .sources = {0}}; } int _cbt_globerr(const char *epath, int eerrno) { cbt_log(CBT_LOG_WARNING, "glob() failed at %s: %s", epath, strerror(eerrno)); return 0; } void _cbt_lib_src(struct cbt_lib *lib, ...) { va_list args; va_start(args, lib); glob_t globbuf = { 0 }; for (char *arg = va_arg(args, char *); arg; arg = va_arg(args, char *)) { memset(&globbuf, 0, sizeof(glob_t)); CBT_FAIL(glob(arg, GLOB_BRACE, _cbt_globerr, &globbuf) != 0); for (size_t i = 0; i < globbuf.gl_pathc; i++) { char *path = globbuf.gl_pathv[i]; cbt_log(CBT_LOG_DEBUG, "gl_pathv[%d] = %s", i, path); if (path) { cbt_da_add(lib->sources, strdup(path)); } } globfree(&globbuf); } va_end(args); } void _cbt_lib_cflags(struct cbt_lib *lib, ...) { va_list args; va_start(args, lib); for (char *arg = va_arg(args, char *); arg; arg = va_arg(args, char *)) { cbt_da_add(lib->cflags, arg); } va_end(args); } void _cbt_lib_ldflags(struct cbt_lib *lib, ...) { va_list args; va_start(args, lib); for (char *arg = va_arg(args, char *); arg; arg = va_arg(args, char *)) { cbt_da_add(lib->ldflags, strdup(arg)); } va_end(args); } bool cbt_lib_build(struct cbt_lib lib, const char *compiler) { cbt_log(CBT_LOG_INFO, "library: %s", lib.libname); cbt_log(CBT_LOG_INFO, " cflags: %s", cbt_escape_argsd(lib.cflags)); cbt_log(CBT_LOG_INFO, " ldflags: %s", cbt_escape_argsd(lib.ldflags)); cbt_log(CBT_LOG_INFO, " sources: %s", cbt_escape_argsd(lib.sources)); cbt_log(CBT_LOG_INFO, " cc: %s", compiler ? compiler : cbt_cc); if (lib.sources.size == 0) { cbt_log(CBT_LOG_WARNING, "cbt_lib_build(): no sources for %s", lib.libname); return false; } { struct cbt_strarr args = {0}; cbt_da_add(args, (char *)(compiler ? compiler : cbt_cc)); cbt_da_merge(args, lib.cflags); cbt_da_add(args, "-c"); cbt_log(CBT_LOG_INFO, "args.size: %d", args.size); cbt_log(CBT_LOG_INFO, "CMD: %s", cbt_escape_argsd(args)); cbt_da_add(args, NULL); // input file cbt_da_add(args, "-o"); cbt_da_add(args, NULL); // output file cbt_da_add(args, NULL); for (size_t i = 0; i < lib.sources.size; i++) { char *source_name = args.items[args.size - 4] = lib.sources.items[i]; char *outname = (char *)cbt_fmt("%s", lib.sources.items[i]); outname[strlen(outname) - 1] = 'o'; args.items[args.size - 2] = outname; cbt_log(CBT_LOG_INFO, "building %s -> %s", lib.sources.items[i], outname); if (cbt_needs_recompilation(source_name, outname)) { struct cbt_proc proc = cbt_proc_newd(CBT_PROC_AUTO, args); if (cbt_proc_wait(proc) != 0) { cbt_log(CBT_LOG_ERROR, "build failed: %s", cbt_escape_argsd(args)); return false; } } else { cbt_log(CBT_LOG_INFO, "skipping (already built)"); } } free(args.items); } if (lib.type == CBT_LIB_SHARED) { cbt_log(CBT_LOG_INFO, "Linking everything together..."); struct cbt_strarr args = {0}; cbt_da_add(args, (char *)strdup(compiler ? compiler : cbt_cc)); for (size_t i = 0; i < lib.sources.size; i++) { char *outname = (char *)cbt_fmt("%s", lib.sources.items[i]); outname[strlen(outname) - 1] = 'o'; cbt_da_add(args, strdup(outname)); } cbt_da_merge(args, lib.ldflags); cbt_da_add(args, strdup("-shared")); cbt_da_add(args, strdup("-o")); cbt_da_add(args, strdup(cbt_fmt("lib%s.so", lib.libname))); cbt_da_add(args, NULL); struct cbt_proc proc = cbt_proc_newd(CBT_PROC_AUTO, args); if (cbt_proc_wait(proc) != 0) { cbt_log(CBT_LOG_ERROR, "build failed: %s", cbt_escape_argsd(args)); return false; } for (size_t i = 0; i < args.size - 1; i++) { free(args.items[i]); } free(args.items); } else if (lib.type == CBT_LIB_STATIC) { cbt_log(CBT_LOG_INFO, "Archiving"); struct cbt_strarr args = {0}; cbt_da_add(args, strdup("ar")); cbt_da_add(args, strdup("rcs")); cbt_da_add(args, strdup(cbt_fmt("lib%s.a", lib.libname))); for (size_t i = 0; i < lib.sources.size; i++) { char *outname = (char *)cbt_fmt("%s", lib.sources.items[i]); outname[strlen(outname) - 1] = 'o'; cbt_da_add(args, strdup(outname)); } cbt_da_add(args, NULL); struct cbt_proc proc = cbt_proc_newd(CBT_PROC_AUTO, args); if (cbt_proc_wait(proc) != 0) { cbt_log(CBT_LOG_ERROR, "build failed: %s", cbt_escape_argsd(args)); return false; } for (size_t i = 0; i < args.size - 1; i++) { free(args.items[i]); } free(args.items); } else { CBT_TODO("Unknown library type"); } return true; } void cbt_lib_free(struct cbt_lib lib) { free(lib.cflags.items); free(lib.ldflags.items); for (size_t i = 0; i < lib.sources.size; i++) { free(lib.sources.items[i]); } free(lib.sources.items); } struct cbt_binary cbt_binary(const char *out_path); void _cbt_binary_add_src(struct cbt_binary bin, ...); bool _cbt_binary_build(struct cbt_binary bin, const char *compiler, ...); void cbt_binary_free(struct cbt_binary bin); #undef CBT_TODO //-*- end #endif #endif