diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 35 | ||||
-rw-r--r-- | cbs.h | 642 | ||||
-rw-r--r-- | make.c | 290 |
4 files changed, 933 insertions, 35 deletions
@@ -1,2 +1,3 @@ +make tiktok *.mo diff --git a/Makefile b/Makefile deleted file mode 100644 index 2cd2405..0000000 --- a/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -CC = cc -CFLAGS = -Wall -Wextra -Wpedantic -std=c23 \ - -I$$(brew --prefix gettext)/include \ - -L$$(brew --prefix gettext)/lib \ - -lintl - -PREFIX = /usr/local -DPREFIX = $(DESTDIR)$(PREFIX) -PODIR = $(DPREFIX)/share/locale - -all: tiktok - -tiktok: main.c - $(CC) $(CFLAGS) -DPODIR='"$(PODIR)"' -o $@ $< - -extract: - xgettext --from-code=UTF-8 -k_ -o po/tiktok.pot main.c - find po -name '*.po' -exec msgmerge {} po/tiktok.pot -o {} \; - -translations: - find po -name '*.po' | \ - while read -r file; do msgfmt "$$file" -o "$${file%po}mo"; done - -install: - mkdir -p "$(DPREFIX)/bin" "$(DPREFIX)/share/man/man1" - find po -type d -maxdepth 2 -mindepth 2 | while read -r path; \ - do \ - mkdir -p "$(PODIR)/$${path#*/}"; \ - msgfmt "$$path/tiktok.po" -o "$(PODIR)/$${path#*/}/tiktok.mo"; \ - done - cp tiktok "$(DPREFIX)/bin" - -clean: - rm tiktok - find po -name '*.mo' -delete @@ -0,0 +1,642 @@ +/* + * BSD Zero Clause License + * + * Copyright © 2023–2024 Thomas Voss + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef C_BUILD_SYSTEM_H +#define C_BUILD_SYSTEM_H + +#define _GNU_SOURCE +#include <sys/stat.h> +#include <sys/wait.h> + +#include <assert.h> +#include <errno.h> +#include <limits.h> +#ifndef CBS_NO_THREADS +# include <pthread.h> +#endif +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wordexp.h> + +#define _vtoxs(...) ((char *[]){__VA_ARGS__}) + +#define lengthof(xs) (sizeof(xs) / sizeof(*(xs))) + +struct strs { + char **buf; + size_t len, cap; +}; + +enum pkg_config_flags { + PC_CFLAGS = 1 << 0, + PC_LIBS = 1 << 1, + PC_SHARED = 1 << 2, + PC_STATIC = 1 << 3, +}; + +static void cbsinit(int, char **); +static void rebuild(const char *); /* Always call via macro wrapper */ +#define rebuild() rebuild(__FILE__) + +static void strsfree(struct strs *); +static void strszero(struct strs *); +static void strspush(struct strs *, char **, size_t); +static void strspushenv(struct strs *, const char *, char **, size_t); +#define strspushl(xs, ...) \ + strspush((xs), _vtoxs(__VA_ARGS__), lengthof(_vtoxs(__VA_ARGS__))) +#define strspushenvl(xs, ev, ...) \ + strspushenv((xs), (ev), _vtoxs(__VA_ARGS__), lengthof(_vtoxs(__VA_ARGS__))) + +static bool fexists(const char *); +static int fmdcmp(const char *, const char *); +static bool fmdolder(const char *, const char *); +static bool fmdnewer(const char *, const char *); +static bool foutdated(const char *, char **, size_t); +#define foutdatedl(s, ...) \ + foutdated((s), _vtoxs(__VA_ARGS__), lengthof(_vtoxs(__VA_ARGS__))) + +static int cmdexec(struct strs); +static pid_t cmdexec_async(struct strs); +static int cmdexec_read(struct strs, char **, size_t *); +static int cmdwait(pid_t); +static void cmdput(struct strs); +static void cmdfput(FILE *, struct strs); + +static char *swpext(const char *, const char *); +static bool pcquery(struct strs *, const char *, int); +static bool binexists(const char *); +static int nproc(void); + +#ifndef CBS_NO_THREADS +typedef void tjob(void *); +typedef void tjob_free(void *); + +struct _tqueue { + void *arg; + tjob *fn; + tjob_free *free; + struct _tqueue *next; +}; + +typedef struct { + bool stop; + size_t tcnt, left; + pthread_t *thrds; + pthread_cond_t cnd; + pthread_mutex_t mtx; + struct _tqueue *head, *tail; +} tpool; + +static void tpinit(tpool *, size_t); +static void tpfree(tpool *); +static void tpwait(tpool *); +static void tpenq(tpool *, tjob *, void *, tjob_free *); +#endif /* !CBS_NO_THREADS */ + +static int _cbs_argc; +static char **_cbs_argv; + +/* Implementation */ + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-function" +#endif + +#ifdef __APPLE__ +# define st_mtim st_mtimespec +#endif + +void +cbsinit(int argc, char **argv) +{ + _cbs_argc = argc; + _cbs_argv = malloc(sizeof(char *) * (argc + 1)); + assert(_cbs_argv != NULL); + for (int i = 0; i < argc; i++) { + _cbs_argv[i] = strdup(argv[i]); + assert(_cbs_argv[i] != NULL); + } + _cbs_argv[argc] = NULL; + + char *s = strrchr(_cbs_argv[0], '/'); + if (s != NULL) { + s[0] = 0; + assert(chdir(_cbs_argv[0]) != -1); + s[0] = '/'; + } +} + +void +(rebuild)(const char *path) +{ + char *src, *dst; + + if ((src = strrchr(path, '/')) != NULL) + src++; + else + src = (char *)path; + + if ((dst = strrchr(*_cbs_argv, '/')) != NULL) + dst++; + else + dst = *_cbs_argv; + + if (!foutdatedl(dst, src)) + return; + + struct strs xs = {0}; + strspushenvl(&xs, "CC", "cc"); +#ifndef CBS_NO_THREADS + strspushl(&xs, "-lpthread"); +#endif + strspushl(&xs, "-o", dst, src); + cmdput(xs); + assert(cmdexec(xs) == EXIT_SUCCESS); + + execvp(*_cbs_argv, _cbs_argv); + assert(!"failed to execute process"); +} + +void +strsfree(struct strs *xs) +{ + free(xs->buf); + xs->buf = NULL; + xs->len = xs->cap = 0; +} + +void +strszero(struct strs *xs) +{ + xs->len = 0; + if (xs->cap > 0) + xs->buf[0] = NULL; +} + +void +strspush(struct strs *xs, char **ys, size_t n) +{ + if (n == 0) + return; + + if (xs->len + n >= xs->cap) { + xs->cap = (xs->len + n) * 2; + xs->buf = realloc(xs->buf, sizeof(char *) * (xs->cap + 1)); + assert(xs->buf != NULL); + } + memcpy(xs->buf + xs->len, ys, n * sizeof(char *)); + xs->len += n; + + assert(xs->len <= xs->cap); + xs->buf[xs->len] = NULL; +} + +void +strspushenv(struct strs *xs, const char *ev, char **ys, size_t n) +{ + /* NOTE: Do your best to NOT modify any pushed envvar! */ + char *p = getenv(ev); + if (p == NULL || *p == 0) { + strspush(xs, ys, n); + return; + } + + wordexp_t we; + assert(wordexp(p, &we, WRDE_NOCMD) == 0); + + /* TODO: Memory leak! */ + for (size_t i = 0; i < we.we_wordc; i++) { + char *w = strdup(we.we_wordv[i]); + assert(w != NULL); + strspushl(xs, w); + } + + wordfree(&we); +} + +bool +fexists(const char *f) +{ + return !access(f, F_OK); +} + +int +fmdcmp(const char *lhs, const char *rhs) +{ + int errnol, errnor; + struct stat sbl, sbr; + + stat(lhs, &sbl); errnol = errno; errno = 0; + stat(rhs, &sbr); errnor = errno; + + assert(errnol == 0 || errnol == ENOENT); + assert(errnor == 0 || errnor == ENOENT); + + if (errnol == ENOENT && errnor == ENOENT) + return 0; + if (errnol == ENOENT) + return -1; + if (errnor == ENOENT) + return +1; + + return sbl.st_mtim.tv_sec == sbr.st_mtim.tv_sec + ? sbl.st_mtim.tv_nsec - sbr.st_mtim.tv_nsec + : sbl.st_mtim.tv_sec - sbr.st_mtim.tv_sec; +} + +bool +fmdnewer(const char *lhs, const char *rhs) +{ + return fmdcmp(lhs, rhs) > 0; +} + +bool +fmdolder(const char *lhs, const char *rhs) +{ + return fmdcmp(lhs, rhs) < 0; +} + +bool +foutdated(const char *src, char **deps, size_t n) +{ + for (size_t i = 0; i < n; i++) { + if (fmdolder(src, deps[i])) + return true; + } + return false; +} + +int +cmdexec(struct strs xs) +{ + 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; + p[*n] = 0; + } + + 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 */ @@ -0,0 +1,290 @@ +#include <sys/types.h> + +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <libgen.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define CBS_NO_THREADS +#include "cbs.h" + +#define streq(x, y) (strcmp(x, y) == 0) + +static void + clean(void), + extract(void), + install(void), + tiktok(void), + translations(void); +static void *xmalloc(size_t n); +static char *xstrdup(const char *s); + +const char *destdir = "", *prefix = "/usr/local"; +static int rv = EXIT_SUCCESS; + +static inline int +rv_(int n) +{ + if (n != 0 && rv != EXIT_SUCCESS) + rv = n; + return n; +} + +[[noreturn]] static void +usage(const char *argv0) +{ + fprintf(stderr, + "Usage: %s [clean | extract | install | tiktok | translations]\n", + argv0); + exit(EXIT_FAILURE); +} + +int +main(int argc, char **argv) +{ + cbsinit(argc, argv); + rebuild(); + + argv[0] = basename(argv[0]); + + const char *ev = getenv("DESTDIR"); + if (ev != nullptr && ev[0] != 0) + destdir = ev; + ev = getenv("PREFIX"); + if (ev != nullptr && ev[0] != 0) + prefix = ev; + + const char *target = "tiktok"; + if (argc == 2) + target = argv[1]; + else if (argc > 2) + usage(argv[0]); + + if (streq(target, "clean")) + clean(); + else if (streq(target, "extract")) + extract(); + else if (streq(target, "install")) + install(); + else if (streq(target, "tiktok")) + tiktok(); + else if (streq(target, "translations")) + translations(); + else + usage(argv[0]); + + return rv; +} + +void +clean(void) +{ + struct strs cmd = {}; + strspushl(&cmd, "rm", "-f", "tiktok"); + cmdput(cmd); + rv_(cmdexec(cmd)); + strszero(&cmd); + strspushl(&cmd, "find", "po", "-name", "*.mo", "-delete"); + cmdput(cmd); + rv_(cmdexec(cmd)); + strsfree(&cmd); +} + +void +extract(void) +{ + struct strs cmd = {}; + strspushl(&cmd, "xgettext", "--from-code=UTF-8", "-k_", + "-o", "po/tiktok.pot", "main.c"); + cmdput(cmd); + if (rv_(cmdexec(cmd)) != 0) + exit(rv); + strszero(&cmd); + strspushl(&cmd, "find", "po", "-name", "*.po", + "-exec", "msgmerge", "{}", "po/tiktok.pot", "-o", "{}", ";"); + cmdput(cmd); + rv_(cmdexec(cmd)); + strsfree(&cmd); +} + +void +install(void) +{ + struct strs cmd = {}; + char bindir[PATH_MAX], mandir[PATH_MAX]; + snprintf(bindir, sizeof(bindir), "%s%s/bin", destdir, prefix); + snprintf(mandir, sizeof(mandir), "%s%s/share/man/man1", destdir, prefix); + strspushl(&cmd, "mkdir", "-p", bindir, mandir); + cmdput(cmd); + if (rv_(cmdexec(cmd)) != 0) + exit(rv); + strszero(&cmd); + + char podirbuf[PATH_MAX]; + const char *ev = getenv("PODIR"); + + if (ev == nullptr || *ev == 0) { + snprintf(podirbuf, sizeof(podirbuf), "%s%s/share/locale", + destdir, prefix); + ev = podirbuf; + } + + DIR *dirp = opendir("po"); + if (dirp == nullptr) + err(EXIT_FAILURE, "opendir: po"); + + struct dirent *dp; + while ((dp = readdir(dirp)) != nullptr) { + /* ‘.’, ‘..’ och filnamn */ + if (strchr(dp->d_name, '.') != nullptr) + continue; + + char buf[PATH_MAX]; + snprintf(buf, sizeof(buf), "%s/%s/LC_MESSAGES", ev, dp->d_name); + char *podstdir = xstrdup(buf); + strspushl(&cmd, "mkdir", "-p", podstdir); + cmdput(cmd); + if (rv_(cmdexec(cmd)) != 0) + exit(rv); + strszero(&cmd); + + char *filepo, *filemo; + snprintf(buf, sizeof(buf), "po/%s/LC_MESSAGES/tiktok.po", dp->d_name); + filepo = xstrdup(buf); + snprintf(buf, sizeof(buf), "%s/tiktok.mo", podstdir); + filemo = buf; + + strspushl(&cmd, "msgfmt", filepo, "-o", filemo); + cmdput(cmd); + if (rv_(cmdexec(cmd)) != 0) + exit(rv); + + free(filepo); + free(podstdir); + strszero(&cmd); + } + if (errno != 0) + err(EXIT_FAILURE, "readdir: po"); + + closedir(dirp); + + strspushl(&cmd, "cp", "tiktok", bindir); + cmdput(cmd); + if (rv_(cmdexec(cmd)) != 0) + exit(rv); + strsfree(&cmd); +} + +void +tiktok(void) +{ + struct strs cmd = {}; + strspushenvl(&cmd, "CC", "cc"); + strspushenvl(&cmd, "CFLAGS", "-Wall", "-Wextra", "-Wpedantic", "-std=c23"); + + char buf[PATH_MAX]; + const char *ev = getenv("PODIR"); + + if (ev == nullptr || *ev == 0) { + snprintf(buf, sizeof(buf), "%s%s/share/locale", destdir, prefix); + ev = buf; + } + + size_t bufsz = sizeof(buf) + sizeof("-DPODIR=\"\""); + char *dpodir = xmalloc(bufsz); + snprintf(dpodir, bufsz, "-DPODIR=\"%s\"", ev); + strspushl(&cmd, dpodir); + +#if __APPLE__ + struct strs brewcmd = {}; + char *brewbuf; + size_t brewbufsz; + + strspushl(&brewcmd, "brew", "--prefix", "gettext"); + if (rv_(cmdexec_read(brewcmd, &brewbuf, &brewbufsz)) != 0) + exit(rv); + strsfree(&brewcmd); + + char *dash_I = xmalloc(brewbufsz + sizeof("-I/include")); + char *dash_L = xmalloc(brewbufsz + sizeof("-L/lib")); + + sprintf(dash_I, "-I%s/include", brewbuf); + sprintf(dash_L, "-L%s/lib", brewbuf); + + strspushl(&cmd, dash_I, dash_L); +#endif + +#if !__GLIBC__ + strspushl(&cmd, "-lintl"); +#endif + + strspushl(&cmd, "-o", "tiktok", "main.c"); + cmdput(cmd); + if (rv_(cmdexec(cmd)) != 0) + exit(rv); + +#if __APPLE__ + free(dash_L); + free(dash_I); +#endif + free(dpodir); + strsfree(&cmd); +} + +void +translations(void) +{ + DIR *dirp = opendir("po"); + if (dirp == nullptr) + err(EXIT_FAILURE, "opendir: po"); + + struct dirent *dp; + struct strs cmd = {}; + + while ((dp = readdir(dirp)) != nullptr) { + /* ‘.’, ‘..’ och filnamn */ + if (strchr(dp->d_name, '.') != nullptr) + continue; + + char *filepo, *filemo; + char buf[PATH_MAX]; + + snprintf(buf, sizeof(buf), "po/%s/LC_MESSAGES/tiktok.po", dp->d_name); + filepo = xstrdup(buf); + snprintf(buf, sizeof(buf), "po/%s/LC_MESSAGES/tiktok.mo", dp->d_name); + filemo = buf; + strspushl(&cmd, "msgfmt", filepo, "-o", filemo); + cmdput(cmd); + if (rv_(cmdexec(cmd)) != 0) + exit(rv); + free(filepo); + strszero(&cmd); + } + if (errno != 0) + err(EXIT_FAILURE, "readdir: po"); + + strsfree(&cmd); + closedir(dirp); +} + +static void * +xmalloc(size_t n) +{ + void *p = malloc(n); + if (p == nullptr) + err(EXIT_FAILURE, "xmalloc"); + return p; +} + +static char * +xstrdup(const char *s) +{ + char *p = strdup(s); + if (p == nullptr) + err(EXIT_FAILURE, "xstrdup"); + return p; +} |