/* @(#)update.c	1.49 04/04/06 Copyright 1985, 88, 91, 1995-2004 J. Schilling */
#ifndef lint
static	char sccsid[] =
	"@(#)update.c	1.49 04/04/06 Copyright 1985, 88, 91, 1995-2004 J. Schilling";
#endif
/*
 *	Make program
 *	Macro handling / Dependency Update
 *
 *	Copyright (c) 1985, 88, 91, 1995-2004 by J. Schilling
 */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; see the file COPYING.  If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <mconfig.h>
#include <stdio.h>
#include <standard.h>
#include <strdefs.h>
#include <schily.h>
#include "make.h"

obj_t	*default_tgt = (obj_t *)NULL;	/* Current 'make' arg or default tgt */
BOOL	found_make;			/* Did we expand the $(MAKE) macro?  */

#define	RTYPE_NONE	-1		/* Undefined type (used to init)    */
#define	RTYPE_DEFAULT	0		/* Rule from .DEFAULT: target	    */
#define	RTYPE_SSUFFIX	1		/* Simple suffix rule		    */
#define	RTYPE_SUFFIX	2		/* Single suffix rule		    */
#define	RTYPE_DSUFFIX	3		/* Double suffix rule		    */
#define	RTYPE_PATTERN	4		/* Pattern matching rule	    */

#define	RTYPE_NEEDFREE	0x1000		/* cmd_t * list needs to be free'd  */

#define	rule_type(t)	((t) & ~RTYPE_NEEDFREE)

EXPORT	void	initchars	__PR((void));
EXPORT	char	*filename	__PR((char * name));
LOCAL	void	copy_dir	__PR((char * name, char *dir));
LOCAL	char	*get_suffix	__PR((char *name, char *suffix));
LOCAL	void	copy_base	__PR((char *name, char *dir, char *suffix));
EXPORT	BOOL	isprecious	__PR((obj_t * obj));
EXPORT	BOOL	isphony		__PR((obj_t * obj));
LOCAL	obj_t	*pattern_rule	__PR((obj_t * obj));
LOCAL	obj_t	*suffix_rule	__PR((obj_t * obj, int *rtypep));
LOCAL	obj_t	*ssuffix_rule	__PR((obj_t * obj));
LOCAL	obj_t	*default_rule	__PR((obj_t * obj, int *rtypep));
EXPORT	list_t	*list_nth	__PR((list_t * list, int n));
EXPORT	BOOL	build_path	__PR((int level, char *name, char *path));
LOCAL	void	grant_gbuf	__PR((int size));
LOCAL	void	sub_put		__PR((char *chunk, int size));
LOCAL	void	sub_c_put	__PR((int c));
LOCAL	void	sub_s_put	__PR((char *chunk));
LOCAL	BOOL	sub_arg		__PR((int n, list_t * depends, obj_t * target));
EXPORT	char	*substitute	__PR((char *cmd, obj_t * obj, obj_t * source, char *suffix));
LOCAL	char	*subst		__PR((char *cmd, obj_t * obj, obj_t * source, char *suffix, list_t * depends));
LOCAL	char	*dynmac		__PR((char *cmd, obj_t * obj, char *suffix, list_t * depends, BOOL  domod));
LOCAL	void	extr_filenames	__PR((char *names));
LOCAL	void	extr_dirnames	__PR((char *names, char *basep));
LOCAL	char	*exp_var	__PR((char end, char *cmd, obj_t * obj, obj_t * source, char *suffix, list_t *depends));
LOCAL	char	*rstr		__PR((char * s1, char * s2));
LOCAL	BOOL	patsub		__PR((char *name, char *f1, char *f2, char *t1, char *t2));
LOCAL	void	patmsub		__PR((char *name, char *f1, char *f2, char *t1, char *t2));
LOCAL	void	parsepat	__PR((char *pat, char **fp1, char **fp2, char **tp1, char **tp2));
EXPORT	char	*shout		__PR((char *cmd));
LOCAL	char	*shsub		__PR((list_t * l, obj_t * obj, obj_t * source, char *suffix, list_t * depends));
#ifdef	USE_SUBPAT
LOCAL	void	subpat		__PR((obj_t *obj, obj_t *source, list_t  *depends, char *pat,
					char **fp1, char **fp2, char **tp1, char **tp2));
#endif
LOCAL	void	exp_name	__PR((char * name, obj_t * obj, obj_t * source, char *suffix, list_t * depends, char *));
LOCAL	date_t	searchobj	__PR((obj_t * obj, int maxlevel, int mode));
LOCAL	obj_t	*patr_src	__PR((char *name, patr_t * prule, int *rtypep, char ** suffixp, cmd_t ** pcmd));
LOCAL	obj_t	*suff_src	__PR((char *name, obj_t * rule, int *rtypep, char ** suffixp, cmd_t ** pcmd));
LOCAL	obj_t	*one_suff_src	__PR((char *name, char *suffix, cmd_t **pcmd));
LOCAL	obj_t	*ssuff_src	__PR((char *name, obj_t * rule, int *rtypep, char ** suffixp, cmd_t ** pcmd));
LOCAL	obj_t	*findsrc	__PR((obj_t *obj, obj_t * rule, int *rtypep, char ** suffixp, cmd_t ** pcmd));
LOCAL	date_t	default_cmd	__PR((obj_t * obj, char *depname, date_t  deptime, int deplevel, BOOL  must_exist));
LOCAL	date_t	make		__PR((obj_t * obj, BOOL lust_exist));
EXPORT	BOOL	domake		__PR((char *name));
EXPORT	BOOL	omake		__PR((obj_t * obj, BOOL  must_exist));
EXPORT	BOOL	xmake		__PR((char *name, BOOL  must_exist));

char	chartype[256];

/*
 * Optimise character classification
 */
EXPORT void
initchars()
{
	char	*p;
	int	c;

	p = "@*<0123456789r^?";

	while ((c = *p++) != '\0') {
		chartype[c] |= DYNCHAR;
	}

	p = "0123456789";

	while ((c = *p++) != '\0') {
		chartype[c] |= NUMBER;
	}
}

/*
 * Return last pathname component.
 */
EXPORT char *
filename(name)
	register char	*name;
{
	register char	*fname;

	for (fname = name; *name; )
		if (*name++ == SLASH)
			fname = name;
	return (fname);
}

/*
 * Copy directory component of pathname.
 */
LOCAL void
copy_dir(name, dir)
	register char	*name;
	register char	*dir;
{
	register char	*p = filename(name);

	if (XDebug > 0)
		error("copy_dir(name:'%s', dir:'%s') fn: '%s' \n", name, dir, p);
	*dir = '\0';
	if (p == name) {
		*dir++ = '.';
		*dir = '\0';
	} else {
		while (name < p)
			*dir++ = *name++;
		*--dir = '\0';		/* POSIX wants the '/' to be removed */
					/* This will make dir(/filename)    */
					/* not usable...		    */
		*dir = '\0';
	}
}

/*
 * Return part after '.' of last pathname component.
 */
