/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 1984-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                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
/*
 * Glenn Fowler
 * AT&T Research
 *
 * make file read routines
 */

#include "make.h"

/*
 * read the base and global rules
 */

static void
readrules(void)
{
	register char*		s;
	register Sfio_t*	tmp;
	register List_t*	p;

	state.global = 1;

	/*
	 * read the base rules
	 */

	if (!(s = state.rules))
		state.rules = null;
	else if (*s)
	{
		tmp = sfstropen();
		edit(tmp, s, KEEP, KEEP, external.object);
		readfile(sfstruse(tmp), COMP_BASE|(state.explicitrules ? COMP_RULES : 0), NiL);
		edit(tmp, s, DELETE, KEEP, DELETE);
		state.rules = strdup(sfstruse(tmp));
		sfstrclose(tmp);
	}
	setvar(external.rules, state.rules, 0)->property |= V_compiled;

	/*
	 * read the explicit global makefiles
	 *
	 * NOTE: internal.tmplist is used to handle the effects
	 *	 of load() on internal list pointers
	 */

	if (p = internal.globalfiles->prereqs)
	{
		for (p = internal.tmplist->prereqs = listcopy(p); p; p = p->next)
			readfile(p->rule->name, COMP_GLOBAL, NiL);
		freelist(internal.tmplist->prereqs);
		internal.tmplist->prereqs = 0;
	}
	state.global = 0;
}

/*
 * read a file given an open file pointer
 */

