instantfptools.pas 6.2 KB

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