LOCAL char *
get_suffix(name, suffix)
	char	*name;
	char	*suffix;
{
	register char	*p;
	register char	*suff = (char *)NULL;

	if (suffix != NULL) {
		p = filename(name);
		suff = rstr(p, suffix);
		if (suff == (char *)NULL) /* No suffix: return end of string */
			suff = &p[strlen(p)];
		return (suff);
	}

	for (p = filename(name); *p; p++)
		if (*p == '.')
			suff = p;
	if (suff == (char *)NULL)	/* No suffix: return end of string */
		suff = p;
	return (suff);
}

/*
 * Copy namebase (everything before '.').
 */
LOCAL void
copy_base(name, dir, suffix)
	register char	*name;
	register char	*dir;
		char	*suffix;
{
	register char	*p = get_suffix(name, suffix);

	while (name < p)
		*dir++ = *name++;
	*dir = '\0';
}

/*
 * Return TRUE if 'obj' is in the list of targets that should not be removed.
 */
EXPORT BOOL
isprecious(obj)
	obj_t	*obj;
{
	list_t	*l;

	if (Precious == (obj_t *)NULL)
		return (FALSE);

	for (l = Precious->o_list; l; l = l->l_next)
		if (obj == l->l_obj)
			return (TRUE);
	return (FALSE);
}

/*
 * Return TRUE if 'obj' is in the list of targets that should not be checked
 * aginst existing files. A .PHONY target is asumed to be never up to date,
 * it is not removed in case a signal is received.
 */
EXPORT BOOL
isphony(obj)
	obj_t	*obj;
{
	list_t	*l;

	if (Phony == (obj_t *)NULL)
		return (FALSE);

	for (l = Phony->o_list; l; l = l->l_next)
		if (obj == l->l_obj)
			return (TRUE);
	return (FALSE);
}

/*
 * Find pattern rule for 'obj' ... not yet ready.
 */
LOCAL obj_t *
pattern_rule(obj)
	register obj_t	*obj;
{
		char	*name;
		char	*p;
	register patr_t *prule;
#ifdef	DEBUG
	register cmd_t	*cmd;
#endif

	if (Patrules == NULL)
		return ((obj_t *)0);

	if (Debug > 1)
		printf("Searching pattern rule for: %s \n", obj->o_name);

	name = obj->o_name;
	for (prule = Patrules; prule != NULL; prule = prule->p_next) {
		name = obj->o_name;
		/*
		 * XXX NeXT Step has a buggy strstr(); returns NULL if p == ""
		 */
		p = (char *)prule->p_tgt_prefix;

		if (*p != '\0' && strstr(name, p) != name)
			continue;		/* no matching prefix */

		name += strlen(p);		/* strip matching prefix */

		if ((p = rstr(name, (char *)prule->p_tgt_suffix)) == NULL)
			continue;

#ifdef	DEBUG
		if (Debug > 1) {
			printf("name: %s (%s %% %s): (%s %% %s)\n", obj->o_name,
				prule->p_tgt_prefix, prule->p_tgt_suffix,
				prule->p_src_prefix, prule->p_src_suffix);
		}
		if (Debug > 1) {
			for (cmd = prule->p_cmd; cmd; cmd = cmd->c_next) {
				printf("\t%s\n", cmd->c_line);
			}
		}
#endif
		break;
	}
	return ((obj_t *)prule);	/* XXX Hack for now, should return prule */
}

/*
 * Find a POSIX suffix rule.
 *
 * Check if obj has a file name with a default dependency for the
 * corresponding source and a rule to compile it.
 */
LOCAL obj_t *
suffix_rule(obj, rtypep)
	register obj_t	*obj;
		int	*rtypep;
{
	list_t	*l;
	list_t	*l2;
	obj_t	*o;
	char	*suffix;
	char	rulename[NAMEMAX*2];
	BOOL	found_suffix = FALSE;

	if (Suffixes == NULL)
		return ((obj_t *)0);

	if (Debug > 1)
		printf("Searching double suffix rule for: %s \n", obj->o_name);

	for (l = Suffixes; l; l = l->l_next) {
		suffix = l->l_obj->o_name;
		if (rstr(obj->o_name, suffix)) {	/* may be a suffix */

			found_suffix = TRUE;

			for (l2 = Suffixes; l2; l2 = l2->l_next) {
				strcatl(rulename, l2->l_obj->o_name, suffix, (char *)NULL);
				if ((o = objlook(rulename, FALSE)) != NULL && o->o_type == COLON) {
					*rtypep = RTYPE_DSUFFIX;
					return (o);
				}
			}
		}
	}
	if (found_suffix)
		return ((obj_t *) 0);

	if (Debug > 1)
		printf("Searching single suffix rule for: %s \n", obj->o_name);

	for (l2 = Suffixes; l2; l2 = l2->l_next) {
		strcatl(rulename, l2->l_obj->o_name, (char *)NULL);
		if ((o = objlook(rulename, FALSE)) != NULL && o->o_type == COLON) {
			*rtypep = RTYPE_SUFFIX;
			return (o);
		}
	}
	return ((obj_t *) 0);
}

/*
 * Find a simple suffix rule.
 *
 * Check if obj has a file name with a default dependency for the
 * corresponding source and a rule to compile it.
 */
LOCAL obj_t *
ssuffix_rule(obj)
	register obj_t	*obj;
{
	register obj_t *rule;
		char	*ext;

	if (!SSuffrules)
		return ((obj_t *)0);

	if (Debug > 1)
		printf("Searching simple suffix rule for: %s \n", obj->o_name);

	ext = get_suffix(obj->o_name, (char *)0); /* Use '.' (dot) suffix only*/

	if (ext[0] == '\0') {
		if (obj->o_list == (list_t *)NULL) {
			ext = "\"\"";		/* obj has no suffix: use "" */
		} else {
			return ((obj_t *)NULL);	/* obj has dependency list   */
		}
	}
	rule = ssufflook(ext, FALSE);
	if (rule == (obj_t *)NULL ||		/* no default rules known   */
	    rule->o_list == (list_t *)NULL ||	/* no source suffix list    */
	    rule->o_cmd == (cmd_t *)NULL)	/* no commands defined	    */
		return ((obj_t *)NULL);
	return (rule);
}

/*
 * Check if a default rules exists for the target.
 */
LOCAL obj_t *
default_rule(obj, rtypep)
	obj_t	*obj;
	int	*rtypep;
{
	obj_t *rule;

#ifdef	NO_SLASH_IMPLICIT
	if (strchr(obj->o_name, SLASH) != NULL) {
		if (Debug > 3)
			error("%s has slash, no implicit dependency searched.\n",
							obj->o_name);
		/*
		 * XXX We need to check if this is a good idea.
		 */
		rule = (obj_t *)NULL;
	}
#endif
	rule = pattern_rule(obj);
	if (rule) {
		*rtypep = RTYPE_PATTERN;
		return (rule);
	}

	rule = suffix_rule(obj, rtypep);
	if (rule) {
		return (rule);
	}

	rule = ssuffix_rule(obj);
	if (rule) {
		*rtypep = RTYPE_SSUFFIX;
		return (rule);
	}

	/*
	 * XXX Must exits wichtig ??
	 */
	*rtypep = RTYPE_DEFAULT;
	rule = Deflt;	/* .DEFAULT:	*/

	if (rule == (obj_t *)NULL || rule->o_cmd == (cmd_t *)NULL)
		return ((obj_t *)NULL);
	return ((obj_t *)rule->o_cmd);	/* XXX Hack for now, should return drule */
}

