instantfptools.pas 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. unit InstantFPTools;
  2. {$mode objfpc}{$H+}
  3. interface
  4. uses
  5. {$IFNDEF MSWINDOWS}
  6. Unix,
  7. {$ENDIF}
  8. Classes, SysUtils, Process;
  9. procedure CheckSourceName(const Filename: string);
  10. procedure CommentShebang(Src: TStringList);
  11. function GetCacheDir: string;
  12. function IsCacheValid(Src: TStringList;
  13. const CachedSrcFile, CachedExeFile: string): boolean;
  14. procedure Compile(const CacheFilename, OutputFilename: string);
  15. function GetCompiler: string;
  16. function GetCompilerParameters(const SrcFilename, OutputFilename: string): string;
  17. procedure Run(const Filename: string);
  18. implementation
  19. procedure AddParam(p: string; var Line: string);
  20. begin
  21. if p='' then exit;
  22. if Line<>'' then Line:=Line+' ';
  23. if (p[1]<>'"') and (System.Pos(' ',p)>0) then
  24. p:='"'+p+'"';
  25. Line:=Line+p;
  26. end;
  27. procedure CheckSourceName(const Filename: string);
  28. var
  29. Ext: String;
  30. begin
  31. // avoid name clashes
  32. Ext:=lowercase(ExtractFileExt(Filename));
  33. if (Ext<>'') and (Ext<>'.pas') and (Ext<>'.pp') and (Ext<>'.p')
  34. and (Ext<>'.lpr') and (Ext<>'.txt') and (Ext<>'.sh')
  35. then begin
  36. writeln('invalid source extension ',Ext);
  37. Halt(1);
  38. end;
  39. end;
  40. procedure CommentShebang(Src: TStringList);
  41. var
  42. Line: string;
  43. i: Integer;
  44. begin
  45. // comment shebang #!
  46. if (Src.Count=0) then exit;
  47. Line:=Src[0];
  48. i:=1;
  49. if copy(Line,1,3)=#$EF#$BB#$BF then
  50. inc(i,3);// UTF8 BOM
  51. if (i>length(Line)) or (Line[i]<>'#') then exit;
  52. Src[0]:=copy(Line,1,i-1)+'//'+copy(Line,i,length(Line));
  53. end;
  54. function GetCacheDir: string;
  55. begin
  56. Result:=GetEnvironmentVariable('INSTANTFPCCACHE');
  57. if Result='' then begin
  58. Result:=GetEnvironmentVariable('HOME');
  59. if Result<>'' then
  60. Result:=IncludeTrailingPathDelimiter(Result)+'.cache'+PathDelim+'instantfpc';
  61. end;
  62. if Result='' then begin
  63. writeln('missing environment variable: HOME or INSTANTFPCCACHE');
  64. Halt(1);
  65. end;
  66. Result:=IncludeTrailingPathDelimiter(ExpandFileName(Result));
  67. if not ForceDirectories(Result) then begin
  68. writeln('unable to create cache directory "'+Result+'"');
  69. Halt(1);
  70. end;
  71. end;
  72. function IsCacheValid(Src: TStringList; const CachedSrcFile,
  73. CachedExeFile: string): boolean;
  74. var
  75. OldSrc: TStringList;
  76. i: Integer;
  77. p: String;
  78. begin
  79. Result:=false;
  80. for i:=1 to Paramcount do begin
  81. p:=ParamStr(i);
  82. if (p='') or (p[1]<>'-') then break;
  83. if p='-B' then exit; // always compile
  84. end;
  85. if not FileExists(CachedSrcFile) then exit;
  86. if not FileExists(CachedExeFile) then exit;
  87. OldSrc:=TStringList.Create;
  88. OldSrc.LoadFromFile(CachedSrcFile);
  89. Result:=Src.Equals(OldSrc);
  90. {$IFDEF IFFreeMem}
  91. OldSrc.Free;
  92. {$ENDIF}
  93. end;
  94. function GetCompiler: string;
  95. const
  96. CompilerParam = '--compiler=';
  97. var
  98. Path: String;
  99. p: Integer;
  100. StartPos: LongInt;
  101. Dir: String;
  102. CompFile: String;
  103. i: Integer;
  104. Param: String;
  105. begin
  106. for i:=1 to Paramcount do begin
  107. Param:=ParamStr(i);
  108. if (Param='') or (Param[1]<>'-') then break;
  109. if copy(Param,1,length(CompilerParam))=CompilerParam then begin
  110. CompFile:=copy(Param,length(CompilerParam)+1,length(Param));
  111. Result:=ExpandFileName(CompFile);
  112. if not FileExists(Result) then begin
  113. writeln('Error: '+CompFile+' not found, check the ',CompilerParam,' parameter.');
  114. Halt(1);
  115. end;
  116. exit;
  117. end;
  118. end;
  119. {$IFDEF Windows}
  120. CompFile:='fpc.exe';
  121. {$ELSE}
  122. CompFile:='fpc';
  123. {$ENDIF}
  124. Path:=GetEnvironmentVariable('PATH');
  125. if PATH<>'' then begin
  126. p:=1;
  127. while p<=length(Path) do begin
  128. StartPos:=p;
  129. while (p<=length(Path)) and (Path[p]<>':') do inc(p);
  130. if StartPos<p then begin
  131. Dir:=copy(Path,StartPos,p-StartPos);
  132. Result:=ExpandFileName(IncludeTrailingPathDelimiter(Dir))+CompFile;
  133. if FileExists(Result) then exit;
  134. end;
  135. inc(p);
  136. end;
  137. end;
  138. writeln('Error: '+CompFile+' not found in PATH');
  139. Halt(1);
  140. end;
  141. procedure Compile(const CacheFilename, OutputFilename: string);
  142. var
  143. Compiler: String;
  144. CompParams: String;
  145. Proc: TProcess;
  146. Count: Int64;
  147. ss: TStringStream;
  148. buf : Array[1..4096] of byte;
  149. begin
  150. Compiler:=GetCompiler;
  151. CompParams:=GetCompilerParameters(CacheFilename,OutputFilename);
  152. //writeln('Compiler=',Compiler,' Params=',CompParams);
  153. if FileExists(OutputFilename) and not DeleteFile(OutputFilename) then begin
  154. writeln('unable to delete ',OutputFilename);
  155. Halt(1);
  156. end;
  157. Proc:=TProcess.Create(nil);
  158. Proc.CommandLine:=Compiler+' '+CompParams;
  159. Proc.Options:= [poUsePipes, poStdErrToOutput];
  160. Proc.ShowWindow := swoHide;
  161. Proc.Execute;
  162. ss:=TStringStream.Create('');
  163. repeat
  164. Count:=Proc.Output.Read(Buf,4096);
  165. if Count>0 then
  166. ss.write(buf,count);
  167. until Count=0;
  168. if (not Proc.WaitOnExit) or (Proc.ExitStatus<>0) then begin
  169. write(ss.DataString);
  170. Halt(1);
  171. end;
  172. ss.Free;
  173. Proc.Free;
  174. end;
  175. function GetCompilerParameters(const SrcFilename, OutputFilename: string): string;
  176. { For example:
  177. /usr/bin/instantfpc -MObjFpc -Sh ./envvars.pas param1
  178. The shebang compile parameters: -MObjFpc -Sh
  179. }
  180. var
  181. p: String;
  182. begin
  183. Result:='';
  184. if (Paramcount>0) then begin
  185. p:=ParamStr(1);
  186. if (p<>'') and (p[1]='-') then
  187. Result:=p; // copy compile params from the script
  188. end;
  189. AddParam('-o'+OutputFilename {$IFDEF MSWINDOWS} + '.exe' {$ENDIF},Result);
  190. AddParam(SrcFilename,Result);
  191. end;
  192. procedure Run(const Filename: string);
  193. var
  194. p: PPChar;
  195. begin
  196. p:=argv;
  197. inc(p);
  198. while (p<>nil) do begin
  199. if (p^<>nil) and (p^^<>'-') then begin
  200. break;
  201. end;
  202. inc(p);
  203. end;
  204. {$IFDEF MSWINDOWS}
  205. Inc(p); //lose the first command-line argument with the the script filename
  206. Halt(ExecuteProcess(Filename,[p^]));
  207. {$ELSE}
  208. Halt(FpExecV(Filename,p));
  209. {$ENDIF}
  210. end;
  211. end.