kpk@gitpyr.UUCP (Kevin P. Kleinfelter) (08/31/86)
I am posting source to a simple version of "make" written in Modula-2. Feel free to use and modify it. If you improve it, please post your improvements.
kpk@gitpyr.UUCP (Kevin P. Kleinfelter) (08/31/86)
Batch file for "make". Store this as "make.bat" ----- CUT HERE ----- m2 \sys\make %1 if not errorlevel 0 goto error $make.bat :error pause error has occured
kpk@gitpyr.UUCP (Kevin P. Kleinfelter) (08/31/86)
Compile the following, and store the ".lod" file as "\sys\make.lod". ----- CUT HERE ------ MODULE Make; (* A simple version of the UNIX "make" utility. Handles only the following input lines file1 : file2 file3 ... ; action file1 : file2 file1 : ; action To use this program you must create a \SYS directory and place "make.bat" in it. This program creates and executes $MAKE.BAT in the \SYS directory. To invoke the program type make makefile or make makefile/n (The /n option may have no leading blanks, and performs no updates. *) IMPORT Options; FROM SYSTEM IMPORT ADR, DOSCALL; FROM Storage IMPORT ALLOCATE; IMPORT ASCII; FROM InOut IMPORT Read, ReadString, termCH, WriteCard, WriteString, WriteLn; FROM ErrorCode IMPORT SetErrorCode; FROM FileSystem IMPORT ReadChar, WriteChar, WriteNBytes, File, Close, Response, Lookup; CONST MaximumNumberOfFiles = 100; StringLength = 100; TYPE TimeStamp = RECORD Date:CARDINAL; Time:CARDINAL; END; (* TimeStamp *) StringType = ARRAY [0..StringLength] OF CHAR; TableIndexType = [1..MaximumNumberOfFiles]; DependencyPointer = POINTER TO DependencyNodeType; DependencyNodeType = RECORD DependsOn:TableIndexType; Next:DependencyPointer; END; (* DependencyNodeType *) ActionPointer = POINTER TO ActionRecord; ActionRecord = RECORD Action:StringType; Next:ActionPointer; END; (* ActionRecord *) TableNode = RECORD FileName: StringType; FileDate:TimeStamp; (* Date and time *) ActionList:ActionPointer; DependencyList:DependencyPointer; (* files this one depends on *) IsARoot:BOOLEAN; (* No one depends on this one *) SubgraphChecked:BOOLEAN; END; (* TableNode *) VAR MakeFileName:StringType; OutputFile:File; CurrentTimeStamp:TimeStamp; Table:ARRAY TableIndexType OF TableNode; (* Table of dependencies *) FreeIndex:TableIndexType; (* Index of first free in Table *) (* "switch" values *) NoExecute:BOOLEAN; PROCEDURE Error(S:ARRAY OF CHAR); BEGIN (* Error *) WriteString(S); WriteLn; SetErrorCode(1); END Error; PROCEDURE Equal(S1,S2:ARRAY OF CHAR):BOOLEAN; VAR I:CARDINAL; BEGIN (* Equal *) I := 0; LOOP IF (I > HIGH (S1)) THEN IF (I > HIGH (S2)) THEN RETURN TRUE; ELSE RETURN FALSE; END; (* IF *) ELSIF I > HIGH (S2) THEN RETURN FALSE; END; (* IF *) IF S1[I] = S2[I] THEN IF S1[I] = 0C THEN RETURN TRUE; END; (* IF *) INC(I); ELSE RETURN FALSE; END; (* IF *) END; (* LOOP *) END Equal; PROCEDURE GetTimeStamp(Name:ARRAY OF CHAR; VAR S:TimeStamp); VAR Handle:CARDINAL; ErrorC:CARDINAL; BEGIN (* GetTimeStamp *) (* Create a file handle *) DOSCALL(3DH, ADR(Name), 00, Handle, ErrorC); IF ErrorC = 0 THEN ELSIF ErrorC = 2 THEN S.Date := 0; S.Time := 0; RETURN; ELSE HALT; END; (* IF *) (* Get time stamp *) DOSCALL(57H, Handle, 00, S.Date, S.Time, ErrorC); IF ErrorC # 0 THEN HALT; END; (* IF *) (* Close handle *) DOSCALL(3EH, Handle, ErrorC); IF ErrorC # 0 THEN HALT; END; (* IF *) END GetTimeStamp; PROCEDURE PlaceInTable (F:StringType; VAR Ix:TableIndexType); VAR I:TableIndexType; BEGIN (* PlaceInTable *) I := 1; WHILE I < FreeIndex DO IF Equal(F,Table[I].FileName) THEN Ix := I; RETURN; END; (* IF *) INC (I); END; (* WHILE *) WITH Table[FreeIndex] DO FileName := F; GetTimeStamp(F,FileDate); ActionList := NIL; DependencyList := NIL; IsARoot := TRUE; SubgraphChecked := FALSE; END; (* WITH *) Ix := FreeIndex; INC (FreeIndex); END PlaceInTable; PROCEDURE ProcessMakeFile(Name:StringType); VAR F:File; Token1:StringType; Token2:StringType; Ch:CHAR; I:CARDINAL; PROCEDURE ReadName(VAR S:StringType); VAR Ix:CARDINAL; BEGIN (* ReadName *) Ix := 0; WHILE NOT F.eof & (Ch # ASCII.EOL) & (Ch # ':') & (Ch # ' ') DO S[Ix] := Ch; INC(Ix); ReadChar(F,Ch); END; (* WHILE *) S[Ix] := 0C; END ReadName; BEGIN (* ProcessMakeFile *) Lookup(F, Name, FALSE); IF NOT (F.res = done) THEN Error('make file not found'); RETURN; END; (* IF *) ReadChar(F,Ch); WHILE NOT F.eof DO ReadName(Token1); IF F.eof THEN Error('Illegal line in makefile (1)'); Close(F); RETURN; END; (* IF *) WHILE (Ch # ':') AND (Ch # ';') AND NOT F.eof DO ReadChar(F,Ch); END; (* WHILE *) IF F.eof THEN Error('Illegal line in makefile (2)'); Close(F); RETURN; END; (* IF *) IF Ch = ':' THEN Ch := ' '; END; (* IF *) WHILE (Ch # ASCII.EOL) AND (NOT F.eof) AND (Ch # ';') DO WHILE (Ch = ' ') AND (NOT F.eof) DO ReadChar(F,Ch); END; (* WHILE *) IF Ch # ';' THEN ReadName(Token2); AddDependency(Token1,Token2); END; (* IF *) END; (* WHILE *) IF Ch = ';' THEN Ch := ' '; WHILE (Ch = ' ') AND (NOT F.eof) DO ReadChar(F,Ch); END; (* WHILE *) I := 0; WHILE (Ch # ASCII.EOL) AND (NOT F.eof) DO Token2[I] := Ch; INC(I); ReadChar(F,Ch); END; (* WHILE *) Token2[I] := 0C; IF I # 0 THEN AddAction(Token1,Token2); END; (* IF *) END; (* IF *) IF Ch = ASCII.EOL THEN ReadChar(F,Ch); END; (* IF *) END; (* WHILE *) Close(F); TraverseAllTrees; END ProcessMakeFile; PROCEDURE TimeStampGreaterThan (TS1,TS2:TimeStamp):BOOLEAN; BEGIN (* TimeStampGreaterThan *) IF TS1.Date > TS2.Date THEN RETURN TRUE; ELSIF TS1.Date < TS2.Date THEN RETURN FALSE ELSE RETURN TS1.Time > TS2.Time END; (* IF *) END TimeStampGreaterThan; PROCEDURE TraverseThisTree(Root:TableIndexType); VAR I:TableIndexType; Tmp:DependencyPointer; ActionNeeded:BOOLEAN; BEGIN (* TraverseThisTree *) IF Table[Root].SubgraphChecked THEN RETURN; END; (* IF *) Table[Root].SubgraphChecked := TRUE; Tmp := Table[Root].DependencyList; ActionNeeded := FALSE; WHILE Tmp # NIL DO TraverseThisTree(Tmp^.DependsOn); IF NOT TimeStampGreaterThan(Table[Root].FileDate, Table[Tmp^.DependsOn].FileDate) THEN Table[Root].FileDate := CurrentTimeStamp; ActionNeeded := TRUE; END; (* IF *) Tmp := Tmp^.Next; END; (* WHILE *) IF ActionNeeded THEN TakeTheseActions(Root); END; (* IF *) END TraverseThisTree; PROCEDURE AddAction(F:StringType;A:StringType); VAR Ix:TableIndexType; Temp:ActionPointer; BEGIN (* AddAction *) PlaceInTable(F,Ix); NEW(Temp); Temp^.Next := Table[Ix].ActionList; Temp^.Action := A; Table[Ix].ActionList := Temp; END AddAction; PROCEDURE TraverseAllTrees; VAR Ix:TableIndexType; BEGIN (* TraverseAllTrees *) FOR Ix := 1 TO (FreeIndex - 1) DO IF Table[Ix].IsARoot THEN TraverseThisTree(Ix); END; (* IF *) END; (* FOR *) END TraverseAllTrees; PROCEDURE AddDependency (F1,F2:StringType); VAR Ix1:TableIndexType; Ix2:TableIndexType; Temp:DependencyPointer; BEGIN (* AddDependency *) PlaceInTable(F1,Ix1); PlaceInTable(F2,Ix2); IF DependencyExists(Ix1,Ix2) THEN RETURN; END; (* IF *) NEW (Temp); Temp^.DependsOn := Ix2; Temp^.Next := Table[Ix1].DependencyList; Table[Ix1].DependencyList := Temp; Table[Ix2].IsARoot := FALSE; END AddDependency; PROCEDURE DependencyExists(Ix1,Ix2:TableIndexType):BOOLEAN; VAR Temp:DependencyPointer; BEGIN (* DependencyExists *) Temp := Table[Ix1].DependencyList; WHILE (Temp # NIL) & (Temp^.DependsOn # Ix2) DO Temp := Temp^.Next; END; (* WHILE *) RETURN (Temp # NIL); END DependencyExists; PROCEDURE WriteCharacterString(S:ARRAY OF CHAR); VAR Dummy:CARDINAL; BEGIN (* WriteCharacterString *) WriteNBytes(OutputFile,ADR(S), HIGH(S)+1, Dummy); END WriteCharacterString; PROCEDURE TakeTheseActions(I:TableIndexType); VAR Tmp:ActionPointer; Ix:CARDINAL; BEGIN (* TakeTheseActions *) Tmp := Table[I].ActionList; WHILE (Tmp # NIL) DO Ix := 0; IF NoExecute THEN WriteChar(OutputFile,'E'); WriteChar(OutputFile,'C'); WriteChar(OutputFile,'H'); WriteChar(OutputFile,'O'); WriteChar(OutputFile,' '); END; (* IF *) WHILE Tmp^.Action[Ix] # 0C DO WriteChar(OutputFile,Tmp^.Action[Ix]); INC(Ix); END; (* WHILE *) WriteChar(OutputFile,ASCII.EOL); Tmp := Tmp^.Next; WriteCharacterString('IF NOT ERRORLEVEL 0 GOTO ERROR'); WriteChar(OutputFile,ASCII.EOL); END; (* WHILE *) END TakeTheseActions; PROCEDURE Initialize():BOOLEAN; VAR Year:CARDINAL; MonthDay:CARDINAL; HourMinute:CARDINAL; SecondsMilliseconds:CARDINAL; Ch:CHAR; T:Options.Termination; R:Options.NamePartSet; S:StringType; Len:CARDINAL; BEGIN (* Initialize *) WriteString('Make begins ...'); WriteLn; FreeIndex := 1; (* Initialize CurrentTimeStamp *) DOSCALL(2AH, Year, MonthDay); DOSCALL(2CH, HourMinute, SecondsMilliseconds); CurrentTimeStamp.Date := ((Year - 1980) * 200H) + (MonthDay DIV 100H) * 20H + (MonthDay MOD 20H); CurrentTimeStamp.Time := (HourMinute DIV 100H) * 800H + (HourMinute MOD 100H) * 20H + ((SecondsMilliseconds DIV 100H) + 2) DIV 2; WriteString("make file name ->"); Options.FileNameAndOptions ("makefile",MakeFileName,T,TRUE,R); WriteLn; IF (T = Options.can) OR (T = Options.esc) THEN RETURN(FALSE); END; (* IF *) Options.GetOption(S,Len); IF Len > 0 THEN Ch := S[0]; ELSE Ch := ' '; END; (* IF *) IF CAP(Ch) = 'N' THEN NoExecute := TRUE; ELSE NoExecute := FALSE; END; (* IF *) Lookup(OutputFile,'\SYS\$MAKE.BAT',TRUE); RETURN(TRUE); END Initialize; PROCEDURE Terminate; BEGIN (* Terminate *) WriteCharacterString('GOTO EXIT'); WriteChar(OutputFile,ASCII.EOL); WriteCharacterString(':ERROR'); WriteChar(OutputFile,ASCII.EOL); WriteCharacterString('PAUSE Terminated due to non-zero return code'); WriteChar(OutputFile,ASCII.EOL); WriteCharacterString(':EXIT'); WriteChar(OutputFile,ASCII.EOL); Close(OutputFile); WriteString('Make ends'); WriteLn; END Terminate; BEGIN (* Make *) IF Initialize() THEN ProcessMakeFile(MakeFileName); Terminate; END; (* IF *) END Make.