/*
 * Return the nth element of a list.
 */
EXPORT list_t *
list_nth(list, n)
	register list_t *list;
		int	n;
{
	for (; list; list = list->l_next)
		if (--n < 0)
			return (list);
	return ((list_t *)NULL);
}

/*
 * Create a new file name from name and the n'th directory in SearchList.
 * SearchList lists sourcedirs before objdirs, starting with
 * n = 0 for '.' and n = 1 for ObjDir.
 */
EXPORT BOOL
build_path(level, name, path)
	int	level;
	char	*name;
	char	*path;
{
	list_t *lp;
	char	*dirname = (char *)NULL;
	register int n = level;

	if (n <= 1) {
		if (level == OBJLEVEL)
			dirname = ObjDir;
	} else if (level != MAXLEVEL) {
		if ((lp = list_nth(SearchList, n - 2)) == (list_t *)NULL)
			return (FALSE);
		dirname = lp->l_obj->o_name;
	}
	if (dirname != (char *)NULL)
		path = strcatl(path, dirname, slash, (char *)NULL);
	strcpy(path, name);
	return (TRUE);
}


/*
 * The growable buffer (gbuf) defines a string with the following layout
 *      "xxxxxxxxxxxxxxxCxxxxxxxxxxxxxxx________"
 *      ^               ^               ^               ^
 *      |               |               |               |
 *      gbuf            textp           sub_ptr         gbufend
 *      textp points to a string that is currently been worked on,
 *	sub_ptr is the write pointer.
 */
static char *sub_ptr = (char *)NULL;

LOCAL void
grant_gbuf(size)
	int	size;
{
	while (sub_ptr + size >= gbufend)
		sub_ptr = growgbuf(sub_ptr);
}

/*
 * Put a string bounded by size into the growable buffer.
 */
LOCAL void
sub_put(chunk, size)
	char	*chunk;
	int	size;
{
	grant_gbuf(size);
	movebytes(chunk, sub_ptr, size);
	sub_ptr += size;
}

/*
 * Put a single character into the growable buffer.
 */
LOCAL void
sub_c_put(c)
	char	c;
{
	grant_gbuf(1);
	*sub_ptr++ = c;
}

/*
 * Put a string bounded by strlen() into the growable buffer.
 */
LOCAL void
sub_s_put(chunk)
	char	*chunk;
{
	sub_put(chunk, strlen(chunk));
}

/*
 * Put one arg into the growable buffer.
 *
 * It target is nonzero, check in addition if the target
 * depends on the date of the currently selected obj too.
 *
 * Return FALSE if no more list elements are available.
 */
LOCAL BOOL
sub_arg(n, depends, target)
	int	n;
	list_t	*depends;
	obj_t	*target;
{
	register obj_t	*obj;
		char	arg[NAMEMAX*2];

	if ((depends = list_nth(depends, n)) == (list_t *)NULL)
		return (FALSE);

	/*
	 * $0 is not available if no implicit source is present!
	 * Just skip it.
	 */
	if ((obj =
	    depends->l_obj) == (obj_t *)NULL)
		return (TRUE);

	if (target != NULL && target->o_date > obj->o_date)
		return (TRUE);

	if (build_path(obj->o_level, obj->o_name, arg)) {
		sub_s_put(arg);
	} else {
		sub_s_put(obj->o_name);
	}
	return (TRUE);
}

/*
 * Do macro substitution. Substitution is done in the growable buffer buf.
 * The buffer is used as stack to allow recursive substitution.
 */
EXPORT char *
substitute(cmd, obj, source, suffix)
	register char	*cmd;
		obj_t	*obj;
		obj_t	*source;
		char	*suffix;
{
		list_t	depends;

	found_make = FALSE;			/* we did not expand $(MAKE) */
	depends.l_obj = source;			/* define implicit source $< */
	depends.l_next = obj->o_list;

	sub_ptr = gbuf;
	return (subst(cmd, obj, source, suffix, &depends));
}

static int	depth = 0;			/* Keep track of recursion   */

/*
 * Substitute macros.
 */
		/* source wird eigentlich nicht gebraucht */
LOCAL char *
subst(cmd, obj, source, suffix, depends)
	register char	*cmd;
		obj_t	*obj;
		obj_t	*source;
		char	*suffix;
		list_t	*depends;
{
		char	*sp = sub_ptr;
		char	*sb = gbuf;
	register char	*p;
		char	name[2];

	if (++depth > 100)
		comerrno(EX_BAD, "Recursion in macro '%s'.\n", cmd);

	name[1] = '\0';
	while ((p = strchr(cmd, '$')) != NULL) {
		sub_put(cmd, p - cmd);
		cmd = ++p;
		switch (*cmd++) {

		default:
			if (chartype[*(Uchar *)p] & DYNCHAR) {
				cmd = dynmac(p, obj, suffix, depends, FALSE);
				continue;
			}
			name[0] = cmd[-1];
			exp_name(name, obj, source, suffix, depends, Nullstr);
			break;
		case '\0':
			*sub_ptr = '\0';
			if (sb != gbuf)
				sp = gbuf + (sp - sb);
			comerrno(EX_BAD,
				"Fatal error '$' at end of string '%s$'\n",
									sp);
		case '$':
			sub_c_put('$');
			break;
		case '(':
			cmd = exp_var(')', cmd, obj, source, suffix, depends);
			break;
		case '{':
			cmd = exp_var('}', cmd, obj, source, suffix, depends);
			break;
		}
	}
	sub_s_put(cmd);
	*sub_ptr = '\0';
	depth--;
	if (sb != gbuf)
		sp = gbuf + (sp - sb);
	return (sp);
}

/*
 * Substitute dynamic macros.
 */
LOCAL char *
dynmac(cmd, obj, suffix, depends, domod)
	char	*cmd;
	obj_t	*obj;
	char	*suffix;
	list_t	*depends;
	BOOL	domod;
{
	int	num;
	char	base[NAMEMAX];
	char	*sp = sub_ptr;
	register char	*p = cmd;

	switch (*cmd++) {

	default:
		return (cmd);

	case '@':
		sub_s_put(obj->o_name);		/* $@ -> full target name */
		break;
	case '*':
		copy_base(filename(obj->o_name), base, suffix);
		sub_s_put(base);		/* $* -> target name base */
		break;
	case '<':
		sub_arg(0, depends, (obj_t *)0); /* $< -> implicit source  */
		break;
	case '0': case '1': case '2': case '3': case '4':
	case '5': case '6': case '7': case '8': case '9':
		cmd = astoi(p, &num);		/* $1 -> first dependency */
		sub_arg(num, depends, (obj_t *)0); /* $0 -> implicit source  */
		break;
	case 'r':
		/*
		 * $r0 -> all dependencies + implicit source
		 * $r1 -> all dependencies
		 */
		cmd = astoi(cmd, &num);
		while (sub_arg(num++, depends, (obj_t *)0))
			sub_c_put(' ');
		break;
	case '^':
		num = 1;			/* $^ -> all dependencies */
		while (sub_arg(num++, depends, (obj_t *)0))
			sub_c_put(' ');
		break;
	case '?':
		num = 1;			/* $? -> outdated dependencies*/
		while (sub_arg(num++, depends, obj))
			sub_c_put(' ');
		break;
	}
	*sub_ptr = '\0';
	if (!domod)
		return (cmd);
	if (*cmd == 'F') {
		extr_filenames(sp);
		return (++cmd);
	}
	if (*cmd == 'D') {
		extr_dirnames(sp, base);
		return (++cmd);
	}
	return (cmd);
}

