Logo Search packages:      
Sourcecode: ksh version File versions  Download package

mamake.c

/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1990-2007 AT&T Intellectual Property          *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                    by AT&T Intellectual Property                     *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
#pragma prototyped

/*
 * mamake -- MAM make
 *
 * coded for portability
 */

static char id[] = "\n@(#)$Id: mamake (AT&T Research) 2007-02-26 $\0\n";

#if _PACKAGE_ast

#include <ast.h>
#include <error.h>

static const char usage[] =
"[-?\n@(#)$Id: mamake (AT&T Research) 2007-02-26 $\n]"
USAGE_LICENSE
"[+NAME?mamake - make abstract machine make]"
"[+DESCRIPTION?\bmamake\b reads \amake abstract machine\a target and"
"     prerequisite file descriptions from a mamfile (see \b-f\b) and executes"
"     actions to update targets that are older than their prerequisites."
"     Mamfiles are generated by the \b--mam\b option of \bnmake\b(1) and"
"     \bgmake\b(1) and are portable to environments that only have"
"     \bsh\b(1) and \bcc\b(1).]"
"[+?In practice \bmamake\b is used to bootstrap build \bnmake\b(1) and"
"     \bksh\b(1) in new environments. Mamfiles are used rather than"
"     old-\bmake\b makefiles because some features are not reliably supported"
"     across all \bmake\b variants:]{"
"           [+action execution?Multi-line actions are executed as a"
"                 unit by \b$SHELL\b. There are some shell constructs"
"                 that cannot be expressed in an old-\bmake\b makefile.]"
"           [+viewpathing?\bVPATH\b is properly interpreted. This allows"
"                 source to be separate from generated files.]"
"           [+recursion?Ordered subdirectory recursion over unrelated"
"                 makefiles.]"
"     }"
"[+?\bmamprobe\b(1) is called to probe and generate system specific variable"
"     definitions. The probe information is regenerated when it is older"
"     than the \bmamprobe\b command.]"
"[+?For compatibility with \bnmake\b(1) the \b-e\b and \b-K\b options and the"
"     \brecurse\b and \bcc-*\b command line targets are ignored.]"
"[e:?Ignored.]"
"[f:?Read \afile\a instead of the default.]:[file:=Mamfile]"
"[i:?Ignore action errors.]"
"[k:?Continue after error with sibling prerequisites.]"
"[n:?Print actions but do not execute. Recursion actions (see \b-r\b) are still"
"     executed. Use \b-N\b to disable recursion actions too.]"
"[r:?Recursively make leaf directories matching \apattern\a. Only leaf"
"     directories containing a makefile named \bNmakefile\b, \bnmakefile\b,"
"     \bMakefile\b or \bmakefile\b are considered. The first makefile"
"     found in each leaf directory is scanned for leaf directory"
"     prerequisites; the recusion order is determined by a topological sort"
"     of these prerequisites.]:[pattern]"
"[C:?Do all work in \adirectory\a. All messages will mention"
"     \adirectory\a.]:[directory]"
"[D:?Set the debug trace level to \alevel\a. Higher levels produce more"
"     output.]#[level]"
"[F:?Force all targets to be out of date.]"
"[K:?Ignored.]"
"[N:?Like \b-n\b but recursion actions (see \b-r\b) are also disabled.]"
"[V:?Print the program version and exit.]"
"[G:debug-symbols?Compile and link with debugging symbol options enabled.]"
"[S:strip-symbols?Strip link-time static symbols from executables.]"

"\n"
"\n[ target ... ] [ name=value ... ]\n"
"\n"

"[+SEE ALSO?\bgmake\b(1), \bmake\b(1), \bmamprobe\b(1),"
"     \bnmake\b(1), \bsh\b(1)]"
;

#else

#define elementsof(x)   (sizeof(x)/sizeof(x[0]))
#define newof(p,t,n,x)  ((p)?(t*)realloc((char*)(p),sizeof(t)*(n)+(x)):(t*)calloc(1,sizeof(t)*(n)+(x)))

#define NiL       ((char*)0)

#endif

#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>

#if !_PACKAGE_ast && defined(__STDC__)
#include <stdlib.h>
#include <string.h>
#endif

#define delimiter(c)    ((c)==' '||(c)=='\t'||(c)=='\n'||(c)==';'||(c)=='('||(c)==')'||(c)=='`'||(c)=='|'||(c)=='&'||(c)=='=')

#define add(b,c)  (((b)->nxt >= (b)->end) ? append(b, "") : NiL, *(b)->nxt++ = (c))
#define get(b)          ((b)->nxt-(b)->buf)
#define set(b,o)  ((b)->nxt=(b)->buf+(o))
#define use(b)          (*(b)->nxt=0,(b)->nxt=(b)->buf)

#define CHUNK           1024
#define KEY(a,b,c,d)    ((((unsigned long)(a))<<15)|(((unsigned long)(b))<<10)|(((unsigned long)(c))<<5)|(((unsigned long)(d))))
#define NOW       ((unsigned long)time((time_t*)0))
#define ROTATE(p,l,r,t) ((t)=(p)->l,(p)->l=(t)->r,(t)->r=(p),(p)=(t))

#define RULE_active     0x0001            /* active target        */
#define RULE_dontcare   0x0002            /* ok if not found            */
#define RULE_error      0x0004            /* not found or not generated */
#define RULE_exists     0x0008            /* target file exists         */
#define RULE_generated  0x0010            /* generated target           */
#define RULE_ignore     0x0020            /* ignore time                */
#define RULE_implicit   0x0040            /* implicit prerequisite      */
#define RULE_made 0x0080            /* already made               */
#define RULE_virtual    0x0100            /* not a file                 */

#define STREAM_KEEP     0x0001            /* don't fclose() on pop()    */
#define STREAM_MUST     0x0002            /* push() file must exist     */
#define STREAM_PIPE     0x0004            /* pclose() on pop()          */

#ifndef S_IXUSR
#define S_IXUSR         0100        /* owner execute permission   */
#endif
#ifndef S_IXGRP
#define S_IXGRP         0010        /* group execute permission   */
#endif
#ifndef S_IXOTH
#define S_IXOTH         0001        /* other execute permission   */
#endif

struct Rule_s;

typedef struct stat Stat_t;
typedef FILE Stdio_t;

typedef struct Buf_s                /* buffer stream        */
{
      struct Buf_s*     old;        /* next dropped buffer        */
      char*       end;        /* 1 past end of buffer       */
      char*       nxt;        /* next char to add           */
      char*       buf;        /* buffer space               */
} Buf_t;

typedef struct Dict_item_s          /* dictionary item            */
{
      struct Dict_item_s*     left; /* left child                 */
      struct Dict_item_s*     right;      /* right child                */
      void*             value;      /* user defined value         */
      char              name[1];/* 0 terminated name        */
} Dict_item_t;

typedef struct Dict_s               /* dictionary handle          */
{
      Dict_item_t*      root;       /* root item                  */
} Dict_t;

typedef struct List_s               /* Rule_t list                */
{
      struct List_s*    next;       /* next in list               */
      struct Rule_s*    rule;       /* list item                  */
} List_t;

typedef struct Rule_s               /* rule item                  */
{
      char*       name;       /* unbound name               */
      char*       path;       /* bound path                 */
      List_t*           prereqs;    /* prerequisites        */
      struct Rule_s*    leaf;       /* recursion leaf alias       */
      int         flags;            /* RULE_* flags               */
      unsigned long     time;       /* modification time          */
} Rule_t;