static void
readfp(Sfio_t* sp, register Rule_t* r, int type)
{
	register char*	s;
	register char*	t;
	int		n;
	int		needrules;
	int		preprocess;
	int		splice;
	char*		name;
	char*		b;
	char*		e;
	char*		objfile;
	Rule_t*		x;
	Sfio_t*		fp;

	objfile = 0;
	name = r->name;
	if (!state.makefile)
	{
		/*
		 * set up the related file names
		 */

		fp = sfstropen();
		setvar(external.file, r->name, 0)->property |= V_compiled;
		edit(fp, r->name, DELETE, KEEP, KEEP);
		state.makefile = strdup(sfstruse(fp));
		sfstrclose(fp);
		objfile = objectfile();
	}
	needrules = !state.base && !state.rules;

	/*
	 * load if object file
	 */

	if (loadable(sp, r, 0))
	{
		if (!state.base && !state.global && !state.list)
			error(3, "%s: explicit make object files must be global", r->name);
		if (!state.rules)
			readrules();
		message((-2, "loading %sobject file %s", state.global ? "global " : null, r->name));
		if (load(sp, r->name, 0) > 0)
		{
			sfclose(sp);
			return;
		}
		error(3, "%s: must be recompiled", name);
	}

	/*
	 * check object corresponding to file
	 */

	if (state.global || !state.forceread && (!(type & COMP_FILE) || needrules))
	{
		fp = sfstropen();
		if (!objfile)
		{
			edit(fp, r->name, DELETE, KEEP, external.object);
			objfile = sfstruse(fp);
		}
		state.init++;
		x = bindfile(NiL, objfile, 0);
		state.init--;
		sfstrclose(fp);
		if (!x || !x->time)
			/* ignore */;
		else if (x == r)
			error(3, "%s must be recompiled", r->name);
		else if (fp = sfopen(NiL, s = x->name, "br"))
		{
			if (needrules)
				x->dynamic |= D_built;
			if (loadable(fp, x, 1))
			{
				if (needrules)
				{
					if (state.rules && !state.explicitrules)
					{
						edit(internal.tmp, state.rules, DELETE, KEEP, DELETE);
						edit(internal.wrk, b = getval(external.rules, VAL_PRIMARY), DELETE, KEEP, DELETE);
						if (strcmp(sfstruse(internal.tmp), sfstruse(internal.wrk)))
						{
							error(state.exec || state.mam.out ? -1 : 1, "%s: base rules changed to %s", sfstrbase(internal.tmp), sfstrbase(internal.wrk));
							state.rules = b;
							state.forceread = 1;
							needrules = 1;
						}
					}
					if (!state.forceread)
					{
						needrules = 0;
						readrules();
					}
				}
				if (!state.forceread)
				{
					message((-2, "loading %s file %s", state.global ? "global" : "object", s));
					n = load(fp, s, 0);
					if (n > 0)
					{
						sfclose(fp);
						sfclose(sp);
						return;
					}
				}
				r = getrule(name);
			}
			sfclose(fp);
			if (state.global)
				error(1, "%s: reading%s", r->name, state.forceread ? " -- should be compiled before local makefiles" : null);
			else if (state.writeobject)
				error(state.exec || state.mam.out ? -1 : 1, "%s: recompiling", s);
		}
	}

	/*
	 * at this point we have to read it
	 * if its the first makefile then the
	 * base rules must be determined and loaded
	 * along with the global rules before the parse
	 */

	preprocess = state.preprocess;
	if (!state.global)
	{
		/*
		 * first check for and apply makefile converter
		 */

		s = 0;
		if (*(t = getval(external.convert, VAL_PRIMARY)))
		{
			char*	u;
			char*	v;
			Sfio_t*	exp;

			exp = sfstropen();
			if (e = strchr(r->name, '/'))
				e++;
			else
				e = r->name;
			b = tokopen(t, 1);
			while ((t = tokread(b)) && (t = colonlist(exp, t, 0, ' ')))
			{
				u = tokopen(t, 0);
				while ((v = tokread(u)) && !streq(e, v));
				tokclose(u);
				if (!(s = tokread(b)))
				{
					error(2, "%s: %s: no action for file", external.convert, t);
					break;
				}
				if (v)
				{
					s = getarg((e = t = strdup(s), &e), NiL);
					break;
				}
				s = 0;
			}
			tokclose(b);
			sfstrclose(exp);
		}
		if (s)
		{
			message((-2, "converting %s using \"%s\"", r->name, s));
			sfclose(sp);
			if (!(sp = fapply(internal.internal, null, r->name, s, CO_ALWAYS|CO_LOCAL|CO_URGENT)))
				error(3, "%s: error in makefile converter \"%s\"", r->name, s);
			free(t);
			preprocess = -1;
		}
		if (needrules)
		{
			if ((s = sfreserve(sp, 0, 0)) && (n = sfvalue(sp)) >= 0)
			{
				int	c;
				int	d;
				int	old;

				if (n > 0)
				{
					if (n > MAXNAME)
						n = MAXNAME;
					else
						n--;
				}

				/*
				 * quick makefile type check while
				 * checking for base rules
				 */

				old = 0;
				splice = 0;
				b = s;
				c = *(s + n);
				*(s + n) = 0;
				for (;;)
				{
					if (e = strchr(s, '\n'))
						*e = 0;
					else if (c != '\n')
						break;
					if (splice)
						/* skip */;
					else if (*s == SALT)
					{
						while (isspace(*++s));
						for (t = s; isalnum(*t); t++);
						d = *t;
						*t = 0;
						if (strneq(s, "rules", 5))
						{
							if (*t = d)
								t++;
							while (*t == ' ' || *t == '\t')
								t++;
							rules(*t == '/' && *(t + 1) == '*' ? null : t);
							break;
						}
						else if (!strmatch(s, "assert|comment|define|elif|else|endif|endmac|error|ident|if|ifdef|ifndef|include|line|macdef|pragma|unassert|undef|warning"))
							old = 1;
						else if (!preprocess)
							preprocess = 1;
						*t = d;
					}
					else if (*s == '<' && *(s + 1) == '<')
					{
						old = preprocess = 0;
						break;
					}
					else
					{
						while (isspace(*s))
							s++;
						if (strneq(s, "rules", 5))
						{
							for (s += 5; *s == ' ' || *s == '\t'; s++);
							rules(*s == '/' && *(s + 1) == '*' ? null : s);
							old = 0;
							break;
						}
						else if (strneq(s, ".SOURCE", 7) && (*(s + 7) == '.' || *(s + 7) == ':' || isspace(*(s + 7))))
						{
							old = 0;
							break;
						}
						else
						{
							d = ':';
							while (*s)
							{
								if (*s == '/' && *(s + 1) == '*' && (isspace(*(s + 2)) || !*(s + 2)))
									break;
								else if (*s == d)
								{
									if (*++s == d)
										s++;
									else if (isalnum(*s))
									{
										while (isalnum(*s))
											s++;
										if (*s == d)
											break;
									}
									d = 0;
								}
								while (*s && *s != d && !isspace(*s))
									s++;
								while (isspace(*s))
									s++;
							}
							if (*s)
							{
								old = 0;
								break;
							}
						}
					}
					if (!(s = e))
						break;
					splice = e > b && *(e - 1) == '\\';
					*s++ = '\n';
				}
				if (e)
					*e = '\n';
				*(b + n) = c;
				if (old)
					punt(1);
			}
			if (!state.rules)
				state.rules = getval(external.rules, VAL_PRIMARY);
			readrules();
			r = getrule(name);
		}
	}

	/*
	 * check for obsolete makefile preprocessor
	 */

	if (preprocess > 0)
	{
		s = "$(MAKEPP) $(MAKEPPFLAGS) $(>)";
		message((-2, "preprocessing %s using \"%s\"", r->name, s));
		sfclose(sp);
		if (!(sp = fapply(internal.internal, null, r->name, s, CO_ALWAYS|CO_LOCAL|CO_URGENT)))
			error(3, "%s: error in makefile preprocessor \"%s\"", r->name, s);
	}

	/*
	 * parse the file
	 */

	if (state.base)
	{
		if (!state.compile)
			state.compile = RECOMPILE;
		state.global = 1;
	}
	n = state.reading;
	state.reading = 1;
	parse(sp, NiL, r->name, NiL);
	sfclose(sp);
	state.reading = n;
	if (!state.compile && !state.global)
		state.compile = RECOMPILE;
	if ((state.questionable & 0x00000400) || !state.global)
		state.forceread = 1;
}

