instantfptools.pas 5.8 KB

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