typedef struct Stream_s             /* input file stream stack    */
{
      Stdio_t*    fp;         /* read stream                */
      char*       file;       /* stream path                */
      unsigned long     line;       /* stream line                */
      int         flags;            /* stream flags               */
} Stream_t;

typedef struct View_s               /* viewpath level       */
{
      struct View_s*    next;       /* next level in viewpath     */
      int         node;       /* viewpath node path length  */
      char        dir[1];           /* viewpath level dir prefix  */
} View_t;

static struct                       /* program state        */
{
      Buf_t*            buf;        /* work buffer                */
      Buf_t*            old;        /* dropped buffers            */
      Buf_t*            opt;        /* option buffer        */

      Dict_t*           leaf;       /* recursion leaf dictionary  */
      Dict_t*           libs;       /* library dictionary         */
      Dict_t*           rules;            /* rule dictionary            */
      Dict_t*           vars;       /* variable dictionary        */

      View_t*           view;       /* viewpath levels            */

      char*       directory;  /* work in this directory     */
      char*       id;         /* command name               */
      char*       file;       /* first input file           */
      char*       pwd;        /* current directory          */
      char*       recurse;    /* recursion pattern          */
      char*       shell;            /* ${SHELL}             */

      int         active;           /* targets currently active   */
      int         debug;            /* negative of debug level    */
      int         errors;           /* some error(s) occurred     */
      int         exec;       /* execute actions            */
      int         force;            /* all targets out of date    */
      int         ignore;           /* ignore command errors      */
      int         indent;           /* debug indent               */
      int         keepgoing;  /* do siblings on error       */
      int         never;            /* never execute        */
      int         peek;       /* next line already in input */
      int         probed;           /* probe already done         */
      int         verified;   /* don't bother with verify() */

      Stream_t    streams[4]; /* input file stream stack    */
      Stream_t*   sp;         /* input stream stack pointer */

      char        input[8*CHUNK];   /* input buffer               */
} state;

static unsigned long    make(Rule_t*);

static char       mamfile[] = "Mamfile";
static char       sh[] = "/bin/sh";

extern char**           environ;

#if !_PACKAGE_ast

#if defined(NeXT) || defined(__NeXT)
#define getcwd(a,b)     getwd(a)
#endif

/*
 * emit usage message and exit
 */

static void
usage()
{
      fprintf(stderr, "Usage: %s [-iknFKNV] [-f mamfile] [-r pattern] [-C directory] [-D level] [target ...] [name=value ...]\n", state.id);
      exit(2);
}

#endif

/*
 * output error message identification
 */

static void
identify(Stdio_t* sp)
{
      if (state.directory)
            fprintf(sp, "%s [%s]: ", state.id, state.directory);
      else
            fprintf(sp, "%s: ", state.id);
}

/*
 * emit error message
 * level:
 *    <0    debug
 *     0    info
 *     1    warning
 *     2    error
 *    >2    exit(level-2)
 */

static void
report(int level, char* text, char* item, unsigned long stamp)
{
      int   i;

      if (level >= state.debug)
      {
            if (level)
                  identify(stderr);
            if (level < 0)
            {
                  fprintf(stderr, "debug%d: ", level);
                  for (i = 1; i < state.indent; i++)
                        fprintf(stderr, "  ");
            }
            else
            {
                  if (state.sp && state.sp->line)
                  {
                        if (state.sp->file)
                              fprintf(stderr, "%s: ", state.sp->file);
                        fprintf(stderr, "%ld: ", state.sp->line);
                  }
                  if (level == 1)
                        fprintf(stderr, "warning: ");
                  else if (level > 1)
                        state.errors = 1;
            }
            if (item)
                  fprintf(stderr, "%s: ", item);
            fprintf(stderr, "%s", text);
            if (stamp && state.debug <= -2)
                  fprintf(stderr, " %10lu", stamp);
            fprintf(stderr, "\n");
            if (level > 2)
                  exit(level - 2);
      }
}

/*
 * don't know how to make or exit code making
 */

static void
dont(Rule_t* r, int code, int keepgoing)
{
      identify(stderr);
      if (!code)
            fprintf(stderr, "don't know how to make %s\n", r->name);
      else
      {
            fprintf(stderr, "*** exit code %d making %s%s\n", code, r->name, state.ignore ? " ignored" : "");
            unlink(r->name);
            if (state.ignore)
                  return;
      }
      if (!keepgoing)
            exit(1);
      state.errors++;
      r->flags |= RULE_error;
}

/*
 * local strrchr()
 */

static char*
last(register char* s, register int c)
{
      register char*    r = 0;

      for (r = 0; *s; s++)
            if (*s == c)
                  r = s;
      return r;
}

/*
 * open a buffer stream
 */

static Buf_t*
buffer(void)
{
      register Buf_t*   buf;

      if (buf = state.old)
            state.old = state.old->old;
      else if (!(buf = newof(0, Buf_t, 1, 0)) || !(buf->buf = newof(0, char, CHUNK, 0)))
            report(3, "out of space [buffer]", NiL, (unsigned long)0);
      buf->end = buf->buf + CHUNK;
      buf->nxt = buf->buf;
      return buf;
}

/*
 * close a buffer stream
 */

static void
drop(Buf_t* buf)
{
      buf->old = state.old;
      state.old = buf;
}

/*
 * append str length n to buffer and return the buffer base
 */

static char*
appendn(Buf_t* buf, char* str, int n)
{
      int   m;
      int   i;

      if ((n + 1) >= (buf->end - buf->nxt))
      {
            i = buf->nxt - buf->buf;
            m = (((buf->end - buf->buf) + n + CHUNK + 1) / CHUNK) * CHUNK;
            if (!(buf->buf = newof(buf->buf, char, m, 0)))
                  report(3, "out of space [buffer resize]", NiL, (unsigned long)0);
            buf->end = buf->buf + m;
            buf->nxt = buf->buf + i;
      }
      memcpy(buf->nxt, str, n + 1);
      buf->nxt += n;
      return buf->buf;
}

/*
 * append str to buffer and return the buffer base
 * if str==0 then next pointer reset to base
 */

static char*
append(Buf_t* buf, char* str)
{
      if (str)
            return appendn(buf, str, strlen(str));
      buf->nxt = buf->buf;
      return buf->buf;
}

/*
 * allocate space for s and return the copy
 */

static char*
duplicate(char* s)
{
      char* t;
      int   n;

      n = strlen(s);
      if (!(t = newof(0, char, n, 1)))
            report(3, "out of space [duplicate]", s, (unsigned long)0);
      strcpy(t, s);
      return t;
}

/*
 * open a new dictionary
 */

static Dict_t*
dictionary(void)
{
      Dict_t*     dict;

      if (!(dict = newof(0, Dict_t, 1, 0)))
            report(3, "out of space [dictionary]", NiL, (unsigned long)0);
      return dict;
}

/*
 * return the value for item name in dictionary dict
 * if value!=0 then name entry value is created if necessary and set
 * uses top-down splaying (ala Tarjan and Sleator)
 */

