-- MakeConfigImpl.Mesa
-- last edit February 4, 1983 1:14 pm
-- last edit May 22, 1983 5:08 pm, Russ Atkinson
-- 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: BOOLFALSE;
starttime: Subr.PackedTime;
hugezone: UNCOUNTED ZONE;
argv: UECP.Argv ← UECP.Parse[commandLine];
flat: Rope.Text;

ThrowAwayThisFile: PROC[name: LONG 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: BOOLTRUE;
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] THENEXIT;
  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: BOOLFALSE;

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];
}.