#ident "@(#)main.c 1.1 94/10/31 Copyright 1986,1987,1988 Sun Micro"

/*
 *	main.c
 *
 *	make program main routine plus some helper routines
 */

/*
 * Included files
 */
#include "defs.h"
#include "report.h"
#include <signal.h>
#include <pwd.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/dk.h>
#include <rpcsvc/rstat.h>

/*
 * Defined macros
 */

/*
 * typedefs & structs
 */

/*
 * Static variables
 */
static	Boolean		env_wins;			/* `-e' */
static	Boolean		ignore_default_mk;		/* `-r' */
static	Dependency_rec  not_auto_depen_struct;
static	Dependency 	not_auto_depen = &not_auto_depen_struct;
static	Boolean		trace_status;			/* `-p' */

/*
 * File table of contents
 */
extern	void		main();
extern	int		cleanup_after_exit();
extern	void		handle_interrupt();
extern	void		doalarm();
extern	Name		read_command_options();
extern	int		parse_command_option();
extern	void		set_target_host_arch();
extern	void		setup_for_projectdir();
extern	void		read_files_and_state();
extern	void		read_environment();
extern	Boolean		read_makefile();
extern	void		make_targets();
extern	void		print_dependencies();
extern	void		print_more_deps();
extern	void		print_deps();
extern	Boolean		should_print_dep();
extern	void		report_recursion();


/*
 *	main(argc, argv)
 *
 *	Parameters:
 *		argc			You know what this is
 *		argv			You know what this is
 *
 *	Static variables used:
 *		trace_status		make -p seen
 *
 *	Global variables used:
 *		debug_level		Should we trace make actions?
 *		keep_state		Set if .KEEP_STATE seen
 *		makeflags		The Name "MAKEFLAGS", used to get macro
 *		remote_command_name	Name of remote invocation cmd ("on")
 *		running_list		List of parallel running processes
 *		stdout_stderr_same	true if stdout and stderr are the same
 *		auto_dependencies	The Name "SUNPRO_DEPENDENCIES"
 *		temp_file_directory	Set to the dir where we create tmp file
 *		trace_reader		Set to reflect tracing status
 *		working_on_targets	Set when building user targets
 */
void
main(argc, argv)
	register int		argc;
	register char		*argv[];
{
	register char		*cp;
	register Property	macro;
	struct stat		out_stat, err_stat;
	Boolean			parallel_flag = false;
	struct sigvec		vec;
	Name			make_machines;

	/* find out if stdout and stderr point to the same place */
	if (fstat(1, &out_stat) < 0) {
		fatal("fstat of standard out failed: %s", errmsg(errno));
	}
	if (fstat(2, &err_stat) < 0) {
		fatal("fstat of standard error failed: %s", errmsg(errno));
	}
	if ((out_stat.st_dev == err_stat.st_dev) &&
	    (out_stat.st_ino == err_stat.st_ino)) {
		stdout_stderr_same = true;
	} else {
		stdout_stderr_same = false;
	}
	/* Make the vroot package scan the path using shell semantics */
	set_path_style(0);

	setup_char_semantics();

	setup_for_projectdir();

	/* if running with .KEEP_STATE curdir will be set with */
	/* the connected directory */
	(void) on_exit(cleanup_after_exit, (char *) NULL);

	load_cached_names();

	set_target_host_arch();

/*
 *	Set command line flags
 */
	for (cp = getenv(makeflags->string);
	     (cp != NULL) && (*cp != (int) nul_char);
	     cp++) {
		(void) parse_command_option(*cp);
	}
	make_machines = read_command_options(argc, argv);
	if (debug_level > 0) {
		cp = getenv(makeflags->string);
		(void) printf("MAKEFLAGS value: %s\n", cp == NULL ? "":cp);
	}
			     

	read_files_and_state(argc, argv);

/*
 *	Set the parallel variables in the environment
 */
	macro = get_prop(remote_command_name->prop, macro_prop);
	if (macro != NULL) {
		macro->body.macro.exported = true;
		(void) SETVAR(remote_command_name,
			      macro->body.macro.value,
			      false);
	}

#ifdef PARALLEL
	parallel_flag = read_make_machines(make_machines);
#else
	parallel_flag = false;
#endif
		
	setup_interrupt();

/*
 *	Enable interrupt handler for alarms
 */
	vec.sv_handler = doalarm;
	vec.sv_mask = 0;
	vec.sv_flags = SV_INTERRUPT;
	if (sigvec(SIGALRM, &vec, (struct sigvec *) NULL) < 0) {
		fatal("sigvec for alarm failed: %s", errmsg(errno));
	}

/*
 *	Check if make should report
 */
	if (getenv(sunpro_dependencies->string) != NULL) {
		FILE *report_file;
		report_dependency("");
		report_file = get_report_file();
		if ((report_file != NULL) && (report_file != (FILE*)-1)) {
			(void) fprintf(report_file, "\n");
		}
	}

/*
 *	Make sure SUNPRO_DEPENDENCIES is exported (or not) properly
 *      and NSE_DEP.
 */
	if (keep_state) {
		maybe_append_prop(sunpro_dependencies, macro_prop)->
		  body.macro.exported = true;
		(void) SETVAR(sunpro_dependencies,
			     GETNAME("", FIND_LENGTH),
			     false);
		(void) setenv("NSE_DEP", get_current_path());
	} else {
		maybe_append_prop(sunpro_dependencies, macro_prop)->
		  body.macro.exported = false;
	}

	working_on_targets = true;
	if (trace_status) {
		dump_make_state();
		exit(0);
	}
	trace_reader = false;
	temp_file_directory = get_current_path();

	make_targets(argc, argv, parallel_flag);

	exit(0);
	/* NOTREACHED */
}