/*
 * Extract the filename parts from a string that contains a list of names.
 */
LOCAL void
extr_filenames(names)
	char	*names;
{
	char	*p;
	char	*np;
	char	*s;
	char	*sp;
	char	*sb;

	sp = ++sub_ptr;
	sb = gbuf;
	/*
	 * Make sure that gbu has enough room for the copy.
	 */
	grant_gbuf(sub_ptr - names);
	if (sb != gbuf)
		sp = gbuf + (sp - sb);

	for (np = p = names, s = sp; np && *np; p = np) {
		np = strchr(np, ' ');
		if (np)
			*np++ = '\0';
		p = filename(p);
		while (*p)
			*s++ = *p++;
		if (np)
			*s++ = ' ';
	}
	*s = '\0';
	/*
	 * Now copy down from uppe part of gbuf.
	 */
	for (s = names, p = sp; *p; )
		*s++ = *p++;
	*s = '\0';
	sub_ptr = s;
}

/*
 * Extract the directory parts from a string that contains a list of names.
 */
LOCAL void
extr_dirnames(names, basep)
	char	*names;
	char	*basep;
{
	char	*p;
	char	*np;
	char	*s;
	char	*sp;
	char	*sb;

	sp = ++sub_ptr;
	sb = gbuf;
	/*
	 * Make sure that gbu has enough room for the copy.
	 */
	grant_gbuf(sub_ptr - names);
	if (sb != gbuf)
		sp = gbuf + (sp - sb);

	for (np = p = names, s = sp; np && *np; p = np) {
		np = strchr(np, ' ');
		if (np)
			*np++ = '\0';
		copy_dir(p, basep);
		p = basep;
		while (*p)
			*s++ = *p++;
		if (np)
			*s++ = ' ';
	}
	*s = '\0';
	/*
	 * Now copy down from uppe part of gbuf.
	 */
	for (s = names, p = sp; *p; )
		*s++ = *p++;
	*s = '\0';
	sub_ptr = s;
}

#define	white(c)	((c) == ' ' || (c) == '\t')

/*
 * Expand a macro.
 * As the replacement may be a suffix rule or a pattern rule too,
 * we first must get the basic name the macro referres to.
 */
#ifdef	PROTOTYPES
LOCAL char *
exp_var(
	register char	end,
	char		*cmd,
	obj_t		*obj,
	obj_t		*source,
	char		*suffix,
	list_t		*depends)
#else
LOCAL char *
exp_var(end, cmd, obj, source, suffix, depends)
	register char	end;
		char	*cmd;
		obj_t	*obj;
		obj_t	*source;
		char	*suffix;
		list_t	*depends;
#endif
{
		char	name[NAMEMAX];
		char	pat[NAMEMAX];
	register char	beg = cmd[-1];
	register char	*s = cmd;
	register char	*rname = name;
	register char	ch;
	register int	nestlevel = 0;
		BOOL	funccall = FALSE;

/*error("end: %c cmd: %.50s\n", end, cmd);*/
	pat[0] = '\0';
	while ((ch = *s) && rname < (name+NAMEMAX-2) && ch != ':' && !white(ch)) {
		if (ch == beg)
			nestlevel++;
		if (ch == end)
			nestlevel--;
		if (nestlevel < 0) {
/*printf("name: %s\n", name);*/
			break;
		}
		*rname++ = *s++;
	}
	*rname = '\0';
	if (ch == ' ')
		funccall = TRUE;

	if (*s != end && *s != ':' && *s != ' ') {
		comerrno(EX_BAD, "Missing '%c' in macro call '%s'\n", end, name);
/*		return (cmd);*/
	}

	if (*s == ':' || *s == ' ') {
		rname = pat;
		if (funccall) {
			while (*s && white(*s))
				s++;
		} else {
			s++;
		}
		while ((ch = *s) && rname < (pat+NAMEMAX-2)) {
			if (ch == beg)
				nestlevel++;
			if (ch == end)
				nestlevel--;
			if (nestlevel < 0) {
/*printf("name: %s\n", name);*/
				break;
			}
			*rname++ = *s++;
		}
		*rname = '\0';
		if (nestlevel >= 0)
			comerrno(EX_BAD, "Missing '%c' in macro call '%s%c%s'\n",
					end, name, funccall?' ':':', pat);
	}
	if (*s)
		s++;

	if (name[0] == 'M' && streql(name, "MAKE"))
		found_make = TRUE;

	/*
	 * If the name of the macro contains a '$', resursively expand it.
	 * We need to check if we should rather expand anything between the
	 * brackets (e.g {...}) however, this may fail to expand long lists.
	 * See also comment in exp_name() regarding USE_SUBPAT
	 */
	if (strchr(name, '$')) {
		char	*sp = sub_ptr;
		char	*sb = gbuf;
		char	*s2;

		*sub_ptr++ = '\0';

		s2 = subst(name, obj, source, suffix, depends);
		if (sb != gbuf)
			sp = gbuf + (sp - sb);
		sub_ptr = sp;
		if (*s2) {
			strcpy(name, s2);
		}
	}

	if (funccall) {
		/*
		 * GNU type macro functions will go here.
		 */
		return (s);
	}
/*printf("name: '%s' pat: '%s'\n", name, pat);*/
	exp_name(name, obj, source, suffix, depends, pat);
	return (s);
}

/*
 * Check if s1 ends in strings s2
 */
LOCAL char *
rstr(s1, s2)
	char	*s1;
	char	*s2;
{
	int	l1;
	int	l2;

	l1 = strlen(s1);
	l2 = strlen(s2);
	if (l2 > l1)
		return ((char *)NULL);

	if (streql(&s1[l1 - l2], s2))
		return (&s1[l1 - l2]);
	return ((char *)NULL);
}

/*
 * Substitute a pattern:
 * 1)	select the part of 'name' that is surrounded by f1 & f2
 * 2a)	output the selected part from 'name' and t1 (suffix)
 * 2b)	output t2 -
 *	if t2 contains '%' substitute the selected part of 'name'
 */
LOCAL BOOL
patsub(name, f1, f2, t1, t2)
	char	*name;
	char	*f1;
	char	*f2;
	char	*t1;
	char	*t2;
{
	int	l;
	char	*p;

	/*
	 * XXX NeXT Step has a buggy strstr(); returns NULL if f1 == ""
	 * XXX f1 is guaranteed to be != NULL
	 */
/*	printf("name: '%s' f1: '%s' f2: '%s' t1: '%s' t2: '%s'\n", name, f1, f2, t1, t2);*/
	if (*f1 != '\0' && strstr(name, f1) != name)
		return (FALSE);		/* no matching prefix */

	name += strlen(f1);		/* strip matching prefix */

	if ((p = rstr(name, f2)) == NULL)
		return (FALSE);		/* no matching suffix */

	l = p - name;
	if (t1 != NULL) {		/* This is a suffix rule */
		sub_put(name, l);
		p = t1;
	} else {			/* This is a pattern rule */
		p = t2;
	}
	while (*p) {
		if (*p == '%') {
			p++;
			sub_put(name, l);
		} else {
			sub_c_put(*p++);
		}
	}
	return (TRUE);
}

