program fppkg; {$mode objfpc}{$H+} uses Classes, SysUtils, TypInfo { add your units here }; Resourcestring // SErrInValidArgument = 'Invalid command-line argument at position %d : %s'; SErrNeedArgument = 'Option at position %d (%s) needs an argument'; SErrMissingConfig = 'Missing configuration Makefile.fpc or fpmake.pp'; SErrRunning = 'The FPC make tool encountered the following error: %s'; SErrFailedToCompileFPCMake = 'Could not compile fpmake driver program'; SLogGeneratingFPMake = 'Generating fpmake.pp'; SLogCompilingFPMake = 'Compiling fpmake.pp: '; SLogRunningFPMake = 'Running fpmake'; Type TRunMode = (rmHelp,rmCompile,rmBuild,rmInstall,rmArchive,rmClean,rmDownload); { TMakeTool } TMakeTool = Class(TObject) Private FConvertOnly, FLogging : Boolean; FCompiler : String; FRunMode : TRunMode; FHaveMakefile : Boolean; FHaveFpmake : Boolean; FFPMakeSrc : String; FFPMakeBin : String; Procedure Log(Msg : String); Procedure Error(Msg : String); Procedure Error(Fmt : String; Args : Array of const); Function GetCompiler : String; Public Procedure ProcessCommandLine; procedure CreateFPMake; procedure CompileFPMake(Extra : Boolean); Function RunFPMake : Integer; Procedure Run; end; EMakeToolError = Class(Exception); { TMakeFileConverter } TSectionType = (stNone,stPackage,stTarget,stclean,stinstall,stCompiler, stDefault,stRequire,stRules,stPrerules); TMakeFileConverter = Class(TObject) FSection : TSectionType; FPackageName, FpackageDir, FPackageOptions, FPackageDeps, FBuilDUnit, FSubName, FPackageVersion : String; // Reading; procedure DoPackageLine(Const S : String); Procedure DoTargetLine(Line : String; Var T,R,D : TStrings); Procedure DoInstallLine(Line : String; Var IFL : TStrings); procedure DoCleanLine(Line : String; Var CFL : TStrings); procedure DoRequireLine(Line : String); procedure DoCompilerLine(Line : String;Var SD : TStrings); // Writing; procedure WriteOSCPUCheck(Src: TStrings;OS,CPU : String); procedure StartPackage(Src : TStrings; Dir,OS : String); procedure EndPackage(Src : TStrings; Dir,OS : String); procedure DoTargets(Src,T,R,SD : TStrings; Dir,Prefix : String); procedure DoCleans(Src,CFL : TStrings); procedure DoInstalls(Src,IFL : TStrings); Procedure StartInstaller(Src : TStrings); Procedure EndInstaller(Src : TStrings); Function GetLine (L : TStrings; Var I : Integer) : String; Public procedure ConvertFile(const AFileName: String; Src: TStrings; Dir,OS : String); Procedure ConvertFile(Const Source,Dest: String); end; { Auxiliary functions } Function GetWord(var S : String; Sep : Char) : String; Var L : Integer; begin L:=Pos(Sep,S); If (L=0) then L:=Length(S)+1; Result:=Copy(S,1,L-1); Delete(S,1,L); S:=Trim(S); end; Function GetWord(var S : String) : String; begin Result:=GetWord(S,' '); end; Function IsCPU (S: String) : Boolean; begin Result:=Pos(lowercase(S)+',','i386,powerpc,arm,alpha,sparc,')<>0 end; Function GetOSCPU(L : String; var OS,CPU : String) : String; Procedure Add(Var A : String; ad : String); begin If (A<>'') then A:=A+','; A:=A+ad; end; Var S : String; begin OS:=''; CPU:=''; S:=GetWord(L,','); While (S<>'') do begin If (S<>'all') then If IsCPU(S) then Add(CPU,S) else Add(OS,S); S:=GetWord(L,','); end; end; { TMakeFileConverter } procedure TMakeFileConverter.StartInstaller(Src: TStrings); begin With Src do begin Add('{$mode objfpc}{$H+}'); Add('program fpmake;'); Add(''); Add(' { Generated automatically by '+ExtractFileName(Paramstr(0))+' on '+DateToStr(Sysutils.Date)+' }'); Add(''); Add('uses fpmkunit;'); Add(''); Add('Var'); Add(' T : TTarget;'); Add(''); Add('begin'); Add(' With Installer do '); Add(' begin'); end; end; procedure TMakeFileConverter.EndInstaller(Src: TStrings); begin With Src do begin Add(' Run;'); Add(' end;'); Add('end.'); Add(''); end; end; Function TMakeFileConverter.GetLine (L : TStrings; Var I : Integer) : String; Var P : Integer; OK : Boolean; begin OK:=False; Result:=''; Repeat Result:=Result+L[i]; P:=Pos('#',Result); If (P>0) then Result:=Copy(Result,1,P-1); Result:=Trim(Result); P:=Length(Result); If (P>0) and (Result[P]='\') then Result:=Copy(Result,1,P-1) else OK:=(Result<>''); if Not OK then Inc(I); Until OK or (I>L.Count-1); end; Function SplitNamevalue(Const S : String; Var AName,AValue : String) : boolean; var L : Integer; begin L:=Pos('=',S); Result:=(L<>0); If Result then begin AName:=LowerCase(trim(Copy(S,1,L-1))); AValue:=S; Delete(AValue,1,L); AValue:=Trim(Avalue); end else begin AName:=''; AValue:=''; end; end; procedure TMakeFileConverter.StartPackage(Src : TStrings; Dir,OS : String); Var S : String; begin With Src do begin Add(' { '); Add(' '+FPackageName); Add(' } '); Add(' StartPackage('''+FPackageName+''');'); If (Dir<>'') then Add(' Directory:='''+ExcludeTrailingPathDelimiter(Dir)+''';'); If (OS<>'') and (OS<>'all') then Add(' OS:=['+OS+'];'); If (FPackageVersion<>'') then Add(' Version:='''+FPackageVersion+''';'); If (FPackageOptions<>'') then Add(' Options:='''+FPackageOptions+''';'); If (FPackageDeps<>'') then begin S:=GetWord(FPackageDeps); While S<>'' do begin Add(' Dependencies.Add('''+S+''');'); S:=GetWord(FPackageDeps); end; end; end; end; procedure TMakeFileConverter.EndPackage(Src : TStrings; Dir,OS : String); begin Src.add(' EndPackage;'); FPackageName:=''; FPackageVersion:=''; FPackageOptions:=''; FBuilDUnit:=''; FPackageDeps:=''; end; procedure TMakeFileConverter.DoPackageLine(Const S : String); Var V,N : String; begin SplitNameValue(S,N,V); If (N='name') then FPackageName:=V else If (N='version') then FPackageVersion:=V else If (N='main') then begin FPackageName:='sub'; FSubName:=V; end else Writeln(StdErr,'Unknown name/value pair in package section :',N); end; { Convert various entries of type XXYY_OSN=words to entries of type prefix_word=OS1,OS2,OS3 OS is never empty, 'all' is default. } Procedure AddStrings(Var L : TStrings; Values,Prefix,OS : String) ; Var S,O : String; i : integer; begin If (L=Nil) then L:=TstringList.Create; If prefix<>'' then prefix:=prefix+'_'; S:=GetWord(Values); While (S<>'') do begin S:=Prefix+S; I:=L.IndexOfName(S); If (I<>-1) then begin O:=L.Values[S]; If (O='all') then O:=''; If (O<>'') then O:=O+','; O:=O+OS; L.Values[S]:=O; end else L.Add(S+'='+OS); S:=GetWord(Values); end; end; procedure TMakeFileConverter.DoTargetLine(Line : String; Var T,R,D : TStrings); Var V,N,OS : String; P : Integer; begin SplitNameValue(Line,N,V); P:=Pos('_',N); If (P=0) then OS:='all' else begin OS:=N; Delete(OS,1,P); N:=Copy(N,1,P-1); end; If (N='dirs') then AddStrings(D,V,'',OS) else If (N='units') then AddStrings(T,V,'unit',OS) else If (N='implicitunits') then AddStrings(T,V,'unit',OS) else If (N='programs') then AddStrings(T,V,'program',OS) else If (N='examples') then AddStrings(T,V,'exampleunit',OS) else If (N='rsts') then AddStrings(R,V,'',OS) else Writeln(StdErr,'Unknown name/value pair in target section : ',Line); end; procedure TMakeFileConverter.DoInstallLine(Line : String; Var IFL : TStrings); Var S,V,N,OS : String; P : Integer; begin SplitNameValue(Line,N,V); P:=Pos('_',N); If (P=0) then OS:='all' else begin OS:=N; Delete(OS,1,P); N:=Copy(N,1,P-1); end; If (N='fpcpackage') then P:=0 // temporary, needs fixing. else If (N='buildunit') then FBuildUnit:=V // temporary, needs fixing. else If (N='units') then begin S:=GetWord(V); While (S<>'') do begin AddStrings(IFL,S+'.o','',OS); AddStrings(IFL,S+'.ppu','',OS); S:=GetWord(V); end; end else Writeln(StdErr,'Unknown name/value pair in install section : ',N); end; procedure TMakeFileConverter.DoCleanLine(Line : String; Var CFL : TStrings); Var V,N,S,OS : String; P : Integer; begin SplitNameValue(Line,N,V); P:=Pos('_',N); If (P=0) then OS:='all' else begin OS:=N; Delete(OS,1,P); N:=Copy(N,1,P-1); end; If (N='fpcpackage') then P:=0 // temporary, needs fixing. else If (N='units') then begin S:=GetWord(V); While (S<>'') do begin AddStrings(CFL,S+'.o','',OS); AddStrings(CFL,S+'.ppu','',OS); S:=GetWord(V); end; end else Writeln(StdErr,'Unknown name/value pair in clean section : ',N); end; procedure TMakeFileConverter.DoRequireLine(Line : String); Var V,N,OS : String; P : Integer; begin SplitNameValue(Line,N,V); P:=Pos('_',N); If (P=0) then OS:='all' else begin OS:=N; Delete(OS,1,P); N:=Copy(N,1,P-1); end; if (N='packages') then FPackageDeps:=V else If (N='libc') and (Upcase(V)='Y') then P:=0 // Set options ? else Writeln(StdErr,'Unknown name/value pair in require section : ',N); end; procedure TMakeFileConverter.DoCompilerLine(Line : String;Var SD : TStrings); Var V,N,OS : String; P : Integer; begin SplitNameValue(Line,N,V); P:=Pos('_',N); If (P=0) then OS:='all' else begin OS:=N; Delete(OS,1,P); N:=Copy(N,1,P-1); end; If (N='includedir') then FPackageOptions:=Trim(FPackageOptions+' -Fi'+V) else If (N='options') then FPackageOptions:=Trim(FPackageOptions+' '+V) else If (N='targetdir') then P:=0 // Ignore else if (N='sourcedir') or (N='unitdir') then begin If (SD=Nil) then SD:=TStringList.Create; SD.Add(OS+'='+V); end else Writeln(StdErr,'Unknown name/value pair in compiler section : ',N); end; Function SearchInDirs(Prefix,AName, Dirs : String) : string; Var D,S : String; begin S:=GetWord(Dirs); Result:=''; While (Result='') and (S<>'') do begin D:=Prefix+S+PathDelim; If FileExists(D+AName+'.pp') or FileExists(D+AName+'.pas') then Result:=S; S:=GetWord(Dirs); end; end; procedure TMakeFileConverter.DoTargets(Src,T,R,SD : TStrings; Dir,Prefix : String); Var I,J,P : Integer; Pre,N,V,D,DOS,OS,CPU : String; Res : Boolean; begin If (Dir<>'') then Dir:=IncludeTrailingPathDelimiter(Dir); If (Prefix<>'') then Prefix:=IncludeTrailingPathDelimiter(Prefix); Dir:=Prefix+Dir; Res:=False; If Assigned(T) then For I:=0 to T.Count-1 do begin T.GetNamevalue(I,N,V); P:=Pos('_',N); If (P<>0) then begin Pre:=Copy(N,1,P-1); Delete(N,1,P); end; If Assigned(R) then Res:=R.IndexOfName(N)<>-1; GetOSCPU(V,OS,CPU); Pre[1]:=Upcase(Pre[1]); Src.Add(' T:=Targets.Add'+Pre+'('''+Prefix+N+''');'); If (CPU<>'') then Src.Add(' T.CPU:=['+CPU+'];'); If (OS<>'') then Src.Add(' T.OS:=['+OS+'];'); If res then Src.add(' T.ResourceStrings:=True;'); If (CompareText(FBuildUnit,N)=0) then Src.add(' T.Install:=False;'); if Assigned(SD) then for J:=0 to SD.Count-1 do begin SD.GetNameValue(J,DOS,D); If (DOS<>'all') then Src.Add(' if (Defaults.OS='+DOS+') then'); Src.add(' T.Directory:='''+SearchInDirs(Dir,N,D)+''';'); end; end; end; procedure TMakeFileConverter.WriteOSCPUCheck(Src: TStrings;OS,CPU : String); Var S : String; begin If (CPU<>'') then S:='(Defaults.CPU='+CPU+')'; If (OS<>'') then begin IF (S<>'') then S:=S+' OR '; S:=S+'(Defaults.OS='+CPU+')'; end; If (S<>'') then Src.Add(' If '+S+' then'); end; procedure TMakeFileConverter.DoInstalls(Src,IFL : TStrings); Var I,J,P : Integer; Pre,N,V,D,DOS,OS,CPU : String; begin If Assigned(IFL) then For I:=0 to IFL.Count-1 do begin IFL.GetNamevalue(I,N,V); GetOSCPU(V,OS,CPU); WriteOSCPUCheck(Src,OS,CPU); Src.add(' InstallFiles.Add('''+N+''');'); end; end; procedure TMakeFileConverter.DoCleans(Src,CFL : TStrings); Var I,J,P : Integer; N,V,DOS,OS,CPU : String; begin If Assigned(CFL) then For I:=0 to CFL.Count-1 do begin CFL.GetNamevalue(I,N,V); GetOSCPU(V,OS,CPU); WriteOSCPUCheck(Src,OS,CPU); Src.add(' CleanFiles.Add('''+N+''');'); end; end; procedure TMakeFileConverter.ConvertFile(const AFileName: String; Src: TStrings; Dir,OS : String); Function IsSection(var S : String) : Boolean; Var L : Integer; begin L:=Length(S); Result:=(L>0) and (S[1]='[') and (S[L]=']'); If Result then S:=trim(Copy(S,2,L-2)); end; Var R,L,T,D,S,SD,IFL,CFL : TStrings; I,J : Integer; Prefix,Line,DN : String; B : Boolean; begin Writeln('Converting '+AFileName); T:=Nil; D:=Nil; S:=Nil; SD:=Nil; R:=Nil; IFL:=Nil; CFL:=Nil; FPackageOptions:=''; FPackageDir:=''; L:=TStringList.Create; try L.LoadFromFile(AFileName); I:=0; While (I'') then begin Prefix:=''; B:=FPackageName<>'sub'; If B then StartPackage(Src,Dir,OS) else Prefix:=Dir; DoTargets(Src,T,R,SD,Dir,Prefix); DoInstalls(Src,IFL); DoCleans(Src,CFL); end; If Assigned(D) then begin If (Dir<>'') then Dir:=IncludeTrailingPathDelimiter(Dir); For I:=0 to D.Count-1 do begin D.GetNameValue(I,DN,Line); If (Line<>'all') and (Line<>'') then OS:=Line; DN:=Dir+DN+PathDelim; If FileExists(DN+'Makefile.fpc') then ConvertFile(DN+'Makefile.fpc',Src,DN,OS); end; end; If B then EndPackage(Src,Dir,OS); Finally S.Free; IFL.Free; CFL.Free; D.Free; SD.Free; T.Free; L.Free; end; end; procedure TMakeFileConverter.ConvertFile(const Source, Dest: String); Var L : TStrings; begin L:=TStringList.Create; Try StartInstaller(L); ConvertFile(Source,L,'',''); EndInstaller(L); L.SaveToFile(Dest); Finally L.Free; end; end; { TMakeTool } procedure TMakeTool.CompileFPMake(Extra: Boolean); Var O,C : String; begin C:=GetCompiler; O:=FFPmakeSrc; If Extra then O:='-Fafpmkext '+O; Log(SLogCompilingFPMake+C+' '+O); If ExecuteProcess(C,O)<>0 then Error(SErrFailedToCompileFPCMake) end; procedure TMakeTool.CreateFPMake; begin Log(SLogGeneratingFPMake); With TMakeFileConverter.Create do try ConvertFile('Makefile.fpc','fpmake.pp'); finally Free; end; end; Function TMakeTool.RunFPMake : Integer; Function MaybeQuote(Const S : String) : String; begin If Pos(' ',S)=0 then Result:=S else Result:='"'+S+'"'; end; Var I : integer; D,O : String; begin Log(SLogRunningFPMake); D:=IncludeTrailingPathDelimiter(GetCurrentDir); O:=''; For I:=1 to ParamCount do begin If (O<>'') then O:=O+' '; O:=O+MaybeQuote(ParamStr(I)); end; Result:=ExecuteProcess(D+FFPMakeBin,O); end; procedure TMakeTool.Log(Msg: String); begin If FLogging then Writeln(stdErr,Msg); end; procedure TMakeTool.Error(Msg: String); begin Raise EMakeToolError.Create(Msg); end; procedure TMakeTool.Error(Fmt: String; Args: array of const); begin Raise EMakeToolError.CreateFmt(Fmt,Args); end; function TMakeTool.GetCompiler: String; begin If (FCompiler='') then begin {$if defined(cpui386)} FCompiler:='ppc386'; {$elseif defined(cpuAlpha)} FCompiler:='ppcaxp'; {$elseif defined(cpusparc)} FCompiler:='ppcsparc'; {$elseif defined(cpuarm)} FCompiler:='ppcarm'; {$elseif defined(cpum68k)} FCompiler:='ppcm68k'; {$elseif defined(cpux86_64)} FCompiler:='ppcx64'; {$elseif defined(cpupowerpc)} FCompiler:='ppcppc'; {$else} {$Fatal Unknown architecture} {$endif} end; If (ExtractFilePath(FCompiler)<>'') then Result:=FCompiler else begin Result:=FileSearch(FCompiler,GetEnvironmentVariable('PATH')); If (Result='') then Result:=FCompiler; end; end; procedure TMakeTool.ProcessCommandLine; Function CheckOption(Index : Integer;Short,Long : String): Boolean; var O : String; begin O:=Paramstr(Index); Result:=(O='-'+short) or (O='--'+long) or (copy(O,1,Length(Long)+3)=('--'+long+'=')); end; Function OptionArg(Var Index : Integer) : String; Var P : Integer; begin if (Length(ParamStr(Index))>1) and (Paramstr(Index)[2]<>'-') then begin If Index2 then begin P:=Pos('=',Paramstr(Index)); If (P=0) then Error(SErrNeedArgument,[Index,ParamStr(Index)]) else begin Result:=Paramstr(Index); Delete(Result,1,P); end; end; end; Var I : Integer; begin I:=0; FLogging:=False; FRunMode:=rmhelp; FConvertOnly:=False; While (I0; end; end; procedure TMakeTool.Run; begin Try ProcessCommandLine; If FConvertOnly then CreateFPMake else begin FHaveMakefile:=FileExists('Makefile.fpc'); FFPMakeSrc:='fpmake.pp'; FHaveFpmake:=FileExists(FFPMakeSrc); If Not FHaveFPMake then begin FHaveFPMake:=FileExists('fpmake.pas'); If FHaveFPMake then FFPMakeSrc:='fpmake.pas'; end; if Not (FHaveFPMake or FHaveMakeFile) then Error(SErrMissingConfig); If (Not FHaveFPMake) or (FileAge(FFPMakeSrc)