-- MakeConfigImpl.Mesa, last edit  February 4, 1983 1:14 pm
-- Pilot 6.0/ Mesa 7.0
--
-- options
-- /a	make all
-- /v	construct dependency graph
-- /z	turn on debugging flag

DIRECTORY
  BcdDefs: TYPE USING [BCD],
  CWF: TYPE USING [FWF0, FWF1, SetWriteProcedure, SWF1, WF0, WF1, WF2, WFCR],
  Directory: TYPE USING [UpdateDates],
  File: TYPE USING [Capability, read],
  FileStream: TYPE USING [Create],
  IO: TYPE USING[Handle, PutChar, UserAbort],
  LongString: TYPE USING [AppendString, EquivalentString, StringBoundsFault],
  MDSubr: TYPE USING [AddToDepends, AddToLook, AnalyzeLocalFiles, CheckNames, 
  	ConvertToLook, entNil, Entry, EntryRecord, EntrySeq, EntrySeqRecord, 
	FreeEntrySeq, FreeLookSeq, GetAnEntry, GetBinderRule, GetCompilerRule, 
	InsertOtherSym, Look, LookLook, lookNil, LookSeq, LookSeqRecord, ModInfo, 
	ModInfoRecord, ParseObject, PrintEntries, ReadInLocalDirectoryAll, StopScanner, 
	StringSeq, StringSeqRecord, StripFirstWord, ThrowAwayLeaves, versionNil, 
	versionUnknown, WalkTheGraph],
  Rope: TYPE USING[ROPE, Text],
  RopeInline: TYPE USING[InlineFlatten],
  Space: TYPE USING [Create, Delete, Handle, LongPointer, Map, Unmap, virtualMemory],
  Stream: TYPE USING [Delete, Handle, PutChar],
  String: TYPE USING [CompareStrings],
  Subr: TYPE USING [AbortMyself, AllocateString, debugflg, EndsIn, errorflg, FreeString, 
  	HugeZone, LongZone, MakeTTYProcs, 
  	NewStream, PackedTime, Prefix, PrintGreeting, 
		strcpy, SubrInit, SubrStop, TTYProcs, Write],
  Time: TYPE USING [Current],
  UECP: TYPE USING[Argv, Parse],
  UserExec: TYPE USING[AcquireResource, AskUser, CommandProc, GetStreams, 
  	RegisterCommand, ReleaseResource];

