<<>> <> <> <> <> <> <> DIRECTORY BasicTime, Commander, DFCachingUtilities USING [CreateEnumerationCache, EnumerationCache, FileSpec, FSErrorOnDF, Miss, NestItem], DFCachingUtilitiesExtras USING [FullEnumerateDFContents], DFUtilities, DFUtilitiesExtras, FileNames USING [StripVersionNumber], FS USING [ComponentPositions, Error, ErrorGroup, ExpandName, FileInfo], FSExtras USING [GetWDir], IO, MakeDo, MakeDoPorting USING [verifyAttachments], MakeDoPrivate USING [NodeClassRep], Process USING [CheckForAbort], RefTab USING [Create, Erase, Fetch, Pairs, Ref, Store], Rope, RopeHash, SymTab USING [Create, Erase, Fetch, Insert, Pairs, Ref, Store]; MakeDoCmdUtilsImpl: CEDAR PROGRAM IMPORTS BasicTime, DFCachingUtilities, DFCachingUtilitiesExtras, DFUtilitiesExtras, FileNames, FS, FSExtras, IO, MakeDo, MakeDoPorting, Process, RefTab, Rope, RopeHash, SymTab EXPORTS MakeDo, MakeDoPrivate SHARES MakeDoPrivate = BEGIN ROPE: TYPE = Rope.ROPE; RefTable: TYPE = MakeDo.RefTable; AnaCacheKey: TYPE ~ REF AnaCacheKeyPrivate; AnaCacheKeyPrivate: TYPE ~ RECORD [wDir, fullDF: ROPE, created: BasicTime.GMT]; AnaCacheEntry: TYPE ~ REF AnaCacheEntryPrivate; AnaCacheEntryPrivate: TYPE ~ RECORD [ deps: ACDepList, verifyGoals, imports, others: MakeDo.RefTable ]; ACDepList: TYPE ~ LIST OF RECORD [dfName: ROPE, created: BasicTime.GMT]; anaCache: PUBLIC RefTab.Ref _ RefTab.Create[hash: ACKHash, equal: ACKEqual]; ACKHash: PROC [key: REF ANY] RETURNS [hash: CARDINAL] --RefTab.HashProc-- ~ { ack: AnaCacheKey ~ NARROW[key]; hash _ LOOPHOLE[ack.created, CARD] MOD 65536; hash _ RopeHash.FromRope[rope: ack.wDir, case: TRUE, seed: hash]; hash _ RopeHash.FromRope[rope: ack.fullDF, case: TRUE, seed: hash]; RETURN}; ACKEqual: PROC [key1, key2: REF ANY] RETURNS [BOOL] --RefTab.EqualProc-- ~ { ack1: AnaCacheKey ~ NARROW[key1]; ack2: AnaCacheKey ~ NARROW[key2]; IF ack1.created # ack2.created THEN RETURN [FALSE]; IF NOT ack1.wDir.Equal[ack2.wDir] THEN RETURN [FALSE]; IF NOT ack1.fullDF.Equal[ack2.fullDF] THEN RETURN [FALSE]; RETURN [TRUE]}; EmptyCmdUtils: PUBLIC PROC ~ { anaCache.Erase[]; RETURN}; AnalyzeDFFile: PUBLIC PROC [dfName: ROPE, goals, supportFiles: MakeDo.RefTable, modifiable: MakeDo.ModifiabilitySpec, doToVerifyGoals, doToOtherOwns, doToImports: MakeDo.DoToFile] = { ack: AnaCacheKey _ NEW [AnaCacheKeyPrivate _ [FSExtras.GetWDir[], NIL, BasicTime.nullGMT]]; ace: AnaCacheEntry; need: BOOL; Pass: PROC [table: MakeDo.RefTable, do: MakeDo.DoToFile] ~ { PassFile: PROC [key, val: REF ANY] RETURNS [stop: BOOL _ FALSE] ~ { n: MakeDo.Node ~ MakeDo.NarrowToNode[key]; [] _ RefTab.Store[ SELECT do FROM makeGoal => goals, makeModifiable => modifiable, makeSupport => supportFiles, ENDCASE => ERROR, n, $T]; IF do=makeGoal THEN [] _ modifiable.Store[n, $T]; RETURN [FALSE]}; IF do#ignore AND table.Pairs[PassFile] THEN ERROR; RETURN}; [fullFName: ack.fullDF, created: ack.created] _ FS.FileInfo[dfName]; ace _ NARROW[anaCache.Fetch[ack].val]; need _ ace=NIL; IF debugLog#NIL THEN debugLog.PutF["Ana[%g, %g, %g] => %g\n", [rope[ack.wDir]], [rope[ack.fullDF]], [time[ack.created]], [boolean[ace#NIL]] ]; IF NOT need THEN { FOR acds: ACDepList _ ace.deps, acds.rest WHILE acds#NIL AND NOT need DO created: BasicTime.GMT _ BasicTime.nullGMT; [created: created] _ FS.FileInfo[acds.first.dfName !FS.Error => CONTINUE]; IF created # acds.first.created THEN { IF debugLog#NIL THEN debugLog.PutF["Ana[%g].%g = %g # %g\n", [rope[dfName]], [rope[acds.first.dfName]], [rope[MakeDo.FmtTime[acds.first.created]]], [rope[MakeDo.FmtTime[created]]] ]; need _ TRUE}; ENDLOOP}; IF need THEN [] _ anaCache.Store[ack, ace _ MakeACE[ack.fullDF, ack.created]]; Pass[ace.verifyGoals, doToVerifyGoals]; Pass[ace.imports, doToImports]; Pass[ace.others, doToOtherOwns]; RETURN}; MakeACE: PROC [dfName: ROPE, dfCreated: BasicTime.GMT] RETURNS [ace: AnaCacheEntry] = { start: BasicTime.GMT ~ BasicTime.Now[]; Do: PROC [verless: ROPE, table: RefTab.Ref] ~ { n: MakeDo.Node ~ MakeDo.GetNode[verless, MakeDo.fileClass, TRUE]; [] _ table.Store[n, $T]; RETURN}; Recurse: PROC [dfName: ROPE, wanted: DFUtilities.Date, privates, imported: BOOL] ~ { readonly: BOOL _ FALSE; created: BasicTime.GMT _ wanted.gmt; Consume: PROC [item: REF ANY] RETURNS [stop: BOOL _ FALSE] ~ { Process.CheckForAbort[]; WITH item SELECT FROM di: REF DFUtilities.DirectoryItem => readonly _ di.readOnly; fi: REF DFUtilities.FileItem => { table: RefTab.Ref ~ IF readonly OR imported THEN ace.imports ELSE IF fi.verifyRoot THEN ace.verifyGoals ELSE ace.others; Do[FileNames.StripVersionNumber[fi.name], table]; }; ii: REF DFUtilities.ImportsItem => SELECT ii.form FROM exports => IF recurseOnExports[ii.exported] THEN Recurse[VerlessShort[ii.path1], ii.date, FALSE, TRUE]; all => Recurse[VerlessShort[ii.path1], ii.date, TRUE, TRUE]; list => {FOR i: NAT IN [0 .. ii.list.nEntries) DO Do[ii.list[i].name, ace.imports]; ENDLOOP}; ENDCASE => ERROR; ii: REF DFUtilities.IncludeItem => Recurse[VerlessShort[ii.path1], ii.date, TRUE, imported]; ci: REF DFUtilities.CommentItem => NULL; wi: REF DFUtilities.WhiteSpaceItem => NULL; ENDCASE => ERROR; RETURN}; IF wanted.format#explicit THEN { [created: created] _ FS.FileInfo[dfName !FS.Error => CONTINUE]; ace.deps _ CONS[[dfName, created], ace.deps]}; DFUtilitiesExtras.ParseFromFile[dfName, Consume, [filterB: IF privates THEN all ELSE public, filterC: all], created ! DFUtilitiesExtras.FileSyntaxError => { MakeDo.Warning[IO.PutFR["DF syntax error (%g) near %g; parsing of %g abandoned", [rope[reason]], [integer[position]], [rope[dfName]] ]]; CONTINUE}; FS.Error => { MakeDo.Warning[IO.PutFR["FS.Error[%g, %g] while analyzing %g%g - skipping the rest of that DF", [atom[error.code]], [rope[error.explanation]], [rope[IF privates THEN IF imported THEN "" ELSE "private part of " ELSE IF imported THEN "imported part of " ELSE "none(!) of "]], [rope[dfName]] ]]; CONTINUE} ]; RETURN}; stop: BasicTime.GMT; ace _ NEW [AnaCacheEntryPrivate _ [ deps: NIL, verifyGoals: RefTab.Create[], imports: RefTab.Create[], others: RefTab.Create[] ]]; Recurse[dfName, [explicit, dfCreated], TRUE, FALSE]; stop _ BasicTime.Now[]; IF debugLog#NIL THEN debugLog.PutF["MakeACE[%g] took %gs\n", [rope[dfName]], [integer[BasicTime.Period[start, stop]]] ]; RETURN}; recurseOnExports: ARRAY BOOL OF BOOL _ [FALSE: TRUE, TRUE: FALSE]; VerlessShort: PROC [given: ROPE] RETURNS [ROPE] ~ { full: ROPE; cp: FS.ComponentPositions; [full, cp, ] _ FS.ExpandName[given]; RETURN full.Substr[start: cp.base.start, len: cp.ext.start+cp.ext.length-cp.base.start]}; NodeClass: PUBLIC TYPE = REF NodeClassRep; NodeClassRep: PUBLIC TYPE = MakeDoPrivate.NodeClassRep; Verify: PUBLIC PROC [pkgList: LIST OF ROPE] ~ { doneDFs: SymTab.Ref ~ SymTab.Create[case: FALSE]; dfdRels: SymTab.Ref ~ SymTab.Create[case: FALSE]; --imported with UsingForm=list eiRels: SymTab.Ref ~ SymTab.Create[case: FALSE]; --imported with UsingForm=exported or all extraRels: SymTab.Ref ~ SymTab.Create[case: FALSE]; goals: MakeDo.RefTable ~ MakeDo.MakeRefTable[]; modifiable: MakeDo.RefTable ~ MakeDo.MakeRefTable[]; modRels: SymTab.Ref ~ SymTab.Create[case: FALSE]; <> neededLeaves: MakeDo.RefTable ~ MakeDo.MakeRefTable[]; hids: RefTab.Ref ~ RefTab.Create[]; optionalLeaves: MakeDo.RefTable ~ MakeDo.MakeRefTable[]; determinerLeaves: MakeDo.RefTable ~ MakeDo.MakeRefTable[]; brokenGoals: MakeDo.RefTable ~ MakeDo.MakeRefTable[]; myFileClass: NodeClass ~ MakeDo.fileClass; CanonizeFileName: PROC [ROPE] RETURNS [ROPE] ~ myFileClass.CanonizeName; wDir: ROPE ~ CanonizeFileName[FSExtras.GetWDir[]]; <> wDirLen: INT ~ wDir.Length; { <> curDir: ROPE _ NIL; readonly: BOOL _ FALSE; ownedDF: LIST OF BOOL _ NIL; ei: LIST OF BOOL _ NIL; pushOwn: BOOL; pushEI: BOOL; dfStack: LIST OF DFCachingUtilities.FileSpec _ NIL; localEIRels: SymTab.Ref ~ SymTab.Create[case:FALSE]; localDfdRels: SymTab.Ref ~ SymTab.Create[case:FALSE]; localModRels: SymTab.Ref ~ SymTab.Create[case:FALSE]; pkg: ROPE; SeeDFContent: PROC [item: REF ANY] RETURNS [stop, clip, dontCache: BOOL _ FALSE] ~ { WITH item SELECT FROM x: REF DFUtilities.DirectoryItem => {readonly _ x.readOnly; curDir _ x.path1}; x: REF DFUtilities.FileItem => { ownedFile: BOOL ~ ownedDF.first AND NOT readonly; cand: ROPE ~ CanonizeFileName[x.name]; rel: ROPE ~ RelativeName[cand, wDirLen]; IF ownedFile THEN { n: MakeDo.Node ~ MakeDo.FindNode[cand, MakeDo.fileClass]; MakeDo.EnsureRefInTable[n, modifiable]; [] _ localModRels.Insert[rel, $T]; IF x.verifyRoot THEN MakeDo.EnsureRefInTable[n, goals]; } ELSE { full: ROPE ~ FS.ExpandName[rel, curDir].fullFName; actualFull, actualAttach: ROPE; actualCreate: BasicTime.GMT; created: BasicTime.GMT _ IF x.date.format=explicit THEN x.date.gmt ELSE BasicTime.nullGMT; relsTabl: SymTab.Ref ~ IF ei.first THEN localEIRels ELSE localDfdRels; <> >> IF dfStack=NIL THEN ERROR; IF NOT relsTabl.Insert[rel, dfStack] THEN MakeDo.Warning[IO.PutFR["Multiple attachments for %g (%g and %g).", [rope[rel]], [rope[FmtDfs[NARROW[relsTabl.Fetch[rel].val]]]], [rope[FmtDfs[dfStack]]] ]]; IF created=BasicTime.nullGMT THEN created _ FS.FileInfo[name: full !FS.Error => {MakeDo.Warning[IO.PutFR["FS.Error[%g, %g, %g].", [rope[groupNames[error.group]]], [atom[error.code]], [rope[error.explanation]] ]]; GOTO DontCheck}].created; [fullFName: actualFull, attachedTo: actualAttach, created: actualCreate] _ FS.FileInfo[name: rel !FS.Error => {MakeDo.Warning[IO.PutFR["FS.Error[%g, %g, %g].", [rope[groupNames[error.group]]], [atom[error.code]], [rope[error.explanation]] ]]; GOTO DontCheck}]; IF MakeDoPorting.verifyAttachments THEN { IF NOT (full.Equal[actualAttach.Substr[len: full.Length], FALSE] AND actualCreate=created) THEN MakeDo.Warning[IO.PutFR["Bad attachment for %g (attached to %g of %g instead of %g of %g).", [rope[rel]], [rope[actualAttach]], [time[actualCreate]], [rope[full]], [time[created]] ]]; } ELSE { IF NOT (actualCreate = created) THEN MakeDo.Warning[IO.PutFR["Bad create date for %g (%g instead of %g, as %g).", [rope[rel]], [time[actualCreate]], [time[created]], [rope[FmtDfs[dfStack]]] ]]; } EXITS DontCheck => pkg _ pkg}; stop _ stop}; x: REF DFUtilities.ImportsItem => { pushOwn _ FALSE; pushEI _ ei.first OR ownedDF.first AND x.form#list; IF x.list#NIL THEN FOR i: NAT IN [0 .. x.list.nEntries) DO rel: ROPE ~ RelativeName[full: CanonizeFileName[x.list[i].name], baseLen: wDirLen]; IF x.list[i].verifyRoot THEN [] _ extraRels.Store[rel, $T]; ENDLOOP; }; x: REF DFUtilities.IncludeItem => { pushOwn _ ownedDF.first; pushEI _ ei.first}; x: REF DFCachingUtilities.NestItem => SELECT x.bracket FROM begin => {short: ROPE ~ VerlessShort[x.df.name]; ownedDF _ CONS[pushOwn, ownedDF]; ei _ CONS[pushEI, ei]; dfStack _ CONS[x.df, dfStack]; IF doneDFs.Fetch[short].found THEN RETURN [clip: TRUE]; IF pushOwn THEN IF NOT doneDFs.Insert[key: short, val: $T] THEN ERROR; }; end => {ownedDF _ ownedDF.rest; ei _ ei.rest; dfStack _ dfStack.rest}; ENDCASE => ERROR; x: REF DFUtilities.CommentItem => NULL; x: REF DFUtilities.WhiteSpaceItem => NULL; ENDCASE => ERROR; }; AddToSymTable: PROC [global, local: SymTab.Ref] ~ { InnerAdd: PROC [key: ROPE, val: REF ANY] RETURNS [BOOL] ~ { [] _ global.Store[key: key, val: val]; RETURN [FALSE]; }; [] _ local.Pairs[InnerAdd]; }; dfName: ROPE; cp: FS.ComponentPositions; dfCache: DFCachingUtilities.EnumerationCache ~ DFCachingUtilities.CreateEnumerationCache[]; FOR pkgRest: LIST OF ROPE _ pkgList, pkgRest.rest WHILE pkgRest # NIL DO pkg _ pkgRest.first; pushOwn _ TRUE; pushEI _ FALSE; [dfName, cp, ] _ FS.ExpandName[pkg]; IF cp.ext.length=0 AND cp.ext.start=cp.base.start+cp.base.length THEN dfName _ dfName.Concat[".df"]; [] _ DFCachingUtilitiesExtras.FullEnumerateDFContents[df: [dfName], side: local, Consumer: SeeDFContent, cache: dfCache ! DFCachingUtilities.FSErrorOnDF => { MakeDo.Warning[IO.PutFR["FS.Error[%g, %g, %g].", [rope[groupNames[error.group]]], [atom[error.code]], [rope[error.explanation]] ]]; RESUME}; DFCachingUtilities.Miss => { MakeDo.Warning[IO.PutFR["%g not found in %g", [rope[dataFileName]], [rope[dfFileName]] ]]; RESUME} ]; IF ownedDF#NIL THEN ERROR; IF ei#NIL THEN ERROR; { <> AddToSymTable[eiRels, localEIRels]; AddToSymTable[dfdRels, localDfdRels]; AddToSymTable[modRels, localModRels]; localEIRels.Erase[]; localDfdRels.Erase[]; localModRels.Erase[]; }; ENDLOOP; }; {actions: MakeDo.ActionList _ MakeDo.GetActions[goals, modifiable, toBeDone, neededLeaves, optionalLeaves, determinerLeaves, brokenGoals]; wronglyUnlisted: RefTab.Ref ~ RefTab.Create[]; wronglyListed: RefTab.Ref ~ RefTab.Create[]; CheckLeafIsListed: PROC [key, val: REF ANY] RETURNS [stop: BOOL] ~ { n: MakeDo.Node ~ MakeDo.NarrowToNode[key]; CheckListness[n, NIL, TRUE]; RETURN [FALSE]}; CheckLeafMaybeListed: PROC [key, val: REF ANY] RETURNS [stop: BOOL] ~ { n: MakeDo.Node ~ MakeDo.NarrowToNode[key]; CheckListness[n, NIL, FALSE]; RETURN [FALSE]}; CheckHidListed: PROC [key, val: REF ANY] RETURNS [BOOL] ~ { n: MakeDo.Node ~ MakeDo.NarrowToNode[key]; refs: MakeDo.ActionList ~ NARROW[val]; CheckListness[n, refs, TRUE]; RETURN [FALSE]}; CheckListness: PROC [n: MakeDo.Node, refs: MakeDo.ActionList, required: BOOL] ~ { name: ROPE; class: MakeDo.NodeClass; [name, class] _ MakeDo.PublicPartsOfNode[n]; IF class # MakeDo.fileClass THEN RETURN; IF NOT wDir.Equal[name.Substr[len: wDirLen], FALSE] THEN { MakeDo.Warning[IO.PutFR["Dependency on %g can't be handled by DFs.", [rope[name]] ]]; RETURN}; required _ required OR hids.Fetch[n].found OR neededLeaves.Fetch[n].found; {rel: ROPE ~ RelativeName[full: name, baseLen: wDirLen]; shouldBeListed: BOOL ~ required OR n.Exists[]; isListed: BOOL ~ modifiable.Fetch[n].found OR dfdRels.Fetch[rel].found OR eiRels.Fetch[rel].found; IF shouldBeListed=isListed THEN RETURN; {wrongTable: RefTab.Ref ~ IF isListed THEN wronglyListed ELSE wronglyUnlisted; needs: RefTab.Ref _ NARROW[wrongTable.Fetch[n].val]; NoteRef: PROC [a: MakeDo.Action, ad: MakeDo.ActionDep] ~ { [] _ needs.Store[a, $T]; RETURN}; IF needs=NIL THEN [] _ wrongTable.Store[n, needs _ RefTab.Create[]]; IF refs#NIL THEN FOR refs _ refs, refs.rest WHILE refs#NIL DO NoteRef[refs.first, data]; ENDLOOP ELSE { n.EnumerateConsumers[data, NoteRef]; n.EnumerateConsumers[cmd, NoteRef]}; [] _ needs.Store[$Fmt, IF isListed THEN IO.PutFR["File %g (affects %%g) doesn't exist but is in DF(s).", [rope[rel]]] ELSE IO.PutFR["File %g (needed by %%g) missing from DF(s).", [rope[rel]]] ]; RETURN}}}; ReportWrong: PROC [key, val: REF ANY] RETURNS [BOOL] ~ { n: MakeDo.Node ~ MakeDo.NarrowToNode[key]; needs: RefTab.Ref ~ NARROW[val]; fmt, nr: ROPE _ NIL; PerAction: PROC [key, val: REF ANY] RETURNS [BOOL] ~ { IF key=$Fmt THEN fmt _ NARROW[val] ELSE { a: MakeDo.Action ~ MakeDo.NarrowToAction[key]; IF nr#NIL THEN nr _ nr.Concat["; "]; nr _ nr.Concat[MakeDo.PublicPartsOfAction[a].cmd]}; RETURN [FALSE]}; IF needs.Pairs[PerAction] THEN ERROR; MakeDo.Warning[IO.PutFR[fmt, [rope[nr]] ]]; RETURN [FALSE]}; CheckDfdRel: PROC [key: ROPE, val: REF ANY] RETURNS [BOOL] ~ { IF extraRels.Fetch[key].found THEN RETURN [FALSE]; {n: MakeDo.Node ~ MakeDo.FindNode[key, MakeDo.fileClass]; IF NOT (neededLeaves.Fetch[n].found OR hids.Fetch[n].found OR optionalLeaves.Fetch[n].found OR determinerLeaves.Fetch[n].found) THEN MakeDo.Warning[IO.PutFR["Imported file %g not needed.", [rope[key]] ]]; RETURN [FALSE]}}; ReportBadGoal: PROC [key, val: REF ANY] RETURNS [BOOL] ~ { n: MakeDo.Node ~ MakeDo.NarrowToNode[key]; name: ROPE ~ MakeDo.PublicPartsOfNode[n].name; MakeDo.Warning[IO.PutFR["Goal %g not happy.", [rope[name]] ]]; RETURN [FALSE]}; seen: RefTab.Ref ~ RefTab.Create[]; toDo: LIST OF REF ANY _ NIL; StartFromGoal: PROC [key, val: REF ANY] RETURNS [stop: BOOL] ~ {IF seen.Store[key, $T] THEN toDo _ CONS[key, toDo]; RETURN [FALSE]}; IF brokenGoals.Pairs[ReportBadGoal] THEN ERROR; IF goals.Pairs[StartFromGoal] THEN ERROR; WHILE toDo#NIL DO this: REF ANY ~ toDo.first; toDo _ toDo.rest; IF MakeDo.IsNode[this] THEN { n: MakeDo.Node ~ MakeDo.NarrowToNode[this]; a: MakeDo.Action ~ MakeDo.GetProducer[n]; IF a#NIL AND seen.Store[a, $T] THEN toDo _ CONS[a, toDo]} ELSE {a: MakeDo.Action ~ MakeDo.NarrowToAction[this]; ac: MakeDo.ActionClass ~ MakeDo.PublicPartsOfAction[a].class; CheckHidden: PROC [n: MakeDo.Node] ~ { IF (NOT modifiable.Fetch[n].found) AND n.PublicPartsOfNode[].class=MakeDo.fileClass THEN { refs: MakeDo.ActionList _ NARROW[hids.Fetch[n].val]; refs _ CONS[a, refs]; [] _ hids.Store[n, refs]}}; PerSource: PROC [n: MakeDo.Node, which: MakeDo.ActionDep, optional: BOOL] ~ { IF modifiable.Fetch[n].found AND seen.Store[n, $T] THEN toDo _ CONS[n, toDo]}; MakeDo.EnumerateSources[a, cmd, PerSource]; MakeDo.EnumerateSources[a, data, PerSource]; IF ac.EnumHiddenDeps#NIL THEN ac.EnumHiddenDeps[a, CheckHidden]; toDo _ toDo}; ENDLOOP; IF dfdRels.Pairs[CheckDfdRel] THEN ERROR; IF hids.Pairs[CheckHidListed] THEN ERROR; IF neededLeaves.Pairs[CheckLeafIsListed] THEN ERROR; IF optionalLeaves.Pairs[CheckLeafMaybeListed] THEN ERROR; IF determinerLeaves.Pairs[CheckLeafMaybeListed] THEN ERROR; IF wronglyUnlisted.Pairs[ReportWrong] THEN ERROR; IF wronglyListed.Pairs[ReportWrong] THEN ERROR; IF actions#NIL THEN { msg: ROPE _ NIL; FOR actions _ actions, actions.rest WHILE actions#NIL DO msg _ msg.Cat["\n\t", MakeDo.PublicPartsOfAction[actions.first].cmd]; ENDLOOP; MakeDo.Warning[Rope.Cat["Need to:", msg, "."]]; }; RETURN}}; FmtDfs: PROC [dfStack: LIST OF DFCachingUtilities.FileSpec] RETURNS [ans: ROPE _ NIL] ~ { FOR dfStack _ dfStack, dfStack.rest WHILE dfStack#NIL DO step: ROPE ~ dfStack.first.name; IF ans#NIL THEN ans _ ans.Cat[" in ", step] ELSE ans _ Rope.Concat["in ", step]; ENDLOOP; RETURN}; RelativeName: PROC [full: ROPE, baseLen: INT] RETURNS [rel: ROPE] ~ { IF full.Length>baseLen AND full.Fetch[baseLen]='< THEN baseLen _ baseLen.SUCC; rel _ full.Substr[start: baseLen]; RETURN}; groupNames: ARRAY FS.ErrorGroup OF ROPE ~ [ ok: "ok", bug: "bug", environment: "environment", lock: "lock", client: "client", user: "user" ]; MakeDoCmdUtilsImplDebug: Commander.CommandProc ~ {debugLog _ cmd.out}; MakeDoCmdUtilsImplDontDebug: Commander.CommandProc ~ {debugLog _ NIL}; debugLog: IO.STREAM _ NIL; <> <> END.