make: Add initial version - sbase - suckless unix tools
 (HTM) git clone git://git.suckless.org/sbase
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 1281dc4f902331be25ead726606226d1196d1e52
 (DIR) parent 055cc1ae1b3a13c3d8f25af0a4a3316590efcd48
 (HTM) 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 +++
         M Makefile                            |      24 +++++++++++++++++++++---
         M README                              |       1 +
         M config.mk                           |       1 +
         A make/defaults.c                     |      61 +++++++++++++++++++++++++++++++
         A make/main.c                         |     376 +++++++++++++++++++++++++++++++
         A make/make.h                         |      86 ++++++++++++++++++++++++++++++
         A make/parser.c                       |    1034 +++++++++++++++++++++++++++++++
         A make/posix.c                        |      96 +++++++++++++++++++++++++++++++
         A make/rules.c                        |     581 +++++++++++++++++++++++++++++++
         M scripts/mkproto                     |      19 +++++++++++++------
       
       11 files changed, 2273 insertions(+), 9 deletions(-)
       ---
 (DIR) 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
 (DIR) 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
 (DIR) 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          .
 (DIR) 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   =
 (DIR) 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";
 (DIR) 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;
       +}
 (DIR) 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[];
 (DIR) 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);
       +}
 (DIR) 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;
       +        }
       +}
 (DIR) 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);
       +}
 (DIR) 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