MakeConfigImpl: PROGRAM
IMPORTS CWF, Directory, FileStream, IO, LongString,
	MDSubr, RopeInline, Space, Stream, String, Subr, Time, UECP, UserExec = {

-- MDS USAGE !!!
longzone: UNCOUNTED ZONE;
compstr, bindstr: LONG STRING;
recur: CARDINAL;
presenttime: Subr.PackedTime;
bcdspace: Space.Handle;	-- this space is 1 page big and used to map in bcd headers
stdout: IO.Handle;
-- endof MDS USAGE

-- local variables
maxlooksize: CARDINAL = 1600;
maxentrysize: CARDINAL = 800;
maxotherstrings: CARDINAL = 50;

Main: UserExec.CommandProc = TRUSTED {
	ENABLE UNWIND => [] ← UserExec.ReleaseResource[$MakeConfig];
	h: Subr.TTYProcs;
	in, out: IO.Handle;
	[in, out] ← UserExec.GetStreams[exec];
	[] ← UserExec.AcquireResource[$MakeConfig, "MakeConfig", exec];
	h ← Subr.MakeTTYProcs[in, out, exec, MyConfirm];
	MakeConfigUsingProcs[h, event.commandLine];
	[] ← UserExec.ReleaseResource[$MakeConfig];
	};

-- this is the procedure called by the Simple Executive

MakeConfigUsingProcs: PROC[h: Subr.TTYProcs, commandLine: Rope.ROPE] = {
entryseq: MDSubr.EntrySeq ← NIL;
lookseq: MDSubr.LookSeq ← NIL;
token: STRING ← [100];
forcemake, verbose: BOOL ← FALSE;
starttime: Subr.PackedTime;
hugezone: UNCOUNTED ZONE;
argv: UECP.Argv ← UECP.Parse[commandLine];
flat: Rope.Text;

	ThrowAwayThisFile: PROC[name: STRING] RETURNS[BOOL] = {
	RETURN[NOT (Subr.EndsIn[name, ".bcd"L] OR Subr.EndsIn[name, ".mesa"L]
		OR Subr.EndsIn[name, ".config"L])];
	};

	Cleanup: PROC = {
	MDSubr.FreeLookSeq[@lookseq];
	MDSubr.FreeEntrySeq[@entryseq];
	MDSubr.StopScanner[];		-- frees memory in scanner
	CloseLineCm[];	-- frees storage for compstr and bindstr
	Subr.SubrStop[];
	};

{
	ENABLE {
		UNWIND => Cleanup[];
		Subr.AbortMyself => {
			CWF.WF0["MakeConfig Aborted.\n"L];
			GOTO out;
			};
		};

compstr ← bindstr ← NIL;
recur ← 0;
stdout ← h.out;
[] ← CWF.SetWriteProcedure[MyPutChar];
presenttime ← starttime ← Time.Current[];
hugezone ← Subr.HugeZone[];
Subr.SubrInit[256];	-- the number of pages to be allocated in the long zone
longzone ← Subr.LongZone[];
Subr.errorflg ← Subr.debugflg ← FALSE;
Subr.PrintGreeting["MakeConfig"L];
lookseq ← hugezone.NEW[MDSubr.LookSeqRecord[maxlooksize]];
lookseq.lookzone ← hugezone;
MDSubr.ReadInLocalDirectoryAll[lookseq,ThrowAwayThisFile];
IF h.in.UserAbort[] THEN Subr.AbortMyself;
entryseq ← hugezone.NEW[MDSubr.EntrySeqRecord[maxentrysize]];
entryseq.entryzone ← hugezone;
bcdspace ← Space.Create[size: 1, parent: Space.virtualMemory];
FOR p: CARDINAL IN [1 .. argv.argc) DO
	flat ← RopeInline.InlineFlatten[argv[p]];
	Subr.strcpy[token, LOOPHOLE[flat]];
	IF token[0] = '- OR token[0] = '/ THEN
	SELECT token[1] FROM
	'a, 'A => forcemake ← TRUE;
	'v, 'V => verbose ← TRUE;
	'z, 'Z => Subr.debugflg ← TRUE;
	ENDCASE => {
		CWF.WF1["Unknown flag '%s'\n"L,token];
		Subr.errorflg ← TRUE;
		}
	ELSE MainLoop[token, verbose, forcemake, entryseq, lookseq, h];
	ENDLOOP;
IF compstr ~= NIL OR bindstr ~= NIL THEN TellAboutCommands[h];
starttime ← Time.Current[] - starttime;
CWF.WF1["\nTotal elapsed time for MakeConfig %lr."L,@starttime];
IF Subr.errorflg THEN CWF.WF0["\tErrors logged."L];
CWF.WFCR[];
EXITS
out => NULL;
};
Space.Delete[bcdspace];
Cleanup[];
};

TellAboutCommands: PROC[h: Subr.TTYProcs] = {
linecm: Stream.Handle;
	
	WFProc: PROC[ch: CHAR] = {
	Stream.PutChar[linecm, ch];
	h.out.PutChar[ch];
	};

linecm ← Subr.NewStream["Line.cm"L, Subr.Write];
CWF.WF0["Execute: "L];
IF compstr ~= NIL THEN CWF.FWF1[WFProc, "Compiler.~ %s;"L, compstr];
IF bindstr ~= NIL THEN CWF.FWF1[WFProc, "Binder.~ %s;"L, bindstr];
CWF.FWF0[WFProc, "\n"L];
Stream.Delete[linecm];
CWF.WF0["(Also written on Line.cm)\n"L];
};

MainLoop: PROC[modulename: STRING, verbose, forcemake: BOOL, 
	entryseq: MDSubr.EntrySeq, lookseq: MDSubr.LookSeq, h: Subr.TTYProcs] = {
firstinx: CARDINAL ← 0;
look: MDSubr.Look;
stemp: STRING ← [100];
trymesa: BOOL ← TRUE;
stringseq: MDSubr.StringSeq;

stringseq ← longzone.NEW[MDSubr.StringSeqRecord[maxotherstrings]];
CWF.WF1["\nMaking '%s'\n"L, modulename];
IF Subr.EndsIn[modulename,".mesa"L] THEN
	modulename.length ← modulename.length - 5
ELSE IF Subr.EndsIn[modulename,".config"L] THEN {
	modulename.length ← modulename.length - 7;
	trymesa ← FALSE;
	}
ELSE IF Subr.EndsIn[modulename,".bcd"L] THEN
	modulename.length ← modulename.length - 4;
MDSubr.InsertOtherSym["MESACOMP"L, "Compiler.~"L, stringseq];
MDSubr.InsertOtherSym["MESABIND"L, "Binder.~"L, stringseq];
FOR i: CARDINAL IN [0.. lookseq.size) DO
	lookseq[i].need ← TRUE;
	ENDLOOP;
MDSubr.CheckNames[entryseq, lookseq];
firstinx ← DependTree[modulename, entryseq, lookseq, stringseq, trymesa];
-- PrintEntries[entryseq, StreamDefs.GetDefaultDisplayStream[],
	-- @stringvar, nstringvars];
-- CWF.WF1["once for %s\n"L, modulename];
IF h.in.UserAbort[] THEN Subr.AbortMyself;
MDSubr.CheckNames[entryseq, lookseq];
MDSubr.ConvertToLook[entryseq,lookseq];
MDSubr.CheckNames[entryseq, lookseq];
FOR i: CARDINAL IN [0.. lookseq.size) DO
	lookseq[i].need ← FALSE;
	lookseq[i].besttime ← MDSubr.versionNil; -- forces recompute
	ENDLOOP;
[] ← Decide[forreal: FALSE, parinx: firstinx, entryseq: entryseq,
	lookseq: lookseq, justmark: TRUE, forcemake: FALSE];
-- PrintEntries[entryseq, StreamDefs.GetDefaultDisplayStream[],
	-- @stringvar, nstringvars];
-- PrintLook[lookseq.size];
-- CWF.WF2["look %d rule %s\n", @entryseq[firstinx].lookinx, entryseq[firstinx].rule];
look ← IF entryseq[firstinx].lookinx~= MDSubr.lookNil THEN  @lookseq[entryseq[firstinx].lookinx] 
	ELSE NIL;
IF (look = NIL OR NOT look.presentonlocaldisk) 
AND entryseq[firstinx].rule = NIL THEN { 
	CWF.WF1["Error: %s not found.\n"L, modulename];
	Subr.errorflg ← TRUE;
	}
ELSE {
	MDSubr.ThrowAwayLeaves[firstinx, entryseq, lookseq, FALSE];
	MDSubr.AnalyzeLocalFiles[oktosort: FALSE, lookseq: lookseq,
		getcreatedate: TRUE];
	IF h.in.UserAbort[] THEN Subr.AbortMyself;
	[] ← Decide[TRUE,firstinx,entryseq,lookseq,FALSE, 
		forcemake];
	IF h.in.UserAbort[] THEN Subr.AbortMyself;
	PrintMade[firstinx, entryseq, lookseq, forcemake];
	IF verbose THEN {
		sh: Stream.Handle;
		CWF.SWF1[stemp, "%s.make$"L, modulename];
		SortEntries[entryseq];
		CWF.WF1["\nDependency Tree is on file '%s'\n"L, stemp];
		sh ← Subr.NewStream[stemp, Subr.Write];
		MDSubr.PrintEntries[entryseq, sh, stringseq, h];
		Stream.Delete[sh];
		};
	};
FOR i: CARDINAL IN [0 .. stringseq.size) DO
	Subr.FreeString[stringseq[i].str];
	Subr.FreeString[stringseq[i].val];
	ENDLOOP;
longzone.FREE[@stringseq];
};

DependTree: PROC[modulename: STRING, entryseq: MDSubr.EntrySeq, 
	lookseq: MDSubr.LookSeq, stringseq: MDSubr.StringSeq, 
	trymesa: BOOL] RETURNS[parent: CARDINAL]={
top, bottom, i, source: CARDINAL;
sfn: STRING ← [100];
stemp: STRING ← [50];
savenstringvars: CARDINAL;
look, lookchild: MDSubr.Look;
lookchildinx: CARDINAL;
-- tlook: Look;

CWF.SWF1[sfn,"%s.bcd"L,modulename];
[parent,] ← MDSubr.GetAnEntry[sfn,entryseq];
IF entryseq[parent].rule ~= NIL THEN RETURN[parent];	-- not new
[look, entryseq[parent].lookinx] ← MDSubr.LookLook[sfn, lookseq];
-- entryseq[parent].lookinx is lookNil if not found
-- tlook ← @lookseq[entryseq[parent].lookinx];
-- CWF.WF2["entry %s look %s\n"L, entryseq[parent].name, tlook.name];
-- CWF.WF2["Entry %s look %s\n"L, entryseq[parent].name, 
	-- IF look ~= NIL THEN look.name ELSE "nil"L];
CWF.SWF1[sfn,IF trymesa THEN "%s.mesa"L ELSE "%s.config"L,modulename];
-- CWF.WF1["Try %s.\n"L,sfn];
[lookchild, lookchildinx] ← MDSubr.LookLook[sfn, lookseq];
-- this special test is needed if there is a .mesa in the look array
-- but it is not on the disk and there is a .config also
IF lookchild = NIL
OR ((NOT lookchild.presentonlocaldisk)) THEN {
	CWF.SWF1[sfn,IF NOT trymesa THEN "%s.mesa"L ELSE "%s.config"L,
		modulename];
	-- CWF.WF1["Try %s.\n"L,sfn];
	-- CWF.WF0["."L];
	[lookchild, lookchildinx] ← MDSubr.LookLook[sfn,lookseq];
	};
IF lookchild = NIL THEN {
	[source,] ← MDSubr.GetAnEntry[sfn,entryseq];
	MDSubr.AddToDepends[parent,source,entryseq];
	entryseq[source].lookinx ← lookseq.size;
	lookchild ← MDSubr.AddToLook[sfn, lookseq];
	CWF.SWF1[sfn,IF trymesa THEN "%s.mesa"L ELSE "%s.config"L,modulename];
	[source,] ← MDSubr.GetAnEntry[sfn,entryseq];
	MDSubr.AddToDepends[parent,source,entryseq];
	entryseq[source].lookinx ← lookseq.size;
	lookchild ← MDSubr.AddToLook[sfn, lookseq];
	RETURN[parent];
	};
-- CWF.WF1["Found %s.\n"L, lookchild.name];
-- PrintLook[lookseq, patharray];
savenstringvars ← stringseq.size;
bottom ← entryseq.size;
IF NOT ConstructDepends[lookchild, parent, entryseq, stringseq] THEN RETURN[parent];
top ← entryseq.size;
-- CWF.WF0["Added:\n"L];
-- FOR i IN [bottom .. top) DO
	-- CWF.WF1["   %s\n"L,entryseq[i].name];
	-- ENDLOOP;
[source,] ← MDSubr.GetAnEntry[sfn,entryseq];
entryseq[source].lookinx ← lookchildinx;
MDSubr.AddToDepends[parent,source,entryseq];
IF Subr.EndsIn[sfn,".config"L] THEN
	entryseq[parent].rule ← MDSubr.GetBinderRule[stringseq, sfn, entryseq.entryzone]
ELSE	entryseq[parent].rule ← MDSubr.GetCompilerRule[stringseq, sfn, entryseq.entryzone];
FOR i IN [bottom .. top) DO
	Subr.strcpy[sto: stemp,sfrom: entryseq[i].name];
	stemp.length ← stemp.length - 4;
	[] ← DependTree[stemp, entryseq, lookseq, stringseq, trymesa];
	ENDLOOP;
-- throw away extensions
-- ResetStringVar[stringseq, savenstringvars];
RETURN[parent];
};

ConstructDepends: PROC[lookchild: MDSubr.Look, parent: CARDINAL,
	entryseq: MDSubr.EntrySeq, stringseq: MDSubr.StringSeq] RETURNS[proceed: BOOL]= {
sfn: STRING ← [50];
sh: Stream.Handle;
pmod: MDSubr.ModInfo;
cap: File.Capability;

Subr.strcpy[sfn, lookchild.name];
IF NOT lookchild.presentonlocaldisk THEN RETURN[FALSE];
cap ← Directory.UpdateDates[lookchild.cap, File.read];
sh ← FileStream.Create[cap];
-- allocate dynamically to avoid large frame, which must be resident
pmod ← longzone.NEW[MDSubr.ModInfoRecord];
MDSubr.ParseObject[sh, parent, entryseq, pmod, NIL, stringseq, sfn];
longzone.FREE[@pmod];
Stream.Delete[sh];
RETURN[TRUE];
};

ResetStringVar: PROC[stringseq: MDSubr.StringSeq, savenstringvars: CARDINAL]={
FOR i: CARDINAL IN [savenstringvars .. stringseq.size) DO
	Subr.FreeString[stringseq[i].str];
	Subr.FreeString[stringseq[i].val];
	ENDLOOP;
stringseq.size ← savenstringvars;
};

SortEntries: PROC[entryseq: MDSubr.EntrySeq] = {
IndexSeqRecord: TYPE = RECORD[
	body: SEQUENCE maxsize: CARDINAL OF CARDINAL
	];
index: LONG POINTER TO IndexSeqRecord;
i,j: CARDINAL;

	EntryLessThan: PROC[left, right: CARDINAL] RETURNS [BOOL] = {
	s1: STRING ← [100];
	s2: STRING ← [100];
	IF entryseq[left].name = NIL OR entryseq[right].name = NIL THEN
		 RETURN[FALSE];
	Subr.strcpy[s1, entryseq[left].name];
	Subr.strcpy[s2, entryseq[right].name];
	RETURN[String.CompareStrings[s1, s2, TRUE] < 0];
	};

	EntryExch: PROC[left, right: CARDINAL] = {
	t: MDSubr.EntryRecord;
	i: CARDINAL;
	stuffl, stuffr: CARDINAL ← MDSubr.entNil;
	t ← entryseq[left];
	entryseq[left] ← entryseq[right];
	entryseq[right] ← t;
	FOR i IN [0..entryseq.size) DO
		IF index[i] = left  THEN stuffr ← i;
		IF index[i] = right THEN stuffl ← i;
		ENDLOOP;
	IF stuffr = MDSubr.entNil OR stuffl = MDSubr.entNil THEN ERROR;
	index[stuffr] ← right;
	index[stuffl] ← left;
	};


index ← longzone.NEW[IndexSeqRecord[entryseq.size]];
FOR i IN [0..entryseq.size) DO
	index[i] ← i;
	ENDLOOP;
TreeSort[entryseq, EntryLessThan, EntryExch];
FOR i IN [0.. entryseq.size ) DO
	j ← 0;
	WHILE entryseq[i].depends[j] ~= MDSubr.entNil DO
		entryseq[i].depends[j] ← index[entryseq[i].depends[j]];
		j ← j + 1;
		ENDLOOP;
	ENDLOOP;
longzone.FREE[@index];
};

TreeSort: PROC[entryseq: MDSubr.EntrySeq, 
	LessThan: PROC[CARDINAL, CARDINAL] RETURNS[BOOL],
	Exch: PROC[CARDINAL, CARDINAL]] = {
left, right: CARDINAL;

	siftUp: PROC[low, high: INTEGER] ={
	k, son: INTEGER;
	left, right: CARDINAL;
	k ← low;
	DO
		IF 2*k>high THEN EXIT;
		left ← 2*k+1-1;
		right ← 2*k-1;
		IF 2*k+1>high OR LessThan[left, right] THEN
			son ← 2*k
		ELSE
			son ← 2*k+1;
		left ← son-1;
		right ← k-1;
		IF LessThan[left, right] THEN 	EXIT;
		Exch[left, right];
		k ← son;
		ENDLOOP;
	};

FOR i: CARDINAL DECREASING IN [1..entryseq.size/2] DO 
	siftUp[i,entryseq.size] 
	ENDLOOP;
FOR i: CARDINAL DECREASING IN [1..entryseq.size) DO
	left ← 0;
	right ← i;
	Exch[left, right];
	siftUp[1,i]
	ENDLOOP;
};

PrintMade: PROC[firstinx: CARDINAL, entryseq:MDSubr.EntrySeq, 
	lookseq: MDSubr.LookSeq, all: BOOL] = {
p: BOOL ← FALSE;

	HandleNode: PROC[parent: MDSubr.Entry] = {
	i: CARDINAL;
	child: MDSubr.Entry;
	stemp: STRING ← [100];
	IF NOT parent.made OR parent.rule = NIL THEN RETURN;
	i ← 0;
	WHILE parent.depends[i] ~= MDSubr.entNil DO
		child ← @entryseq[parent.depends[i]];
		IF Subr.EndsIn[child.name, "mesa"L]
		OR Subr.EndsIn[child.name, "config"L] THEN {
			IF NOT p THEN {
				CWF.WF0[
				"\nThese files were compiled or bound:\n"L];
				p ← TRUE;
				};
			CWF.WF1["  %s"L, child.name];
			Subr.strcpy[stemp, child.name];
			CWF.WFCR[];
			};
		i ← i + 1;
		ENDLOOP;
	};

MDSubr.WalkTheGraph[firstinx, entryseq, HandleNode];
CWF.WFCR[];
};


-- if noextrastuff is TRUE, then the str is just written on line.cm
-- if FALSE,  str must be a complete command!
-- CR not added to str
WriteLineCm: PROC[str: LONG STRING, noextrastuff: BOOL] = {
command: STRING ← [700];
printcmd: STRING ← [50];
sf: STRING ← [30];
Subr.strcpy[command, str];
MDSubr.StripFirstWord[command, sf];	-- puts first word into sf
IF Subr.Prefix[sf, "compile"L] THEN {
	IF compstr = NIL THEN 
		compstr ← Subr.AllocateString[3000];
	LongString.AppendString[compstr, command
		! LongString.StringBoundsFault => {
			CWF.WF0["Error - Command Line Too Long.\n"L];
			CONTINUE;
			}];
	Subr.strcpy[printcmd, "Compile.~"L];
	}
ELSE IF Subr.Prefix[sf, "bind"L] THEN {
	IF bindstr = NIL THEN 
		bindstr ← Subr.AllocateString[3000];
	LongString.AppendString[bindstr, command
		! LongString.StringBoundsFault => {
			CWF.WF0["Error - Command Line Too Long.\n"L];
			CONTINUE;
			}];
	Subr.strcpy[printcmd, "Bind.~"L];
	}
ELSE CWF.WF1["Error - can only compile or bind= '%s'\n"L, sf];
CWF.WF2["%s %s\n"L, printcmd, command];
};

CloseLineCm: PROC = {
IF compstr ~= NIL THEN Subr.FreeString[compstr];
IF bindstr ~= NIL THEN Subr.FreeString[bindstr];
compstr ← bindstr ← NIL;
};

MAXRECUR: CARDINAL = 30;

-- this crucial proc is called in at least three modes
-- 1. to simply set need true for all children of some parent
--	(forreal: FALSE, justmark: TRUE)
-- 2. to set the need flag if we'll actually need to compile this
--	(forreal: FALSE, justmark: FALSE, localonly: FALSE)
-- 3. to actually make the dependencies if necessary
--	(forreal: TRUE)
--
-- uses look array
-- before this is called if forreal and presentonlocaldisk
-- all the localversions must be filled in (perhaps by BringInRemoteFiles)
Decide: PROC[forreal: BOOL, parinx: CARDINAL,
	entryseq: MDSubr.EntrySeq, lookseq: MDSubr.LookSeq, 
	justmark, forcemake: BOOL]
	RETURNS[LONG CARDINAL] = {
FOR i: CARDINAL IN [0 .. entryseq.size) DO
	entryseq[i].visited ← FALSE;
	ENDLOOP;
FOR i: CARDINAL IN [0 .. lookseq.size) DO
	lookseq[i].besttime ← MDSubr.versionNil;
	ENDLOOP;
RETURN[InternalDecide[forreal, parinx, entryseq, lookseq, justmark, forcemake]];
};

InternalDecide: PROC[forreal: BOOL, parinx: CARDINAL,
	entryseq: MDSubr.EntrySeq, lookseq: MDSubr.LookSeq, 
	justmark, forcemake: BOOL]
	RETURNS[LONG CARDINAL] = {
parenttime, besttime: LONG CARDINAL;
childinx: CARDINAL;
parent, child: MDSubr.Entry;
i: CARDINAL;
isbcd, needmake: BOOL;
lookinx: CARDINAL;
nowhere: BOOL;
look, lookchild: MDSubr.Look;
localerr: BOOL;

IF parinx = MDSubr.entNil THEN {
	CWF.WF0["Error - Decide - parinx is nil\n"L];
	RETURN[0];
	};
parent ← @entryseq[parinx];
lookinx ← parent.lookinx;
IF lookinx = MDSubr.lookNil THEN {
	CWF.WF0["bad lookinx\n"L];
	RETURN[0];
	};
look ← @lookseq[lookinx];
IF NOT LongString.EquivalentString[look.name, parent.name] THEN {
	CWF.WF2["Oops names don't agree - %s vs. %s.\n"L,look.name,
		 parent.name];
	RETURN[0];
	};
-- if nowhere is TRUE then we must make it
nowhere ← NOT look.presentonlocaldisk;

besttime ← look.localversion;
-- leaves are given 0 time
-- not on local or remote disk => present time
parenttime←IF NOT nowhere THEN besttime ELSE
	(IF parent.depends[0] = MDSubr.entNil AND parent.rule = NIL THEN
		MDSubr.versionUnknown ELSE presenttime);
look.need ← TRUE;
-- CWF.WF1["need %s\n"L, look.name];
-- a parent with no children without a rule just gives us info
IF parent.depends[0] = MDSubr.entNil AND parent.rule = NIL THEN RETURN[parenttime];
-- a parent with no children with a rule will always need to make itself
recur ← recur + 1;
IF recur >= MAXRECUR THEN {
	recur ← 32767;
	CWF.WF1["Error - '%s' seems to be in a recursive chain\n"L, parent.name];
	Subr.errorflg ← TRUE;
	RETURN[MDSubr.versionUnknown];
	};
isbcd ← Subr.EndsIn[parent.name, ".bcd"L];
i← 0;
needmake ← (parent.depends[0] = MDSubr.entNil) OR nowhere;
IF forcemake THEN needmake ← TRUE;
localerr ← FALSE;
WHILE parent.depends[i] ~= MDSubr.entNil DO
	childinx ← parent.depends[i];
	child ← @entryseq[childinx];
	lookchild ← @lookseq[child.lookinx];
	IF lookchild.besttime = MDSubr.versionNil THEN
		lookchild.besttime ←
			InternalDecide[forreal,childinx,entryseq, lookseq,
				justmark, forcemake];
	IF isbcd
	AND forreal
	AND (Subr.EndsIn[child.name,".mesa"L] OR Subr.EndsIn[child.name, ".config"L])
	AND look.presentonlocaldisk
	AND lookchild.presentonlocaldisk
	THEN {
		IF MustComp[parinx, childinx, entryseq, lookseq] THEN
			needmake ← TRUE;
		}
	ELSE IF lookchild.besttime >= parenttime THEN needmake ← TRUE;
	IF forreal AND lookchild.besttime = MDSubr.versionUnknown THEN BEGIN
		IF NOT child.visited THEN { 
			CWF.WF1["\nError - Can't find '%s'\n"L,child.name];
			Subr.errorflg ← TRUE;
			};
		localerr ← TRUE;
		child.visited ← TRUE;
		END;
	i ← i + 1;
	ENDLOOP;
-- if we haven't made any children then we really don't need to look at them
IF NOT needmake AND NOT justmark THEN {
	i← 0;
	WHILE parent.depends[i] ~= MDSubr.entNil DO
		childinx ← parent.depends[i];
		lookchild ← @lookseq[entryseq[childinx].lookinx];
		lookchild.need ← FALSE;
		i ← i + 1;
		ENDLOOP;
	};
IF needmake THEN {
	parenttime ← presenttime;
	IF forreal THEN {
		-- WF.WF1["About to make '%s'\n"L,parent.name];
		IF NOT parent.made AND parent.rule ~= NIL THEN {
			WriteLineCm[parent.rule, FALSE];
			};
		parent.made ← TRUE;
		};
	};
recur ← recur - 1;
RETURN[parenttime];
};

-- returns TRUE if the source must be recompiled
-- will return TRUE if the create time in the BCD is
-- different from the .mesa time, even if the create time is "better"
MustComp: PROC[parinx, childinx: CARDINAL, entryseq: MDSubr.EntrySeq, 
	 lookseq: MDSubr.LookSeq] RETURNS[mustcomp: BOOL] = {
sourcetimecompiled: LONG CARDINAL;
wantconfig, isconfig: BOOL;
look, lookchild: MDSubr.Look;
-- time: LONG CARDINAL;

-- CWF.WF1["file %s: "L, entryseq[parinx].name];
look ← @lookseq[entryseq[parinx].lookinx];
lookchild ← @lookseq[entryseq[childinx].lookinx];
[sourcetimecompiled, wantconfig] ← BcdCap[look.cap, look.name,
	look.localversion];
-- IF wantconfig THEN CWF.WF0["wantconfig "L];
isconfig ← Subr.EndsIn[entryseq[childinx].name, ".config"L];
-- IF isconfig THEN CWF.WF0["isconfig "L];
IF (isconfig AND NOT wantconfig) OR (NOT isconfig AND wantconfig) THEN
	RETURN[TRUE];
mustcomp ← lookchild.localversion ~= sourcetimecompiled;
-- time ← lookchild.localversion;
-- IF mustcomp THEN 
	-- CWF.WF2["mustcomp time in bcd is %lt, time of .mesa is %lt\n"L, 
		-- @bcdtime, @time];
};

-- space is passed in to avoid extra Space.Create's
BcdCap: PROC[cap: File.Capability, name: LONG STRING, time: LONG CARDINAL] 
	RETURNS[sourcecreatetime: LONG CARDINAL, isconfig: BOOL] = {
bcd: LONG POINTER TO BcdDefs.BCD;
Space.Map[space: bcdspace, window: [file: cap, base: 1]];
bcd ← Space.LongPointer[bcdspace];
sourcecreatetime ← bcd.sourceVersion.time;
isconfig ← (bcd.nConfigs > 0);
Space.Unmap[bcdspace];
};

MyConfirm: PROC[in, out: IO.Handle, data: REF ANY, msg: Rope.ROPE, dch: CHAR]
	RETURNS[CHAR] = {
	value: ATOM;
	value ← UserExec.AskUser[msg: msg, exec: NARROW[data], 
		keyList: LIST[$Yes, $No]];  -- order is important
	SELECT value FROM
	$No => RETURN['n];
	$Yes => RETURN['y];
	ENDCASE => ERROR;
	};

MyPutChar: PROC[ch: CHAR] = {
	stdout.PutChar[ch];
	};
	
UserExec.RegisterCommand["MakeConfig.~", Main];
}.