<<>> <> <> <> <> <> <> DIRECTORY BasicTime USING [ GMT, Now, nullGMT, Unpack ], MobDefs USING [ Base, MobBase, FTHandle, FTIndex, FTNull, FTRecord, NameRecord, NameString, VersionStamp ], Commander USING [ CommandProc, Register ], CommanderOps USING [ ArgumentVector, Failed, Parse ], Convert USING [RopeFromInt], DFUtilities USING [ Date ], FileNames USING [ GetShortName, StripVersionNumber ], GenerateDFClosure USING [ ActionProc, GenerateClosureToProc ], IO, List USING [ Compare, CompareProc, Sort ], MobMapper USING [AlterMob, AlterMobResults, BadMobContents], MessagesOut USING [ PutRopes ], PFS USING [ Close, Error, GetInfo, Open, OpenFile, PathFromRope, Read ], Process USING [ CheckForAbort, Pause, SecondsToTicks ], Prop USING [ PropList, Put ], RedBlackTree USING [ Compare, Create, EachNode, EnumerateIncreasing, GetKey, Insert, Lookup, Table ], Rope USING [ ActionType, Compare, Concat, Fetch, Flatten, FromProc, Length, Map, Match, ROPE, Substr ], TiogaAccess USING [ Create, Put, TiogaChar, WriteFile, Writer ], UserProfile USING [ Token ], VM USING [ AddressForPageNumber, Free, Interval, PagesForBytes, SimpleAllocate ]; Dependencies: CEDAR MONITOR IMPORTS BasicTime, Commander, CommanderOps, Convert, FileNames, GenerateDFClosure, IO, List, MobMapper, MessagesOut, PFS, Process, Prop, RedBlackTree, Rope, TiogaAccess, UserProfile, VM = BEGIN GMT: TYPE = BasicTime.GMT; ROPE: TYPE = Rope.ROPE; Switches: TYPE ~ PACKED ARRAY CHAR['a..'z] OF BOOL; tempfile: ROPE ¬ "///Temp/Dependencies.temp$"; triesForServerGlitch: NAT ¬ 8; <<# of times to retry a server glitch>> pauseForServerGlitch: NAT ¬ 15; <<# of seconds to pause for a server glitch>> forkers: NAT ¬ 1; <<# of processes to fork for help>> suspectFrom: ROPE ¬ NIL; suspectTo: ROPE ¬ NIL; SuspectSeen: SIGNAL = CODE; BaseEntry: TYPE = REF BaseEntryRep; BaseEntryList: TYPE = LIST OF BaseEntry; BaseEntryRep: TYPE = RECORD [ name: ROPE, from: ROPE ¬ NIL, version: VersionStamp, dependents: BaseEntryList ¬ NIL, next: BaseEntry ¬ NIL ]; ObjectStyle: TYPE ~ { bcd, mob }; VersionStamp: TYPE ~ RECORD [ SELECT style: ObjectStyle FROM mob => [ mob: MobDefs.VersionStamp ] ENDCASE ]; DumpOutput: PROC [ outName: ROPE, table: RedBlackTree.Table, switches: Switches ] ~ { <> <<[charSet: 0, char: '\n, looks: LOOKS[], format: NIL, comment: FALSE, endOfNode: TRUE, deltaLevel: 1, propList: LIST[^[key: $FromTiogaFile, val: $Yes]]]>> <> <<[charSet: 0, char: 'F, looks: LOOKS[], format: $code, comment: TRUE, endOfNode: FALSE, deltaLevel: 0, propList: NIL]>> <> <<[charSet: 0, char: 'F, looks: LOOKS[], format: $code, comment: FALSE, endOfNode: FALSE, deltaLevel: 0, propList: NIL]>> <> <<[charSet: 0, char: '\n, looks: LOOKS[], format: $code, comment: TRUE, endOfNode: TRUE, deltaLevel: 1, propList: NIL]>> tc: TiogaAccess.TiogaChar ¬ [charSet: 0, char: '\n, looks: ALL[FALSE], format: NIL, comment: FALSE, endOfNode: TRUE, deltaLevel: 1, propList: Prop.Put[propList: NIL, key: $NewlineDelimiter, val: Rope.Flatten["\n"]] ]; PutCharB: Rope.ActionType = { tc.char ¬ c; TiogaAccess.Put[writer, tc] }; PutRope: PROC [ rope: ROPE ] = { [] ¬ rope.Map[action: PutCharB] }; PutRopeBold: PROC [ rope: ROPE ] = { tc.looks['b] ¬ TRUE; [] ¬ rope.Map[action: PutCharB]; tc.looks['b] ¬ FALSE; }; PutRopeItalic: PROC [ rope: ROPE ] = { tc.looks['i] ¬ TRUE; [] ¬ rope.Map[action: PutCharB]; tc.looks['i] ¬ FALSE; }; EndNode: PROC [ delta: INTEGER ¬ 0, format: ATOM ¬ NIL ] = { tc.char ¬ '\n; tc.format ¬ format; tc.deltaLevel ¬ delta; tc.endOfNode ¬ TRUE; TiogaAccess.Put[writer, tc]; tc.endOfNode ¬ FALSE; }; EachNode: RedBlackTree.EachNode = { WITH data SELECT FROM base: BaseEntry => { WHILE ( base # NIL ) DO dependents: BaseEntryList ¬ base.dependents ¬ SortDependents[base.dependents]; SELECT TRUE FROM ( ( dependents = NIL ) AND ( NOT switches['u] ) ) => { NULL }; ( ( base.from = NIL ) AND ( NOT switches['v] ) ) => { NULL }; ENDCASE => { tc.format ¬ $block; PutRopeBold[base.name]; SELECT base.version.style FROM mob => { IF ( switches['m] ) THEN PutRope["[mob]"] }; ENDCASE => { NULL }; -- ??? PutRope[": "]; PutRopeItalic["("]; PutRopeItalic[IF ( base.from = NIL ) THEN "??" ELSE base.from]; PutRopeItalic[")"]; IF ( dependents = NIL ) THEN { EndNode[0, $block] } ELSE { EndNode[1, $block]; tc.format ¬ $ragged; WHILE ( dependents # NIL ) DO PutRope[dependents.first.name]; PutRope[" "]; dependents ¬ dependents.rest; ENDLOOP; EndNode[-1, $ragged]; }; }; IF ( suspectTo.Match[base.name, FALSE] ) THEN SIGNAL SuspectSeen; base ¬ base.next; ENDLOOP; }; ENDCASE => { NULL }; }; writer: TiogaAccess.Writer ¬ TiogaAccess.Create[]; tyme: BasicTime.GMT = BasicTime.Now[]; TiogaAccess.Put[writer, tc]; tc.propList ¬ NIL; tc.comment ¬ TRUE; tc.endOfNode ¬ FALSE; PutRope[outName]; EndNode[1]; PutRope[ IO.PutFR1["Copyright Ó %g by Xerox Corporation. All rights reserved.", [rope[Convert.RopeFromInt[BasicTime.Unpack[tyme].year]]] ] ]; EndNode[]; PutRope[IO.PutFR1["Written %g", [time[tyme]] ]]; EndNode[-1]; EndNode[]; tc.comment ¬ FALSE; RedBlackTree.EnumerateIncreasing[table, EachNode]; TiogaAccess.WriteFile[writer, outName]; }; MyData: TYPE = REF MyDataRep; MyDataRep: TYPE = RECORD [ table: RedBlackTree.Table ¬ NIL, abortRequested: BOOL ¬ FALSE, fsCareful: BOOL ¬ TRUE, errs: IO.STREAM ¬ NIL ]; EnsureProc: TYPE ~ PROC [objectName: ROPE, version: VersionStamp]; DependencyProc: TYPE ~ PROC [fromObject: ROPE, version: VersionStamp, toName: ROPE, toVersion: VersionStamp]; EachFile: GenerateDFClosure.ActionProc = { myData: MyData ¬ CheckAbort[data]; table: RedBlackTree.Table ¬ myData.table; EnterDependency: ENTRY DependencyProc = { <> ENABLE UNWIND => { NULL }; baseFrom: BaseEntry ¬ GetBaseRecord[table, fromObject, version]; baseTo: BaseEntry ¬ GetBaseRecord[table, toName, toVersion]; baseTo.dependents ¬ CONS[baseFrom, baseTo.dependents]; baseFrom.from ¬ FileNames.StripVersionNumber[from]; IF ( suspectFrom.Match[fromObject, FALSE] ) THEN SIGNAL SuspectSeen; IF ( suspectTo.Match[toName, FALSE] ) THEN SIGNAL SuspectSeen; }; EnsureFrom: ENTRY EnsureProc = { ENABLE UNWIND => { NULL }; base: BaseEntry ¬ GetBaseRecord[table, objectName, version]; IF ( base.from = NIL ) THEN base.from ¬ FileNames.StripVersionNumber[from]; }; ProcessObjectFile: PROC [ kind: ObjectStyle, shortName: ROPE, triesLeft: NAT ] ~ { <> shortName ¬ shortName.Flatten[len: ( shortName.Length[] - 4 )]; DO ENABLE PFS.Error => { SELECT error.code FROM $serverInaccessible => { MessagesOut.PutRopes[myData.errs, "Server glitch: ", name]; Process.Pause[Process.SecondsToTicks[pauseForServerGlitch]]; IF ( (triesLeft ¬ triesLeft.PRED) # 0 ) THEN LOOP; }; ENDCASE; MessagesOut.PutRopes[myData.errs, "File not found: ", name, IO.PutFR["\n from: %g\n reason: %g\n", IO.rope[from], IO.rope[error.explanation] ] ]; IF ( error.group # bug ) THEN EXIT ELSE REJECT; }; <> file: PFS.OpenFile; file ¬ PFS.Open[name: PFS.PathFromRope[name], wantedUniqueID: [egmt: [gmt: date.gmt, usecs: 0]] ]; <> { bytes: INT ~ file.GetInfo[].bytes; interval: VM.Interval ~ VM.SimpleAllocate[VM.PagesForBytes[bytes]]; p: LONG POINTER ~ VM.AddressForPageNumber[interval.page]; TRUSTED { [] ¬ file.Read[filePosition: 0, nBytes: bytes, to: p] }; SELECT kind FROM mob => TRUSTED { mob: MobDefs.MobBase ~ LOOPHOLE[p]; limit: CARD ~ bytes / BYTES[UNIT]; mobRes: MobMapper.AlterMobResults ¬ noop; mobRes ¬ MobMapper.AlterMob[mob, LOOPHOLE[mob], limit ! MobMapper.BadMobContents => { mobRes ¬ badVersion; CONTINUE} ]; IF mobRes = badVersion THEN MessagesOut.PutRopes[myData.errs, "MobMapper.AlterMob failed for ", name] ELSE MobCase[shortName, mob, EnsureFrom, EnterDependency]; }; ENDCASE => { NULL }; TRUSTED { VM.Free[interval] }; file.Close[]; }; EXIT; ENDLOOP; }; SELECT kind FROM file => { triesLeft: NAT ¬ triesForServerGlitch; shortName: ROPE ¬ FileNames.GetShortName[name]; SELECT TRUE FROM Rope.Match["*.mob", shortName, FALSE] => { ProcessObjectFile[mob, shortName, triesLeft]; }; ENDCASE => { NULL }; }; ENDCASE; }; <> CharSeq: TYPE = RECORD [ PACKED SEQUENCE COMPUTED CARD16 OF CHAR ]; RopeForNameRecord: PROC [ mob: MobDefs.MobBase, n: MobDefs.NameRecord ] RETURNS [ rope: ROPE ] = TRUSTED { ssb: MobDefs.NameString ~ LOOPHOLE[mob + mob.ssOffset.units]; index: CARD16 ~ n+4; ss: LONG POINTER TO CharSeq ~ LOOPHOLE[ssb]; len: CARD16 ~ ss[index]-0C; i: INT ¬ index.SUCC; GetFromNameString: SAFE PROC RETURNS [ char: CHAR ] = TRUSTED { char ¬ ss[i]; i ¬ i + 1 }; rope ¬ Rope.FromProc[len, GetFromNameString]; IF ( ( len > 0 ) AND ( rope.Fetch[len.PRED] = '. ) ) THEN rope ¬ rope.Substr[len: len.PRED]; -- ??? }; MobCase: PROC [ shortName: ROPE, mob: MobDefs.MobBase, ensureFrom: EnsureProc, enterDependency: DependencyProc ] ~ TRUSTED { DoOneFile: PROC [ fth: MobDefs.FTHandle, fti: MobDefs.FTIndex ] RETURNS [ BOOL ¬ FALSE ] = TRUSTED { <> file: ROPE ¬ RopeForNameRecord[mob, fth.name]; enterDependency[shortName, [mob[mob.version]], file, [mob[fth.version]]]; }; IF ( mob.nConfigs = 0 ) THEN [] ¬ ProcessFiles[mob, DoOneFile]; ensureFrom[shortName, [mob[mob.version]]]; }; ProcessFiles: PROC [ mob: MobDefs.MobBase, proc: PROC [MobDefs.FTHandle, MobDefs.FTIndex] RETURNS [BOOL] ] RETURNS [ fth: MobDefs.FTHandle, fti: MobDefs.FTIndex ] = TRUSTED { ftb: MobDefs.Base = LOOPHOLE[mob + mob.ftOffset.units]; FOR fti ¬ FIRST[MobDefs.FTIndex], fti + SIZE[MobDefs.FTRecord] UNTIL ( fti = mob.ftLimit ) DO fth ¬ @ftb[fti]; IF ( proc[fth, fti] ) THEN RETURN; ENDLOOP; RETURN[NIL, MobDefs.FTNull]; }; <> GetBaseRecord: INTERNAL PROC [ table: RedBlackTree.Table, file: ROPE, version: VersionStamp ] RETURNS [ base: BaseEntry ] = { old: BaseEntry ¬ NIL; WITH table.Lookup[file] SELECT FROM be: BaseEntry => WHILE ( be # NIL ) DO IF ( SameVersion[be.version, version] ) THEN { base ¬ be; RETURN }; old ¬ be; be ¬ be.next; ENDLOOP; ENDCASE; base ¬ NEW[BaseEntryRep ¬ [name: file, version: version]]; IF ( old # NIL ) THEN old.next ¬ base ELSE table.Insert[base, file]; }; SameVersion: PROC [ first: VersionStamp, second: VersionStamp ] RETURNS [ yup: BOOL ¬ FALSE ] ~ { IF ( first.style # second.style ) THEN RETURN; WITH first SELECT FROM left: mob VersionStamp => { WITH second SELECT FROM right: mob VersionStamp => { IF ( left # right ) THEN RETURN }; ENDCASE => { ERROR }; }; ENDCASE => { ERROR }; yup ¬ TRUE; }; GetKey: RedBlackTree.GetKey = { RETURN [data] }; Compare: RedBlackTree.Compare = { key: ROPE ¬ WITH k SELECT FROM base: BaseEntry => base.name, rope: ROPE => rope, ENDCASE => ERROR; WITH data SELECT FROM base: BaseEntry => { RETURN [key.Compare[base.name, FALSE]] }; ENDCASE => { ERROR }; }; CompareEntries: List.CompareProc = { WITH ref1 SELECT FROM base1: BaseEntry => { WITH ref2 SELECT FROM base2: BaseEntry => { RETURN [base1.name.Compare[base2.name, FALSE]] }; ENDCASE => { ERROR }; }; ENDCASE => { ERROR }; }; SortDependents: PROC [list: BaseEntryList] RETURNS [BaseEntryList] = TRUSTED { <> RETURN [LOOPHOLE[List.Sort[LOOPHOLE[list], CompareEntries]]]; }; <> CheckAbort: PROC [ref: REF] RETURNS [MyData] = { WITH ref SELECT FROM mine: MyData => { SELECT TRUE FROM mine.abortRequested => { NULL }; ENDCASE => RETURN [mine]; }; ENDCASE => { NULL }; ERROR ABORTED; }; DependenciesCommandProc: Commander.CommandProc = { out: IO.STREAM = cmd.out; switches: Switches ¬ ALL[FALSE]; ProcessSwitches: PROC [arg: ROPE] = { sense: BOOL ¬ TRUE; FOR index: INT IN [0..arg.Length[]) DO char: CHAR ¬ arg.Fetch[index]; SELECT char FROM '- => { LOOP }; '~ => { sense ¬ NOT sense; LOOP }; IN ['a..'z] => switches[char] ¬ sense; IN ['A..'Z] => switches[char + ('a-'A)] ¬ sense; ENDCASE; sense ¬ TRUE; ENDLOOP; }; ProcessArgument: PROC [arg: ROPE] = { table: RedBlackTree.Table ¬ RedBlackTree.Create[getKey: GetKey, compare: Compare]; myData: MyData ¬ NEW[MyDataRep ¬ [table: table, fsCareful: NOT switches['f], errs: out]]; dfName: ROPE ¬ arg.Concat[".df"]; outName: ROPE ¬ arg.Concat[".depends"]; Process.CheckForAbort[]; IO.PutF1[out, "Generating dependencies for %g\n", IO.rope[dfName] ]; IO.PutF1[out, " %g\n", IO.time[] ]; [] ¬ GenerateDFClosure.GenerateClosureToProc[dfName, out, EachFile, myData, [toFork: forkers, followImports: NOT switches['s] ] ! ABORTED, UNWIND => { myData.abortRequested ¬ TRUE } ]; <> IO.PutF1[out, "\nWriting dependencies to %g", IO.rope[outName] ]; DumpOutput[outName, table, switches]; IO.PutF1[out, ".\n\n%g\n", IO.time[] ]; }; argsProcessed: NAT ¬ 0; argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd: cmd ! CommanderOps.Failed => { msg ¬ errorMsg; GO TO failed } ]; ProcessSwitches[UserProfile.Token["Dependencies.DefaultSwitches"]]; FOR i: NAT IN [1..argv.argc) DO arg: ROPE ~ argv[i]; Process.CheckForAbort[]; IF ( arg.Length[] = 0 ) THEN LOOP; IF ( arg.Fetch[0] = '- ) THEN { ProcessSwitches[arg]; LOOP }; ProcessArgument[arg]; ENDLOOP; EXITS failed => { result ¬ $Failure }; }; doc: ROPE = "{switch | item}*\nGenerates object file dependency list -m: mobs (qualify entries with [MOB]) -s: shallow (don't follow imports) -u: show NIL dependents -v: show files not from DF files -f: use fs caching when reading files"; Commander.Register["Dependencies", DependenciesCommandProc, doc]; END.