static void*
search(register Dict_t* dict, char* name, void* value)
{
      register int            cmp;
      register Dict_item_t*   root;
      register Dict_item_t*   t;
      register Dict_item_t*   left;
      register Dict_item_t*   right;
      register Dict_item_t*   lroot;
      register Dict_item_t*   rroot;

      root = dict->root;
      left = right = lroot = rroot = 0;
      while (root)
      {
            if (!(cmp = strcmp(name, root->name)))
                  break;
            else if (cmp < 0)
            {     
                  if (root->left && (cmp = strcmp(name, root->left->name)) <= 0)
                  {
                        ROTATE(root, left, right, t);
                        if (!cmp)
                              break;
                  }
                  if (right)
                        right->left = root;
                  else
                        rroot = root;
                  right = root;
                  root = root->left;
                  right->left = 0;
            }
            else
            {     
                  if (root->right && (cmp = strcmp(name, root->right->name)) >= 0)
                  {
                        ROTATE(root, right, left, t);
                        if (!cmp)
                              break;
                  }
                  if (left)
                        left->right = root;
                  else
                        lroot = root;
                  left = root;
                  root = root->right;
                  left->right = 0;
            }
      }
      if (root)
      {
            if (right)
                  right->left = root->right;
            else
                  rroot = root->right;
            if (left)
                  left->right = root->left;
            else
                  lroot = root->left;
      }
      else if (value)
      {
            if (!(root = newof(0, Dict_item_t, 1, strlen(name))))
                  report(3, "out of space [dictionary]", name, (unsigned long)0);
            strcpy(root->name, name);
      }
      if (root)
      {
            if (value)
                  root->value = value;
            root->left = lroot;
            root->right = rroot;
            dict->root = root;
            return value ? (void*)root->name : root->value;
      }
      if (left)
      {
            left->right = rroot;
            dict->root = lroot;
      }
      else if (right)
      {
            right->left = lroot;
            dict->root = rroot;
      }
      return 0;
}

/*
 * low level for walk()
 */

static int
apply(Dict_t* dict, Dict_item_t* item, int (*func)(Dict_item_t*, void*), void* handle)
{
      register Dict_item_t*   right;

      do
      {
            right = item->right;
            if (item->left && apply(dict, item->left, func, handle))
                  return -1;
            if ((*func)(item, handle))
                  return -1;
      } while (item = right);
      return 0;
}

/*
 * apply func to each dictionary item
 */

static int
walk(Dict_t* dict, int (*func)(Dict_item_t*, void*), void* handle)
{
      return dict->root ? apply(dict, dict->root, func, handle) : 0;
}

/*
 * return a rule pointer for name
 */

static Rule_t*
rule(char* name)
{
      Rule_t*     r;

      if (!(r = (Rule_t*)search(state.rules, name, NiL)))
      {
            if (!(r = newof(0, Rule_t, 1, 0)))
                  report(3, "out of space [rule]", name, (unsigned long)0);
            r->name = (char*)search(state.rules, name, (void*)r);
      }
      return r;
}

/*
 * prepend p onto rule r prereqs
 */

static void
cons(Rule_t* r, Rule_t* p)
{
      register List_t*  x;

      for (x = r->prereqs; x && x->rule != p; x = x->next);
      if (!x)
      {
            if (!(x = newof(0, List_t, 1, 0)))
                  report(3, "out of space [list]", r->name, (unsigned long)0);
            x->rule = p;
            x->next = r->prereqs;
            r->prereqs = x;
      }
}

/*
 * initialize the viewpath
 */

static void
view(void)
{
      register char*          s;
      register char*          t;
      register char*          p;
      register View_t*  vp;

      View_t*                 zp;
      int               c;
      int               n;

      Stat_t                  st;
      Stat_t                  ts;

      char              buf[CHUNK];

      if (stat(".", &st))
            report(3, "cannot stat", ".", (unsigned long)0);
      if ((s = (char*)search(state.vars, "PWD", NiL)) && !stat(s, &ts) &&
          ts.st_dev == st.st_dev && ts.st_ino == st.st_ino)
            state.pwd = s;
      if (!state.pwd)
      {
            if (!getcwd(buf, sizeof(buf) - 1))
                  report(3, "cannot determine PWD", NiL, (unsigned long)0);
            state.pwd = duplicate(buf);
            search(state.vars, "PWD", state.pwd);
      }
      if ((s = (char*)search(state.vars, "VPATH", NiL)) && *s)
      {
            zp = 0;
            for (;;)
            {
                  for (t = s; *t && *t != ':'; t++);
                  if (c = *t)
                        *t = 0;
                  if (!state.view)
                  {
                        /*
                         * determine the viewpath offset
                         */

                        if (stat(s, &st))
                              report(3, "cannot stat top view", s, (unsigned long)0);
                        if (stat(state.pwd, &ts))
                              report(3, "cannot stat", state.pwd, (unsigned long)0);
                        if (ts.st_dev == st.st_dev && ts.st_ino == st.st_ino)
                              p = ".";
                        else
                        {
                              p = state.pwd + strlen(state.pwd);
                              while (p > state.pwd)
                                    if (*--p == '/')
                                    {
                                          if (p == state.pwd)
                                                report(3, ". not under VPATH", s, (unsigned long)0);
                                          *p = 0;
                                          if (stat(state.pwd, &ts))
                                                report(3, "cannot stat", state.pwd, (unsigned long)0);
                                          *p = '/';
                                          if (ts.st_dev == st.st_dev && ts.st_ino == st.st_ino)
                                          {
                                                p++;
                                                break;
                                          }
                                    }
                              if (p <= state.pwd)
                                    report(3, "cannot determine viewpath offset", s, (unsigned long)0);
                        }
                  }
                  n = strlen(s);
                  if (!(vp = newof(0, View_t, 1, strlen(p) + n + 1)))
                        report(3, "out of space [view]", s, (unsigned long)0);
                  vp->node = n + 1;
                  strcpy(vp->dir, s);
                  *(vp->dir + n) = '/';
                  strcpy(vp->dir + n + 1, p);
                  report(-4, vp->dir, "view", (unsigned long)0);
                  if (!state.view)
                        state.view = zp = vp;
                  else
                        zp = zp->next = vp;
                  if (!c)
                        break;
                  *t++ = c;
                  s = t;
            }
      }
}

/*
 * return next '?' or '}' in nested '}'
 */

static char*
cond(register char* s)
{
      register int      n;

      if (*s == '?')
            s++;
      n = 0;
      for (;;)
      {
            switch (*s++)
            {
            case 0:
                  break;
            case '{':
                  n++;
                  continue;
            case '}':
                  if (!n--)
                        break;
                  continue;
            case '?':
                  if (!n)
                        break;
                  continue;
            default:
                  continue;
            }
            break;
      }
      return s - 1;
}

/*
 * expand var refs from s into buf
 */