/*
 *	cleanup_after_exit()
 *
 *	Called from exit(), performs cleanup actions.
 *
 *	Parameters:
 *		status		The argument exit() was called with
 *
 *	Global variables used:
 *		command_changed	Set if we think .make.state should be rewritten
 *		current_line	Is set we set commands_changed
 *		do_not_exec_rule True if -n flag on
 *		done		The Name ".DONE", rule we run
 *		keep_state	Set if .KEEP_STATE seen
 *		parallel	True if building in parallel
 *		quest		If -q is on we do not run .DONE
 *		report_dependencies True if -P flag on
 *		running_list	List of parallel running processes
 *		temp_file_name	The temp file is removed, if any
 */
int
cleanup_after_exit(status)
	int	status;
{
	Running		rp;
	Property	line;
	char		push_cmd[MAXPATHLEN + MAXPATHLEN +
				 NSE_TFS_PUSH_LEN + 3];
	char		*active;

	parallel = false;
	/* Build the target .DONE or .FAILED if we caught an error */
	if (!quest) {
		Name		failed_name;

		failed_name = GETNAME(".FAILED", FIND_LENGTH);
		if ((status != 0) && (failed_name->prop != NULL)) {
			(void) doname(failed_name, false, true);
		} else {
			(void) doname(done, false, true);
		}
	}
	/* Remove the temp file utilities report dependencies thru if it is */
	/* still around */
	if (temp_file_name != NULL) {
		(void) unlink(temp_file_name->string);
	}
	/* Do not save the current command in .make.state if make */
	/* was interrupted */
	if (current_line != NULL) {
		command_changed = true;
		current_line->body.line.command_used = NULL;
	}
	/*
	 * For each parallel build process running, remove the temp files
	 * and zap the command line so it won't be put in .make.state
	 */
	for (rp = running_list; rp != NULL; rp = rp->next) {
		if (rp->temp_file != NULL) {
			(void) unlink(rp->temp_file->string);
		}
		if (rp->stdout_file != NULL) {
			(void) unlink(rp->stdout_file->string);
		}
		if (rp->stderr_file != NULL) {
			(void) unlink(rp->stderr_file->string);
		}
		command_changed = true;
		line = get_prop(rp->target->prop, line_prop);
		if (line != NULL) {
			line->body.line.command_used = NULL;
		}
	}
	/* Remove the statefile lock file */
	if (make_state_lockfile != NULL) {
		(void) unlink(make_state_lockfile);
	}
	/* Write .make.state */
	write_state_file(1);
        /* If running inside an activated environment, push the */
	/* .nse_depinfo file (if written) */
	active = getenv(NSE_VARIANT_ENV);
	if (keep_state &&
	    (NULL != active) &&
	    !IS_EQUAL(active, NSE_RT_SOURCE_NAME) &&
	    !do_not_exec_rule &&
	    !report_dependencies_only) {
		(void) sprintf(push_cmd,
			       "%s %s/%s",
			       NSE_TFS_PUSH,
			       get_current_path(),
			       NSE_DEPINFO);
		(void) system(push_cmd);
	}
}

/*
 *	handle_interrupt()
 *
 *	This is where C-C traps are caught.
 *
 *	Parameters:
 *
 *	Global variables used:
 *		current_target		Sometimes the current target is removed
 *		do_not_exec_rule	But not if -n is on
 *		quest			or -q
 *		running_list		List of parallel running processes
 *		touch			Current target is not removed if -t on
 */
void
handle_interrupt()
{
	Property		member;
	Running			rp;

	(void) fflush(stdout);
#ifdef PARALLEL
	/* Clean up all parallel children already finished */
        finish_children(false);
#endif
	/* Make sure the processes running under us terminate first */
	while (wait((union wait *) NULL) != -1);
	/* Delete the current targets unless they are precious */
	if ((current_target != NULL) &&
	    current_target->is_member &&
	    ((member = get_prop(current_target->prop, member_prop)) != NULL)) {
		current_target = member->body.member.library;
	}
	if (!do_not_exec_rule &&
	    !touch &&
	    !quest &&
	    (current_target != NULL) &&
	    !current_target->stat.is_precious) {
		if (exists(current_target) != (int) file_doesnt_exist) {
			(void) fprintf(stderr,
				       "\n*** %s ",
				       current_target->string);
			if (current_target->stat.is_dir) {
				(void) fprintf(stderr,
					       "not removed.\n",
					       current_target->string);
			} else if (unlink(current_target->string) == 0) {
				(void) fprintf(stderr,
					       "removed.\n",
					       current_target->string);
			} else {
				(void) fprintf(stderr,
					       "could not be removed: %s.\n",
					       current_target->string,
					       errmsg(errno));
			}
		}
	}
	for (rp = running_list; rp != NULL; rp = rp->next) {
		if (rp->state != build_running) {
			continue;
		}
		if (rp->target->is_member &&
		    ((member = get_prop(rp->target->prop, member_prop)) !=
		     NULL)) {
			rp->target = member->body.member.library;
		}
		if (!do_not_exec_rule &&
		    !touch &&
		    !quest &&
		    !rp->target->stat.is_precious) {
			if (exists(rp->target) != (int) file_doesnt_exist) {
				(void) fprintf(stderr,
					       "\n*** %s ",
					       rp->target->string);
				if (rp->target->stat.is_dir) {
					(void) fprintf(stderr,
						       "not removed.\n",
						       rp->target->string);
				} else if (unlink(rp->target->string) == 0) {
					(void) fprintf(stderr,
						       "removed.\n",
						       rp->target->string);
				} else {
					(void) fprintf(stderr,
						       "could not be removed: %s.\n",
						       rp->target->string,
						       errmsg(errno));
				}
			}
		}
	}
	exit(2);
}

