| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 | unit InstantFPTools;{$mode objfpc}{$H+}{$define UseFpExecV}{$ifdef WINDOWS}  {$undef UseFpExecV}  {$define HASEXEEXT}{$endif WINDOWS}{$ifdef go32v2}  {$undef UseFpExecV}  {$define HASEXEEXT}{$endif go32v2}{$ifdef watcom}  {$undef UseFpExecV}  {$define HASEXEEXT}{$endif watcom}{$ifdef os2}  {$undef UseFpExecV}  {$define HASEXEEXT}{$endif go32v2}{$IFNDEF VER2_4}{$DEFINE UseExeSearch}{$ENDIF}{$if defined(Windows) or defined(darwin) or defined(os2) or defined(go32v2) or defined(watcom)}{$define CaseInsensitiveFilenames}{$endif}interfaceuses  {$IFDEF UseFpExecV}  Unix,  {$ENDIF}  Classes, SysUtils, Process;procedure CheckSourceName(const Filename: string);procedure CommentShebang(Src: TStringList);function GetCacheDir: string;procedure SetCacheDir(AValue : string);function IsCacheValid(Src: TStringList;                      const CachedSrcFile, CachedExeFile: string): boolean;procedure Compile(const SrcFilename, CacheFilename, OutputFilename: string);procedure WriteCompilerOutput(SrcFilename, CacheFilename, CompilerOutput: string);function GetCompiler: string;procedure SetCompiler(AValue : string);function GetCompilerParameters(const SrcFilename, OutputDirectory, OutputFilename: string): string;procedure Run(const Filename: string);implementationVar  CmdCacheDir : String;  CmdCompiler : String;procedure AddParam(p: string; var Line: string);begin  if p='' then exit;  if Line<>'' then Line:=Line+' ';  if (p[1]<>'"') and (System.Pos(' ',p)>0) then    p:='"'+p+'"';  Line:=Line+p;end;procedure CheckSourceName(const Filename: string);var  Ext: String;begin  // avoid name clashes  Ext:=lowercase(ExtractFileExt(Filename));  if (Ext<>'') and (Ext<>'.pas') and (Ext<>'.pp') and (Ext<>'.p')  and (Ext<>'.lpr') and (Ext<>'.txt') and (Ext<>'.sh') and (Ext<>'.cgi')  then begin    writeln('invalid source extension ',Ext);    Halt(1);  end;end;procedure CommentShebang(Src: TStringList);var  Line: string;  i: Integer;begin  // comment shebang #!  if (Src.Count=0) then exit;  Line:=Src[0];  i:=1;  if copy(Line,1,3)=#$EF#$BB#$BF then    inc(i,3);// UTF8 BOM  if (i>length(Line)) or (Line[i]<>'#') then exit;  Src[0]:=copy(Line,1,i-1)+'//'+copy(Line,i,length(Line));end;procedure SetCacheDir(AValue : string);begin  CmdCacheDir:=AValue;end;function GetCacheDir: string;begin  Result:=CmdCacheDir;  if (Result='') then    begin    Result:=GetEnvironmentVariable('INSTANTFPCCACHE');    if Result='' then      begin      Result:=GetEnvironmentVariable('HOME');{$ifdef WINDOWS}      if Result='' then        Result:=GetEnvironmentVariable('LOCALAPPDATA');{$endif WINDOWS}      if Result<>'' then        Result:=IncludeTrailingPathDelimiter(Result)+'.cache'+PathDelim+'instantfpc';      end;    end;  if Result='' then begin    writeln('missing environment variable: HOME or INSTANTFPCCACHE or LOCALAPPDATA');    Halt(1);  end;  Result:=IncludeTrailingPathDelimiter(ExpandFileName(Result));  if not ForceDirectories(Result) then begin    writeln('unable to create cache directory "'+Result+'"');    Halt(1);  end;end;function IsCacheValid(Src: TStringList; const CachedSrcFile,  CachedExeFile: string): boolean;var  OldSrc: TStringList;  i: Integer;  p: String;begin  Result:=false;  for i:=1 to Paramcount do begin    p:=ParamStr(i);    if (p='') or (p[1]<>'-') then break;    if p='-B' then exit; // always compile  end;  if not FileExists(CachedSrcFile) then exit;  if not FileExists(CachedExeFile) then exit;  OldSrc:=TStringList.Create;  OldSrc.LoadFromFile(CachedSrcFile);  Result:=Src.Equals(OldSrc);  {$IFDEF IFFreeMem}  OldSrc.Free;  {$ENDIF}end;procedure SetCompiler(AValue : string);begin  CmdCompiler:=AValue;end;procedure WriteCompilerOutput(SrcFilename, CacheFilename, CompilerOutput: string);var  Lines: TStringList;  i: Integer;  Line: String;  p: SizeInt;begin  // replace in compiler output CacheFilename with SrcFilename  Lines:=TStringList.Create;  Lines.Text:=CompilerOutput;  {$IFDEF CaseInsensitiveFilenames}  CacheFilename:=LowerCase(CacheFilename);  {$ENDIF}  for i:=0 to Lines.Count-1 do begin    repeat      Line:=Lines[i];      {$IFDEF CaseInsensitiveFilenames}      Line:=LowerCase(Line);      {$ENDIF}      p:=Pos(CacheFilename,Line);      if p<1 then break;      {$IFDEF CaseInsensitiveFilenames}      Line:=Lines[i];      {$ENDIF}      Lines[i]:=copy(Line,1,p-1)+SrcFilename+copy(Line,p+length(CacheFilename),length(Line));    until false;  end;  // write to stdout  writeln(Lines.Text);  {$IFDEF IFFreeMem}  Lines.Free;  {$ENDIF}end;function GetCompiler: string;var  CompFile: String;{$IFNDEF UseExeSearch}  Path: String;  p: Integer;  StartPos: LongInt;  Dir: String;{$ENDIF}begin  Result:=CmdCompiler;  if (Result<>'') then    begin    Result:=ExpandFileName(Result);    if not FileExists(Result) then      begin      writeln('Error: '+Result+' not found, check the --compiler parameter.');      Halt(1);      end;    exit;    end;  {$IFDEF HASEXEEXT}  CompFile:='fpc.exe';  {$ELSE}  CompFile:='fpc';  {$ENDIF}  {$IFDEF UseExeSearch}  Result:=ExeSearch(CompFile);  {$ELSE}  Path:=GetEnvironmentVariable('PATH');  if Path<>'' then begin    p:=1;    while p<=length(Path) do begin      StartPos:=p;      while (p<=length(Path)) and (Path[p]<>':') do inc(p);      if StartPos<p then begin        Dir:=copy(Path,StartPos,p-StartPos);        Result:=ExpandFileName(IncludeTrailingPathDelimiter(Dir))+CompFile;        if FileExists(Result) then exit;      end;      inc(p);    end;  end;  {$ENDIF}  if (Result='') then    begin    writeln('Error: '+CompFile+' not found in PATH');    Halt(1);    end;end;procedure DeleteDirectory(Directory: string);var  FileInfo: TSearchRec;  aFilename: String;begin  Directory:=ExcludeTrailingPathDelimiter(Directory);  if not DirectoryExists(Directory) then exit;  if FindFirst(Directory+PathDelim+AllFilesMask,faAnyFile,FileInfo)=0 then begin    repeat      if (FileInfo.Name='.') or (FileInfo.Name='..') then continue;      aFilename:=Directory+PathDelim+FileInfo.Name;      if (FileInfo.Attr and faDirectory)>0 then        DeleteDirectory(aFilename)      else if not DeleteFile(aFilename) then begin        writeln('unable to delete file "'+aFilename+'"');        Halt(1);      end;    until FindNext(FileInfo)<>0;    Findclose(FileInfo);  end;  if not RemoveDir(Directory) then begin    writeln('unable to delete directory "'+Directory+'"');    Halt(1);  end;end;procedure Compile(const SrcFilename, CacheFilename, OutputFilename: string);var  Compiler: String;  CompParams: String;  Proc: TProcess;  Count: Int64;  ss: TStringStream;  buf : Array[1..4096] of byte;  pid: SizeUInt;  BuildDir: String;  OutputFilenameExe, BuildOutputFilename: String;  procedure CleanUp;  begin    if BuildDir<>'' then begin      // delete build directory      DeleteDirectory(BuildDir);    end;  end;begin  Compiler:=GetCompiler;  pid:=GetProcessID;  BuildDir:='';  OutputFilenameExe:=OutputFilename {$IFDEF HASEXEEXT} + '.exe' {$ENDIF};  BuildOutputFilename:=OutputFilenameExe;  if pid>0 then begin    BuildDir:=ExtractFilePath(OutputFilenameExe)+'__tmp'+IntToStr(pid)+PathDelim;    BuildOutputFilename:=BuildDir+ExtractFileName(OutputFilenameExe);  end;  //writeln('Compiler=',Compiler,' Params=',CompParams);  if FileExists(OutputFilenameExe) and not DeleteFile(OutputFilenameExe) then begin    writeln('unable to delete ',OutputFilenameExe);    Halt(1);  end;  if BuildDir<>'' then begin    if FileExists(BuildOutputFilename) and not DeleteFile(BuildOutputFilename)    then begin      writeln('unable to delete ',BuildOutputFilename);      Halt(1);    end;    if not DirectoryExists(BuildDir) and not CreateDir(BuildDir) then begin      writeln('unable to mkdir ',BuildDir);      Halt(1);    end;  end;  try    CompParams:=GetCompilerParameters(CacheFilename,BuildDir,BuildOutputFilename);    Proc:=TProcess.Create(nil);    Proc.CommandLine:=Compiler+' '+CompParams;  {$WARNING Unconditional use of pipes breaks for targets not supporting them}    Proc.Options:= [poUsePipes, poStdErrToOutput];    Proc.ShowWindow := swoHide;    Proc.Execute;    ss:=TStringStream.Create('');    repeat      Count:=Proc.Output.Read(Buf{%H-},4096);      if Count>0 then        ss.write(buf,count);    until Count=0;    if (not Proc.WaitOnExit) or (Proc.ExitStatus<>0) then begin      WriteCompilerOutput(SrcFilename,BuildOutputFilename,ss.DataString);      CleanUp;      Halt(1);    end;    if BuildDir<>'' then begin      // move from build directory to cache      if not RenameFile(BuildOutputFilename,OutputFilenameExe) then begin        writeln('unable to move "',BuildOutputFilename,'" to "',OutputFilenameExe,'"');        Halt(1);      end;    end;    ss.Free;    Proc.Free;  finally    CleanUp;  end;end;function GetCompilerParameters(const SrcFilename, OutputDirectory,  OutputFilename: string): string;{ For example:    /usr/bin/instantfpc -MObjFpc -Sh ./envvars.pas param1  The shebang compile parameters: -MObjFpc -Sh}  procedure InterpretParam(p : String);  begin    if (Copy(p,1,1)='-') and (copy(p,1,2)<>'--') then      AddParam(P,Result);  end;var  p: String;  i,j : integer;begin  Result:=GetEnvironmentVariable('INSTANTFPCOPTIONS');  I:=1;  While (I<=ParamCount) and (Copy(ParamStr(i),1,1)='-') do    begin    p:=ParamStr(i);    if (I<>1) then      begin      InterpretParam(p);      end    else      begin      // The linux kernel passes all arguments in the shebang line as 1 argument.      // We must parse and split it ourselves.      Repeat        J:=Pos(' ',P);        if (J=0) then          J:=Length(P)+1;        InterpretParam(Copy(P,1,J-1));        Delete(P,1,J);      Until (P='');      end;    inc(I);    end;  if OutputDirectory<>'' then    AddParam('-FU'+OutputDirectory,Result);  AddParam('-o'+OutputFilename,Result);  AddParam(SrcFilename,Result);end;procedure Run(const Filename: string);var  p : PPChar;  {$IFNDEF UseFpExecV}  i : integer;  args : array of string;  {$ENDIF}begin  p:=argv;  inc(p);  while (p<>nil) do begin    if (p^<>nil) and (p^^<>'-') then begin      break;    end;    inc(p);  end;  {$IFDEF UseFpExecV}    Halt(FpExecV(Filename,p));  {$ELSE}    if paramcount>1 then      begin        setlength(args,paramcount-1);        for i:=2 to paramcount do           args[i-2]:=paramstr(i);      end;    Halt(ExecuteProcess(Filename,args));  {$ENDIF}end;end.
 |