#ifdef	needed
/*
 * Do pattern substitution in a macro that may only contain one word.
 */
LOCAL void
patssub(name, f1, f2, t1, t2)
	char	*name;
	char	*f1;
	char	*f2;
	char	*t1;
	char	*t2;
{
	char	*osp = name;
	char	*sp = sub_ptr;
	char	*sb = gbuf;

	*sub_ptr++ = '\0';

	if (!patsub(name, f1, f2, t1, t2)) {
		sub_ptr = sp;
		return;
	}
	*sub_ptr = '\0';
	sub_ptr = osp;
	if (sb != gbuf) {
		sp = gbuf + (sp - sb);
		sub_ptr = gbuf + (sub_ptr - sb);
	}
	sub_s_put(&sp[1]);
}
#endif

/*
 * Do pattern substitution in a macro that may contain multiple words.
 * Subtitution is applied separately to each word.
 */
LOCAL void
patmsub(name, f1, f2, t1, t2)
	char	*name;
	char	*f1;
	char	*f2;
	char	*t1;
	char	*t2;
{
	char	*osp = name;
	char	*sp = sub_ptr;
	char	*sb = gbuf;
	char	*b;

	*sub_ptr++ = '\0';

	do {
		b = name;
		while (*b != '\0' && !white(*b))
			b++;
		if (*b != '\0')
			*b++ = '\0';
		else
			b = NULL;

		if (!patsub(name, f1, f2, t1, t2)) {
			sub_s_put(name);
		}

		if (b) {
			while (*b != '\0' && white(*b))
				b++;
		}
		name = b;
		if (b)
			sub_c_put(' ');
	} while (b);
	*sub_ptr = '\0';
	sub_ptr = osp;
	if (sb != gbuf) {
		sp = gbuf + (sp - sb);
		sub_ptr = gbuf + (sub_ptr - sb);
	}
	sub_s_put(&sp[1]);
}

/*
 * Parse a pattern and divide pattern into parts.
 *
 * Check if this is a suffix rule or a pattern rule.
 *
 * If this is a suffix rule (suf1=suf2), tp2 will point to a NULL pointer,
 * if this is a pattern rule (pref1%suf1=pref1%suf2) tp1 will point to NULL.
 */
LOCAL void
parsepat(pat, fp1, fp2, tp1, tp2)
	char	*pat;
	char	**fp1;
	char	**fp2;
	char	**tp1;
	char	**tp2;
{
	char	*f1;
	char	*f2;
	char	*t1;
	char	*t2;

	t1 = strchr(pat, '=');
	if (t1 == NULL)
		comerrno(EX_BAD, "'=' missing in macro substitution.\n");
	*t1++ = '\0';

	f2 = strchr(pat, '%');		/* Find '%' in from patttern */
	if (f2 != NULL) {
		*f2++ = '\0';
		f1 = pat;
	} else {
		f2 = pat;
		f1 = Nullstr;
	}
	if (f1 == pat) {		/* This is a pattern rule */
		t2 = t1;
		t1 = NULL;
	} else {			/* This is a suffix rule */
		t2 = NULL;
	}
	*fp1 = f1;
	*fp2 = f2;
	*tp1 = t1;
	*tp2 = t2;
/*	printf("f1: '%s' f2: '%s' t1: '%s' t2: '%s'\n", f1, f2, t1, t2);*/
}

/*
 * Call shell command and capture the outpout in the growable buffer.
 */
EXPORT char *
shout(cmd)
	char	*cmd;
{
	FILE	*f;
	int	c;
	char	*sptr = sub_ptr;

	if (sub_ptr == NULL)
		sptr = sub_ptr = gbuf;

	f = popen(cmd, "r");
	if (f == NULL)
		comerr("Cannot popen '%s'.\n", cmd);

	while ((c = getc(f)) != EOF) {
		if (c == '\t' || c == '\n')
			c = ' ';
		sub_c_put(c);
	}
	if ((c = pclose(f)) != 0)
		comerr("Shell returns %d from command line.\n", c);
	*sub_ptr = '\0';
	return (sptr);
}

/*
 * Substitute a shell command output
 */
LOCAL char *
shsub(l, obj, source, suffix, depends)
	register list_t *l;
		obj_t	*obj;
		obj_t	*source;
		char	*suffix;
		list_t  *depends;
{
	char	*sptr = sub_ptr;
	char	*sbuf = gbuf;

	if (sub_ptr == NULL)
		sptr = sub_ptr = gbuf;

	for (;;) {
		subst(l->l_obj->o_name, obj, source, suffix, depends);
		if ((l = l->l_next) != NULL)
			sub_c_put(' ');
		else
			break;
	}
	*sub_ptr = '\0';
	if (sbuf != gbuf)
		sptr = gbuf + (sptr - sbuf);
	sub_ptr = sptr;

	return (shout(sptr));
}

#ifdef	USE_SUBPAT
LOCAL void
subpat(obj, source, depends, pat, fp1, fp2, tp1, tp2)
	obj_t	*obj;
	obj_t	*source;
	list_t  *depends;
	char	*pat;
	char	**fp1;
	char	**fp2;
	char	**tp1;
	char	**tp2;
{
	char	epat[NAMEMAX];
	char	*sp;
	char	*sb;

	sp = sub_ptr;
	sb = gbuf;
	subst(pat, obj, source, depends);
	if (sb != gbuf)
		sp = gbuf + (sp - sb);
	strcpy(epat, sp);
	sub_ptr = sp;
	parsepat(epat, fp1, fp2, tp1, tp2);
}
#endif

/*
 * Expand a macro for which the name is explicitely known.
 */