/*
 *	doalarm(sig, code, scp)
 *
 *	Handle the alarm interrupt but do nothing.  Side effect is to
 *	cause return from wait3.
 *
 *	Parameters:
 *		sig
 *		code
 *		scp
 *
 *	Global variables used:
 */
/*ARGSUSED*/
static void
doalarm(sig, code, scp)
	int			sig;
	int			code;
	struct sigcontext	*scp;
{
	return;
}


/*
 *	read_command_options(argc, argv)
 *
 *	Scan the command line options and process the ones that start with "-"
 *
 *	Return value:
 *				-M argument, if any
 *
 *	Parameters:
 *		argc		You know what this is
 *		argv		You know what this is
 *
 *	Global variables used:
 */
static Name
read_command_options(argc, argv)
	register int		argc;
	register char		**argv;
{
	register int		i;
	register int		j;
	register char		ch;
	register int		makefile_next = 0; /* flag to note -f option */
	Name			make_machines = NULL;

	for (i = 1; i < argc; i++) {
		switch (makefile_next) {
		case 1:
			makefile_next = 0;
			continue;
		case 2:
			make_state = GETNAME(argv[i], FIND_LENGTH);
			makefile_next = 0;
			continue;
		case 4:
			make_machines = GETNAME(argv[i], FIND_LENGTH);
			makefile_next = 0;
			continue;
		}
		if ((argv[i] != NULL) && (argv[i][0] == (int) hyphen_char)) {
			for (j = 1; (ch = argv[i][j]) != (int) nul_char; j++) {
				makefile_next |=
				  parse_command_option(ch);
			}
			switch (makefile_next) {
			case 0:
				argv[i] = NULL;
				break;
			case 1:	 /* -f seen */
				argv[i] = "-f";
				break;
			case 2:	/* -F seen */
				argv[i] = "-F";
				break;
			case 4:	/* -M seen */
				argv[i] = "-M";
				break;
			default:	 /* more than one of -f, -F, -M seen */
				fatal("Illegal command line. More than one option requiring\nfile argument given in the same argument group");
			}
		}
	}
	return make_machines;
}

/*
 *	parse_command_option(ch)
 *
 *	Parse make command line options.
 *
 *	Return value:
 *				Indicates if any -f -F or -M were seen
 *
 *	Parameters:
 *		ch		The character to parse
 *
 *	Static variables used:
 *		env_wins		Set for make -e
 *		ignore_default_mk	Set for make -r
 *		trace_status		Set for make -p
 *
 *	Global variables used:
 *		continue_after_error	Set for make -k
 *		debug_level		Set for make -d
 *		do_not_exec_rule	Set for make -n
 *		filter_stderr		Set for make -X
 *		ignore_errors		Set for make -i
 *		no_parallel		Set for make -R
 *		quest			Set for make -q
 *		read_trace_level	Set for make -D
 *		report_dependencies	Set for make -P
 *		silent			Set for make -s
 *		touch			Set for make -t
 */
