comprsrc.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. {
  2. Copyright (c) 1998-2002 by Florian Klaempfl
  3. Handles the resource files handling
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  15. ****************************************************************************
  16. }
  17. unit comprsrc;
  18. {$i fpcdefs.inc}
  19. interface
  20. uses
  21. Systems, cstreams;
  22. type
  23. tresoutput = (roRES, roOBJ);
  24. tresourcefile = class(TAbstractResourceFile)
  25. private
  26. fname : ansistring;
  27. public
  28. constructor Create(const fn : ansistring);override;
  29. procedure Compile(output: tresoutput; const OutName: ansistring);virtual;
  30. procedure PostProcessResourcefile(const s : ansistring);virtual;
  31. function IsCompiled(const fn : ansistring) : boolean;virtual;
  32. procedure Collect(const fn : ansistring);virtual;
  33. end;
  34. TWinLikeResourceFile = class(tresourcefile)
  35. private
  36. FOut: TCFileStream;
  37. FLastIconID: longint;
  38. FLastCursorID: longint;
  39. public
  40. function IsCompiled(const fn : ansistring) : boolean;override;
  41. procedure Collect(const fn : ansistring);override;
  42. end;
  43. procedure CompileResourceFiles;
  44. procedure CollectResourceFiles;
  45. Var
  46. ResCompiler : String;
  47. RCCompiler : String;
  48. implementation
  49. uses
  50. SysUtils,
  51. cutils,cfileutils,cclasses,
  52. Globtype,Globals,Verbose,Fmodule,
  53. Script;
  54. const
  55. GlobalResName = 'fpc-res';
  56. {****************************************************************************
  57. TRESOURCEFILE
  58. ****************************************************************************}
  59. constructor tresourcefile.create(const fn : ansistring);
  60. begin
  61. fname:=fn;
  62. end;
  63. procedure tresourcefile.PostProcessResourcefile(const s : ansistring);
  64. begin
  65. end;
  66. function tresourcefile.IsCompiled(const fn: ansistring): boolean;
  67. begin
  68. Result:=CompareText(ExtractFileExt(fn), target_info.resobjext) = 0;
  69. end;
  70. procedure tresourcefile.Collect(const fn: ansistring);
  71. begin
  72. if fn='' then
  73. exit;
  74. fname:=fn;
  75. Compile(roOBJ, ChangeFileExt(fn, target_info.resobjext));
  76. end;
  77. procedure tresourcefile.compile(output: tresoutput; const OutName: ansistring);
  78. Function SelectBin(Const Bin1,Bin2 : String) : String;
  79. begin
  80. If (Bin1<>'') then
  81. SelectBin:=Bin1
  82. else
  83. SelectBin:=Bin2;
  84. end;
  85. function WindresFileName(filename: TCmdStr): TCmdStr;
  86. // to be on the safe side, only give short file names with forward slashes to
  87. // windres
  88. var
  89. i: longint;
  90. begin
  91. Result := GetShortName(filename);
  92. for I:=1 to Length(Result) do
  93. if Result[I] in AllowDirectorySeparators then
  94. Result[i]:='/';
  95. end;
  96. var
  97. respath,
  98. srcfilepath,
  99. preprocessorbin,
  100. s,
  101. bin,
  102. resbin,
  103. fnameparam : TCmdStr;
  104. usewindres,
  105. resfound,
  106. objused : boolean;
  107. begin
  108. if output=roRES then
  109. Bin:=SelectBin(RCCompiler,target_res.rcbin)
  110. else
  111. Bin:=SelectBin(ResCompiler,target_res.resbin);
  112. if bin='' then
  113. exit;
  114. resfound:=false;
  115. usewindres:= bin='windres';
  116. if utilsdirectory<>'' then
  117. resfound:=FindFile(utilsprefix+bin+source_info.exeext,utilsdirectory,false,resbin);
  118. if not resfound then
  119. resfound:=FindExe(utilsprefix+bin,false,resbin);
  120. { get also the path to be searched for the windres.h }
  121. respath:=ExtractFilePath(resbin);
  122. if (not resfound) and not(cs_link_nolink in current_settings.globalswitches) then
  123. begin
  124. Message(exec_e_res_not_found);
  125. current_settings.globalswitches:=current_settings.globalswitches+[cs_link_nolink];
  126. end;
  127. srcfilepath:=ExtractFilePath(current_module.mainsource^);
  128. if usewindres then
  129. fnameparam:=WindresFileName(fname)
  130. else
  131. fnameparam:=maybequoted(fname);
  132. if output=roRES then
  133. begin
  134. s:=target_res.rccmd;
  135. Replace(s,'$RES',maybequoted(OutName));
  136. Replace(s,'$RC',fnameparam);
  137. ObjUsed:=False;
  138. end
  139. else
  140. begin
  141. s:=target_res.rescmd;
  142. ObjUsed:=(pos('$OBJ',s)>0);
  143. Replace(s,'$OBJ',maybequoted(OutName));
  144. Replace(s,'$RES',fnameparam);
  145. end;
  146. { windres doesn't like empty include paths }
  147. if respath='' then
  148. respath:='.';
  149. if usewindres then
  150. Replace(s,'$INC',WindresFileName(respath))
  151. else
  152. Replace(s,'$INC',maybequoted(respath));
  153. if (target_res.resbin='windres') then
  154. begin
  155. if (srcfilepath<>'') then
  156. s:=s+' --include '+WindresFileName(srcfilepath);
  157. { try to find a preprocessor }
  158. preprocessorbin := respath+'cpp'+source_info.exeext;
  159. if FileExists(preprocessorbin,true) then
  160. s:=s+' --preprocessor='+maybequoted(preprocessorbin);
  161. end;
  162. { Execute the command }
  163. if not (cs_link_nolink in current_settings.globalswitches) then
  164. begin
  165. Message1(exec_i_compilingresource,fname);
  166. Message2(exec_d_resbin_params,resbin,s);
  167. FlushOutput;
  168. try
  169. if ExecuteProcess(resbin,s) <> 0 then
  170. begin
  171. Message(exec_e_error_while_linking);
  172. current_settings.globalswitches:=current_settings.globalswitches+[cs_link_nolink];
  173. end;
  174. except
  175. on E:EOSError do
  176. begin
  177. Message(exec_e_cant_call_linker);
  178. current_settings.globalswitches:=current_settings.globalswitches+[cs_link_nolink];
  179. end
  180. end;
  181. end;
  182. if output=roOBJ then
  183. PostProcessResourcefile(OutName);
  184. { Update asmres when externmode is set }
  185. if cs_link_nolink in current_settings.globalswitches then
  186. AsmRes.AddLinkCommand(resbin,s,'');
  187. if (output=roOBJ) and ObjUsed then
  188. current_module.linkunitofiles.add(OutName,link_always);
  189. end;
  190. function TWinLikeResourceFile.IsCompiled(const fn: ansistring): boolean;
  191. const
  192. ResSignature : array [1..32] of byte =
  193. ($00,$00,$00,$00,$20,$00,$00,$00,$FF,$FF,$00,$00,$FF,$FF,$00,$00,
  194. $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00);
  195. var
  196. f : file;
  197. oldfmode : byte;
  198. buf: array[1..32] of byte;
  199. i: longint;
  200. begin
  201. Result:=CompareText(ExtractFileExt(fn), target_info.resext) = 0;
  202. if Result or not FileExists(fn, False) then exit;
  203. oldfmode:=Filemode;
  204. Filemode:=0;
  205. assign(f,fn);
  206. reset(f,1);
  207. BlockRead(f, buf, SizeOf(buf), i);
  208. close(f);
  209. Filemode:=oldfmode;
  210. if i<>SizeOf(buf) then
  211. exit;
  212. for i:=1 to 32 do
  213. if buf[i]<>ResSignature[i] then
  214. exit;
  215. Result:=True;
  216. end;
  217. procedure TWinLikeResourceFile.Collect(const fn: ansistring);
  218. const
  219. zeroes: array[1..3] of byte = (0,0,0);
  220. type
  221. TResHeader = packed record
  222. DataSize: dword;
  223. HeaderSize: dword;
  224. ResTypeFlag: word;
  225. ResTypeID: word;
  226. end;
  227. PIconHeader = ^TIconHeader;
  228. TIconHeader = packed record
  229. Reserved: word;
  230. wType: word;
  231. wCount: word;
  232. end;
  233. PIconDir = ^TIconDir;
  234. TIconDir = packed record
  235. bWidth: byte;
  236. bHeight: byte;
  237. bColorCount: byte;
  238. bReserved: byte;
  239. wPlanes: word;
  240. wBitCount: word;
  241. lBytesInRes: dword;
  242. wNameOrdinal: word;
  243. end;
  244. var
  245. fs: TCFileStream;
  246. i, sz, rsz, MaxIconID, MaxCursorID: longint;
  247. hdr: TResHeader;
  248. P: pointer;
  249. PData: PIconHeader;
  250. PDir: PIconDir;
  251. ResNameBuf: array[0..1] of word;
  252. begin
  253. if fn='' then
  254. begin
  255. if FOut<>nil then
  256. begin
  257. FOut.Free;
  258. Compile(roOBJ,ChangeFileExt(fname,target_info.resobjext));
  259. end;
  260. end
  261. else
  262. try
  263. fs:=TCFileStream.Create(fn,fmOpenRead or fmShareDenyNone);
  264. if CStreamError<>0 then
  265. begin
  266. fs.Free;
  267. Comment(V_Error,'Can''t open resource file: '+fn);
  268. Include(current_settings.globalswitches, cs_link_nolink);
  269. exit;
  270. end;
  271. if FOut=nil then
  272. begin
  273. FOut:=TCFileStream.Create(fname,fmCreate);
  274. { writing res signature }
  275. FOut.CopyFrom(fs, 32);
  276. end
  277. else
  278. fs.Seek(32, soFromBeginning);
  279. sz:=fs.Size;
  280. MaxIconID := 0;
  281. MaxCursorID := 0;
  282. repeat
  283. fs.ReadBuffer(hdr, SizeOf(hdr));
  284. FOut.WriteBuffer(hdr, SizeOf(hdr));
  285. rsz:=hdr.HeaderSize + hdr.DataSize - SizeOf(hdr);
  286. if fs.Position + rsz > sz then
  287. begin
  288. Comment(V_Error,'Invalid resource file: '+fn);
  289. Include(current_settings.globalswitches, cs_link_nolink);
  290. fs.Free;
  291. exit;
  292. end;
  293. { Adjusting cursor and icon IDs }
  294. if hdr.ResTypeFlag = $FFFF then { resource type is ordinal }
  295. case hdr.ResTypeID of
  296. 1, 3:
  297. { cursor or icon resource }
  298. begin
  299. fs.ReadBuffer(ResNameBuf, SizeOf(ResNameBuf));
  300. if ResNameBuf[0] = $FFFF then { resource name is ordinal }
  301. if hdr.ResTypeID = 1 then
  302. begin
  303. if ResNameBuf[1] > MaxCursorID then
  304. MaxCursorID:=ResNameBuf[1];
  305. Inc(ResNameBuf[1], FLastCursorID);
  306. end
  307. else
  308. begin
  309. if ResNameBuf[1] > MaxIconID then
  310. MaxIconID:=ResNameBuf[1];
  311. Inc(ResNameBuf[1], FLastIconID);
  312. end;
  313. FOut.WriteBuffer(ResNameBuf, SizeOf(ResNameBuf));
  314. Dec(rsz, SizeOf(ResNameBuf));
  315. end;
  316. 12, 14:
  317. { cursor or icon group resource }
  318. begin
  319. GetMem(P, rsz);
  320. fs.ReadBuffer(P^, rsz);
  321. PData := PIconHeader(P + hdr.HeaderSize - sizeof(hdr));
  322. PDir := PIconDir(Pointer(PData) + sizeof(TIconHeader));
  323. for i := 0 to PData^.wCount-1 do
  324. begin
  325. if hdr.ResTypeID = 12 then
  326. Inc(PDir^.wNameOrdinal, FLastCursorID)
  327. else
  328. Inc(PDir^.wNameOrdinal, FLastIconID);
  329. Inc(PDir);
  330. end;
  331. FOut.WriteBuffer(P^, rsz);
  332. rsz:=0;
  333. FreeMem(P);
  334. end;
  335. end;
  336. { copy rest of the resource data }
  337. FOut.CopyFrom(fs, rsz);
  338. { align resource to dword }
  339. i:=4 - FOut.Position mod 4;
  340. if i<4 then
  341. FOut.WriteBuffer(zeroes, i);
  342. { position to the next resource }
  343. i:=4 - fs.Position mod 4;
  344. if i<4 then
  345. fs.Seek(i, soFromCurrent);
  346. until fs.Position + SizeOf(hdr) >= sz;
  347. fs.Free;
  348. Inc(FLastCursorID, MaxCursorID);
  349. Inc(FLastIconID, MaxIconID);
  350. except
  351. on E:EOSError do begin
  352. Comment(V_Error,'Error processing resource file: '+fn+': '+E.Message);
  353. Include(current_settings.globalswitches, cs_link_nolink);
  354. end;
  355. end;
  356. end;
  357. procedure CompileResourceFiles;
  358. var
  359. resourcefile : tresourcefile;
  360. res: TCmdStrListItem;
  361. p,s : TCmdStr;
  362. src,dst : TCFileStream;
  363. outfmt : tresoutput;
  364. begin
  365. { OS/2 (EMX) must be processed elsewhere (in the linking/binding stage).
  366. same with MacOS}
  367. if target_info.system in [system_i386_os2,system_i386_emx,system_powerpc_macos] then exit;
  368. p:=ExtractFilePath(ExpandFileName(current_module.mainsource^));
  369. res:=TCmdStrListItem(current_module.ResourceFiles.First);
  370. while res<>nil do
  371. begin
  372. if target_info.res=res_none then
  373. Message(scan_e_resourcefiles_not_supported);
  374. s:=res.FPStr;
  375. if not path_absolute(s) then
  376. s:=p+s;
  377. resourcefile:=TResourceFile(resinfos[target_info.res]^.resourcefileclass.create(s));
  378. if resourcefile.IsCompiled(s) then
  379. begin
  380. resourcefile.free;
  381. if AnsiCompareFileName(IncludeTrailingPathDelimiter(ExpandFileName(current_module.outputpath^)), p) <> 0 then
  382. begin
  383. { Copy .res file to units output dir }
  384. res.FPStr:=ExtractFileName(res.FPStr);
  385. src:=TCFileStream.Create(s,fmOpenRead or fmShareDenyNone);
  386. if CStreamError<>0 then
  387. begin
  388. Comment(V_Error,'Can''t open resource file: '+src.FileName);
  389. Include(current_settings.globalswitches, cs_link_nolink);
  390. exit;
  391. end;
  392. dst:=TCFileStream.Create(current_module.outputpath^+res.FPStr,fmCreate);
  393. if CStreamError<>0 then
  394. begin
  395. Comment(V_Error,'Can''t create resource file: '+dst.FileName);
  396. Include(current_settings.globalswitches, cs_link_nolink);
  397. exit;
  398. end;
  399. dst.CopyFrom(src,src.Size);
  400. dst.Free;
  401. src.Free;
  402. end;
  403. end
  404. else
  405. begin
  406. res.FPStr:=ExtractFileName(res.FPStr);
  407. if target_res.rcbin='' then
  408. begin
  409. { if target does not have .rc to .res compiler, create obj }
  410. outfmt:=roOBJ;
  411. res.FPStr:=ChangeFileExt(res.FPStr,target_info.resobjext);
  412. end
  413. else
  414. begin
  415. outfmt:=roRES;
  416. res.FPStr:=ChangeFileExt(res.FPStr,target_info.resext);
  417. end;
  418. resourcefile.compile(outfmt, current_module.outputpath^+res.FPStr);
  419. resourcefile.free;
  420. end;
  421. res:=TCmdStrListItem(res.Next);
  422. end;
  423. end;
  424. procedure CollectResourceFiles;
  425. var
  426. resourcefile : tresourcefile;
  427. procedure ProcessModule(u : tmodule);
  428. var
  429. res : TCmdStrListItem;
  430. s : TCmdStr;
  431. begin
  432. res:=TCmdStrListItem(u.ResourceFiles.First);
  433. while assigned(res) do
  434. begin
  435. if path_absolute(res.FPStr) then
  436. s:=res.FPStr
  437. else
  438. begin
  439. s:=u.path^+res.FPStr;
  440. if not FileExists(s,True) then
  441. s:=u.outputpath^+res.FPStr;
  442. end;
  443. resourcefile.Collect(s);
  444. res:=TCmdStrListItem(res.Next);
  445. end;
  446. end;
  447. var
  448. hp : tused_unit;
  449. s : TCmdStr;
  450. begin
  451. if (target_info.res=res_none) or (target_res.rcbin='') then
  452. exit;
  453. if cs_link_nolink in current_settings.globalswitches then
  454. exit;
  455. s:=main_module.outputpath^+GlobalResName+target_info.resext;
  456. resourcefile:=TResourceFile(resinfos[target_info.res]^.resourcefileclass.create(s));
  457. hp:=tused_unit(usedunits.first);
  458. while assigned(hp) do
  459. begin
  460. ProcessModule(hp.u);
  461. hp:=tused_unit(hp.next);
  462. end;
  463. ProcessModule(current_module);
  464. { Finish collection }
  465. resourcefile.Collect('');
  466. resourcefile.free;
  467. end;
  468. end.