/*
 * read a makefile
 */

int
readfile(register char* file, int type, char* filter)
{
	register Rule_t*	r;
	Sfio_t*			rfp;
	Stat_t			st;

	if (streq(file, "-") && (file = "/dev/null") || isdynamic(file))
	{
		rfp = sfstropen();
		expand(rfp, file);
		state.init++;
		file = makerule(sfstruse(rfp))->name;
		state.init--;
		sfstrclose(rfp);
	}
	state.init++;
	r = bindfile(NiL, file, BIND_MAKEFILE|BIND_RULE);
	state.init--;
	if (r && (r->time || strneq(r->name, "/dev/", 5) && !rstat(r->name, &st, 0)))
	{
		compref(r, type);
		r->dynamic |= D_scanned;
		file = r->name;
		if (rfp = filter ? fapply(internal.internal, null, file, filter, CO_ALWAYS|CO_LOCAL|CO_URGENT) : rsfopen(file))
		{
			if (state.mam.dynamic || state.mam.regress)
				mampush(state.mam.out, r, P_force);
			fcntl(sffileno(rfp), F_SETFD, FD_CLOEXEC);
			if (state.user)
			{
				r->status = EXISTS;
				parse(rfp, NiL, file, NiL);
				sfclose(rfp);
			}
			else
				readfp(rfp, r, type);
			if (state.mam.dynamic || state.mam.regress)
				mampop(state.mam.out, r, 0);
			if ((type & COMP_BASE) && r->uname)
			{
				oldname(r);
				r->dynamic &= ~D_bound;
			}
			return(1);
		}
		if ((type & COMP_DONTCARE) || (r->property & P_dontcare))
		{
			r->property |= P_dontcare;
			return(0);
		}
	}
	if (!(type & COMP_DONTCARE))
		error((type & COMP_INCLUDE) ? 2 : 3, "%s: cannot read%s", file, (type & COMP_INCLUDE) ? " include file" : (type & COMP_GLOBAL) ? " global rules" : (type & COMP_BASE) ? " base rules" : null);
	return(0);
}