static void
substitute(Buf_t* buf, register char* s)
{
      register char*    t;
      register char*    v;
      register char*    q;
      register int      c;
      register int      n;
      int         a = 0;

      while (c = *s++)
      {
            if (c == '$' && *s == '{')
            {
                  for (n = *(t = ++s) == '-' ? 0 : '-'; (c = *s) && c != '?' && c != '+' && c != n && c != ':' && c != '=' && c != '[' && c != '}'; s++);
                  *s = 0;
                  if (c == '[')
                  {
                        append(buf, t - 2);
                        *s = c;
                        continue;
                  }
                  v = (char*)search(state.vars, t, NiL);
                  if ((c == ':' || c == '=') && (!v || c == ':' && !*v))
                  {
                        append(buf, t - 2);
                        *s = c;
                        continue;
                  }
                  if (t[0] == 'A' && t[1] == 'R' && t[2] == 0)
                        a = 1;
                  *s = c;
                  if (c && c != '}')
                  {
                        n = 1;
                        for (t = ++s; *s; s++)
                              if (*s == '{')
                                    n++;
                              else if (*s == '}' && !--n)
                                    break;
                  }
                  switch (c)
                  {
                  case '?':
                        q = cond(t - 1);
                        if (v)
                        {
                              if (((q - t) != 1 || *t != '*') && strncmp(v, t, q - t))
                                    v = 0;
                        }
                        else if (q == t)
                              v = s;
                        t = cond(q);
                        if (v)
                        {
                              if (t > q)
                              {
                                    c = *t;
                                    *t = 0;
                                    substitute(buf, q + 1);
                                    *t = c;
                              }
                        }
                        else
                        {
                              q = cond(t);
                              if (q > t)
                              {
                                    c = *q;
                                    *q = 0;
                                    substitute(buf, t + 1);
                                    *q = c;
                              }
                        }
                        break;
                  case '+':
                  case '-':
                        if ((v == 0 || *v == 0) == (c == '-'))
                        {
                              c = *s;
                              *s = 0;
                              substitute(buf, t);
                              *s = c;
                              break;
                        }
                        if (c != '-')
                              break;
                        /*FALLTHROUGH*/
                  case 0:
                  case '=':
                  case '}':
                        if (v)
                        {
                              if (a && t[0] == 'm' && t[1] == 'a' && t[2] == 'm' && t[3] == '_' && t[4] == 'l' && t[5] == 'i' && t[6] == 'b')
                              {
                                    for (t = v; *t == ' '; t++);
                                    for (; *t && *t != ' '; t++);
                                    if (*t)
                                          *t = 0;
                                    else
                                          t = 0;
                                    substitute(buf, v);
                                    if (t)
                                          *t = ' ';
                              }
                              else
                                    substitute(buf, v);
                        }
                        break;
                  }
                  if (*s)
                        s++;
            }
            else
                  add(buf, c);
      }
}

/*
 * expand var refs from s into buf and return buf base
 */

static char*
expand(Buf_t* buf, char* s)
{
      substitute(buf, s);
      return use(buf);
}

/*
 * stat() with .exe check
 */

static char*
status(Buf_t* buf, int off, char* path, struct stat* st)
{
      int         r;
      char*       s;
      Buf_t*            tmp;

      if (!stat(path, st))
            return path;
      if (!(tmp = buf))
      {
            tmp = buffer();
            off = 0;
      }
      if (off)
            set(tmp, off);
      else
            append(tmp, path);
      append(tmp, ".exe");
      s = use(tmp);
      r = stat(s, st);
      if (!buf)
      {
            drop(tmp);
            s = path;
      }
      if (r)
      {
            if (off)
                  s[off] = 0;
            s = 0;
      }
      return s;
}

/*
 * return path to file
 */

static char*
find(Buf_t* buf, char* file, struct stat* st)
{
      char*       s;
      View_t*           vp;
      int         node;
      int         c;
      int         o;

      if (s = status(buf, 0, file, st))
      {
            report(-3, s, "find", (unsigned long)0);
            return s;
      }
      if (vp = state.view)
      {
            node = 0;
            if (*file == '/')
            {
                  do
                  {
                        if (!strncmp(file, vp->dir, vp->node))
                        {
                              file += vp->node;
                              node = 2;
                              break;
                        }
                  } while (vp = vp->next);
            }
            else
                  vp = vp->next;
            if (vp)
                  do
                  {
                        if (node)
                        {
                              c = vp->dir[vp->node];
                              vp->dir[vp->node] = 0;
                              append(buf, vp->dir);
                              vp->dir[vp->node] = c;
                        }
                        else
                        {
                              append(buf, vp->dir);
                              append(buf, "/");
                        }
                        append(buf, file);
                        o = get(buf);
                        s = use(buf);
                        if (s = status(buf, o, s, st))
                        {
                              report(-3, s, "find", (unsigned long)0);
                              return s;
                        }
                  } while (vp = vp->next);
      }
      return 0;
}

/*
 * bind r to a file and return the modify time
 */

static unsigned long
bind(Rule_t* r)
{
      char*       s;
      Buf_t*            buf;
      struct stat st;

      buf = buffer();
      if (s = find(buf, r->name, &st))
      {
            if (s != r->name)
                  r->path = duplicate(s);
            r->time = st.st_mtime;
            r->flags |= RULE_exists;
      }
      drop(buf);
      return r->time;
}

/*
 * pop the current input file
 */

static int
pop(void)
{
      int   r;

      if (!state.sp)
            report(3, "input stack underflow", NiL, (unsigned long)0);
      if (!state.sp->fp || (state.sp->flags & STREAM_KEEP))
            r = 0;
      else if (state.sp->flags & STREAM_PIPE)
            r = pclose(state.sp->fp);
      else
            r = fclose(state.sp->fp);
      if (state.sp == state.streams)
            state.sp = 0;
      else
            state.sp--;
      return r;
}

/*
 * push file onto the input stack
 */

static int
push(char* file, Stdio_t* fp, int flags)
{
      char*       path;
      Buf_t*            buf;
      struct stat st;

      if (!state.sp)
            state.sp = state.streams;
      else if (++state.sp >= &state.streams[elementsof(state.streams)])
            report(3, "input stream stack overflow", NiL, (unsigned long)0);
      if (state.sp->fp = fp)
            state.sp->file = "pipeline";
      else if (flags & STREAM_PIPE)
            report(3, "pipe error", file, (unsigned long)0);
      else if (!file || !strcmp(file, "-") || !strcmp(file, "/dev/stdin"))
      {
            flags |= STREAM_KEEP;
            state.sp->file = "/dev/stdin";
            state.sp->fp = stdin;
      }
      else
      {
            buf = buffer();
            if (path = find(buf, file, &st))
            {
                  if (!(state.sp->fp = fopen(path, "r")))
                        report(3, "cannot read", path, (unsigned long)0);
                  state.sp->file = duplicate(path);
                  drop(buf);
            }
            else
            {
                  drop(buf);
                  pop();
                  if (flags & STREAM_MUST)
                        report(3, "not found", file, (unsigned long)0);
                  return 0;
            }
      }
      state.sp->flags = flags;
      state.sp->line = 0;
      return 1;
}

/*
 * return the next input line
 */

static char*
input(void)
{
      char* e;

      if (!state.sp)
            report(3, "no input file stream", NiL, (unsigned long)0);
      if (state.peek)
            state.peek = 0;
      else if (!fgets(state.input, sizeof(state.input), state.sp->fp))
            return 0;
      else if (*state.input && *(e = state.input + strlen(state.input) - 1) == '\n')
            *e = 0;
      state.sp->line++;
      return state.input;
}

/*
 * pass shell action s to ${SHELL:-/bin/sh}
 * the -c wrapper ensures that scripts are run in the selected shell
 * even on systems that otherwise demand #! magic (can you say cygwin)
 */

static int
execute(register char* s)
{
      register int      c;
      Buf_t*            buf;

      if (!state.shell && (!(state.shell = (char*)search(state.vars, "SHELL", NiL)) || !strcmp(state.shell, sh)))
            state.shell = sh;
      buf = buffer();
      append(buf, state.shell);
      append(buf, " -c '");
      while (c = *s++)
      {
            if (c == '\'')
            {
                  add(buf, c);
                  for (s--; *s == c; s++)
                  {
                        add(buf, '\\');
                        add(buf, c);
                  } 
            }
            add(buf, c);
      }
      add(buf, '\'');
      s = use(buf);
      report(-5, s, "exec", (unsigned long)0);
      if ((c = system(s)) > 255)
            c >>= 8;
      drop(buf);
      return c;
}

/*
 * run action s to update r
 */