LOCAL void
exp_name(name, obj, source, suffix, depends, pat)
		char	*name;
		obj_t	*obj;
		obj_t	*source;
		char	*suffix;
		list_t  *depends;
		char	*pat;
{
	register list_t	*l = NULL;
		obj_t	*o = NULL;
		BOOL	ispat;
		char	epat[NAMEMAX];
		char	*f1, *f2;
		char	*t1, *t2;
		char	*sp;
		char	*sb;

	if ((chartype[*(Uchar *)name] & DYNCHAR) == 0 ||
			(*name == 'r' && (chartype[((Uchar *)name)[1]] & NUMBER) == 0)) {
		/*
		 * Allow dynamic macros to appear in $() or ${} too.
		 */
		o = objlook(name, FALSE);
		if (o == (obj_t *)NULL)
			return;
		if ((l = o->o_list) == (list_t *)NULL)
			return;
	}
	if (streql(pat, "sh")) {
		/*
		 * Allow SunPRO make compatible shell expansion as $(NAME:sh)
		 */
		if (o != NULL) {
			shsub(o->o_list, obj, source, suffix, depends);
		} else {
			sp = sub_ptr;
			sb = gbuf;
			dynmac(name, obj, suffix, depends, TRUE);
/*error("expanded1: '%s'\n", sp);*/
			if (sb != gbuf)
				sp = gbuf + (sp - sb);
			sub_ptr = sp;
			(void) shout(sp);
		}
		return;
	}
	ispat = pat[0] != '\0';
	if (ispat) {
#ifdef	USE_SUBPAT
		subpat(obj, source, depends, pat, &f1, &f2, &t1, &t2);
#else
		/*
		 * XXX Es sollte eine bessere Lösung geben (ohne static epat)
		 * XXX Aber die aktuelle subpat Implementierung kann nicht
		 * XXX Funktionieren, da das epat[] aus subpat() nicht
		 * XXX exportiert werden kann. Vielleicht geht eine Lösung
		 * XXX mit 'gbuf'.
		 */
		sp = sub_ptr;
		sb = gbuf;
		subst(pat, obj, source, suffix, depends);
		if (sb != gbuf)
			sp = gbuf + (sp - sb);
		strcpy(epat, sp);
		sub_ptr = sp;
		parsepat(epat, &f1, &f2, &t1, &t2);
#endif
	}

	if (o == NULL) {
		sp = sub_ptr;
		sb = gbuf;
		dynmac(name, obj, suffix, depends, TRUE);
/*error("expanded1: '%s'\n", sp);*/
		if (ispat) {
			if (sb != gbuf)
				sp = gbuf + (sp - sb);
			patmsub(sp, f1, f2, t1, t2);
/*error("expanded2: '%s'\n", sp);*/
		}
		return;
	}
	for (;;) {
		sp = sub_ptr;
		sb = gbuf;
		subst(l->l_obj->o_name, obj, source, suffix, depends);
/*error("expanded1: '%s'\n", sp);*/
		if (ispat) {
			if (sb != gbuf)
				sp = gbuf + (sp - sb);
			patmsub(sp, f1, f2, t1, t2);
/*error("expanded2: '%s'\n", sp);*/
		}
		if ((l = l->l_next) != NULL)
			sub_c_put(' ');
		else
			break;
/*error("expanded3: '%s'\n", sp);*/
	}
}

/*
 * Find a file in .SEARCHLIST
 * .SEARCHLIST is a list of of src/obj dir pairs
 * Before .SEARCHLIST is searched, look for "." and .OBJDIR
 */
LOCAL date_t
searchobj(obj, maxlevel, mode)
	register obj_t	*obj;
		int	maxlevel;
		int	mode;
{
		char	name[NAMEMAX*3];
	register int	level = MAXLEVEL;
		date_t	filedate;

	name[0] = '\0';			/* for clean debug print */

	if ((maxlevel & 1) == 0)	/* Source has lower level	*/
		maxlevel += 1;		/* include OBJ directory level	*/

	if (obj->o_date == PHONYTIME) {
		filedate = PHONYTIME;

	} else if (isphony(obj)) {
		obj->o_date = filedate = PHONYTIME;

	} else if (strchr(obj->o_name, SLASH) != (char *)NULL) {
		/*
		 * Do not search for pathnames.
		 */
		filedate = gftime(obj->o_name);
	} else for (level = 0, filedate = 0; level <= maxlevel; level++) {
		if (level & 1 ? mode & SOBJ : mode & SSRC) {
			if (!build_path(level, obj->o_name, name))
				break;
			if ((filedate = gftime(name)) != 0)
				break;
			if (level == WDLEVEL && ObjDir == (char *)NULL) {
				/*
				 * Working dir has just been searched don't
				 * look again if no .OBJDIR has been defined.
				 */
				level++;
			}
		}
	}
	if (filedate == 0 || filedate == PHONYTIME)
		level = MAXLEVEL;

	obj->o_date = filedate;
	obj->o_level = level;

	if (Debug > 2) {
		error("search(%s, %d, %s) = %s %s %d\n",
				obj->o_name, maxlevel, searchtype(mode),
				prtime(filedate), name, level);
	}
	if (filedate == PHONYTIME)
		return (NOTIME);
	return (filedate);
}

/*
 * Find a source file for a given object file, use pattern matching rules.
 */
LOCAL obj_t *
patr_src(name, prule, rtypep, suffixp, pcmd)
	char	*name;
	patr_t	*prule;
	int	*rtypep;
	char	**suffixp;
	cmd_t	**pcmd;
{
		char	*xname = name;
	register obj_t	*source;
		obj_t	newsource;
		char	sourcename[NAMEMAX*2];
		char	pat[NAMEMAX];
		char	*p;

	*suffixp = (char *)prule->p_tgt_suffix;

	p = (char *)prule->p_tgt_prefix;
	xname += strlen(p);		/* strip matching prefix */
/*error("name: '%s'\n", xname);*/
	p = rstr(xname, (char *)prule->p_tgt_suffix);
/*error("p: '%s'\n", p);*/
	strncpy(pat, xname, p - xname);
	pat[p-xname] = '\0';
/*error("pat: '%s'\n", pat);*/
	strcatl(sourcename, prule->p_src_prefix, pat, prule->p_src_suffix, (char *)NULL);
/*error("sourcename: '%s'\n", sourcename);*/

	newsource.o_name = sourcename;
	newsource.o_date = 0;
	newsource.o_type = 0;
	newsource.o_list = (list_t *)NULL;
	newsource.o_cmd = (cmd_t *)NULL;

	if ((source = objlook(sourcename, FALSE)) != (obj_t *)NULL) {
		make(source, TRUE);		/* source found, update it  */

	} else if (make(&newsource, FALSE) != 0) {  /* found or made source */
		source = objlook(sourcename, TRUE);
		source->o_date = newsource.o_date;
		source->o_level = newsource.o_level;
	} else {
		*pcmd = (cmd_t *)NULL;
		return ((obj_t *)0);
	}

	*pcmd = prule->p_cmd;
	return (source);
}

/*
 * Find a source file for a given object file, use POSIX suffix rules.
 * Loop over all possible source names for all possible target suffixes.
 */
LOCAL obj_t *
suff_src(name, rule, rtypep, suffixp, pcmd)
	char	*name;
	obj_t	*rule;
	int	*rtypep;
	char	**suffixp;
	cmd_t	**pcmd;
{
	register list_t	*suf;		/* List of possible suffixes */
	register obj_t	*source;
	register char	*suffix = NULL;
		BOOL	found_suffix = FALSE;

	for (suf = Suffixes; suf; suf = suf->l_next) {
		suffix = suf->l_obj->o_name;
		if (rstr(name, suffix) == NULL)	/* may not be a suffix */
			continue;

		found_suffix = TRUE;
		*suffixp = suffix;
		if ((source = one_suff_src(name, suffix, pcmd)) != NULL)
			return (source);
	}
	if (found_suffix) {
		*pcmd = (cmd_t *)NULL;
		return ((obj_t *)0);
	}
	/*
	 * XXX We should never come here.
	 */
	errmsgno(EX_BAD, "XXX Expected double suffix rule but found none.\n");
	return (one_suff_src(name, Nullstr, pcmd));
}

/*
 * Find a source file for a given object file, use POSIX suffix rules.
 * Loop over all possible source names for one given target suffix.
 */