static int
parse_command_option(ch)
	register char		ch;
{
	static int		invert_next = 0;
	int			invert_this = invert_next;

	invert_next = 0;
	switch (ch) {
	case '~':			 /* Invert next option */
		invert_next = 1;
		return 0;
	case 'B':			 /* Obsolete */
		return 0;
	case 'b':			 /* Obsolete */
		return 0;
	case 'D':			 /* Show lines read */
		if (invert_this) {
			read_trace_level--;
		} else {
			read_trace_level++;
		}
		return 0;
	case 'd':			 /* debug flag */
		if (invert_this) {
			debug_level--;
		} else {
			debug_level++;
		}
		return 0;
	case 'e':			 /* environment override flag */
		if (invert_this) {
			env_wins = false;
		} else {
			env_wins = true;
		}
		return 0;
	case 'F':			 /* Read alternative .make.state */
		return 2;
	case 'f':			 /* Read alternative makefile(s) */
		return 1;
	case 'g':			 /* sccs get files not found */
		return 0;
	case 'i':			 /* ignore errors */
		if (invert_this) {
			ignore_errors = false;
		} else {
			ignore_errors = true;
		}
		return 0;
	case 'k':			 /* Keep making even after errors */
		if (invert_this) {
			continue_after_error = false;
		} else {
			continue_after_error = true;
		}
		return 0;
	case 'm':			 /* Obsolete */
		return 0;
#ifdef PARALLEL
	case 'M':			 /* Read alt make.machines file */
		return 4;
#endif PARALLEL
	case 'n':			 /* print, not exec commands */
		if (invert_this) {
			do_not_exec_rule = false;
		} else {
			do_not_exec_rule = true;
		}
		return 0;
	case 'N':			 /* Reverse -n */
		if (invert_this) {
			do_not_exec_rule = true;
		} else {
			do_not_exec_rule = false;
		}
		return 0;
	case 'P':			 /* print for selected targets */
		if (invert_this) {
			report_dependencies_only = false;
		} else {
			report_dependencies_only = true;
		}
		return 0;
	case 'p':			 /* print description */
		if (invert_this) {
			trace_status = false;
		} else {
			trace_status = true;
		}
		return 0;
	case 'q':			 /* question flag */
		if (invert_this) {
			quest = false;
		} else {
			quest = true;
		}
		return 0;
	case 'r':			 /* turn off internal rules */
		if (invert_this) {
			ignore_default_mk = false;
		} else {
			ignore_default_mk = true;
		}
		return 0;
#ifdef PARALLEL
	case 'R':			/* don't run in parallel */
		if (invert_this) {
			no_parallel = false;
		} else {
			no_parallel = true;
		}
		return 0;
#endif
	case 'S':			 /* Reverse -k */
		if (invert_this) {
			continue_after_error = true;
		} else {
			continue_after_error = false;
		}
		return 0;
	case 's':			 /* silent flag */
		if (invert_this) {
			silent = false;
		} else {
			silent = true;
		}
		return 0;
	case 't':			 /* touch flag */
		if (invert_this) {
			touch = false;
		} else {
			touch = true;
		}
		return 0;
	case 'X':			/* Filter stdout */
		if (invert_this) {
			filter_stderr = true;
		} else {
			filter_stderr = true;
		}
		return 0;
	default:
		fatal("Unknown option `-%c'", ch);
	}
	return 0;
}

/*
 *	set_target_host_arch()
 *
 *	Set the magic macros TARGET_ARCH, HOST_ARCH,
 *	TARGET_MACH & HOST_MACH
 *
 *	Parameters:
 *
 *	Global variables used:
 */
static void
set_target_host_arch()
{
	String_rec	result_string;
	char		buffer[STRING_BUFFER_LENGTH];
	FILE		*pipe;
	Name		name;
	register int	ch;

	INIT_STRING_FROM_STACK(result_string, buffer);
	append_char((int) hyphen_char, &result_string);
	if ((pipe = popen("/bin/arch", "r")) == NULL) {
		fatal("Execute of /bin/arch failed");
	}
	while ((ch = getc(pipe)) != EOF) {
		append_char(ch, &result_string);
	}
	if (pclose(pipe) != NULL) {
		fatal("Execute of /bin/arch failed");
	}

	name = GETNAME(result_string.buffer.start,
		       strlen(result_string.buffer.start)-1);
	(void) SETVAR(host_arch, name, false);
	(void) SETVAR(target_arch, name, false);
	INIT_STRING_FROM_STACK(result_string, buffer);
	append_char((int) hyphen_char, &result_string);
	if ((pipe = popen("/bin/mach", "r")) == NULL) {
		fatal("Execute of /bin/mach failed");
	}
	while ((ch = getc(pipe)) != EOF) {
		append_char(ch, &result_string);
	}
	if (pclose(pipe) != NULL) {
		fatal("Execute of /bin/mach failed");
	}

	name = GETNAME(result_string.buffer.start,
		       strlen(result_string.buffer.start)-1);
	(void) SETVAR(GETNAME("HOST_MACH", FIND_LENGTH), name, false);
	(void) SETVAR(GETNAME("TARGET_MACH", FIND_LENGTH), name, false);
}

/*
 *	setup_for_projectdir()
 *
 *	Read the PROJECTDIR variable, if defined, and set the sccs path
 *
 *	Parameters:
 *
 *	Global variables used:
 *		sccs_dir_path	Set to point to SCCS dir to use
 */
static void
setup_for_projectdir()
{
	char			path[MAXPATHLEN];

	/* Check if we should use PROJECTDIR when reading the SCCS dir */
	sccs_dir_path = getenv("PROJECTDIR");
	if ((sccs_dir_path != NULL) &&
	    (sccs_dir_path[0] != (int) slash_char)) {
		struct passwd *pwent;

		if ((pwent = getpwnam(sccs_dir_path)) == NULL) {
			fatal("Bogus PROJECTDIR '%s'", sccs_dir_path);
		}
		(void) sprintf(path, "%s/src", pwent->pw_dir);
		if (access(path, F_OK) == 0) {
			sccs_dir_path = path;
		} else {
			(void) sprintf(path, "%s/source", pwent->pw_dir);
			if (access(path, F_OK) == 0) {
				sccs_dir_path = path;
			} else {
				sccs_dir_path = NULL;
			}
		}
	}
}