static unsigned long
run(Rule_t* r, register char* s)
{
      register Rule_t*  q;
      register char*          t;
      register int            c;
      register View_t*  v;
      int               i;
      int               j;
      int               x;
      Stat_t                  st;
      Buf_t*                  buf;

      if (r->flags & RULE_error)
            return r->time;
      buf = buffer();
      if (!strncmp(s, "mamake -r ", 10))
      {
            state.verified = 1;
            x = !state.never;
      }
      else
            x = state.exec;
      if (x)
            append(buf, "trap - 1 2 3 15\nPATH=.:$PATH\nset -x\n");
      if (state.view)
      {
            do
            {
                  for (; delimiter(*s); s++)
                        add(buf, *s);
                  for (t = s; *s && !delimiter(*s); s++);
                  c = *s;
                  *s = 0;
                  if (c == '=')
                  {
                        append(buf, t);
                        continue;
                  }
                  if ((q = (Rule_t*)search(state.rules, t, NiL)) && q->path && !(q->flags & RULE_generated))
                        append(buf, q->path);
                  else
                  {
                        append(buf, t);
                        if (*t == '-' && *(t + 1) == 'I' && (*(t + 2) || c))
                        {
                              if (*(t + 2))
                                    i = 2;
                              else
                              {
                                    for (i = 3; *(t + i) == ' ' || *(t + i) == '\t'; i++);
                                    *s = c;
                                    for (s = t + i; *s && *s != ' ' && *s != '\t' && *s != '\n'; s++);
                                    c = *s;
                                    *s = 0;
                                    append(buf, t + 2);
                              }
                              if (*(t + i) && *(t + i) != '/')
                              {
                                    v = state.view;
                                    while (v = v->next)
                                    {
                                          add(buf, ' ');
                                          for (j = 0; j < i; j++)
                                                add(buf, *(t + j));
                                          append(buf, v->dir);
                                          if (*(t + i) != '.' || *(t + i + 1))
                                          {
                                                add(buf, '/');
                                                append(buf, t + i);
                                          }
                                    }
                              }
                        }
                  }
            } while (*s = c);
            s = use(buf);
      }
      else if (x)
      {
            append(buf, s);
            s = use(buf);
      }
      if (x)
      {
            if (c = execute(s))
                  dont(r, c, state.keepgoing);
            if (status((Buf_t*)0, 0, r->name, &st))
            {
                  r->time = st.st_mtime;
                  r->flags |= RULE_exists;
            }
            else
                  r->time = NOW;
      }
      else
      {
            fprintf(stdout, "%s\n", s);
            if (state.debug)
                  fflush(stdout);
            r->time = NOW;
            r->flags |= RULE_exists;
      }
      drop(buf);
      return r->time;
}

/*
 * return the full path for s using buf workspace
 */

static char*
path(Buf_t* buf, char* s, int must)
{
      register char*    p;
      register char*    d;
      register char*    x;
      char*       e;
      register int      c;
      int         t;
      int         o;
      Stat_t            st;

      for (e = s; *e && *e != ' ' && *e != '\t'; e++);
      t = *e;
      if ((x = status(buf, 0, s, &st)) && (st.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))
            return x;
      if (!(p = (char*)search(state.vars, "PATH", NiL)))
            report(3, "variable not defined", "PATH", (unsigned long)0);
      do
      {
            for (d = p; *p && *p != ':'; p++);
            c = *p;
            *p = 0;
            if (*d && (*d != '.' || *(d + 1)))
            {
                  append(buf, d);
                  add(buf, '/');
            }
            *p = c;
            if (t)
                  *e = 0;
            append(buf, s);
            if (t)
                  *e = t;
            o = get(buf);
            x = use(buf);
            if ((x = status(buf, o, x, &st)) && (st.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))
                  return x;
      } while (*p++);
      if (must)
            report(3, "command not found", s, (unsigned long)0);
      return 0;
}

/*
 * generate (if necessary) and read the MAM probe information
 * done on the first `setv CC ...'
 */

static void
probe(void)
{
      register char*    cc;
      register char*    s;
      unsigned long     h;
      unsigned long     q;
      Buf_t*            buf;
      Buf_t*            pro;
      Buf_t*            tmp;
      struct stat st;

      static char let[] = "ABCDEFGHIJKLMNOP";
      static char cmd[] = "mamprobe";

      if (!(cc = (char*)search(state.vars, "CC", NiL)))
            cc = "cc";
      buf = buffer();
      s = path(buf, cmd, 1);
      q = stat(s, &st) ? (unsigned long)0 : (unsigned long)st.st_mtime;
      pro = buffer();
      s = cc = path(pro, cc, 1);
      for (h = 0; *s; s++)
            h = h * 0x63c63cd9L + *s + 0x9c39c33dL;
      if (!(s = (char*)search(state.vars, "INSTALLROOT", NiL)))
            report(3, "variable must be defined", "INSTALLROOT", (unsigned long)0);
      append(buf, s);
      append(buf, "/lib/probe/C/mam/");
      for (h &= 0xffffffffL; h; h >>= 4)
            add(buf, let[h & 0xf]);
      s = use(buf);
      h = stat(s, &st) ? (unsigned long)0 : (unsigned long)st.st_mtime;
      if (h < q || !push(s, (Stdio_t*)0, 0))
      {
            tmp = buffer();
            append(tmp, cmd);
            add(tmp, ' ');
            append(tmp, s);
            add(tmp, ' ');
            append(tmp, cc);
            if (execute(use(tmp)))
                  report(3, "cannot generate probe info", s, (unsigned long)0);
            drop(tmp);
            if (!push(s, (Stdio_t*)0, 0))
                  report(3, "cannot read probe info", s, (unsigned long)0);
      }
      drop(pro);
      drop(buf);
      make(rule(""));
      pop();
}

/*
 * add attributes in s to r
 */

static void
attributes(register Rule_t* r, register char* s)
{
      register char*    t;
      register int      n;

      for (;;)
      {
            for (; *s == ' '; s++);
            for (t = s; *s && *s != ' '; s++);
            if (!(n = s - t))
                  break;
            switch (*t)
            {
            case 'd':
                  if (n == 8 && !strncmp(t, "dontcare", n))
                        r->flags |= RULE_dontcare;
                  break;
            case 'g':
                  if (n == 9 && !strncmp(t, "generated", n))
                        r->flags |= RULE_generated;
                  break;
            case 'i':
                  if (n == 6 && !strncmp(t, "ignore", n))
                        r->flags |= RULE_ignore;
                  else if (n == 8 && !strncmp(t, "implicit", n))
                        r->flags |= RULE_implicit;
                  break;
            case 'v':
                  if (n == 7 && !strncmp(t, "virtual", n))
                        r->flags |= RULE_virtual;
                  break;
            }
      }
}

/*
 * define ${mam_libX} for library reference lib
 */

