instantfptools.pas 7.3 KB

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