/*
 *	read_files_and_state(argc, argv)
 *
 *	Read the makefiles we care about and the environment
 *	Also read the = style command line options
 *
 *	Parameters:
 *		argc		You know what this is
 *		argv		You know what this is
 *
 *	Static variables used:
 *		env_wins	make -e, determines if env vars are RO
 *		ignore_default_mk make -r, determines if default.mk is read
 *		not_auto_depen	dwight
 *
 *	Global variables used:
 *		default_target_to_build	Set to first proper target from file
 *		do_not_exec_rule Set to false when makfile is made
 *		dot		The Name ".", used to read current dir
 *		empty_name	The Name "", use as macro value
 *		keep_state	Set if KEEP_STATE is in environment
 *		make_state	The Name ".make.state", used to read file
 *		makefile_type	Set to type of file being read
 *		makeflags	The Name "MAKEFLAGS", used to set macro value
 *		not_auto	dwight
 *		nse		Set if NSE_ENV is in the environment
 *		read_trace_level Checked to se if the reader should trace
 *		report_dependencies If -P is on we do not read .make.state
 *		trace_reader	Set if reader should trace
 *		virtual_root	The Name "VIRTUAL_ROOT", used to check value
 */
static void
read_files_and_state(argc, argv)
	int			argc;
	char			**argv;
{
	register int		i;
	register char		*cp;
	register Name		name;
	register Boolean	makefile_read = false;
	Boolean			save_do_not_exec_rule;
	register Property	macro;
	Name			keep_state_name;
	Name			Makefile;
	Name			makefile_name;
	Boolean			temp;
	String_rec		makeflags_string;
	char			buffer[100];

	keep_state_name = GETNAME("KEEP_STATE", FIND_LENGTH);
	Makefile = GETNAME("Makefile", FIND_LENGTH);
	makefile_name = GETNAME("makefile", FIND_LENGTH);

/*
 *	Set flag if NSE is active
 */
	if (getenv("NSE_ENV") != NULL) {
		nse = true;
	}

/*
 *	initialize global dependency entry for .NOT_AUTO
 */
	not_auto_depen->next = NULL;
	not_auto_depen->name = not_auto;
	not_auto_depen->automatic = not_auto_depen->stale = false;

/*
 *	Read internal definitions and rules.
 */
	if (read_trace_level > 1) {
		trace_reader = true;
	}
	if (!ignore_default_mk) {
		Name		default_makefile;

		default_makefile = GETNAME("default.mk", FIND_LENGTH);
		default_makefile->stat.is_file = true;

		(void) read_makefile(default_makefile,
				     true,
				     false,
				     false);
	}
	trace_reader = false;

/*
 *	Read environment args.  Let file args which follow override unless
 *	 -e option seen. If -e option is not mentioned.
 */
	read_environment(env_wins);
	if (getvar(virtual_root)->hash.length == 0) {
		maybe_append_prop(virtual_root, macro_prop)
		  ->body.macro.exported = true;
		(void) SETVAR(virtual_root,
			     GETNAME("/", FIND_LENGTH),
			      false);
	}

/*
 *	Read state file
 */
	if (!report_dependencies_only) {
		makefile_type = reading_statefile;
		if (read_trace_level > 1) {
			trace_reader = true;
		}
		(void) read_simple_file(make_state,
					false,
					false,
					false,
					false,
					false,
					true);
		trace_reader = false;
	}
	default_target_to_build = NULL;

/*
 *	Set MFLAGS and MAKEFLAGS
 */
	INIT_STRING_FROM_STACK(makeflags_string, buffer);
	append_char((int) hyphen_char, &makeflags_string);

	switch (read_trace_level) {
	case 2:
		append_char('D', &makeflags_string);
	case 1:
		append_char('D', &makeflags_string);
	}
	switch (debug_level) {
	case 2:
		append_char('d', &makeflags_string);
	case 1:
		append_char('d', &makeflags_string);
	}
	if (env_wins) {
		append_char('e', &makeflags_string);
	}
	if (ignore_errors) {
		append_char('i', &makeflags_string);
	}
	if (continue_after_error) {
		append_char('k', &makeflags_string);
	}
	if (do_not_exec_rule) {
		append_char('n', &makeflags_string);
	}
	if (report_dependencies_only) {
		append_char('P', &makeflags_string);
	}
	if (trace_status) {
		append_char('p', &makeflags_string);
	}
	if (quest) {
		append_char('q', &makeflags_string);
	}
#ifdef PARALLEL
	if (no_parallel) {
		append_char('P', &makeflags_string);
	}
#endif
	if (silent) {
		append_char('s', &makeflags_string);
	}
	if (touch) {
		append_char('t', &makeflags_string);
	}
	if (filter_stderr) {
		append_char('X', &makeflags_string);
	}

/*
 *	Make sure MAKEFLAGS is exported
 */
	maybe_append_prop(makeflags, macro_prop)->
	  body.macro.exported = true;
	if (makeflags_string.buffer.start[1] != (int) nul_char) {
		(void) SETVAR(GETNAME("MFLAGS", FIND_LENGTH),
			     GETNAME(makeflags_string.buffer.start,
				     FIND_LENGTH),
			      false);
	}
	macro = maybe_append_prop(makeflags, macro_prop);
	temp = macro->body.macro.read_only;
	macro->body.macro.read_only = false;
	(void) SETVAR(makeflags,
		     GETNAME(makeflags_string.buffer.start + 1, FIND_LENGTH),
		      false);
	macro->body.macro.read_only = temp;
	if (makeflags_string.free_after_use) {
		retmem(makeflags_string.buffer.start);
	}
	makeflags_string.buffer.start = NULL;

/*
 *	Read command line "=" type args and make them readonly.
 */
	makefile_type = reading_nothing;
	for (i = 1; i < argc; ++i) {
		if ((argv[i] != NULL) &&
		    (argv[i][0] != (int) hyphen_char) &&
		    ((cp = strchr(argv[i], (int) equal_char)) != NULL) &&
		    ((argv[i-1] == NULL) ||
		     (argv[i-1][0] != (int) hyphen_char))) {
			while (isspace(*(cp-1))) {
				cp--;
			}
			name = GETNAME(argv[i], cp-argv[i]);
			maybe_append_prop(name, macro_prop)->
			  body.macro.exported = true;
			while (*cp != (int) equal_char) {
				cp++;
			}
			cp++;
			while (isspace(*cp) && (*cp != (int) nul_char)) {
				cp++;
			}
			if (*cp == (int) nul_char) {
				SETVAR(name, (Name) NULL, false)->
				  body.macro.read_only = true;
			} else {
				SETVAR(name, GETNAME(cp, FIND_LENGTH), false)->
				  body.macro.read_only = true;
			}
			argv[i] = NULL;
		}
	}

/*
 *	Read command line "-f" arguments and ignore "-F" and "-M" arguments.
 */
	save_do_not_exec_rule = do_not_exec_rule;
	do_not_exec_rule = false;
	if (read_trace_level > 0) {
		trace_reader = true;
	}
	for (i = 1; i < argc; i++) {
		if (argv[i] &&
		    (argv[i][0] == (int) hyphen_char) &&
		    (argv[i][1] == 'f') &&
		    (argv[i][2] == (int) nul_char)) {
			argv[i] = NULL;		/* Remove -f */
			if (i >= argc - 1) {
				fatal("No filename argument after -f flag");
			}
			(void) read_makefile(GETNAME(argv[++i], FIND_LENGTH),
					     true,
					     true,
					     true);
			argv[i] = NULL;		/* Remove filename */
			makefile_read = true;
		} else if (argv[i] &&
			   (argv[i][0] == (int) hyphen_char) &&
			   (argv[i][1] == 'F') &&
			   (argv[i][2] == (int) nul_char)) {
			argv[i++] = NULL;
			argv[i] = NULL;
		} else if (argv[i] &&
			   (argv[i][0] == (int) hyphen_char) &&
			   (argv[i][1] == 'M') &&
			   (argv[i][2] == (int) nul_char)) {
			argv[i++] = NULL;
			argv[i] = NULL;
		}
	}

/*
 *	If no command line "-f" args then look for "makefile", and then for
 *	"Makefile" if "makefile" isn't found
 */
	if (!makefile_read) {
		(void) read_dir(dot,
				(char *) NULL,
				(Property) NULL,
				(char *) NULL);
		if (makefile_name->stat.is_file) {
			if (Makefile->stat.is_file) {
				warning("Both `makefile' and `Makefile' exists");
			}
			makefile_read = read_makefile(makefile_name,
						      false,
						      false,
						      true);
		}
		if (!makefile_read &&
		    Makefile->stat.is_file) {
			makefile_read = read_makefile(Makefile,
						      false,
						      false,
						      true);
		}
	}
	trace_reader = false;
	do_not_exec_rule = save_do_not_exec_rule;

/*
 *	Make sure KEEP_STATE is in the environment if KEEP_STATE is on
 */
	macro = get_prop(keep_state_name->prop, macro_prop);
	if ((macro != NULL) &&
	    macro->body.macro.exported) {
		keep_state = true;
	}
	if (keep_state) {
		if (macro == NULL) {
			macro = maybe_append_prop(keep_state_name,
						  macro_prop);
		}
		macro->body.macro.exported = true;
		(void) SETVAR(keep_state_name,
			      empty_name,
			      false);
	}
}

