sbase

suckless unix tools
git clone git://git.suckless.org/sbase
Log | Files | Refs | README | LICENSE

commit 1281dc4f902331be25ead726606226d1196d1e52
parent 055cc1ae1b3a13c3d8f25af0a4a3316590efcd48
Author: Roberto E. Vargas Caballero <k0ga@shike2.net>
Date:   Tue,  7 Oct 2025 15:33:02 +0200

make: Add initial version

This implementation is ported from the scc compiler with
the author permission to re license it with sbase license.
Using this make implementation to bootstrap sbase removes
the problems found by some buggy make implementations
(specially in the case of OpenBSD). It has a drawback
that the options passed for parallel build with -j are
ignored(improvement are expected).

Due to the multi file nature of make, embedding it in
sbas-box creates some problems, and for now, we keep
it out of sbase-box.

Diffstat:
M.gitignore | 3+++
MMakefile | 24+++++++++++++++++++++---
MREADME | 1+
Mconfig.mk | 1+
Amake/defaults.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amake/main.c | 376+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amake/make.h | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amake/parser.c | 1034+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amake/posix.c | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amake/rules.c | 581+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mscripts/mkproto | 19+++++++++++++------
11 files changed, 2273 insertions(+), 9 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,8 +1,11 @@ *.o +/scripts/make /build +/make/make /getconf.h /libutf.a /libutil.a +/proto /basename /cal /cat diff --git a/Makefile b/Makefile @@ -131,6 +131,7 @@ BIN =\ logger\ logname\ ls\ + make/make\ md5sum\ mkdir\ mkfifo\ @@ -191,9 +192,20 @@ BIN =\ xinstall\ yes -OBJ = $(LIBUTFOBJ) $(LIBUTILOBJ) +MAKEOBJ =\ + make/defaults.o\ + make/main.o\ + make/parser.o\ + make/posix.o\ + make/rules.o\ -all: $(BIN) +OBJ = $(LIBUTFOBJ) $(LIBUTILOBJ) $(MAKEOBJ) + +all: scripts/make + $(SMAKE) $(BIN) + +scripts/make: + $(CC) -o $@ make/*.c $(BIN): $(LIB) @@ -208,6 +220,11 @@ $(OBJ) $(BIN): $(HDR) .c: $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< $(LIB) +$(MAKEOBJ): make/make.h + +make/make: $(MAKEOBJ) + $(CC) $(LDFLAGS) -o $@ $(MAKEOBJ) $(LIB) + libutf.a: $(LIBUTFOBJ) $(AR) $(ARFLAGS) $@ $? $(RANLIB) $@ @@ -237,7 +254,7 @@ sbase-box-uninstall: sbase-box proto dist: clean mkdir -p sbase - cp -R LICENSE Makefile README TODO config.mk *.c *.1 *.h libutf libutil sbase + cp -R LICENSE Makefile README TODO config.mk *.c *.1 *.h libutf libutil make sbase mv sbase sbase-$(VERSION) tar -cf sbase-$(VERSION).tar sbase-$(VERSION) gzip sbase-$(VERSION).tar @@ -249,6 +266,7 @@ sbase-box: $(BIN) clean: rm -f $(BIN) $(OBJ) $(LIB) sbase-box sbase-$(VERSION).tar.gz + rm -f scripts/make rm -f getconf.h rm -f proto rm -rf build diff --git a/README b/README @@ -84,6 +84,7 @@ The following tools are implemented: 0=*|o logger . 0=*|o logname . 0#* o ls (-C, -k, -m, -p, -s, -x) + * o make (-j) 0=*|x md5sum . 0=*|o mkdir . 0=*|o mkfifo . diff --git a/config.mk b/config.mk @@ -9,6 +9,7 @@ MANPREFIX = $(PREFIX)/share/man #CC = #AR = RANLIB = ranlib +SMAKE = scripts/make # -lrt might be needed on some systems # CFLAGS = diff --git a/make/defaults.c b/make/defaults.c @@ -0,0 +1,61 @@ +char defaults[] = + ".SUFFIXES: .o .c .y .l .a .sh .f\n" + + "AR = ar\n" + "ARFLAGS = -rv\n" + "CC = c99\n" + "CFLAGS = -O\n" + "FC = fort77\n" + "FFLAGS = -O 1\n" + "LDFLAGS =\n" + "LEX = lex\n" + "LFLAGS =\n" + "YACC = yacc\n" + "YFLAGS =\n" + "SHELL = /bin/sh\n" + + ".c:\n" + "\t${CC} ${CFLAGS} ${LDFLAGS} -o $@ $<\n" + + ".f:\n" + "\t${FC} ${FFLAGS} ${LDFLAGS} -o $@ $<\n" + + ".sh:\n" + "\tcp $< $@\n" + "\tchmod a+x $@\n" + + ".c.o:\n" + "\t${CC} ${CFLAGS} -c $<\n" + + ".f.o:\n" + "\t${FC} ${FFLAGS} -c $<\n" + + ".y.o:\n" + "\t${YACC} ${YFLAGS} $<\n" + "\t${CC} ${CFLAGS} -c y.tab.c\n" + "\trm -f y.tab.c\n" + "\tmv y.tab.o $@\n" + + ".l.o:\n" + "\t${LEX} ${LFLAGS} $<\n" + "\t${CC} ${CFLAGS} -c lex.yy.c\n" + "\trm -f lex.yy.c\n" + "\tmv lex.yy.o $@\n" + + ".y.c:\n" + "\t${YACC} ${YFLAGS} $<\n" + "\tmv y.tab.c $@\n" + + ".l.c:\n" + "\t${LEX} ${LFLAGS} $<\n" + "\tmv lex.yy.c $@\n" + + ".c.a:\n" + "\t${CC} -c ${CFLAGS} $<\n" + "\t${AR} ${ARFLAGS} $@ $*.o\n" + "\trm -f $*.o\n" + + ".f.a:\n" + "\t${FC} -c ${FFLAGS} $<\n" + "\t${AR} ${ARFLAGS} $@ $*.o\n" + "\trm -f $*.o\n"; diff --git a/make/main.c b/make/main.c @@ -0,0 +1,376 @@ +#include <errno.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "make.h" + +#ifndef SIGINT +#define SIGINT -1 +#endif + +#ifndef SIGTERM +#define SIGTERM -1 +#endif + +#ifndef SIGQUIT +#define SIGQUIT -1 +#endif + +#ifndef SIGHUP +#define SIGHUP -1 +#endif + +int kflag, dflag, nflag, iflag, sflag; +int eflag, pflag, tflag, qflag; +int exitstatus; +volatile sig_atomic_t stop; + +void +debug(char *fmt, ...) +{ + va_list va; + + if (!dflag) + return; + + va_start(va, fmt); + vfprintf(stdout, fmt, va); + fputc('\n', stdout); + va_end(va); +} + +int +hash(char *name) +{ + int c; + unsigned h = 5381; + + while (c = *name++) + h = h*33 ^ c; + + return h; +} + +void * +emalloc(size_t siz) +{ + void *p; + + if ((p = malloc(siz)) == NULL) { + perror("make"); + exit(EXIT_FAILURE); + } + + return p; +} + +void * +erealloc(void *p, size_t siz) +{ + if ((p = realloc(p, siz)) == NULL) { + perror("make"); + exit(EXIT_FAILURE); + } + + return p; +} + +char * +estrdup(char *s) +{ + size_t len; + + len = strlen(s) + 1; + return memcpy(emalloc(len), s, len); +} + +void +sighandler(int signo) +{ + stop = signo; +} + +static void +usage(void) +{ + fputs("usage: make [-eiknprSstd] [-f file] [-j jobs] " + "[macro=value ...] [target ...]\n", + stderr); + exit(EXIT_FAILURE); +} + +static char * +getarg(char **args, char ***argv) +{ + char *s; + + if ((*args)[1]) { + s = (*args) + 1; + *args += strlen(*args) - 1; + return s; + } + + if (!argv) + usage(); + + if ((*argv)[1] == NULL) + usage(); + (*argv)++; + + return **argv; +} + +static void +appendmakeflags(char *text) +{ + int n; + char *s, *t, *fmt; + + s = getmacro("MAKEFLAGS"); + fmt = *s ? "%s %s" : "%s%s"; + n = snprintf(NULL, 0, fmt, s, text); + + t = emalloc(n+1); + snprintf(t, n+1, fmt, s, text); + setmacro("MAKEFLAGS", t, MAKEFLAGS, EXPORT); + + free(t); +} + +static int +hasargs(int c) +{ + return c == 'f' || c == 'j'; +} + +static void +parseflag(int flag, char **args, char ***argv) +{ + if (hasargs(flag)) + getarg(args, argv); + + switch (flag) { + case 'j': + case 'f': + break; + case 'e': + eflag = 1; + appendmakeflags("-e"); + break; + case 'i': + iflag = 1; + appendmakeflags("-i"); + break; + case 'k': + kflag = 1; + appendmakeflags("-k"); + break; + case 'n': + nflag = 1; + appendmakeflags("-n"); + break; + case 'p': + pflag = 1; + break; + case 'q': + qflag = 1; + appendmakeflags("-q"); + break; + case 'r': + addtarget(".SUFFIXES", 0); + appendmakeflags("-r"); + break; + case 'S': + kflag = 0; + appendmakeflags("-S"); + break; + case 's': + sflag = 1; + appendmakeflags("-s"); + break; + case 't': + tflag = 1; + appendmakeflags("-t"); + break; + case 'd': + dflag = 1; + appendmakeflags("-d"); + break; + default: + usage(); + } +} + +static int +assign(char *s, int where, int export) +{ + int pos; + char *t; + + if ((t = strchr(s, '=')) == NULL) + return 0; + + pos = t - s; + + appendmakeflags(s); + t = estrdup(s); + t[pos] = '\0'; + + setmacro(t, t+pos+1, where, export); + free(t); + return 1; +} + +static void +parseargv(char **argv, char ***targets, int where, int export) +{ + char *s; + char *hm = NULL; + + for ( ; *argv; ++argv) { + s = *argv; + if (hm == NULL && strcmp(s, "--") == 0) { + hm = *argv; + } else if (hm && s[0] == '-') { + break; + } else if (s[0] != '-') { + if (!assign(s, where, export)) + break; + continue; + } + while (hm == NULL && *++s) + parseflag(*s, &s, &argv); + } + + if (targets) + *targets = argv; +} + +static void +parsemakeflags(void) +{ + int c, n; + char *s, *flags, **arr; + + if ((flags = getenv("MAKEFLAGS")) == NULL) + return; + + setmacro("MAKEFLAGS", "", MAKEFLAGS, EXPORT); + + while (*flags == ' ' || *flags == '\t') + flags++; + + if (flags[0] != '-' && !strchr(flags, '=')) { + while (*flags) { + parseflag(*flags, &flags, NULL); + flags++; + } + } else { + n = 0; + arr = NULL; + for (s = strtok(flags, " \t"); s; s = strtok(NULL, " \t")) { + n++; + arr = erealloc(arr, sizeof(char *) * (n+1)); + arr[n-1] = s; + arr[n] = NULL; + } + + if (arr) + parseargv(arr, NULL, MAKEFLAGS, NOEXPORT); + free(arr); + } +} + +static void +parsemakefiles(char **argv) +{ + char *s, *arg; + int c, hasmake; + + hasmake = 0; + for ( ; *argv && **argv == '-'; ++argv) { + for (s = *argv; c = *s; ++s) { + if (hasargs(c)) + arg = getarg(&s, &argv); + + if (c == 'f') { + if (strcmp(arg, "-") == 0) + arg = NULL; + parse(arg); + hasmake = 1; + } + } + } + + if (hasmake) + return; + + if (parse("makefile")) + return; + if (parse("Makefile")) + return; +} + +/* + * We want to enable debug as earlier as possible, + * if we wait until we read the Makefiles then + * we are going to lose to much debug information. + */ +static void +enadebug(char *argv[]) +{ + int c; + char *p; + + for ( ; *argv && **argv == '-'; ++argv) { + p = *argv; + for (++p; c = *p; ++p) { + if (hasargs(c)) + getarg(&p, &argv); + if (c == 'd') + dflag = 1; + } + } +} + +int +main(int argc, char *argv[]) +{ + char *arg0, **targets; + + signal(SIGINT, sighandler); + signal(SIGHUP, sighandler); + signal(SIGTERM, sighandler); + signal(SIGQUIT, sighandler); + + targets = NULL; + arg0 = *argv++; + + enadebug(argv); + inject(defaults); + setmacro("MAKE", arg0, MAKEFILE, NOEXPORT); + + parsemakeflags(); + parseargv(argv, &targets, CMDLINE, EXPORT); + parsemakefiles(argv); + + if (pflag) { + dumpmacros(); + dumprules(); + exit(EXIT_SUCCESS); + } + + if (!*targets) { + build(NULL); + } else { + while (*targets) + build(*targets++); + } + + exit(exitstatus); + + return 0; +} diff --git a/make/make.h b/make/make.h @@ -0,0 +1,86 @@ +#include <stddef.h> +#include <time.h> + +typedef struct target Target; + +enum { + NOEXPORT, + EXPORT, +}; + +enum { + UNDEF, + ENVIRON, + CMDLINE, + INTERNAL, + MAKEFILE, + MAKEFLAGS, +}; + +struct loc { + char *fname; + int lineno; +}; + +struct action { + char *line; + struct loc loc; +}; + +struct target { + char *name; + char *target; + char *req; + time_t stamp; + int defined; + + int ndeps; + struct target **deps; + + int nactions; + struct action *actions; + + struct target *next; +}; + +extern void *emalloc(size_t); +extern void *erealloc(void *, size_t); +extern char *estrdup(char *); + +extern void dumprules(void); +extern void dumpmacros(void); + +extern char *expandstring(char *, Target *, struct loc *); +extern void addtarget(char *, int); +extern void inject(char *); +extern int build(char *); +extern int hash(char *); +extern int parse(char *); +extern void debug(char *, ...); +extern void error(char *, ...); +extern void warning(char *, ...); +extern void adddep(char *, char *); +extern void addrule(char *, struct action *, int); +extern void freeloc(struct loc *); + +extern char *getmacro(char *); +extern void setmacro(char *, char *, int, int); + +/* system depdendant */ +extern time_t stamp(char *); +extern int launch(char *, int); +extern int putenv(char *); +extern void exportvar(char *, char *); +extern int is_dir(char *); + +/* main.c */ +extern int kflag, dflag, nflag, iflag, sflag; +extern int eflag, pflag, tflag, qflag; +extern int exitstatus; + +#ifdef SIGABRT +extern volatile sig_atomic_t stop; +#endif + +/* defaults.c */ +extern char defaults[]; diff --git a/make/parser.c b/make/parser.c @@ -0,0 +1,1034 @@ +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "make.h" + +#define MAXREPL 30 +#define TABSIZ 64 +#define MAXTOKEN FILENAME_MAX +#define ITEM 128 + +typedef struct macro Macro; + +enum inputype { + FTFILE, + FTEXPAN, +}; + +enum { + STBEGIN, + STINTERNAL, + STREPLACE, + STTO, + STEND, +}; + +struct input { + int siz; + int type; + + FILE *fp; + struct loc loc; + + int pos; + char *buf; + + struct input *prev; +}; + +struct macro { + char *name; + char *value; + int where; + + struct macro *next; +}; + +static struct input *input; +static char token[MAXTOKEN]; +static int tok; +static Macro *htab[TABSIZ]; + +void +dumpmacros(void) +{ + Macro **pp, *p; + + for (pp = htab; pp < &htab[TABSIZ]; ++pp) { + for (p = *pp; p; p = p->next) + printf("%s = %s\n", p->name, getmacro(p->name)); + } +} + +static Macro * +lookup(char *name) +{ + Macro *mp; + int h = hash(name) & TABSIZ-1; + + for (mp = htab[h]; mp && strcmp(mp->name, name); mp = mp->next) + ; + + if (mp) + return mp; + + mp = emalloc(sizeof(*mp)); + mp->name = estrdup(name); + mp->value = estrdup(""); + mp->next = htab[h]; + mp->where = UNDEF; + htab[h] = mp; + + return mp; +} + +static char * +macroinfo(char *name, int *pwhere, Macro **mpp) +{ + char *s, *t; + int hide, where; + Macro *mp = lookup(name); + + hide = 0; + if (!strcmp(name, "SHELL") || !strcmp(name, "MAKEFLAGS")) + hide = 1; + + s = mp->value; + where = mp->where; + + if (!hide && (where == UNDEF || where == INTERNAL || eflag)) { + t = getenv(name); + if (t) { + where = ENVIRON; + s = t; + } + } + + if (pwhere) + *pwhere = where; + if (mpp) + *mpp = mp; + + return s; +} + +char * +getmacro(char *name) +{ + return macroinfo(name, NULL, NULL); +} + +void +setmacro(char *name, char *val, int where, int export) +{ + int owhere, set; + char *s; + Macro *mp; + + assert(where != ENVIRON); + + s = macroinfo(name, &owhere, &mp); + + /* + * Default values are defined before anything else, and marked + * as INTERNAL because they are injected as parseable text, and + * MAKEFILE and INTERNAL variables are always overriden. ENVIRON + * macros are generated in macroinfo() and this is why this function + * should not receive a where == ENVIRON ever. + */ + switch (owhere) { + case UNDEF: + case INTERNAL: + case MAKEFILE: + set = 1; + break; + case ENVIRON: + set = (where == MAKEFLAGS || where == CMDLINE); + set |= (where == MAKEFILE && !eflag); + break; + case MAKEFLAGS: + set = (where == CMDLINE || where == MAKEFLAGS); + break; + case CMDLINE: + set = (where == CMDLINE); + break; + default: + abort(); + } + + if (!set) { + debug("hidding override of %s from '%s' to '%s'", name, s, val); + } else { + debug("override %s from '%s' to '%s'", name, s, val); + free(mp->value); + mp->value = estrdup(val); + mp->where = where; + + if (export && strcmp(name, "SHELL") != 0) { + debug("exporting macro %s", name); + exportvar(name, val); + } + } +} + +void +freeloc(struct loc *loc) +{ + free(loc->fname); +} + +static struct loc * +getloc(void) +{ + struct input *ip; + + for (ip = input; ip && ip->type != FTFILE; ip = ip->prev) + ; + if (!ip) + return NULL; + + return &ip->loc; +} + + +void +error(char *fmt, ...) +{ + va_list va; + struct loc *loc; + + fprintf(stderr, "make: error: "); + if ((loc = getloc()) != NULL) + fprintf(stderr, "%s:%d: ", loc->fname, loc->lineno); + + va_start(va, fmt); + vfprintf(stderr, fmt, va); + va_end(va); + putc('\n', stderr); + + exit(EXIT_FAILURE); +} + +void +warning(char *fmt, ...) +{ + va_list va; + struct loc *loc; + + fprintf(stderr, "make: warning: "); + if ((loc = getloc()) != NULL) + fprintf(stderr, "%s:%d: ", loc->fname, loc->lineno); + + va_start(va, fmt); + vfprintf(stderr, fmt, va); + va_end(va); + putc('\n', stderr); +} + +static void +pop(void) +{ + struct input *ip = input->prev; + + if (input->type == FTFILE) { + if (input->fp) + fclose(input->fp); + freeloc(&input->loc); + } + free(input->buf); + free(input); + + input = ip; +} + +static void +push(int type, ...) +{ + int line, len, pos; + FILE *fp = NULL; + char *buf, *s, *fname = NULL; + va_list va; + struct input *ip; + + va_start(va, type); + switch (type) { + case FTFILE: + fp = va_arg(va, FILE *); + s = va_arg(va, char *); + line = va_arg(va, int); + fname = estrdup(s); + buf = emalloc(BUFSIZ); + pos = len = BUFSIZ; + break; + case FTEXPAN: + s = va_arg(va, char *); + buf = estrdup(s); + line = pos = 0; + len = strlen(s); + break; + } + va_end(va); + + ip = emalloc(sizeof(*ip)); + ip->siz = len; + ip->buf = buf; + ip->type = type; + ip->fp = fp; + ip->loc.fname = fname; + ip->loc.lineno = line; + ip->pos = pos; + ip->prev = input; + + input = ip; +} + +static char * +trim(char *s) +{ + size_t len; + + while (isspace(*s)) + s++; + + for (len = strlen(s); len > 0 && isspace(s[len-1]); --len) + s[len-1] = '\0'; + + return s; +} + +static void +include(char *s) +{ + int len; + FILE *fp; + char *fil, *t; + + s = trim(s); + fil = expandstring(s, NULL, getloc()); + + t = trim(fil); + if (strlen(t) != 0) { + debug("including '%s'", t); + if ((fp = fopen(t, "r")) == NULL) + error("opening %s:%s", t, strerror(errno)); + push(FTFILE, fp, t, 0); + } + + free(fil); +} + +static char * +nextline(void) +{ + int c; + FILE *fp; + char *s, *lim; + + assert(input->type == FTFILE); + +repeat: + fp = input->fp; + if (!fp || feof(fp)) + return NULL; + + lim = &input->buf[input->siz]; + for (s = input->buf; s < lim; *s++ = c) { + c = getc(fp); + if (c == '\n' || c == EOF) { + input->loc.lineno++; + *s++ = '\n'; + break; + } + if (c > UCHAR_MAX || c < 0) + error("invalid character '%c' (%d)", c, c); + } + + + if (s == lim) + error("too long line"); + if (ferror(fp)) + error(strerror(errno)); + *s = '\0'; + + if (!strcmp(input->buf, "")) + goto repeat; + + if (!strncmp(input->buf, "include", 7) && isblank(input->buf[7])) { + input->pos = input->siz; + include(input->buf+7); + goto repeat; + } + + input->pos = 0; + + + return input->buf; +} + +static int +empty(struct input *ip) +{ + return ip->pos == ip->siz || ip->buf[ip->pos] == '\0'; +} + +static int +moreinput(void) +{ + while (input) { + if (!empty(input)) + break; + + switch (input->type) { + case FTEXPAN: + pop(); + break; + case FTFILE: + if (!nextline()) + pop(); + break; + } + } + + return input != NULL; +} + +static int +nextc(void) +{ + if (!moreinput()) + return EOF; + + return input->buf[input->pos++]; +} + +/* + * This function only can be called after a call to nextc + * that didn't return EOF. It can return '\0', but as + * it is used only to check against '$' then it is not + * a problem. + */ +static int +ahead(void) +{ + return input->buf[input->pos]; +} + +static int +back(int c) +{ + if (c == EOF) + return c; + assert(input->pos > 0); + return input->buf[--input->pos] = c; +} + +static void +comment(void) +{ + int c; + + while ((c = nextc()) != EOF && c != '\n') { + if (c == '\\' && nextc() == EOF) + break; + } +} + +static void +skipspaces(void) +{ + int c; + + for (c = nextc(); c == ' ' || c == '\t'; c = nextc()) + ; + back(c); +} + +static int +validchar(int c) +{ + if (c == EOF) + return 0; + return c == '.' || c == '/' || c == '_' || c == '-' || isalnum(c); +} + +static char * +expandmacro(char *name) +{ + char *s; + + s = expandstring(getmacro(name), NULL, getloc()); + debug("macro %s expanded to '%s'", name, s); + + return s; +} + +static void +replace(char *line, char *repl, char *to) +{ + int siz, at, len, replsiz, tosiz, sep, pos; + char *oline, *s, *cur, *buf; + + debug("replacing '%s', with '%s' to '%s'", line, repl, to); + oline = line; + tosiz = strlen(to); + replsiz = strlen(repl); + + buf = NULL; + for (pos = 0; *line; pos += siz) { + cur = NULL; + siz = 0; + + for (siz = 0; *line == ' ' || *line == '\t'; ++siz) { + cur = erealloc(cur, siz+1); + cur[siz] = *line++; + } + + len = strcspn(line, " \t"); + at = len - replsiz; + if (at < 0 || memcmp(line + at, repl, replsiz)) { + cur = erealloc(cur, siz + len); + memcpy(cur + siz, line, len); + siz += len; + } else { + cur = erealloc(cur, siz + at + tosiz); + memcpy(cur + siz, line, at); + memcpy(cur + siz + at, to, tosiz); + siz += at + tosiz; + } + + line += len; + buf = erealloc(buf, pos + siz); + memcpy(buf + pos, cur, siz); + free(cur); + } + + if (pos > 0) { + buf = erealloc(buf, pos + 1); + buf[pos] = '\0'; + debug("\treplace '%s' with '%s'", oline, buf); + push(FTEXPAN, buf); + } + + free(buf); +} + +static void +expandsimple(Target *tp) +{ + char *s; + Target **p; + int len, c; + + switch (c = nextc()) { + case '@': + if (!tp || !tp->target) + return; + push(FTEXPAN, tp->target); + break; + case '<': + if (!tp || !tp->req) + return; + push(FTEXPAN, tp->req); + break; + case '*': + if (!tp || !tp->target) + return; + s = strrchr(tp->target, '.'); + if (!s) { + push(FTEXPAN, tp->target); + return; + } + + len = s - tp->target; + s = emalloc(len+1); + memcpy(s, tp->target, len); + s[len] = '\0'; + push(FTEXPAN, s); + free(s); + break; + case '?': + if (!tp) + return; + + if (tp->req && stamp(tp->req) > tp->stamp) { + push(FTEXPAN, " "); + push(FTEXPAN, tp->req); + } + + for (p = tp->deps; p && *p; ++p) { + if (stamp((*p)->name) > tp->stamp) { + push(FTEXPAN, " "); + push(FTEXPAN, (*p)->name); + } + } + break; + default: + token[0] = c; + token[1] = '\0'; + s = expandmacro(token); + push(FTEXPAN, s); + free(s); + break; + } +} + +static int +internal(int ch) +{ + switch (ch) { + case '@': + case '?': + case '*': + case '<': + return 1; + default: + return 0; + } +} + +static void +expansion(Target *tp) +{ + int delim, c, repli, toi, namei, st; + char name[MAXTOKEN], repl[MAXREPL], to[MAXREPL]; + char *s, *erepl; + + c = nextc(); + if (c == '(') + delim = ')'; + else if (c == '{') + delim = '}'; + else + delim = 0; + + if (!delim) { + back(c); + expandsimple(tp); + return; + } + + s = NULL; + namei = repli = toi = 0; + st = STBEGIN; + + while (st != STEND && (c = nextc()) != EOF) { + switch (st) { + case STBEGIN: + if (c == ':') { + st = STREPLACE; + name[namei] = '\0'; + s = expandmacro(name); + break; + } + if (c == delim) { + name[namei] = '\0'; + s = expandmacro(name); + goto no_replace; + } + if (namei == MAXTOKEN-1) + error("expansion text too long"); + + if (namei == 0 && internal(c)) { + name[namei++] = '$'; + name[namei++] = c; + name[namei] = '\0'; + st = STINTERNAL; + s = expandstring(name, tp, getloc()); + break; + } + + if (!validchar(c)) + error("invalid macro name in expansion"); + name[namei++] = c; + break; + case STINTERNAL: + if (c == delim) + goto no_replace; + if (c != ':') + error("invalid internal macro in expansion"); + st = STREPLACE; + break; + case STREPLACE: + if (c == '=') { + st = STTO; + break; + } + if (c == delim) + error("invalid replacement pattern in expansion"); + if (repli == MAXREPL-1) + error("macro replacement too big"); + repl[repli++] = c; + break; + case STTO: + if (c == delim) { + st = STEND; + break; + } + + if (toi == MAXREPL-1) + error("macro substiturion too big"); + to[toi++] = c; + break; + } + } + + if (c == EOF) + error("found eof while parsing expansion"); + + repl[repli] = '\0'; + to[toi] = '\0'; + + erepl = expandstring(repl, tp, getloc()); + replace(s, erepl, to); + + free(erepl); + free(s); + return; + +no_replace: + push(FTEXPAN, s); + free(s); +} + +/* + * Horrible hack to do string expansion. + * We cannot use normal push and nextc because that + * would consume characters of the current file too. + * For that reason it cleans the input and it recovers + * it later. + */ +char * +expandstring(char *line, Target *tp, struct loc *loc) +{ + int c, n; + char *s; + struct input *ip = input; + + input = NULL; + push(FTFILE, NULL, loc->fname, loc->lineno); + push(FTEXPAN, line); + + n = 0; + s = NULL; + while ((c = nextc()) != EOF) { + if (c != '$') { + s = erealloc(s, ++n); + s[n-1] = c; + continue; + } + + if ((c = nextc()) == '$') { + s = erealloc(s, n += 2); + s[n-2] = '$'; + s[n-1] = '$'; + } else { + back(c); + expansion(tp); + } + } + + s = erealloc(s, n+1); + s[n] = '\0'; + input = ip; + + return s; +} + +static int +item(void) +{ + int c; + char *s; + char buf[MAXTOKEN]; + + for (s = buf; s < &buf[MAXTOKEN] - 1; ) { + c = nextc(); + if (c == '$' && ahead() != '$') + expansion(NULL); + else if (validchar(c)) + *s++ = c; + else + break; + } + back(c); + + if (s >= &buf[MAXTOKEN] - 1) + error("token too long"); + if (s == buf) + error("invalid empty token"); + *s++ = '\0'; + memcpy(token, buf, s - buf); + + return ITEM; +} + +static int +next(void) +{ + int c; + +repeat: + /* + * It is better to avoid skipspaces() here, because + * it can generate the need for 2 calls to back(), + * and we need the character anyway. + */ + c = nextc(); + if (c == ' ' || c == '\t') + goto repeat; + + if (c == '\\') { + if ((c = nextc()) == '\n') + goto repeat; + back(c); + c = '\\'; + } + + switch (c) { + case EOF: + strcpy(token, "<EOF>"); + tok = EOF; + break; + case '$': + if ((c = nextc()) == '$') + goto single; + back(c); + expansion(NULL); + goto repeat; + case '#': + comment(); + c = '\n'; + case ';': + case ':': + case '=': + case '\n': + single: + token[0] = c; + token[1] = '\0'; + tok = c; + break; + default: + if (!validchar(c)) + error("unexpected character '%c'", c); + back(c); + tok = item(); + break; + } + + return tok; +} + +static char * +readmacrodef(void) +{ + int n, c; + char *line; + + n = 0; + line = NULL; + while ((c = nextc()) != EOF) { + line = erealloc(line, n+1); + if (c == '\n') + break; + if (c == '#') { + comment(); + break; + } + if (c == '\\') { + if ((c = nextc()) != '\n') { + back(c); + c = '\\'; + } else { + skipspaces(); + c = ' '; + } + } + + line[n++] = c; + } + if (c == EOF) + error("EOF while looking for end of line"); + line[n] = '\0'; + + return line; +} + +static struct action +readcmd(void) +{ + int n, c; + struct loc *loc; + struct action act; + + skipspaces(); + + loc = getloc(); + act.loc.fname = estrdup(loc->fname); + act.loc.lineno = loc->lineno; + + n = 0; + act.line = NULL; + while ((c = nextc()) != EOF) { + act.line = erealloc(act.line, n+1); + if (c == '\n') + break; + if (c == '\\') { + if ((c = nextc()) == '\n') { + if ((c = nextc()) != '\t') + back(c); + continue; + } + back(c); + c = '\\'; + } + act.line[n++] = c; + } + if (c == EOF) + error("EOF while looking for end of command"); + act.line[n] = '\0'; + + return act; +} + +static void +rule(char *targets[], int ntargets) +{ + int c, i, j, ndeps, nactions; + struct action *acts; + char **deps = NULL; + + if (ntargets == 0) + error("missing target"); + + for (ndeps = 0; next() == ITEM; ++ndeps) { + deps = erealloc(deps, (ndeps+1) * sizeof(char *)); + deps[ndeps] = estrdup(token); + } + + if (tok != '\n' && tok != ';') + error("garbage at the end of the line"); + + nactions = 0; + acts = NULL; + if (tok == ';') { + nactions++; + acts = erealloc(acts, nactions * sizeof(*acts)); + acts[nactions-1] = readcmd(); + } + + for (;;) { + if ((c = nextc()) == '#') { + comment(); + continue; + } + if (c != '\t') + break; + nactions++; + acts = erealloc(acts, nactions * sizeof(*acts)); + acts[nactions-1] = readcmd(); + } + back(c); + + for (i = 0; i < ntargets; i++) { + addtarget(targets[i], ndeps); + for (j = 0; j < ndeps; j++) + adddep(targets[i], deps[j]); + if (nactions > 0) + addrule(targets[i], acts, nactions); + } + + for (i = 0; i < ndeps; i++) + free(deps[i]); + free(deps); + + for (i = 0; i < nactions; i++) { + free(acts[i].line); + freeloc(&acts[i].loc); + } + free(acts); +} + +static void +assign(char *macros[], int where, int n) +{ + char *defs; + + if (n != 1) + error("invalid macro definition"); + + skipspaces(); + defs = readmacrodef(); + setmacro(*macros, defs, where, NOEXPORT); + free(defs); +} + +void +parseinput(int where) +{ + int i, n; + char **targets; + + while (moreinput()) { + n = 0; + targets = NULL; + + next(); + if (tok == '\n') + continue; + + while (tok == ITEM) { + n++; + targets = erealloc(targets, n * sizeof(char *)); + targets[n-1] = estrdup(token); + next(); + } + + switch (tok) { + case ':': + rule(targets, n); + break; + case '=': + assign(targets, where, n); + break; + default: + error("unexpected token '%s'(%d)", token, tok); + } + + for (i = 0; i < n; i++) + free(targets[i]); + free(targets); + } +} + +int +parse(char *fname) +{ + FILE *fp; + + if (!fname) { + fp = stdin; + fname = "<stdin>"; + } else if ((fp = fopen(fname, "r")) == NULL) { + return 0; + } + + debug("parsing %s", fname); + push(FTFILE, fp, fname, 0); + parseinput(MAKEFILE); + + return 1; +} + +void +inject(char *s) +{ + push(FTFILE, NULL, "<internal>", 0); + push(FTEXPAN, s); + parseinput(INTERNAL); +} diff --git a/make/posix.c b/make/posix.c @@ -0,0 +1,96 @@ +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L + +#include <signal.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include "make.h" + +int +is_dir(char *fname) +{ + struct stat st; + + if (stat(fname, &st) < 0) + return 0; + return S_ISDIR(st.st_mode); +} + +void +exportvar(char *var, char *value) +{ + int n; + char *buf; + + n = snprintf(NULL, 0, "%s=%s", var, value); + buf = emalloc(n+1); + snprintf(buf, n+1, "%s=%s", var, value); + putenv(buf); +} + +time_t +stamp(char *name) +{ + struct stat st; + + if (stat(name, &st) < 0) + return -1; + + return st.st_mtime; +} + +int +launch(char *cmd, int ignore) +{ + int st; + pid_t pid; + char *name, *shell; + char *args[] = {NULL, "-ec" , cmd, NULL}; + static int initsignals; + extern char **environ; + extern void sighandler(int); + + + if (!initsignals) { + struct sigaction act = { + .sa_handler = sighandler + }; + + /* avoid BSD weirdness signal restart handling */ + sigaction(SIGINT, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGQUIT, &act, NULL); + initsignals = 1; + } + + switch (pid = fork()) { + case -1: + return -1; + case 0: + shell = getmacro("SHELL"); + + if (ignore) + args[1] = "-c"; + if ((name = strrchr(shell, '/')) != NULL) + ++name; + else + name = shell; + args[0] = name; + execve(shell, args, environ); + _exit(127); + default: + if (wait(&st) < 0) { + kill(pid, SIGTERM); + wait(&st); + } + + return st; + } +} diff --git a/make/rules.c b/make/rules.c @@ -0,0 +1,581 @@ +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "make.h" + +#define TABSIZ 128 +#define FORCE 1 +#define NOFORCE 0 + +static Target *htab[TABSIZ], *deftarget; + +void +dumprules(void) +{ + int i; + Target **pp, **q, *p; + + for (pp = htab; pp < &htab[TABSIZ]; ++pp) { + for (p = *pp; p; p = p->next) { + if (!p->defined) + continue; + printf("%s:", p->name); + for (q = p->deps; q && *q; ++q) + printf(" %s", (*q)->name); + putchar('\n'); + for (i = 0; i < p->nactions; i++) + printf("\t%s\n", p->actions[i].line); + putchar('\n'); + } + } +} + +static Target * +lookup(char *name) +{ + Target *tp; + int h = hash(name) & TABSIZ-1; + + for (tp = htab[h]; tp && strcmp(tp->name, name); tp = tp->next) + ; + + if (tp) + return tp; + + tp = emalloc(sizeof(*tp)); + tp->name = estrdup(name); + tp->target = tp->name; + tp->req = NULL; + tp->ndeps = 0; + tp->deps = NULL; + tp->actions = NULL; + tp->nactions = 0; + tp->next = htab[h]; + tp->defined = 0; + htab[h] = tp; + + return tp; +} + +static void +cleanup(Target *tp) +{ + int precious; + Target *p, **q; + + printf("make: signal %d arrived\n", stop); + + precious = 0; + p = lookup(".PRECIOUS"); + for (q = p->deps; q && *q; q++) { + if (strcmp((*q)->name, tp->name) == 0) { + precious = 1; + break; + } + } + + if (!precious && !nflag && !qflag && !is_dir(tp->name)) { + printf("make: trying to remove target %s\n", tp->name); + remove(tp->name); + } + + signal(stop, SIG_DFL); + raise(stop); +} + +static int +depends(char *target, char *dep) +{ + int i; + Target **p, *tp = lookup(target); + + for (p = tp->deps; p && *p; ++p) { + if (strcmp((*p)->name, target) == 0) + return 1; + } + + return 0; +} + +static int +is_suffix(char *s) +{ + int n; + + if (s[0] != '.') + return 0; + + for (n = 0; s = strchr(s, '.'); n++) + s++; + + return n == 2; +} + +void +addtarget(char *target, int ndeps) +{ + Target *tp = lookup(target); + + tp->defined = 1; + if (!deftarget && target[0] != '.') { + deftarget = tp; + return; + } + + if (strcmp(target, ".SUFFIXES") == 0 && ndeps == 0) { + free(tp->deps); + tp->deps = NULL; + tp->ndeps = 0; + return; + } + + if (strcmp(target, ".DEFAULT") == 0) { + if (ndeps > 0) + error("DEFAULT rule with prerequisites"); + return; + } + + if (strcmp(target, ".SILENT") == 0 && ndeps == 0) { + sflag = 1; + return; + } + + if (strcmp(target, ".IGNORE") == 0 && ndeps == 0) { + iflag = 1; + return; + } +} + +void +adddep(char *target, char *dep) +{ + int i; + size_t siz; + Target **p, *tp = lookup(target); + + if (depends(dep, target)) { + warning("circular dependency %s <- %s dropped", target, dep); + return; + } + + for (p = tp->deps; p && *p; ++p) { + if (strcmp((*p)->name, dep) == 0) + return; + } + + tp->ndeps++; + siz = (tp->ndeps + 1) * sizeof(Target *); + tp->deps = erealloc(tp->deps, siz); + tp->deps[tp->ndeps-1] = lookup(dep); + tp->deps[tp->ndeps] = NULL; + + debug("adding dependency %s <- %s", target, dep); +} + +static void +freeaction(struct action *act) +{ + free(act->line); + freeloc(&act->loc); +} + +void +addrule(char *target, struct action *acts, int n) +{ + int i; + struct action *v; + Target *tp = lookup(target); + + debug("adding actions for target %s", target); + + if (tp->actions) { + debug("overring actions of target %s", target); + for (i = 0; i < tp->nactions; i++) + freeaction(&tp->actions[i]); + free(tp->actions); + } + + v = emalloc(n * sizeof(*v)); + for (i = 0; i < n; i++) { + v[i].line = estrdup(acts[i].line); + v[i].loc.lineno = acts[i].loc.lineno; + v[i].loc.fname = estrdup(acts[i].loc.fname); + } + + tp->nactions = n; + tp->actions = v; +} + +static int +execline(Target *tp, char *line, int ignore, int silence) +{ + char *s, *t; + Target *p, **q; + int r, at, plus, minus, l; + + debug("executing '%s'", line); + + at = plus = minus = 0; + for (s = line; ; s++) { + switch (*s) { + case '@': + at = 1; + break; + case '-': + minus = 1; + break; + case '+': + plus = 1; + break; + default: + goto out_loop; + } + } + +out_loop: + /* unescape $$ */ + for (l = strlen(s)+1, t = s; *t; --l, ++t) { + if (t[0] == '$' && t[1] == '$') { + memmove(t+1, t+2, l-2); + l--; + } + } + + if (tflag && !plus) + return 0; + + if (sflag || silence || (qflag && !plus)) + at = 1; + if (nflag) + at = 0; + if (!at) { + puts(s); + fflush(stdout); + } + + if ((nflag || qflag) && !plus) { + if (qflag) + exitstatus = 1; + return 0; + } + + if (minus || iflag || ignore) + ignore = 1; + + r = launch(s, ignore); + if (ignore) + return 0; + + return r; +} + +static int +touch(char *name, int ignore, int silence) +{ + char *cmd; + int r, n; + + n = snprintf(NULL, 0, "touch %s", name) + 1; + cmd = emalloc(n); + snprintf(cmd, n, "touch %s", name); + + if (!sflag && !silence) + puts(cmd); + + r = system(cmd); + free(cmd); + + if (ignore || iflag) + return 0; + + return r; +} + +static int +touchdeps(Target *tp, int ignore, int silent) +{ + int r; + Target **p; + + if (tp->req) { + r = touch(tp->req, silent, ignore); + if (r) + return r; + } + + for (p = tp->deps; p && *p; ++p) { + r = touch((*p)->name, silent, ignore); + if (r) + return r; + } + + return 0; +} + +static int +run(Target *tp) +{ + int r, i, ignore, silent; + char *s; + Target *p, **q; + + silent = 0; + p = lookup(".SILENT"); + for (q = p->deps; q && *q; ++q) { + if (strcmp((*q)->name, tp->name) == 0) { + debug("target %s error silent by .SILENT", tp->name); + silent = 1; + } + } + + ignore = 0; + p = lookup(".IGNORE"); + for (q = p->deps; q && *q; ++q) { + if (strcmp((*q)->name, tp->name) == 0) { + debug("target %s error ignored by .IGNORE", tp->name); + ignore = 1; + } + } + + if (tflag) { + r = touchdeps(tp, ignore, silent); + if (r) + return r; + } + + for (i = 0; i < tp->nactions; i++) { + struct action *p; + + if (stop) + cleanup(tp); + + p = &tp->actions[i]; + debug("executing action '%s'", p->line); + s = expandstring(p->line, tp, &p->loc); + r = execline(tp, s, ignore, silent); + free(s); + + if (r) + return r; + } + + if (tflag) { + r = touch(tp->target, ignore, silent); + if (r) + return r; + } + + return 0; +} + +static int +enabled(char *suffix) +{ + Target **p, *tp = lookup(".SUFFIXES"); + + for (p = tp->deps; p && *p; ++p) { + if (strcmp(suffix, (*p)->name) == 0) + return 1; + } + + return 0; +} + +static Target * +inference(Target *tp, int force) +{ + time_t t; + int tolen, r; + char *to, *from; + Target *q, **p, *suffixes; + char buf[FILENAME_MAX], fname[FILENAME_MAX]; + + debug("searching an inference rule for %s", tp->name); + + to = strrchr(tp->name, '.'); + if (to && !enabled(to)) + return NULL; + tolen = to ? to - tp->name : strlen(tp->name); + + if (!to) + to = ""; + + suffixes = lookup(".SUFFIXES"); + for (p = suffixes->deps; p && *p; ++p) { + from = (*p)->name; + debug("trying suffix %s", from); + + r = snprintf(buf, + sizeof(buf), + "%s%s", + from, to); + + if (r < 0 || r >= sizeof(buf)) + error("suffixes too long %s %s", from, to); + + q = lookup(buf); + if (!q->actions) + continue; + + r = snprintf(fname, + sizeof(fname), + "%*.*s%s", + tolen, tolen, tp->name, from); + + if (r < 0 || r >= sizeof(fname)) { + error("prerequisite name too long %s %s", + tp->name, from); + } + + debug("\tsearching prerequisite %s", fname); + + t = stamp(fname); + if (t == -1) { + debug("\tprerequisite %s not found", fname); + continue; + } + + if (!force && t <= tp->stamp) { + debug("\tdiscarded because is newer"); + debug("\t%s: %s", tp->name, ctime(&tp->stamp)); + debug("\t%s: %s", fname, ctime(&t)); + continue; + } + + free(q->req); + q->req = estrdup(fname); + q->deps = tp->deps; + q->target = tp->name; + q->stamp = tp->stamp; + + debug("using inference rule %s with %s", q->name, fname); + return q; + } + + return NULL; +} + +static int +update(Target *tp) +{ + Target *p; + + debug("%s needs to be updated", tp->name); + + if (tp->actions) { + debug("using target rule to build %s", tp->name); + return run(tp); + } + + if ((p = inference(tp, FORCE)) != NULL) { + debug("using inference rule %s", p->name); + return run(p); + } + + p = lookup(".DEFAULT"); + if (p->defined) { + debug("using default rule"); + return run(p); + } + + debug("not rule found to update %s", tp->name); + + if (!tp->defined) + error("don't know how to make %s", tp->name); + + return 0; +} + +static int +rebuild(Target *tp, int *buildp) +{ + Target **p, *q;; + int r, need, build, err, def; + + debug("checking rebuild of %s", tp->name); + + tp->stamp = stamp(tp->name); + + def = err = need = 0; + for (p = tp->deps; p && *p; ++p) { + if (stop) + cleanup(tp); + + q = *p; + debug("checking dependency %s", q->name); + + if (strcmp(q->name, tp->name) == 0 && q->actions) + def = 1; + + build = 0; + if (rebuild(q, &build) != 0) { + err = 1; + continue; + } + + if (build) { + debug("rebuild of %s forces rebuild of %s", + q->name, tp->name); + need = 1; + } else if (q->stamp > tp->stamp) { + debug("dependency %s is newer than %s", + q->name, tp->name); + need = 1; + } + } + + if (tp->stamp == -1) { + need = 1; + } else if (!def) { + debug("no action found for %s, looking a inference rule", + tp->name); + if (inference(tp, NOFORCE)) + need = 1; + } + + if (err) { + warning("target %s not remade because of errors", tp->name); + return 1; + } else if (need) { + *buildp = 1; + + debug("target %s needs updating", tp->name); + r = update(tp); + if (r == 0) + return 0; + + if (stop) + cleanup(tp); + + exitstatus = 1; + + if (!kflag) + error("target %s: error %d", tp->name, r); + else + warning("target %s: error %d", tp->name, r); + return r; + } + + return 0; +} + +int +build(char *name) +{ + int build, r; + + if (!name) { + if (!deftarget) { + printf("make: no target to make\n"); + return 0; + } + name = deftarget->name; + } + + debug("checking target %s", name); + + build = 0; + return rebuild(lookup(name), &build); +} diff --git a/scripts/mkproto b/scripts/mkproto @@ -10,15 +10,22 @@ prefix=${1?$(usage)} manprefix=${2?$(usage)} proto=${3?$(usage)} -trap "rm -f scripts/proto" EXIT INT QUIT TERM +trap "rm -f $proto" EXIT INT QUIT TERM (set -e echo d $prefix/bin $prefix/bin 755 -find . ! -name . -prune -type f \( -perm -u+x -o -perm -g+x -o -perm o+x \) | -sed "s@.*@c & $prefix/bin/& 755@" - echo d $manprefix/man1 $manprefix/man1 755 -find . ! -name . -prune -name '*.1' | -sed "s@.*@c & $manprefix/man1/& 644@") > $proto +ls -ld * make/* |\ +awk ' +/^-/ && $1 ~ /x/ { + base = $9 + sub(".*/", "", base) + printf "c %s '$prefix/bin/'%s 755\n", $9, base +} +/^-/ && $9 ~ /\.1$/ { + base = $9 + sub(".*/", "", base) + printf "c %s '$manprefix/man1/'%s 644\n", $9, base +}') > $proto trap "" EXIT INT QUIT TERM