Dependencies.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) October 31, 1985 1:22:23 pm PST
DIRECTORY
BasicTime,
BcdDefs,
BcdOps,
Commander,
CommandTool,
FileNames,
FS,
FSBackdoor,
GenerateDFClosure,
IO,
List,
MessagesOut,
Process,
RedBlackTree,
Rope,
TiogaAccess,
UserProfile,
VM;
Dependencies: CEDAR MONITOR
IMPORTS BcdOps, Commander, CommandTool, FileNames, FS, FSBackdoor, GenerateDFClosure, IO, List, MessagesOut, Process, RedBlackTree, Rope, TiogaAccess, UserProfile, VM
= BEGIN
GMT: TYPE = BasicTime.GMT;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
suspectFrom: ROPENIL;
suspectTo: ROPENIL;
SuspectSeen: SIGNAL = CODE;
MyData: TYPE = REF MyDataRep;
MyDataRep: TYPE = RECORD [
table: RedBlackTree.Table ← NIL,
abortRequested: BOOLFALSE,
errs: STREAMNIL
];
BaseEntry: TYPE = REF BaseEntryRep;
BaseEntryList: TYPE = LIST OF BaseEntry;
BaseEntryRep: TYPE = RECORD [
name: ROPE,
from: ROPENIL,
version: BcdDefs.VersionStamp,
dependents: BaseEntryList ← NIL,
next: BaseEntry ← NIL
];
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
DependenciesCommandProc: Commander.CommandProc = {
[cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...]
out: STREAM = cmd.out;
switches: PACKED ARRAY CHAR['a..'z] OF BOOLALL[FALSE];
ProcessSwitches: PROC [arg: ROPE] = {
sense: BOOLTRUE;
FOR index: INT IN [0..Rope.Length[arg]) DO
char: CHAR ← Rope.Fetch[arg, 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, errs: out]];
dfName: ROPE ← Rope.Concat[arg, ".df"];
outName: ROPE ← Rope.Concat[arg, ".depends"];
Process.CheckForAbort[];
[] ← GenerateDFClosure.GenerateClosureToProc[dfName, out, EachFile, myData, [toFork: forkers, followImports: NOT switches['s]]
! ABORTED, UNWIND => myData.abortRequested ← TRUE];
Now that we have out data structure, we should print it.
First Char:
[charSet: 0, char: '\n, looks: LOOKS[], format: NIL, comment: FALSE, endOfNode: TRUE, deltaLevel: 1, propList: LIST[^[key: $FromTiogaFile, val: $Yes]]]
Comment Char:
[charSet: 0, char: 'F, looks: LOOKS[], format: $code, comment: TRUE, endOfNode: FALSE, deltaLevel: 0, propList: NIL]
Normal Char:
[charSet: 0, char: 'F, looks: LOOKS[], format: $code, comment: FALSE, endOfNode: FALSE, deltaLevel: 0, propList: NIL]
Comment end of node (next node nested):
[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: List.PutAssoc[key: $FromTiogaFile, val: $Yes, aList: NIL]
];
PutRope: PROC [rope: ROPE] = {
[] ← Rope.Map[base: rope, action: PutCharB];
};
PutRopeBold: PROC [rope: ROPE] = {
tc.looks['b] ← TRUE;
[] ← Rope.Map[base: rope, action: PutCharB];
tc.looks['b] ← FALSE;
};
PutRopeItalic: PROC [rope: ROPE] = {
tc.looks['i] ← TRUE;
[] ← Rope.Map[base: rope, action: PutCharB];
tc.looks['i] ← FALSE;
};
PutCharB: Rope.ActionType = {
[c: CHAR] RETURNS [quit: BOOL ← FALSE]
tc.char ← c;
TiogaAccess.Put[writer, tc];
};
EndNode: PROC [delta: INTEGER ← 0, format: ATOMNIL] = {
tc.endOfNode ← TRUE;
tc.char ← '\n;
tc.format ← format;
tc.deltaLevel ← delta;
TiogaAccess.Put[writer, tc];
tc.endOfNode ← FALSE;
};
eachNode: RedBlackTree.EachNode = {
[data: RedBlackTree.UserData] RETURNS [stop: BOOL ← FALSE]
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] => {};
base.from = NIL AND NOT switches['v] => {};
ENDCASE => {
tc.format ← $block;
PutRopeBold[base.name];
PutRope[": "];
PutRopeItalic["("];
IF base.from = NIL
THEN PutRopeItalic["??"]
ELSE PutRopeItalic[base.from];
PutRopeItalic[")"];
EndNode[1, $block];
tc.format ← $block;
WHILE dependents # NIL DO
PutRope[dependents.first.name];
PutRope[" "];
dependents ← dependents.rest;
ENDLOOP;
EndNode[-1, $indent];
};
IF Rope.Match[suspectTo, base.name, FALSE] THEN SIGNAL SuspectSeen;
base ← base.next;
ENDLOOP;
ENDCASE;
};
writer: TiogaAccess.Writer ← TiogaAccess.Create[];
TiogaAccess.Put[writer, tc];
tc.comment ← TRUE;
tc.endOfNode ← FALSE;
tc.propList ← NIL;
PutRope[outName];
EndNode[];
EndNode[];
tc.comment ← FALSE;
IO.PutF1[out, "Writing dependencies to %g", [rope[outName]] ];
RedBlackTree.EnumerateIncreasing[table, eachNode];
TiogaAccess.WriteFile[writer, outName];
IO.PutRope[out, ".\n"];
};
};
argsProcessed: NAT ← 0;
# of arguments processed
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd: cmd, starExpand: FALSE
! CommandTool.Failed => {msg ← errorMsg; GO TO failed}];
When parsing the command line, be prepared for failure. The error is reported to the user
ProcessSwitches[UserProfile.Token["Dependencies.DefaultSwitches"]];
Allows the user to specify personal defaults for the switches via the user's profile.
FOR i: NAT IN [1..argv.argc) DO
Each argument can either be a switch specification or a genuine argument to be processed. The first argument (argv[0]) is not examined, because by convention it is the name of the command as given by the user.
arg: ROPE = argv[i];
Process.CheckForAbort[];
It is a good idea to periodically check for a process abort request.
IF Rope.Length[arg] = 0 THEN LOOP;
Ignore null arguments (it is not easy to generate them, even).
IF Rope.Fetch[arg, 0] = '- THEN {
This argument sets switches for the remaining patterns. By convention, switches are normally "sticky", in that they stay set until explicitly changed.
ProcessSwitches[arg];
LOOP;
};
ProcessArgument[arg];
Perform whatever processing is necessary for a normal argument.
ENDLOOP;
EXITS
failed => {result ← $Failure};
};
EachFile: GenerateDFClosure.ActionProc = {
[data: REF ANY, kind: GenerateDFClosure.ActionKind, name: ROPE, date: DFUtilities.Date, from: ROPE]
myData: MyData ← CheckAbort[data];
table: RedBlackTree.Table ← myData.table;
EnterDependency: ENTRY PROC [bcdName: ROPE, version: BcdDefs.VersionStamp, to: ROPE, toVersion: BcdDefs.VersionStamp] = {
The from file depends on the to file. The version stamps are present to remove name ambiguity.
ENABLE UNWIND => NULL;
baseFrom: BaseEntry ← GetBaseRecord[bcdName, version];
baseTo: BaseEntry ← GetBaseRecord[to, toVersion];
baseTo.dependents ← CONS[baseFrom, baseTo.dependents];
baseFrom.from ← FileNames.StripVersionNumber[from];
IF Rope.Match[suspectFrom, bcdName, FALSE] THEN SIGNAL SuspectSeen;
IF Rope.Match[suspectTo, to, FALSE] THEN SIGNAL SuspectSeen;
};
EnsureFrom: ENTRY PROC [bcdName: ROPE, version: BcdDefs.VersionStamp] = {
ENABLE UNWIND => NULL;
base: BaseEntry ← GetBaseRecord[bcdName, version];
IF base.from = NIL THEN base.from ← FileNames.StripVersionNumber[from];
};
GetBaseRecord: INTERNAL PROC [file: ROPE, version: BcdDefs.VersionStamp] RETURNS [base: BaseEntry] = {
old: BaseEntry ← NIL;
WITH RedBlackTree.Lookup[table, file] SELECT FROM
be: BaseEntry => {
WHILE be # NIL DO
IF be.version = version THEN RETURN [be];
old ← be;
be ← be.next;
ENDLOOP;
};
ENDCASE;
base ← NEW[BaseEntryRep ← [name: file, version: version]];
IF old = NIL
THEN RedBlackTree.Insert[table, base, file]
ELSE old.next ← base;
};
SELECT kind FROM
file => {
triesLeft: NAT ← triesForServerGlitch;
shortName: ROPE ← FileNames.GetShortName[name];
IF Rope.Match["*.bcd", shortName, FALSE] THEN {
shortName ← Rope.Flatten[shortName, 0, Rope.Length[shortName]-4];
DO
ENABLE
FS.Error => {
SELECT error.code FROM
$serverInaccessible => {
MessagesOut.PutRopes[myData.errs, "Server glitch: ", name];
Process.Pause[Process.SecondsToTicks[pauseForServerGlitch]];
IF (triesLeft ← triesLeft - 1) # 0 THEN LOOP;
};
ENDCASE;
MessagesOut.PutRopes[
myData.errs, "File not found: ", name,
IO.PutFR["\n from: %g\n reason: %g\n",
[rope[from]], [rope[error.explanation]]]];
IF error.group # bug THEN EXIT ELSE REJECT;
};
Process this file for dependencies
file: FS.OpenFile;
tempName: ROPE ← name;
useTemp: BOOLNOT IsInFileCache[name, date.gmt];
IF useTemp THEN
tempName ← FS.Copy[
from: name,
to: "///Temp/Dependencies.temp$",
setKeep: TRUE,
keep: 20,
wantedCreatedTime: date.gmt,
remoteCheck: FALSE
];
file ← FS.Open[name: tempName, wantedCreatedTime: date.gmt, remoteCheck: FALSE];
TRUSTED {
pages: INTFS.GetInfo[file].pages;
interval: VM.Interval ← VM.SimpleAllocate[pages];
bcd: BcdDefs.BcdBase ← VM.AddressForPageNumber[interval.page];
RopeForNameRecord: PROC [bcd: BcdDefs.BcdBase, name: BcdDefs.NameRecord] RETURNS [r: ROPE] = TRUSTED {
ssb: BcdDefs.NameString = LOOPHOLE[bcd + bcd.ssOffset];
len: NAT;
i: INT ← name;
GetFromNameString: SAFE PROC RETURNS [char: CHAR] = TRUSTED {
char ← ssb.string[i]; i ← i + 1};
r ← Rope.FromProc[ssb.size[name], GetFromNameString];
len ← r.Length[];
IF len > 0 AND r.Fetch[len-1] = '. THEN r ← r.Substr[len: len-1];
};
DoOneFile: PROC [fth: BcdDefs.FTHandle, fti: BcdDefs.FTIndex] RETURNS [BOOLFALSE] = TRUSTED {
file: ROPE ← RopeForNameRecord[bcd, fth.name];
EnterDependency[shortName, bcd.version, file, fth.version];
};
FS.Read[file: file, from: 0, nPages: pages, to: bcd];
Now insert the dependencies for this file
IF bcd.nConfigs = 0 THEN [] ← BcdOps.ProcessFiles[bcd, DoOneFile];
EnsureFrom[shortName, bcd.version];
VM.Free[interval];
FS.Close[file];
IF useTemp THEN FS.Delete[tempName];
};
EXIT;
ENDLOOP;
};
};
ENDCASE;
};
IsInFileCache: PUBLIC PROC [name: ROPE, gmt: GMT] RETURNS [inCache: BOOLFALSE] = {
cacheChecker: FSBackdoor.InfoProc = {
[fullGName: ROPE, created: BasicTime.GMT, bytes: INT, keep: CARDINAL]
RETURNS [continue: BOOL]
IF bytes > 0 THEN {
IF gmt # BasicTime.nullGMT THEN {
We will only accept a specific date
IF created # gmt THEN RETURN [TRUE];
};
At this point we will either accept anything, or we have a match on the time.
inCache ← TRUE;
RETURN [FALSE];
};
RETURN [TRUE];
};
FSBackdoor.EnumerateCacheForInfo[cacheChecker, NIL, name];
};
GetKey: RedBlackTree.GetKey = {
[data: RedBlackTree.UserData] RETURNS [RedBlackTree.Key]
RETURN [data];
};
Compare: RedBlackTree.Compare = {
[k: RedBlackTree.Key, data: RedBlackTree.UserData] RETURNS [Basics.Comparison]
key: ROPENIL;
WITH k SELECT FROM
base: BaseEntry => key ← base.name;
rope: ROPE => key ← rope;
ENDCASE => ERROR;
WITH data SELECT FROM
base: BaseEntry => RETURN [Rope.Compare[key, base.name, FALSE]];
ENDCASE;
ERROR;
};
CompareEntries: List.CompareProc = {
[ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison]
WITH ref1 SELECT FROM
base1: BaseEntry =>
WITH ref2 SELECT FROM
base2: BaseEntry =>
RETURN [Rope.Compare[base1.name, base2.name, FALSE]];
ENDCASE;
ENDCASE;
ERROR;
};
SortDependents: PROC [list: BaseEntryList] RETURNS [BaseEntryList] = TRUSTED {
We can use List.Sort because BaseEntryList is a list of REFs, and List.Sort only plays with the rest fields, not the first fields.
RETURN [LOOPHOLE[List.Sort[LOOPHOLE[list], CompareEntries]]];
};
CheckAbort: PROC [ref: REF] RETURNS [MyData] = {
WITH ref SELECT FROM
mine: MyData => {
SELECT TRUE FROM
mine.abortRequested => {};
ENDCASE => RETURN [mine];
};
ENDCASE;
ERROR ABORTED;
};
doc: ROPE = "{switch | item}*\nGenerates object file dependency list
-s: shallow (don't follow imports)
-u: show NIL dependents
-v: show files not from DF files";
Commander.Register[
key: "Dependencies",
proc: DependenciesCommandProc,
doc: doc,
clientData: NIL,
interpreted: TRUE
];
END.