static char*
require(char* lib, int dontcare)
{
      register int      c;
      char*       s;
      char*       r;
      FILE*       f;
      Buf_t*            buf;
      Buf_t*            tmp;
      struct stat st;

      static int  dynamic = -1;

      if (dynamic < 0)
            dynamic = (s = search(state.vars, "mam_cc_L", NiL)) ? atoi(s) : 0;
      if (!(r = search(state.vars, lib, NiL)))
      {
            buf = buffer();
            tmp = buffer();
            s = 0;
            for (;;)
            {
                  if (s)
                        append(buf, s);
                  if (r = search(state.vars, "mam_cc_PREFIX_ARCHIVE", NiL))
                        append(buf, r);
                  append(buf, lib + 2);
                  if (r = search(state.vars, "mam_cc_SUFFIX_ARCHIVE", NiL))
                        append(buf, r);
                  r = expand(tmp, use(buf));
                  if (!stat(r, &st))
                        break;
                  if (s)
                  {
                        r = lib;
                        break;
                  }
                  s = "${INSTALLROOT}/lib/";
                  if (dynamic)
                  {
                        append(buf, s);
                        if (r = search(state.vars, "mam_cc_PREFIX_SHARED", NiL))
                              append(buf, r);
                        append(buf, lib + 2);
                        if (r = search(state.vars, "mam_cc_SUFFIX_SHARED", NiL))
                              append(buf, r);
                        r = expand(tmp, use(buf));
                        if (!stat(r, &st))
                        {
                              r = lib;
                              break;
                        }
                  }
            }
            if (r != lib)
                  r = duplicate(r);
            search(state.vars, lib, r);
            append(tmp, lib + 2);
            append(tmp, ".req");
            if (!(f = fopen(use(tmp), "r")))
            {
                  append(tmp, "${INSTALLROOT}/lib/lib/");
                  append(tmp, lib + 2);
                  f = fopen(expand(buf, use(tmp)), "r");
            }
            if (f)
            {
                  for (;;)
                  {
                        while ((c = fgetc(f)) == ' ' || c == '\t' || c == '\n');
                        if (c == EOF)
                              break;
                        do
                        {
                              add(tmp, c);
                        } while ((c = fgetc(f)) != EOF && c != ' ' && c != '\t' && c != '\n');
                        s = use(tmp);
                        if (s[0] && (s[0] != '-' || s[1]))
                        {
                              add(buf, ' ');
                              append(buf, require(s, 0));
                        }
                  }
                  fclose(f);
                  r = use(buf);
            }
            else if (dontcare)
            {
                  append(tmp, "set -\n");
                  append(tmp, "cd /tmp\n");
                  append(tmp, "echo 'int main(){return 0;}' > x.${!-$$}.c\n");
                  append(tmp, "${CC} ${CCFLAGS} -o x.${!-$$}.x x.${!-$$}.c ");
                  append(tmp, r);
                  append(tmp, " >/dev/null 2>&1\n");
                  append(tmp, "c=$?\n");
                  append(tmp, "rm -f x.${!-$$}.[cox]\n");
                  append(tmp, "exit $c\n");
                  if (execute(expand(buf, use(tmp))))
                        r = "";
            }
            r = duplicate(r);
            search(state.vars, lib, r);
            append(tmp, "mam_lib");
            append(tmp, lib + 2);
            search(state.vars, use(tmp), r);
            drop(tmp);
            drop(buf);
      }
      return r;
}

/*
 * input() until `done r'
 */

static unsigned long
make(Rule_t* r)
{
      register char*          s;
      register char*          t;
      register char*          u;
      register char*          v;
      register Rule_t*  q;
      unsigned long           z;
      unsigned long           x;
      Buf_t*                  buf;
      Buf_t*                  cmd;

      if (r->flags & RULE_active)
            state.active++;
      if (*r->name)
      {
            z = bind(r);
            state.indent++;
            report(-1, r->name, "make", r->time);
      }
      else
            z = 0;
      buf = buffer();
      cmd = 0;
      while (s = input())
      {
            for (; *s == ' '; s++);
            for (; isdigit(*s); s++);
            for (; *s == ' '; s++);
            for (u = s; *s && *s != ' '; s++);
            if (*s)
            {
                  for (*s++ = 0; *s == ' '; s++);
                  for (t = s; *s && *s != ' '; s++);
                  if (*s)
                        for (*s++ = 0; *s == ' '; s++);
                  v = s;
            }
            else
                  t = v = s;
            switch (KEY(u[0], u[1], u[2], u[3]))
            {
            case KEY('b','i','n','d'):
                  if ((t[0] == '-' || t[0] == '+') && t[1] == 'l' && (s = require(t, !strcmp(v, "dontcare"))) && strncmp(r->name, "FEATURE/", 8) && strcmp(r->name, "configure.h"))
                        for (;;)
                        {
                              for (t = s; *s && *s != ' '; s++);
                              if (*s)
                                    *s = 0;
                              else
                                    s = 0;
                              if (*t)
                              {
                                    q = rule(expand(buf, t));
                                    attributes(q, v);
                                    x = bind(q);
                                    if (z < x)
                                          z = x;
                                    if (q->flags & RULE_error)
                                          r->flags |= RULE_error;
                              }
                              if (!s)
                                    break;
                              for (*s++ = ' '; *s == ' '; s++);
                        }
                  continue;
            case KEY('d','o','n','e'):
                  q = rule(expand(buf, t));
                  if (q != r)
                        report(2, "improper done statement", t, (unsigned long)0);
                  attributes(r, v);
                  if (cmd && state.active && (state.force || r->time < z || !r->time && !z))
                  {
                        substitute(buf, use(cmd));
                        x = run(r, use(buf));
                        if (z < x)
                              z = x;
                  }
                  r->flags |= RULE_made;
                  if (!(r->flags & (RULE_dontcare|RULE_error|RULE_exists|RULE_generated|RULE_implicit|RULE_virtual)))
                        dont(r, 0, state.keepgoing);
                  break;
            case KEY('e','x','e','c'):
                  r->flags |= RULE_generated;
                  if (r->path)
                  {
                        free(r->path);
                        r->path = 0;
                        r->time = 0;
                  }
                  if (state.active)
                  {
                        if (cmd)
                              add(cmd, '\n');
                        else
                              cmd = buffer();
                        append(cmd, v);
                  }
                  continue;
            case KEY('m','a','k','e'):
                  q = rule(expand(buf, t));
                  attributes(q, v);
                  x = make(q);
                  if (!(q->flags & RULE_ignore) && z < x)
                        z = x;
                  if (q->flags & RULE_error)
                        r->flags |= RULE_error;
                  continue;
            case KEY('p','r','e','v'):
                  q = rule(expand(buf, t));
                  if (!(q->flags & RULE_ignore) && z < q->time)
                        z = q->time;
                  if (q->flags & RULE_error)
                        r->flags |= RULE_error;
                  state.indent++;
                  report(-2, q->name, "prev", q->time);
                  state.indent--;
                  continue;
            case KEY('s','e','t','v'):
                  if (!search(state.vars, t, NiL))
                  {
                        if (*v == '"')
                        {
                              s = v + strlen(v) - 1;
                              if (*s == '"')
                              {
                                    *s = 0;
                                    v++;
                              }
                        }
                        search(state.vars, t, duplicate(expand(buf, v)));
                  }
                  if (!state.probed && t[0] == 'C' && t[1] == 'C' && !t[2])
                  {
                        state.probed = 1;
                        probe();
                  }
                  continue;
            default:
                  continue;
            }
            break;
      }
      drop(buf);
      if (cmd)
            drop(cmd);
      if (*r->name)
      {
            report(-1, r->name, "done", z);
            state.indent--;
      }
      if (r->flags & RULE_active)
            state.active--;
      return r->time = z;
}

/*
 * verify that active targets were made
 */

static int
verify(Dict_item_t* item, void* handle)
{
      Rule_t*     r = (Rule_t*)item->value;

      if ((r->flags & (RULE_active|RULE_error|RULE_made)) == RULE_active)
            dont(r, 0, 1);
      return 0;
}

/*
 * return 1 if name is an initializer
 */

static int
initializer(char* name)
{
      register char*    s;

      if (s = last(name, '/'))
            s++;
      else
            s = name;
      return s[0] == 'I' && s[1] == 'N' && s[2] == 'I' && s[3] == 'T';
}

/*
 * update recursion leaf r and its prerequisites
 */

