program Make; {$mode objfpc}{$H+} uses {$IFDEF UNIX}{$IFDEF UseCThreads} cthreads, {$ENDIF}{$ENDIF} Classes, SysUtils, CustApp { you can add units after this }, RegExpr, Math, Process; type { TMake } TMake = class(TCustomApplication) protected procedure DoRun; override; public constructor Create(TheOwner: TComponent); override; procedure WriteHelp; virtual; end; TAbhArt = (aaFinal,aaEinsZuEins,aaNZuN,aaIgnore); TDateienMitDaten = array of ^TDateiMitDatum; tStringlistBArray = array[boolean] of tStringlist; { TAbhaengigkeit } TAbhaengigkeit = class(TObject) AbhArt: TAbhArt; QuellenRE: TRegExpr; ZieleFkt, Befehle: TStringlist; Quellen, Ziele: TDateienMitDaten; Erben: array of ^TAbhaengigkeit; Prioritaet: Integer; constructor create; overload; constructor create(Original: TAbhaengigkeit); overload; destructor destroy; override; end; TAbhaengigkeiten = array of ^TAbhaengigkeit; TDateiMitDatum = record Name: AnsiString; Datum: TDateTime; end; { TAbhaengigkeit } constructor TAbhaengigkeit.create; begin inherited create; QuellenRE:=TRegExpr.create; QuellenRE.Expression:=''; ZieleFkt:=TStringlist.create; SetLength(Quellen,0); SetLength(Ziele,0); Befehle:=TStringlist.create; SetLength(Erben,0); AbhArt:=aaNZuN; Prioritaet:=42; end; constructor TAbhaengigkeit.create(Original: TAbhaengigkeit); var i: integer; begin inherited create; QuellenRE:=TRegExpr.create; QuellenRE.Expression:=Original.QuellenRE.Expression; ZieleFkt:=TStringlist.create; ZieleFkt.Text:=Original.ZieleFkt.Text; SetLength(Quellen,length(Original.Quellen)); for i:=0 to length(Original.Quellen)-1 do Quellen[i]:=Original.Quellen[i]; SetLength(Ziele,length(Original.Ziele)); for i:=0 to length(Original.Ziele)-1 do Ziele[i]:=Original.Ziele[i]; Befehle:=TStringlist.create; Befehle.Text:=Original.Befehle.Text; SetLength(Erben,length(Original.Erben)); for i:=0 to length(Original.Erben)-1 do Erben[i]:=Original.Erben[i]; Prioritaet:=Original.Prioritaet; AbhArt:=Original.AbhArt; end; destructor TAbhaengigkeit.destroy; begin QuellenRE.free; SetLength(Quellen,0); SetLength(Ziele,0); ZieleFkt.free; Befehle.free; SetLength(Erben,0); inherited destroy; end; function testeSummen(sumNam: string): tStringlistBArray; var p: tProcess; rb,i: longint; s: string; erg: tStringlist; gut: boolean; const leseHappen=65536; begin p:=tProcess.create(nil); p.Executable:='/usr/bin/sha512sum'; p.Parameters.add('-c'); p.Parameters.add(sumNam); p.Options:=p.Options + [poUsePipes]; p.Execute; rb:=0; s:=''; while p.Running do begin if p.Output.NumBytesAvailable=0 then sleep(10) else begin setlength(s,rb+leseHappen); rb:=rb+p.Output.Read(s[rb+1],leseHappen); end; end; if p.Output.NumBytesAvailable>0 then begin setlength(s,rb+leseHappen); rb:=rb+p.Output.Read(s[rb+1],leseHappen); end; p.free; setlength(s,rb); erg:=tStringlist.create; erg.text:=s; s:=''; for gut:=false to true do begin result[gut]:=tStringlist.create; rb:=0; for i:=0 to erg.count-1 do if not gut xor (rightStr(erg[i],4)=': OK') then begin result[gut].add(leftStr(erg[i],pos(':',erg[i])-1)); inc(rb); end; end; erg.free; end; function liesZeile(var dat: textfile; out s: string): boolean; begin repeat result:=not eof(dat); if not result then exit; readln(dat,s); if pos('#',s)>0 then delete(s,pos('#',s),length(s)); while (length(s)>0) and (s[length(s)] in [#9,' ']) do delete(s,length(s),1); until s<>''; end; procedure sammleDateien(wo: string; rekursiv: boolean; var dats: TDateienMitDaten); var sr: TSearchRec; err: integer; begin err:=FindFirst(wo,faReadOnly or faHidden or faSysFile or (byte(rekursiv)*faDirectory),sr); while err=0 do begin if sr.Attr and faDirectory <> 0 then begin if rekursiv and (sr.Name<>'.') and (sr.Name<>'..') then sammleDateien(extractfilepath(wo)+sr.Name+'/'+extractfilename(wo),rekursiv,dats); end else begin setlength(dats,length(dats)+1); getmem(dats[length(dats)-1],sizeof(TDateiMitDatum)); fillchar(dats[length(dats)-1]^,sizeof(TDateiMitDatum),0); dats[length(dats)-1]^.Name:=extractfilepath(wo)+sr.name; dats[length(dats)-1]^.Datum:=FileDateTodateTime(FileAge(dats[length(dats)-1]^.Name)); end; err:=FindNext(sr); end; FindClose(sr); end; function anzCs(c: char; s: string): longint; var i: longint; begin result:=0; for i:=1 to length(s) do if s[i]=c then inc(result); end; function notQuotedPos(was,worin: string): longint; begin result:=0; repeat if pos(was,copy(worin,result+1,length(worin)-result))=0 then begin result:=0; exit; end; result:=result+pos(was,copy(worin,result+1,length(worin)-result)); if not odd(anzCs('"',copy(worin,1,result-1))) then exit; until false; end; function unescapedpos(was,worin: string): integer; var Ebene: longint; begin result:=0; Ebene:=0; while pos(was,copy(worin,result+1,length(worin)))>0 do begin repeat result:=result+1; case worin[result] of '{': inc(Ebene); '}': dec(Ebene); end; until (result>=length(worin)) or ((copy(worin,result,length(was))=was) and (Ebene=0)); if ((result=1) or (worin[result-1]<>'\')) and (copy(worin,result,length(was))=was) then exit; end; result:=0; end; function liesMakeFile(datNam: string; out Abh: TAbhaengigkeiten; out dats: TDateienMitDaten): boolean; var f: textfile; s,t: string; NA: TAbhaengigkeit; beg,fin,rek: boolean; wo,num: integer; begin result:=false; NA:=TAbhaengigkeit.create; setlength(dats,0); setlength(Abh,0); wo:=0; num:=0; assignfile(f,datNam); reset(f); while liesZeile(f,s) do begin if wo=0 then begin if notQuotedPos(':',s)=0 then begin while (length(s)>0) and (s[1] in [' ',#9]) do delete(s,1,1); if length(s)=0 then continue; s:=s+' '; rek:=pos('-r',s)=1; if rek then delete(s,1,pos(' ',s)); while pos(' ',s)>0 do begin sammleDateien(copy(s,1,pos(' ',s)-1),rek,dats); delete(s,1,pos(' ',s)); end; end else wo:=1; end; if wo=1 then begin fin:=s[length(s)]=';'; if fin then delete(s,length(s),1); beg:=not (s[1] in [' ',#9]); if (notQuotedPos(':',s)=0) and beg then begin closefile(f); writeln('In der Quell-Ziel-Zuordnung muss ein (unzitierter) Doppelpunkt auftauchen!'); exit; end; if beg then begin SetLength(NA.Quellen,0); NA.QuellenRE.Expression:=''; SetLength(NA.Ziele,0); NA.ZieleFkt.Clear; NA.AbhArt:=aaFinal; if pos('1>1 ',s)=1 then NA.AbhArt:=aaEinsZuEins; if pos('n>n ',s)=1 then NA.AbhArt:=aaNZuN; if NA.AbhArt=aaFinal then NA.AbhArt:=aaNZuN // der Standard else delete(s,1,pos(' ',s)); t:=copy(s,1,notQuotedPos(':',s)-1)+' '; while pos(' ',t)>0 do begin while pos(' ',t)=1 do delete(t,1,1); NA.ZieleFkt.Add(copy(t,1,pos(' ',t)-1)); delete(t,1,pos(' ',t)); end; delete(s,1,notQuotedPos(':',s)); while pos(' ',s)=1 do delete(s,1,1); NA.QuellenRE.Expression:=s; end else begin while s[1] in [' ',#9] do delete(s,1,1); while unescapedpos(';',s)>0 do s:=copy(s,1,unescapedpos(';',s)-1)+' &&'+copy(s,unescapedpos(';',s)+1,length(s)-unescapedpos(';',s)); NA.Befehle.Add(s); end; if fin then begin setlength(Abh,length(Abh)+1); getmem(Abh[length(Abh)-1],sizeof(TAbhaengigkeit)); fillchar(Abh[length(Abh)-1]^,sizeof(TAbhaengigkeit),0); Abh[length(Abh)-1]^:=TAbhaengigkeit.create(NA); Abh[length(Abh)-1]^.Prioritaet:=num; inc(num); SetLength(NA.Quellen,0); NA.QuellenRE.Expression:=''; SetLength(NA.Ziele,0); NA.ZieleFkt.Clear; setlength(NA.Erben,0); NA.Befehle.Clear; end; end; end; closefile(f); NA.free; result:=true; end; function liesPruefsummenfile(sumNam: string; var dats: tDateienMitDaten; jetzt: extended): boolean; var f: textfile; i,j: longint; gutschlecht: tStringlistBArray; gefunden,gut: boolean; begin result:=false; if sumNam='' then exit; if not fileexists(sumNam) then begin assignfile(f,sumNam); rewrite(f); closefile(f); end; if not fileexists(sumNam) then exit; gutschlecht:=testeSummen(sumNam); for gut:=false to true do for i:=0 to gutschlecht[gut].count-1 do begin gefunden:=false; for j:=0 to length(dats)-1 do if dats[j]^.Name=gutschlecht[gut][i] then begin dats[j]^.Datum:=jetzt*byte(not gut) + 1; // Prüfsumme OK => Datei formal (ur)alt: // -> immer überschreiben, wenn es eine Quelle ohne (gültige) Prüfsumme gibt oder diese entstehen wird // -> von sich aus nie Auslöser für Kompilation (außer eines der Ziele existiert nicht) // Prüfsumme nicht OK => Datei formal von morgen: // -> löst Kompilation aus, wenn es eine Quelle ist // -> wird weiterhin durch Kompilation als Ziel erneuert gefunden:=true; break; end; if not gefunden then begin if not gut then begin // Dateien mit ungültiger Prüfsumme können auch nicht vorhanden sein, setlength(dats,length(dats)+1); // dann werden sie einfach eingefügt getmem(dats[length(dats)-1],sizeof(tDateiMitDatum)); dats[length(dats)-1]^.Name:=gutschlecht[gut][i]; dats[length(dats)-1]^.Datum:=0; // siehe oben continue; end; for gefunden:=false to true do gutschlecht[gefunden].free; exit; end; end; result:=true; end; function unescape(s: string): string; begin result:=s; while pos('\,',result)>0 do delete(result,pos('\,',result),1); end; function escape(s,toe: string; ec: char): string; var i,j: longint; b: boolean; begin result:=''; for i:=1 to length(s) do begin b:=false; for j:=1 to length(toe) do b:=b or (toe[j]=s[i]); if b then result:=result+ec; result:=result+s[i]; end; end; function Quellersetzung(var worin: string; Quelle, inputfile: string): boolean; var s,t: string; i,a,e: integer; begin result:=false; while pos('%nurmit''',worin)>0 do begin i:=pos('%nurmit''',worin); while worin[i]<>'''' do inc(i); repeat inc(i); until worin[i]=''''; if pos(copy(worin,pos('%nurmit''',worin)+8,i-pos('%nurmit''',worin)-8),Quelle)=0 then exit; delete(worin,pos('%nurmit''',worin),i-pos('%nurmit''',worin)+1); end; while pos('%nurohne''',worin)>0 do begin i:=pos('%nurohne''',worin); while worin[i]<>'''' do inc(i); repeat inc(i); until worin[i]=''''; if pos(copy(worin,pos('%nurohne''',worin)+9,i-pos('%nurohne''',worin)-9),Quelle)>0 then exit; delete(worin,pos('%nurohne''',worin),i-pos('%nurohne''',worin)+1); end; result:=true; while pos('%in',worin)>0 do worin:=copy(worin,1,pos('%in',worin)-1)+Quelle+copy(worin,pos('%in',worin)+3,length(worin)); s:=extractfilename(Quelle); while pos('%ifile',worin)>0 do worin:=copy(worin,1,pos('%ifile',worin)-1)+s+copy(worin,pos('%ifile',worin)+6,length(worin)); if pos('.',s)>0 then begin while s[length(s)]<>'.' do delete(s,length(s),1); delete(s,length(s),1); end; while pos('%basename',worin)>0 do worin:=copy(worin,1,pos('%basename',worin)-1)+s+copy(worin,pos('%basename',worin)+9,length(worin)); while pos('%basedir(',worin)>0 do begin a:=pos('%basedir(',worin); s:=copy(worin,1,a-1); while (a<=length(worin)) and (worin[a]<>'(') do inc(a); e:=a; while (e<=length(worin)) and (worin[e]<>')') do inc(e); i:=strtoint(copy(worin,a+1,e-a-1)); if (e0 do begin while (a>0) and (t[a]='/') do dec(a); while (a>0) and (t[a]<>'/') do dec(a); dec(i); end; delete(t,1,a); worin:=s+t+copy(worin,e+1,length(worin)); end; s:=extractfilepath(Quelle); while pos('%basedir/',worin)>0 do worin:=copy(worin,1,pos('%basedir/',worin)-1)+s+copy(worin,pos('%basedir/',worin)+9,length(worin)); while pos('%basedir',worin)>0 do worin:=copy(worin,1,pos('%basedir',worin)-1)+s+copy(worin,pos('%basedir',worin)+8,length(worin)); while pos('%BASEDIR/',worin)>0 do worin:=copy(worin,1,pos('%BASEDIR/',worin)-1)+extractfilepath(inputfile)+copy(worin,pos('%BASEDIR/',worin)+9,length(worin)); while pos('%BASEDIR',worin)>0 do worin:=copy(worin,1,pos('%BASEDIR',worin)-1)+extractfilepath(inputfile)+copy(worin,pos('%BASEDIR',worin)+8,length(worin)); while pos('%num''',worin)>0 do begin a:=pos('%num''',worin); e:=a; while (e<=length(worin)) and (worin[e]<>'''') do inc(e); inc(e); while (e<=length(worin)) and (worin[e]<>'''') do inc(e); s:=copy(worin,a,e-a-1); for i:=length(s) downto 1 do if s[i] in ['0'..'9'] then break else delete(s,i,1); for i:=length(s) downto 1 do if not (s[i] in ['0'..'9']) then begin delete(s,1,i); break; end; worin:=copy(worin,1,a-1)+s+copy(worin,e+1,length(worin)); end; end; function bashMatch(was,worauf: string): boolean; var RE: TRegExpr; begin if pos('*',was)=0 then begin result:=was=worauf; exit; end; RE:=TRegExpr.create; RE.Expression:='^'+escape(escape(was,'.|()^$','\'),'*','.')+'$'; result:=RE.Exec(worauf); RE.free; end; procedure ZieleHinzufuegen(var Ziele: TDateienMitDaten; Ziel,Quelle,inputfile: string; var dats: TDateienMitDaten); var i,Anz,Ende: integer; s,t,u: string; wasda: boolean; begin if pos('{',Ziel)=0 then begin if not Quellersetzung(Ziel,Quelle,inputfile) then exit; Ziel:=unescape(Ziel); i:=0; wasda:=false; while i0) or (Ziel[Ende]<>'}') do begin inc(Ende); case Ziel[Ende] of '{': inc(Anz); '}': dec(Anz); end{of case}; end; s:=copy(Ziel,1,pos('{',Ziel)-1); t:=copy(Ziel,pos('{',Ziel)+1,Ende-pos('{',Ziel)-1); u:=copy(Ziel,Ende+1,length(Ziel)); if unescapedpos('..',t)>0 then begin for i:=strtoint(copy(t,1,unescapedpos('..',t)-1)) to strtoint(copy(t,unescapedpos('..',t)+2,length(t))) do ZieleHinzufuegen(Ziele,s+inttostr(i)+u,Quelle,inputfile,dats); end else begin t:=t+','; while unescapedpos(',',t)>0 do begin ZieleHinzufuegen(Ziele,s+copy(t,1,unescapedpos(',',t)-1)+u,Quelle,inputfile,dats); delete(t,1,unescapedpos(',',t)); end; end; end; end; procedure findeMehrZiele(var Ziele: TDateienMitDaten; ZieleFkt: TStringList; Quelle,inputfile: string; var dats: TDateienMitDaten); var i: integer; begin for i:=0 to ZieleFkt.Count-1 do ZieleHinzufuegen(Ziele,ZieleFkt[i],Quelle,inputfile,dats); end; procedure findeZiele(out Ziele: TDateienMitDaten; ZieleFkt: TStringList; Quelle,inputfile: string; var dats: TDateienMitDaten); begin setlength(Ziele,0); findeMehrZiele(Ziele,ZieleFkt,Quelle,inputfile,dats); end; procedure findeWasZuTunIst(var mgl: TAbhaengigkeiten; out zuTun: TAbhaengigkeiten; var dats: TDateienMitDaten; inputfile: string; jetzt: extended); var i,j,k,l: integer; neues,schonda: boolean; quNeu, ziAlt: TDateTime; s: string; begin setlength(zuTun,0); repeat neues:=false; i:=0; while i''; for i:=0 to length(zuTun)-1 do Prior:=max(Prior,zuTun[i]^.Prioritaet); if ms then begin geaenderteDateien:=TStringlist.create; if allesNeu then for i:=0 to length(alles)-1 do begin for j:=0 to length(alles[i]^.Ziele)-1 do geaenderteDateien.add(alles[i]^.Ziele[j]^.Name); for j:=0 to length(alles[i]^.Quellen)-1 do geaenderteDateien.add(alles[i]^.Quellen[j]^.Name); end; for i:=0 to length(zuTun)-1 do begin for j:=0 to length(zuTun[i]^.Ziele)-1 do geaenderteDateien.add(zuTun[i]^.Ziele[j]^.Name); for j:=0 to length(zuTun[i]^.Quellen)-1 do geaenderteDateien.add(zuTun[i]^.Quellen[j]^.Name); end; end; while Prior>=0 do begin for i:=0 to length(zuTun)-1 do if zuTun[i]^.Prioritaet=Prior then for j:=0 to zuTun[i]^.Befehle.Count-1 do Befehle.Add(zuTun[i]^.Befehle[j]); dec(Prior); end; if ms then begin for i:=geaenderteDateien.count-1 downto 0 do begin if geaenderteDateien[i]='.uralt.' then begin geaenderteDateien.delete(i); continue; end; for j:=0 to i-1 do if geaenderteDateien[i]=geaenderteDateien[j] then begin geaenderteDateien.Delete(i); break; end; end; if geaenderteDateien.count>0 then Befehle.Add('echo -ne "Sha512summen erneuern ..."'); for i:=0 to geaenderteDateien.count-1 do Befehle.Add('/usr/bin/sha512sum '+geaenderteDateien[i]+' >> '+Summendatei); if geaenderteDateien.count>0 then Befehle.Add('echo " fertig"'); if not nurAnzeigen then begin alteDateien:=tStringlist.create; alteDateien.LoadFromFile(Summendatei); for i:=alteDateien.Count-1 downto 0 do for j:=0 to geaenderteDateien.count-1 do if trim(copy(alteDateien[i],pos(' ',alteDateien[i]),length(alteDateien[i])))=geaenderteDateien[j] then begin alteDateien.delete(i); break; end; alteDateien.SaveToFile(Summendatei); alteDateien.free; end; end; if length(zuTun)=0 then Befehle.Add('echo "Es gibt hier nichts zu tun!"'); Befehle.Add('sleep 2'); if nurAnzeigen then begin writeln('Befehle:'); for i:=0 to Befehle.Count-1 do writeln(Befehle[i]); end else begin Assignfile(Ausg,Ausgabedatei); Rewrite(Ausg); for i:=0 to Befehle.Count-1 do writeln(Ausg,'('+Befehle[i]+') || (read -p "Ein Fehler ist aufgetreten! ... "; exit 1)'); Closefile(Ausg); end; Befehle.Free; if ms then geaenderteDateien.Free; end; { TMake } procedure TMake.DoRun; var inputfile,pruefsummenfile,ErrorMsg: String; mglAbhaengigkeiten,zutunAbhaengigkeiten: TAbhaengigkeiten; Dateien: TDateienMitDaten; begin ErrorMsg:=CheckOptions('A:HWD:P:a','Ausgabe: Hilfe Watte Datei: Prüfsummen: alleSummenErneuern'); if ErrorMsg<>'' then begin ShowException(Exception.Create(ErrorMsg+#10'Hilfe: '+Exename+' -H/--Hilfe')); Terminate; Exit; end; if HasOption('H','Hilfe') then begin WriteHelp; Terminate; Exit; end; if (GetOptionValue('A','Ausgabe')='') and not HasOption('W','Watte') then begin ShowException(Exception.Create('Ausgabedatei wird benötigt!')); Terminate; Exit; end; if HasOption('D','Datei') then inputfile:=GetOptionValue('D','Datei') else inputfile:='Machdatei'; if HasOption('P','Prüfsummen') then pruefsummenfile:=GetOptionValue('P','Prüfsummen') else pruefsummenfile:=''; if HasOption('a','alleSummenErneuern') and (pruefsummenfile='') then begin ShowException(Exception.Create('Ich kann alle Summen nur dann erneuern (-a/--alleSummenErneuern), wenn ich eine Prüfsummendatei habe (-P/--Pfrüfsummen)!')); Terminate; Exit; end; if fileexists(inputfile) then begin if not liesMakeFile(inputfile,mglAbhaengigkeiten,Dateien) then begin ShowException(Exception.Create('Datei '''+inputfile+''' ist fehlerhaft!')); Terminate; Exit; end; end else begin ShowException(Exception.Create('Datei '''+inputfile+''' existiert nicht!')); Terminate; Exit; end; if (pruefsummenfile<>'') and (not liesPruefsummenfile(pruefsummenfile,Dateien,now)) then begin ShowException(Exception.Create('Datei '''+pruefsummenfile+''' ist fehlerhaft!')); Terminate; Exit; end; writeln('Regeln: '+inttostr(length(mglAbhaengigkeiten))+', Dateien: '+inttostr(length(Dateien))); findeWasZuTunIst(mglAbhaengigkeiten,zuTunAbhaengigkeiten,Dateien,inputfile,now); writeln('anzuwendende Regeln: '+inttostr(length(zuTunAbhaengigkeiten))); tueWasZuTunIst(mglAbhaengigkeiten,zuTunAbhaengigkeiten,HasOption('W','Watte'),HasOption('a','alleSummenErneuern'),GetOptionValue('A','Ausgabe'),pruefsummenfile); Terminate; end; constructor TMake.Create(TheOwner: TComponent); begin inherited Create(TheOwner); StopOnException:=True; end; procedure TMake.WriteHelp; begin writeln('Verwendung:'); writeln(' ',ExeName,' -A/--Ausgabe $Ausgabedatei -H/--Hilfe -W/--Watte -D/--Datei $Machdatei -P/--Prüfsummen $Prüfsummendatei -a/--alleSummenErneuern'); end; var Application: TMake; begin Application:=TMake.Create(nil); Application.Run; Application.Free; end.