/*
 *	read_environment(read_only)
 *
 *	This routine reads the process environment when make starts and enters
 *	it as make macros. The environment variable SHELL is ignored.
 *
 *	Parameters:
 *		read_only	Should we make env vars read only?
 *
 *	Global variables used:
 *		report_pwd	Set if this make was started by other make
 */
static void
read_environment(read_only)
	Boolean			read_only;
{
	register char		**environment;
	register char		*value;
	register char		*name;
	register Name		macro;
	Property		val;

	reading_environment = true;
	environment = environ;
	for (; *environment; environment++) {
		name = *environment;
		value = strchr(name, (int) equal_char);
		if (IS_EQUALN(name, "SHELL=", 6)) {
			continue;
		}
		if (IS_EQUALN(name, "MAKEFLAGS=", 6)) {
			report_pwd = true;
		}
		macro = GETNAME(name, value - name);
		maybe_append_prop(macro, macro_prop)->body.macro.exported =
		  true;
		if ((value == NULL) || ((value + 1)[0] == (int) nul_char)) {
			val = SETVAR(macro, (Name) NULL, false);
		} else {
			val = SETVAR(macro,
				     GETNAME(value + 1, FIND_LENGTH),
				     false);
		}
		val->body.macro.read_only = read_only;
	}
	reading_environment = false;
}

/*
 *	read_makefile(makefile, complain, must_exist, report_file)
 *
 *	Read one makefile and check the result
 *
 *	Return value:
 *				false is the read failed
 *
 *	Parameters:
 *		makefile	The file to read
 *		complain	Passed thru to read_simple_file()
 *		must_exist	Passed thru to read_simple_file()
 *		report_file	Passed thru to read_simple_file()
 *
 *	Global variables used:
 *		makefile_type	Set to indicate we are reading main file
 *		recursion_level	Initialized
 */