static int
update(register Rule_t* r)
{
      register List_t*  x;
      Buf_t*                  buf;

      static char       cmd[] = "${MAMAKE} -C ";
      static char       arg[] = " ${MAMAKEARGS}";

      r->flags |= RULE_made;
      if (r->leaf)
            r->leaf->flags |= RULE_made;
      for (x = r->prereqs; x; x = x->next)
            if (x->rule->leaf && !(x->rule->flags & RULE_made))
                  update(x->rule);
      buf = buffer();
      substitute(buf, cmd);
      append(buf, r->name);
      substitute(buf, arg);
      run(r, use(buf));
      drop(buf);
      return 0;
}

/*
 * scan makefile prereqs
 */

static int
scan(Dict_item_t* item, void* handle)
{
      register Rule_t*  r = (Rule_t*)item->value;
      register char*          s;
      register char*          t;
      register char*          u;
      register char*          w;
      Rule_t*                 q;
      int               i;
      int               j;
      int               k;
      int               p;
      Buf_t*                  buf;

      static char*            files[] =
                        {
                              "Nmakefile",
                              "nmakefile",
                              "Makefile",
                              "makefile"
                        };

      /*
       * drop non-leaf rules
       */

      if (!r->leaf)
            return 0;

      /*
       * always make initializers
       */

      if (initializer(r->name))
      {
            if (!(r->flags & RULE_made))
                  update(r);
            return 0;
      }
      buf = buffer();
      for (i = 0; i < elementsof(files); i++)
      {
            append(buf, r->name);
            add(buf, '/');
            append(buf, files[i]);
            if (push(use(buf), (Stdio_t*)0, 0))
            {
                  while (s = input())
                  {
                        j = p = 0;
                        while (*s)
                        {
                              for (k = 1; (i = *s) == ' ' || i == '\t' || i == '"' || i == '\''; s++);
                              for (t = s; (i = *s) && i != ' ' && i != '\t' && i != '"' && i != '\'' && i != '\\' && i != ':'; s++)
                                    if (i == '/')
                                          t = s + 1;
                                    else if (i == '.' && *(s + 1) != 'c' && *(s + 1) != 'C' && *(s + 1) != 'h' && *(s + 1) != 'H' && t[0] == 'l' && t[1] == 'i' && t[2] == 'b')
                                          *s = 0;
                              if (*s)
                                    *s++ = 0;
                              if (!t[0])
                                    k = 0;
                              else if ((t[0] == '-' || t[0] == '+') && t[1] == 'l' && t[2])
                              {
                                    append(buf, "lib");
                                    append(buf, t + 2);
                                    t = use(buf);
                              }
                              else if (p)
                              {
                                    if (t[0] == '+' && !t[1])
                                          p = 2;
                                    else if (p == 1)
                                    {
                                          if (i != ':' || strncmp(s, "command", 7))
                                          {
                                                append(buf, "lib");
                                                append(buf, t);
                                                t = use(buf);
                                          }
                                          if (i == ':')
                                                while (*s && (*s == ' ' || *s == '\t'))
                                                      s++;
                                    }
                              }
                              else if (i == ':')
                              {
                                    if (j != ':' || !isupper(*t))
                                          k = 0;
                                    else if (!strcmp(t, "PACKAGE"))
                                    {
                                          p = 1;
                                          k = 0;
                                    }
                                    else
                                          for (u = t; *u; u++)
                                                if (isupper(*u))
                                                      *u = tolower(*u);
                                                else if (!isalnum(*u))
                                                {
                                                      k = 0;
                                                      break;
                                                }
                              }
                              else if (t[0] != 'l' || t[1] != 'i' || t[2] != 'b')
                                    k = 0;
                              else
                                    for (u = t + 3; *u; u++)
                                          if (!isalnum(*u))
                                          {
                                                k = 0;
                                                break;
                                          }
                              if (k && ((q = (Rule_t*)search(state.leaf, t, NiL)) && q != r || *t++ == 'l' && *t++ == 'i' && *t++ == 'b' && *t && (q = (Rule_t*)search(state.leaf, t, NiL)) && q != r))
                              {
                                    for (t = w = r->name; *w; w++)
                                          if (*w == '/')
                                                t = w + 1;
                                    if (t[0] == 'l' && t[1] == 'i' && t[2] == 'b')
                                          t += 3;
                                    for (u = w = q->name; *w; w++)
                                          if (*w == '/')
                                                u = w + 1;
                                    if (strcmp(t, u))
                                          cons(r, q);
                              }
                              j = i;
                        }
                  }
                  pop();
                  for (s = 0, w = r->name; *w; w++)
                        if (*w == '/')
                              s = w;
                  if (s)
                  {
                        if ((s - r->name) > 3 && *(s - 1) == 'b' && *(s - 2) == 'i' && *(s - 3) == 'l' && *(s - 4) != '/')
                        {
                              /*
                               * foolib : foo : libfoo
                               */

                              *(s - 3) = 0;
                              q = (Rule_t*)search(state.leaf, r->name, NiL);
                              if (q && q != r)
                                    cons(r, q);
                              for (t = w = r->name; *w; w++)
                                    if (*w == '/')
                                          t = w + 1;
                              append(buf, "lib");
                              append(buf, t);
                              q = (Rule_t*)search(state.leaf, use(buf), NiL);
                              if (q && q != r)
                                    cons(r, q);
                              *(s - 3) = 'l';
                        }
                        else if (((s - r->name) != 3 || *(s - 1) != 'b' || *(s - 2) != 'i' || *(s - 3) != 'l') && (*(s + 1) != 'l' || *(s + 2) != 'i' || *(s + 3) != 'b'))
                        {
                              /*
                               * huh/foobar : lib/libfoo
                               */

                              s++;
                              t = s + strlen(s);
                              while (--t > s)
                              {
                                    append(buf, "lib/lib");
                                    appendn(buf, s, t - s);
                                    q = (Rule_t*)search(state.leaf, use(buf), NiL);
                                    if (q && q != r)
                                          cons(r, q);
                              }
                        }
                  }
                  break;
            }
      }
      drop(buf);
      return 0;
}

/*
 * descend into op and its prereqs
 */

static int
descend(Dict_item_t* item, void* handle)
{
      Rule_t*     r = (Rule_t*)item->value;

      if (!state.active && (!(r->flags & RULE_active) || !(r = (Rule_t*)search(state.leaf, r->name, NiL))))
            return 0;
      return r->leaf && !(r->flags & RULE_made) ? update(r) : 0;
}

/*
 * append the non-leaf active targets to state.opt
 */

static int
active(Dict_item_t* item, void* handle)
{
      Rule_t*     r = (Rule_t*)item->value;

      if (r->flags & RULE_active)
      {
            if (r->leaf || search(state.leaf, r->name, NiL))
                  state.active = 0;
            else
            {
                  add(state.opt, ' ');
                  append(state.opt, r->name);
            }
      }
      return 0;
}

/*
 * recurse on mamfiles in subdirs matching pattern
 */

static int
recurse(char* pattern)
{
      register char*    s;
      register char*    t;
      Rule_t*           r;
      Buf_t*            buf;
      Buf_t*            tmp;
      struct stat st;

      /*
       * first determine the MAM subdirs
       */

      tmp = buffer();
      buf = buffer();
      state.exec = !state.never;
      state.leaf = dictionary();
      append(buf, "ls -d ");
      append(buf, pattern);
      s = use(buf);
      push("recurse", popen(s, "r"), STREAM_PIPE);
      while (s = input())
      {
            append(buf, s);
            add(buf, '/');
            append(buf, mamfile);
            if (find(tmp, use(buf), &st))
            {
                  r = rule(s);
                  if (t = last(r->name, '/'))
                        t++;
                  else
                        t = r->name;
                  r->leaf = rule(t);
                  search(state.leaf, t, r);
            }
      }
      pop();
      drop(buf);
      drop(tmp);

      /*
       * grab the non-leaf active targets
       */

      if (!state.active)
      {
            state.active = 1;
            walk(state.rules, active, NiL);
      }
      search(state.vars, "MAMAKEARGS", duplicate(use(state.opt) + 1));

      /*
       * scan the makefile and descend
       */

      walk(state.rules, scan, NiL);
      state.view = 0;
      walk(state.rules, descend, NiL);
      return 0;
}

