mamake.c   [plain text]


/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*                     Copyright (c) 1994-2011 AT&T                     *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                               by AT&T                                *
*                                                                      *
*                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) 2009-05-05 $\0\n";

#if _PACKAGE_ast

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

static const char usage[] =
"[-?\n@(#)$Id: mamake (AT&T Research) 2009-05-05 $\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>
#include <time.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 char*	b;
	register int	c;
	register int	n;
	int		a = 0;
	int		i;

	while (c = *s++)
	{
		if (c == '$' && *s == '{')
		{
			b = s - 1;
			i = 1;
			for (n = *(t = ++s) == '-' ? 0 : '-'; (c = *s) && c != '?' && c != '+' && c != n && c != ':' && c != '=' && c != '[' && c != '}'; s++)
				if (!isalnum(c) && c != '_')
					i = 0;
			*s = 0;
			if (c == '[')
			{
				append(buf, b);
				*s = c;
				continue;
			}
			v = (char*)search(state.vars, t, NiL);
			if ((c == ':' || c == '=') && (!v || c == ':' && !*v))
			{
				append(buf, b);
				*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);
				}
				else if (i)
				{
					c = *s;
					*s = 0;
					append(buf, b);
					*s = c;
					continue;
				}
				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;
}