static Boolean
read_makefile(makefile, complain, must_exist, report_file)
	register Name		makefile;
	Boolean			complain;
	Boolean			must_exist;
	Boolean			report_file;
{
	makefile_type = reading_makefile;
	recursion_level = 0;
	return read_simple_file(makefile,
				true,
				true,
				complain,
				must_exist,
				report_file,
				false);
}

/*
 *	make_targets(argc, argv, parallel_flag)
 *
 *	Call doname on the specified targets
 *
 *	Parameters:
 *		argc		You know what this is
 *		argv		You know what this is
 *		parallel_flag	True if building in parallel
 *
 *	Global variables used:
 *		build_failed_seen Used to generated message after failed -k
 *		commands_done	Used to generate message "Up to date"
 *		default_target_to_build	First proper target in makefile
 *		init		The Name ".INIT", use to run command
 *		parallel	Global parallel building flag
 *		quest		make -q, suppresses messages
 *		recursion_level	Initialized, used for tracing
 *		report_dependencies make -P, regroves whole process
 */
static void
make_targets(argc, argv, parallel_flag)
	int			argc;
	char			**argv;
	Boolean			parallel_flag;
{
	int			i;
	char			*cp;
	Doname			result;
	register Boolean	target_to_make_found = false;

	(void) doname(init, true, true);
	recursion_level = 1;
	parallel = parallel_flag;
/*
 *	make remaining args
 */
#ifdef PARALLEL
	if (!report_dependencies_only && parallel) {
		/*
		 * If building targets in parallel, start all of the
		 * remaining args to build in parallel.
		 */
		for (i = 1; i < argc; i++) {
			if ((cp = argv[i]) != NULL) {
				commands_done = false;
				if ((cp[0] == (int) period_char) &&
				    (cp[1] == (int) slash_char)) {
					cp += 2;
				}
				default_target_to_build = GETNAME(cp,
								  FIND_LENGTH);
				result = doname_check(default_target_to_build,
						      true,
						      false,
						      false);
				if (!commands_done &&
				    (result == build_ok) &&
				    !quest &&
				    (exists(default_target_to_build) >
				     (int) file_doesnt_exist)) {
					(void) printf("`%s' is up to date.\n",
						      default_target_to_build->
						      string);
				}
			}
		}
		/* Now wait for all of the targets to finish running */
		finish_running();
	}
#endif PARALLEL
	for (i = 1; i < argc; i++) {
		if ((cp = argv[i]) != NULL) {
			target_to_make_found = true;
			if ((cp[0] == (int) period_char) &&
			    (cp[1] == (int) slash_char)) {
				cp += 2;
			}
			default_target_to_build = GETNAME(cp, FIND_LENGTH);
			commands_done = false;
			if (parallel) {
				result = default_target_to_build->state;
			} else {
				result = doname_check(default_target_to_build,
						      true,
						      false,
						      false);
			}
			if (result != build_failed) {
				report_recursion(default_target_to_build);
			}
			if (build_failed_seen) {
				warning("Target `%s' not remade because of errors",
					default_target_to_build->string);
			}
			build_failed_seen = false;
			if (report_dependencies_only) {
				print_dependencies(default_target_to_build,
						   get_prop(default_target_to_build->prop,
							    line_prop));
			}
			default_target_to_build->stat.time =
			  (int) file_no_time;
			if (default_target_to_build->colon_splits > 0) {
				default_target_to_build->state =
				  build_dont_know;
			}
			if (!parallel &&
			    !commands_done &&
			    (result == build_ok) &&
			    !quest &&
			    !report_dependencies_only &&
			    (exists(default_target_to_build) >
			     (int) file_doesnt_exist)) {
				(void) printf("`%s' is up to date.\n",
					      default_target_to_build->string);
			}
		}
	}

/*
 *	If no file arguments have been encountered,
 *	make the first name encountered that doesnt start with a dot
 */
	if (!target_to_make_found) {
		if (default_target_to_build == NULL) {
			fatal("No arguments to build");
		}
		commands_done = false;
#ifdef PARALLEL
		result = doname_parallel(default_target_to_build, true, false);
#else
		result = doname_check(default_target_to_build, true,
				      false, false);
#endif PARALLEL
		report_recursion(default_target_to_build);
		if (build_failed_seen) {
			warning("Target `%s' not remade because of errors",
				default_target_to_build->string);
		}
		build_failed_seen = false;
		if (report_dependencies_only) {
			print_dependencies(default_target_to_build,
					   get_prop(default_target_to_build->
						    prop,
						    line_prop));
		}
		default_target_to_build->stat.time = (int) file_no_time;
		if (default_target_to_build->colon_splits > 0) {
			default_target_to_build->state = build_dont_know;
		}
		if (!commands_done &&
		    (result == build_ok) &&
		    !quest &&
		    !report_dependencies_only &&
		    (exists(default_target_to_build) >
		     (int) file_doesnt_exist)) {
			(void) printf("`%s' is up to date.\n",
				      default_target_to_build->string);
		}
	}
}