int
main(int argc, char** argv)
{
      register char**         e;
      register char*          s;
      register char*          t;
      register char*          v;
      Buf_t*                  tmp;
      int               c;

      /*
       * initialize the state
       */

      state.id = "mamake";
      state.active = 1;
      state.exec = 1;
      state.file = mamfile;
      state.opt = buffer();
      state.rules = dictionary();
      state.vars = dictionary();
      search(state.vars, "MAMAKE", *argv);

      /*
       * parse the options
       */

#if _PACKAGE_ast
      error_info.id = state.id;
      for (;;)
      {
            switch (optget(argv, usage))
            {
            case 'e':
                  continue;
            case 'i':
                  append(state.opt, " -i");
                  state.ignore = 1;
                  continue;
            case 'k':
                  append(state.opt, " -k");
                  state.keepgoing = 1;
                  continue;
            case 'N':
                  state.never = 1;
                  /*FALLTHROUGH*/
            case 'n':
                  append(state.opt, " -n");
                  state.exec = 0;
                  continue;
            case 'F':
                  append(state.opt, " -F");
                  state.force = 1;
                  continue;
            case 'K':
                  continue;
            case 'V':
                  fprintf(stdout, "%s\n", id + 10);
                  exit(0);
            case 'f':
                  append(state.opt, " -f ");
                  append(state.opt, opt_info.arg);
                  state.file = opt_info.arg;
                  continue;
            case 'r':
                  state.recurse = opt_info.arg;
                  continue;
            case 'C':
                  state.directory = opt_info.arg;
                  continue;
            case 'D':
                  append(state.opt, " -D");
                  append(state.opt, opt_info.arg);
                  state.debug = -opt_info.num;
                  continue;
            case 'G':
                  append(state.opt, " -G");
                  search(state.vars, "-debug-symbols", "1");
                  continue;
            case 'S':
                  append(state.opt, " -S");
                  search(state.vars, "-strip-symbols", "1");
                  continue;
            case '?':
                  error(ERROR_USAGE|4, "%s", opt_info.arg);
                  continue;
            case ':':
                  error(2, "%s", opt_info.arg);
                  continue;
            }
            break;
      }
      if (error_info.errors)
            error(ERROR_USAGE|4, "%s", optusage(NiL));
      argv += opt_info.index;
#else
      while ((s = *++argv) && *s == '-')
      {
            if (*(s + 1) == '-')
            {
                  if (!*(s + 2))
                  {
                        append(state.opt, " --");
                        argv++;
                        break;
                  }
                  for (t = s += 2; *t && *t != '='; t++);
                  if (!strncmp(s, "debug-symbols", t - s) && append(state.opt, " -G") || !strncmp(s, "strip-symbols", t - s) && append(state.opt, " -S"))
                  {
                        if (*t)
                        {
                              v = t + 1;
                              if (t > s && *(t - 1) == '+')
                                    t--;
                              c = *t;
                              *t = 0;
                        }
                        else
                        {
                              c = 0;
                              v = "1";
                        }
                        search(state.vars, s - 1, v);
                        if (c)
                              *t = c;
                        continue;
                  }
                  usage();
                  break;
            }
            for (;;)
            {
                  switch (*++s)
                  {
                  case 0:
                        break;
                  case 'e':
                        continue;
                  case 'i':
                        append(state.opt, " -i");
                        state.ignore = 1;
                        continue;
                  case 'k':
                        append(state.opt, " -k");
                        state.keepgoing = 1;
                        continue;
                  case 'N':
                        state.never = 1;
                        /*FALLTHROUGH*/
                  case 'n':
                        append(state.opt, " -n");
                        state.exec = 0;
                        continue;
                  case 'F':
                        append(state.opt, " -F");
                        state.force = 1;
                        continue;
                  case 'G':
                        append(state.opt, " -G");
                        search(state.vars, "-debug-symbols", "1");
                        continue;
                  case 'K':
                        continue;
                  case 'S':
                        append(state.opt, " -S");
                        search(state.vars, "-strip-symbols", "1");
                        continue;
                  case 'V':
                        fprintf(stdout, "%s\n", id + 10);
                        exit(0);
                  case 'f':
                  case 'r':
                  case 'C':
                  case 'D':
                        t = s;
                        if (!*++s && !(s = *++argv))
                        {
                              report(2, "option value expected", t, (unsigned long)0);
                              usage();
                        }
                        else
                              switch (*t)
                              {
                              case 'f':
                                    append(state.opt, " -f ");
                                    append(state.opt, s);
                                    state.file = s;
                                    break;
                              case 'r':
                                    state.recurse = s;
                                    break;
                              case 'C':
                                    state.directory = s;
                                    break;
                              case 'D':
                                    append(state.opt, " -D");
                                    append(state.opt, s);
                                    state.debug = -atoi(s);
                                    break;
                              }
                        break;
                  default:
                        report(2, "unknown option", s, (unsigned long)0);
                  case '?':
                        usage();
                        break;
                  }
                  break;
            }
      }
#endif

      /*
       * load the environment
       */

      for (e = environ; s = *e; e++)
            for (t = s; *t; t++)
                  if (*t == '=')
                  {
                        *t = 0;
                        search(state.vars, s, t + 1);
                        *t = '=';
                        break;
                  }

      /*
       * grab the command line targets and variable definitions
       */

      while (s = *argv++)
      {
            for (t = s; *t; t++)
                  if (*t == '=')
                  {
                        v = t + 1;
                        if (t > s && *(t - 1) == '+')
                              t--;
                        c = *t;
                        *t = 0;
                        search(state.vars, s, v);
                        tmp = buffer();
                        append(tmp, s);
                        append(tmp, ".FORCE");
                        search(state.vars, use(tmp), v);
                        drop(tmp);
                        *t = c;
                        break;
                  }
            if (!*t)
            {
                  /*
                   * handle a few targets for nmake compatibility
                   */

                  if (*s == 'e' && !strncmp(s, "error 0 $(MAKEVERSION:", 22))
                        exit(1);
                  if (*s == 'r' && !strcmp(s, "recurse") || *s == 'c' && !strncmp(s, "cc-", 3))
                        continue;
                  rule(s)->flags |= RULE_active;
                  state.active = 0;
                  if (state.recurse)
                        continue;
            }
            add(state.opt, ' ');
            add(state.opt, '\'');
            append(state.opt, s);
            add(state.opt, '\'');
      }

      /*
       * initialize the views
       */

      if (state.directory && chdir(state.directory))
            report(3, "cannot change working directory", NiL, (unsigned long)0);
      view();

      /*
       * recursion drops out here
       */

      if (state.recurse)
            return recurse(state.recurse);

      /*
       * read the mamfile(s) and bring the targets up to date
       */

      search(state.vars, "MAMAKEARGS", duplicate(use(state.opt) + 1));
      push(state.file, (Stdio_t*)0, STREAM_MUST);
      make(rule(""));
      pop();

      /*
       * verify that active targets were made
       */

      if (!state.active && !state.verified)
            walk(state.rules, verify, NiL);

      /*
       * done
       */

      return state.errors != 0;
}

Generated by  Doxygen 1.6.0   Back to index