LOCAL obj_t *
one_suff_src(name, suffix, pcmd)
	char	*name;
	char	*suffix;
	cmd_t	**pcmd;
{
	register list_t	*suf;		/* List of possible suffixes */
	register obj_t	*o;
	register obj_t	*source;
		obj_t	newsource;
		char	sourcename[NAMEMAX*2];
		char	*endbase;
		char	rulename[NAMEMAX*2];

	copy_base(name, sourcename, suffix);
	endbase = sourcename + strlen(sourcename);
	newsource.o_name = sourcename;
	newsource.o_date = 0;
	newsource.o_type = 0;
	newsource.o_list = (list_t *)NULL;
	newsource.o_cmd = (cmd_t *)NULL;

	for (suf = Suffixes; suf; suf = suf->l_next) {
		strcatl(rulename, suf->l_obj->o_name, suffix, (char *)NULL);
		if ((o = objlook(rulename, FALSE)) == NULL || o->o_type != COLON)
			continue;

		if (Debug > 1)
			error("Trying %s suffix rule '%s' for: %s (suffix: '%s')\n",
				*suffix?"double":"single", rulename, name, suffix);

		strcatl(endbase, suf->l_obj->o_name, (char *)NULL);
		if ((source = objlook(sourcename, FALSE)) != (obj_t *)NULL) {
			make(source, TRUE);	/* source known, update it */

			*pcmd = o->o_cmd;
			return (source);
		}
		if (make(&newsource, FALSE) != 0) {  /* found or made source */
			source = objlook(sourcename, TRUE);
			source->o_date = newsource.o_date;
			source->o_level = newsource.o_level;

			*pcmd = o->o_cmd;
			return (source);
		}
	}
	*pcmd = (cmd_t *)NULL;
	return ((obj_t *)0);
}

/*
 * Find a source file for a given object file, use simple suffix rules.
 */
LOCAL obj_t *
ssuff_src(name, rule, rtypep, suffixp, pcmd)
	char	*name;
	obj_t	*rule;
	int	*rtypep;
	char	**suffixp;
	cmd_t	**pcmd;
{
	register list_t	*suf = rule->o_list;	/* List of possible suffixes */
	register cmd_t	*cmd = rule->o_cmd;	/* List of implicit commands */
		cmd_t	*ncmd;			/* for allocated new command */
	register obj_t	*source;
		obj_t	newsource;
		char	sourcename[NAMEMAX*2];
		char	*endbase;

	*suffixp = get_suffix(name, (char *)0);	/* Use '.' (dot) suffix only */

	copy_base(name, sourcename, (char *)NULL);
	endbase = sourcename + strlen(sourcename);
	newsource.o_name = sourcename;
	newsource.o_date = 0;
	newsource.o_type = 0;
	newsource.o_list = (list_t *)NULL;
	newsource.o_cmd = (cmd_t *)NULL;
	do {
		strcatl(endbase, suf->l_obj->o_name, (char *)NULL);
		if ((source = objlook(sourcename, FALSE)) != (obj_t *)NULL) {
			make(source, TRUE);	/* source known, update it */
			break;
		}
		if (make(&newsource, FALSE) != 0) {  /* found or made source */
			source = objlook(sourcename, TRUE);
			source->o_date = newsource.o_date;
			source->o_level = newsource.o_level;
			break;
		}
	} while ((suf = suf->l_next) != (list_t *)NULL &&
				(cmd = cmd->c_next) != (cmd_t *)NULL);
	if (source) {
		ncmd = (cmd_t *)fastalloc(sizeof (cmd_t));
		ncmd->c_next = (cmd_t *)NULL;
		ncmd->c_line = cmd->c_line;
		*pcmd = ncmd;
		*rtypep |= RTYPE_NEEDFREE;
	} else {
		*pcmd = (cmd_t *)NULL;
	}
	return (source);
}

LOCAL obj_t	*BadObj;
/*
 * Find a source file for a given object file.
 */
LOCAL obj_t *
findsrc(obj, rule, rtypep, suffixp, pcmd)
	obj_t	*obj;
	obj_t	*rule;
	int	*rtypep;
	char	**suffixp;
	cmd_t	**pcmd;
{
	int	rtype = *rtypep;

	if (BadObj == NULL) {
		BadObj = objlook("BAD OBJECT", TRUE);
		BadObj->o_date = BADTIME;
		BadObj->o_type = ':';
	}
	*suffixp = (char *)NULL;
	*pcmd = (cmd_t *)NULL;

	switch (rtype) {

	case RTYPE_PATTERN:
		return (patr_src(obj->o_name, (patr_t *)rule, rtypep, suffixp, pcmd));

	case RTYPE_DSUFFIX:
		return (suff_src(obj->o_name, rule, rtypep, suffixp, pcmd));

	case RTYPE_SUFFIX:
		*suffixp = Nullstr;
		return (one_suff_src(obj->o_name, Nullstr, pcmd));

	case RTYPE_SSUFFIX:
		return (ssuff_src(obj->o_name, rule, rtypep, suffixp, pcmd));

	case RTYPE_DEFAULT:
		*pcmd = (cmd_t *)rule;
		return (obj);		/* This causes $< to be == $@ */

	default:
		break;
	}
	return (BadObj);
}

/*
 * Process a target with no explicit command list.
 */
LOCAL date_t
default_cmd(obj, depname, deptime, deplevel, must_exist)
	register obj_t	*obj;
		char	*depname;
		date_t	deptime;
		int	deplevel;
		BOOL	must_exist;
{
	obj_t	*rule;
	obj_t	*source;
	cmd_t	*cmd;
	cmd_t	*cp;
	char	*suffix;
	int	rtype = RTYPE_NONE;

	if ((rule = default_rule(obj, &rtype)) == (obj_t *)NULL) {
		/*
		 * Found no default dependency rule for this target.
		 */
		if (obj->o_list != (list_t *)NULL) { /* Place holder or macro */
			obj->o_level = deplevel;
			return (deptime);
		}
		/*
		 * Found no rule to make target.
		 */
		if (obj == default_tgt) {
			errmsgno(EX_BAD, "'%s' does not say how to make '%s'.\n",
				MakeFileNames[obj->o_fileindex], obj->o_name);
			return (BADTIME);
		}
		/*
		 * Check if it is a source.
		 */
		if (searchobj(obj, MAXLEVEL, SSRC))
			return (obj->o_date);
		if (!must_exist)
			return (0);
		errmsgno(EX_BAD, "'%s' does not exist.\n", obj->o_name);
		return (BADTIME);
	}
	if ((source =
		findsrc(obj, rule, &rtype, &suffix, &cmd)) == (obj_t *)NULL) {

		/* Probably a obj file name. */
		if (searchobj(obj, MAXLEVEL, ObjSearch))
			return (obj->o_date);

		if (Debug > 2)
			error("Cannot find source for: %s->time: %s %s(type: %X)\n",
				obj->o_name, prtime(obj->o_date),
				must_exist?"but must ":"", obj->o_type);
		if (!must_exist)
			return (0);
		/*
		 * A target must not exist, a dependency must.
		 */
		if (obj->o_type == COLON)
			return (0);
		if (!NoWarn)
			errmsgno(EX_BAD,
				"Can't find any source for '%s'.\n",
							obj->o_name);
		return (BADTIME);
	}

	if (source->o_date == BADTIME)
		goto badtime;

	if (source->o_date > deptime) {	/* target depends on this source too */
		depname = source->o_name;
		deptime = source->o_date;
	}
	if (source->o_level < deplevel)
		deplevel = source->o_level;
	if (deptime == BADTIME)		/* Errors occured cannot make source.*/
		goto badtime;
	if (deptime >= newtime ||		/* made some targets    */
	    !searchobj(obj, deplevel, SALL) ||	/* found no obj file    */
	    deptime > obj->o_date) {		/* target is out of date */

		if (Debug > 0)
			error("Default: %s is out of date to: %s\n",
						obj->o_name, depname);
		if (Tflag) {
			if (!touch_file(obj->o_name))
				goto badtime;
		} else for (cp = cmd; cp; cp = cp->c_next) {
			if (docmd(substitute(cp->c_line, obj, source, suffix), obj)) {
				goto badtime;
			}
		}
		obj->o_date = newtime;
		obj->o_level = WDLEVEL;
	}
	if ((rtype & RTYPE_NEEDFREE) != 0 && cmd != NULL)
		fastfree((char *)cmd, sizeof (*cmd));

	if (!Tflag && ObjDir != (char *)NULL && obj->o_level == WDLEVEL) {
		if (Debug > 3) {
			list_t	*l = list_nth(SearchList, source->o_level-2);

			error("%s: obj->o_level: %d %s: source->o_level: %d '%s'\n",
					obj->o_name, obj->o_level,
					source->o_name, source->o_level,
					l ? l->l_obj->o_name:Nullstr);
		}
		obj->o_level = source->o_level;
		if ((source->o_level & 1) == 0)	/* Source has lower level  */
			obj->o_level += 1;	/* use corresponding ObjDir*/

		if (!move_tgt(obj)) {		/* move target into ObjDir */
			obj->o_level = WDLEVEL;
			return (BADTIME);	/* move failed */
		}
/*		obj->o_level = OBJLEVEL;*/
	}
	return (obj->o_date);
badtime:
	if ((rtype & RTYPE_NEEDFREE) != 0 && cmd != NULL)
		fastfree((char *)cmd, sizeof (*cmd));

	return (BADTIME);
}