/*
 *	print_dependencies(target, line)
 *
 *	Print all the dependencies of a target. First print all the Makefiles.
 *	Then print all the dependencies. Finally, print all the .INIT
 *	dependencies.
 *
 *	Parameters:
 *		target		The target we print dependencies for
 *		line		We get the dependency list from here
 *
 *	Global variables used:
 *		done		The Name ".DONE"
 *		init		The Name ".INIT"
 *		makefiles_used	List of all makefiles read
 */
static void
print_dependencies(target, line)
	register Name           target;
	register Property	line;
{
	register Dependency	dependencies;

        for (dependencies = makefiles_used; dependencies != NULL;
    	     dependencies = dependencies->next) {
    		(void) printf("%s ", dependencies->name->string);
	}
	(void)  printf("\n");
	print_deps(target, line, true);
	print_more_deps(target, init);
	print_more_deps(target, done);
}

/*
 *	print_more_deps(target, name)
 *
 *	Print some special dependencies.
 *	These are the dependencies for the .INIT and .DONE targets.
 *
 *	Parameters:
 *		target		Target built during make run
 *		name		Special target to print dependencies for
 *
 *	Global variables used:
 */
static void
print_more_deps(target, name)
	Name		target;
	Name		name;
{
	Property	line;
	register Dependency	dependencies;

	line = get_prop(name->prop, line_prop);
	if (line != NULL && line->body.line.dependencies != NULL) {
		(void) printf("%s:\t", target->string);
		for (dependencies = line->body.line.dependencies;
		     dependencies != NULL;
		     dependencies = dependencies->next) {
			(void) printf("%s ", dependencies->name->string);
		}
		(void) printf("\n");
		for (dependencies = line->body.line.dependencies;
		     dependencies != NULL;
		     dependencies = dependencies->next) {
	                 print_deps(dependencies->name,
				    get_prop(dependencies->name->prop,
					     line_prop),
				    true);
		}
	}
}

/*
 *	print_deps(target, line, go_recursive)
 *
 *	Print a regular dependency list.
 *
 *	Parameters:
 *		target		target to print dependencies for
 *		line		We get the dependency list from here
 *		go_recursive	Should we show all dependencies recursively?
 *
 *	Global variables used:
 *		recursive_name	The Name ".RECURSIVE", printed
 */
static void
print_deps(target, line, go_recursive)
	register Name		target;
	register Property	line;
	register Boolean	go_recursive;
{
	register Dependency	dependencies;
	register Property	recursive;

	if (target->dependency_printed) {
		return;
	}
	target->dependency_printed = true;
	if (target->has_recursive_dependency) {
		for (recursive = get_prop(target->prop, recursive_prop);
		     recursive != NULL;
		     recursive = get_prop(recursive->next, recursive_prop)) {
			(void) printf("%s:\t%s %s %s",
				      target->string,
				      recursive_name->string,
				      recursive->body.
				      recursive.directory->string,
				      recursive->body.
				      recursive.target->string);
			for (dependencies =
			     recursive->body.recursive.makefiles;
			     dependencies != NULL;
			     dependencies = dependencies->next) {
				(void) printf(" %s",
					      dependencies->name->string);
			}
			(void) printf("\n");
		}
	}
	/* only print entries that are actually derived and are not leaf
	 * files and are not the result of sccs get.
	 */
	if (should_print_dep(line)) {
		(void) printf("%s:\t", target->string);
		for (dependencies = line->body.line.dependencies;
		     dependencies != NULL;
		     dependencies = dependencies->next) {
			(void) printf("%s ", dependencies->name->string);
		}
		(void) printf("\n");
		if (go_recursive) {
			for (dependencies = line->body.line.dependencies;
			     dependencies != NULL;
			     dependencies = dependencies->next) {
				print_deps(dependencies->name,
					   get_prop(dependencies->name->prop,
						    line_prop),
					   go_recursive);
			}
		}
	}
}

/*
 *	should_print_dep(line)
 *
 *	Test if we should print the dependencies of this target.
 *	The line must exist and either have children dependencies
 *	or have a command that is not an SCCS command.
 *
 *	Return value:
 *				true if the dependencies should be printed
 *
 *	Parameters:
 *		line		We get the dependency list from here
 *
 *	Global variables used:
 */
static Boolean
should_print_dep(line)
	Property	line;
{
	if (line == NULL) {
		return false;
	}
	if (line->body.line.dependencies != NULL) {
		return true;
	}
	if (!line->body.line.sccs_command &&
	    (line->body.line.command_template != NULL)) {
		return true;
	}
	return false;
}

/*
 *	report_recursion(target)
 *
 *	If this is a recursive make and the parent make has KEEP_STATE on
 *	this routine reports the dependency to the parent make
 *
 *	Parameters:
 *		target		Target to report
 *
 *	Global variables used:
 *		makefiles_used		List of makefiles read
 *		recursive_name		The Name ".RECURSIVE", printed
 *		report_dependency	dwight
 */
static void
report_recursion(target)
	register Name		target;
{
	register Dependency	dp;
	register FILE		*report_file = get_report_file();

	if ((report_file == NULL) || (report_file == (FILE*)-1)) {
		return;
	}
	(void) fprintf(report_file,
		      "%s: %s ",
		      get_target_being_reported_for(),
		      recursive_name->string);
	report_dependency(get_current_path());
	report_dependency(target->string);
	for (dp = makefiles_used; dp != NULL; dp = dp->next) {
		report_dependency(dp->name->string);
	}
	(void) fprintf(report_file, "\n");
}

