instantfptools.pas 7.7 KB

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