/*
 * Do all commands that are required to make the current target up to date.
 *
 * If any step fails, return BADTIME. This will stop dependent commands but
 * all other commands, not related to the failed target may be made.
 *
 * If must_exist is FALSE, return value 0 if no source file exists.
 * In this case, findsrc will try other suffixes in the implicit rules.
 */
LOCAL date_t
make(obj, must_exist)
	register obj_t	*obj;
		BOOL	must_exist;
{
		char	*depname = 0;
		date_t	deptime = 0;
		date_t	xt = 0;
		short	deplevel = MAXLEVEL;
	register list_t	*l;
	register obj_t	*dep;
	register cmd_t	*cmd;

	if (Debug > 1)
		error("%sing %s\n", must_exist?"Mak":"Check", obj->o_name);

	if (obj->o_date != 0) {
		if (Debug > 2)
			error("make: %s->time: %s\n",
				obj->o_name, prtime(obj->o_date));
		/*
		 * We have been here before!
		 */
		if (obj->o_date == RECURSETIME)
			comerrno(EX_BAD, "Recursion in dependencies for '%s'.\n",
				obj->o_name);
		return (obj->o_date);
	}
	obj->o_date = RECURSETIME;	/* mark obj "we have been here" */

	/*
	 * Loop through the list of dependencies for obj.
	 */
	for (l = obj->o_list; l; l = l->l_next) {
		dep = l->l_obj;
		xt = make(dep, TRUE);


		if (dep->o_date > deptime) {
			/*
			 * Remember the date of the newest
			 * object in the list.
			 */
			depname = dep->o_name;
			deptime = dep->o_date;
		}
		if (dep->o_level < deplevel) {
			/*
			 * Remember the highest index in the search path
			 * for all objects in the list.
			 */
			deplevel = dep->o_level;
		}
	}
	if (deptime == BADTIME)		/* Errors occured cannot make target.*/
		return (obj->o_date = BADTIME);

	if (obj->o_cmd == (cmd_t *)NULL)	/* Get default command list */
		return (obj->o_date = default_cmd(obj, depname, deptime,
							deplevel, must_exist));

	/*
	 * There is an explicit command list for the current target.
	 */
	if (Debug > 2)
		error("deptime: %s\n", prtime(deptime));
	if (deptime >= newtime ||			/* made some targets */
	    !searchobj(obj, deplevel, ObjSearch) ||	/* found no obj file */
	    deptime > obj->o_date) {			/* tgt is out of date*/

		if (Debug > 0)
			error("Make:    %s is out of date to: %s\n",
						obj->o_name, depname);
		if (Tflag) {
			if (!touch_file(obj->o_name))
				return (obj->o_date = BADTIME);
		} else for (cmd = obj->o_cmd; cmd; cmd = cmd->c_next)
			if (docmd(substitute(cmd->c_line, obj, (obj_t *)NULL, (char *)NULL),
									obj)) {
				return (obj->o_date = BADTIME);
			}

		if (obj->o_level == MAXLEVEL)
			searchobj(obj, deplevel, SALL);
		obj->o_date = newtime;
	}
	return (obj->o_date);
}

/*
 * This is the interface function that is to be called from main()
 * If called with a NULL pointer, it checks for the default target first,
 * otherwise the named target is made.
 */
EXPORT BOOL
domake(name)
	char	*name;
{
	date_t	mtime;

	if (name) {
		default_tgt = objlook(name, TRUE);
	} else if (default_tgt) {
		name = default_tgt->o_name;
		if (Debug > 0)
			error("Target:  %s\n", name);
	}
	if (default_tgt) {
		mtime = make(default_tgt, TRUE);
		if (mtime == BADTIME) {
			errmsgno(EX_BAD, "Couldn't make '%s'.\n", name);
			if (Debug > 0)
				error("Current working directory:  %s\n", curwdir());
			return (FALSE);
		}
		if (mtime != (date_t)0 && mtime != newtime) {
			/*
			 * Nothing to do.
			 */
			if (!Qflag)
				errmsgno(EX_BAD, "'%s' is up to date.\n", name);
			return (TRUE);
		}
		if (Qflag)
			return (FALSE);
		return (TRUE);
	}
	errmsgno(EX_BAD, "No default Command defined.\n");
	usage(1);
	return (FALSE);	/* keep lint happy */
}

/*
 * Try to make target 'obj'.
 * Return TRUE, if target could be made.
 *
 * omake and xmake are used to make intermediate targets with no direct
 * dependency (e.g. .INIT and included files).
 */
EXPORT BOOL
omake(obj, must_exist)
	obj_t	*obj;
	BOOL	must_exist;
{
	date_t	mtime;

	if (obj == (obj_t *)NULL)
		return (TRUE);

	if (Debug > 2)
		error("xmake: %s->time: %s\n",
			obj->o_name, prtime(obj->o_date));
	mtime = make(obj, must_exist);

	if (Debug > 2)
		error("xmake: %s\n", prtime(mtime));
	if (mtime == BADTIME) {
/*		errmsgno(EX_BAD, "Couldn't make '%s'.\n", obj->o_name);*/
		return (FALSE);
	}
	return (TRUE);
}

/*
 * First do an objlook(name), then make it using xmake.
 */
EXPORT BOOL
xmake(name, must_exist)
	char	*name;
	BOOL	must_exist;
{
	obj_t	*o;

	o = objlook(name, TRUE);
	return (omake(o, must_exist));
}
