1 {
2 ***************************************************************************
3 * *
4 * This source 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 * *
9 * This code is distributed in the hope that it will be useful, but *
10 * WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
12 * General Public License for more details. *
13 * *
14 * A copy of the GNU General Public License is available on the World *
15 * Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
16 * obtain it by writing to the Free Software Foundation, *
17 * Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. *
18 * *
19 ***************************************************************************
20
21 Abstract:
22 This dialog is typically called by the 'Test' button on the compiler options
23 dialog.
24 A dialog testing for common misconfigurations in some compiler options.
25 }
26 unit CheckCompilerOpts;
27
28 {$mode objfpc}{$H+}
29
30 {$I ide.inc}
31
32 interface
33
34 uses
35 Classes, SysUtils, Laz_AVL_Tree,
36 // LCL
37 Forms, Controls, Dialogs, Clipbrd, StdCtrls, Menus, ExtCtrls, ButtonPanel, ComCtrls,
38 // LazUtils
39 LazFileCache, FileUtil, LazFileUtils, LazUTF8, AvgLvlTree,
40 // Codetools
41 CodeToolManager, FileProcs, DefineTemplates, LinkScanner,
42 // IDEIntf
43 ProjectIntf, MacroIntf, IDEExternToolIntf, LazIDEIntf, IDEDialogs,
44 PackageIntf, IDEMsgIntf,
45 // IDE
46 Project, PackageSystem, IDEProcs,
47 LazarusIDEStrConsts, PackageDefs, CompilerOptions, TransferMacros;
48
49 type
50 TCompilerOptionsTest = (
51 cotNone,
52 cotCheckCompilerExe,
53 cotCheckAmbiguousFPCCfg,
54 cotCheckRTLUnits,
55 cotCheckCompilerDate,
56 cotCheckCompilerConfig, // e.g. fpc.cfg
57 cotCheckAmbiguousPPUsInUnitPath,
58 cotCheckFPCUnitPathsContainSources,
59 cotCompileBogusFiles
60 );
61
62 TCompilerCheckMsgLvl = (
63 ccmlHint,
64 ccmlWarning,
65 ccmlError
66 );
67
68 { TCheckCompilerOptsDlg }
69
70 TCheckCompilerOptsDlg = class(TForm)
71 ButtonPanel: TButtonPanel;
72 CopyOutputMenuItem: TMenuItem;
73 OutputPopupMenu: TPopupMenu;
74 OutputTreeView: TTreeView;
75 Splitter1: TSplitter;
76 TestMemo: TMemo;
77 LabelTest: TLabel;
78 LabelOutput: TLabel;
79 procedure ApplicationOnIdle(Sender: TObject; var {%H-}Done: Boolean);
80 procedure CopyOutputMenuItemClick(Sender: TObject);
81 private
82 FIdleConnected: boolean;
83 FMacroList: TTransferMacroList;
84 FOptions: TCompilerOptions;
85 FTest: TCompilerOptionsTest;
86 FLastLineIsProgress: boolean;
87 FDirectories: TStringList;
88 procedure SetIdleConnected(const AValue: boolean);
89 procedure SetMacroList(const AValue: TTransferMacroList);
90 procedure SetOptions(const AValue: TCompilerOptions);
91 procedure SetMsgDirectory(Index: integer; const CurDir: string);
CheckSpecialCharsInPathnull92 function CheckSpecialCharsInPath(const Title, ExpandedPath: string): TModalResult;
CheckNonExistingSearchPathsnull93 function CheckNonExistingSearchPaths(const Title, ExpandedPath: string): TModalResult;
CheckCompilerExecutablenull94 function CheckCompilerExecutable(const CompilerFilename: string): TModalResult;
CheckCompilerConfignull95 function CheckCompilerConfig(CfgCache: TPCTargetConfigCache): TModalResult;
FindAllPPUFilesnull96 function FindAllPPUFiles(const AnUnitPath: string): TStrings;
CheckRTLUnitsnull97 function CheckRTLUnits(CfgCache: TPCTargetConfigCache): TModalResult;
CheckCompilerDatenull98 function CheckCompilerDate(CfgCache: TPCTargetConfigCache): TModalResult;
CheckForAmbiguousPPUsnull99 function CheckForAmbiguousPPUs(SearchForPPUs: TStrings;
100 SearchInPPUs: TStrings = nil): TModalResult;
CheckFPCUnitPathsContainSourcesnull101 function CheckFPCUnitPathsContainSources(const FPCCfgUnitPath: string
102 ): TModalResult;
CheckOutputPathInSourcePathsnull103 function CheckOutputPathInSourcePaths(CurOptions: TCompilerOptions): TModalResult;
CheckOrphanedPPUsnull104 function CheckOrphanedPPUs(CurOptions: TCompilerOptions): TModalResult;
CheckCompileBogusFilenull105 function CheckCompileBogusFile(const CompilerFilename: string): TModalResult;
CheckPackagePathsIntersectionsnull106 function CheckPackagePathsIntersections(CurOptions: TCompilerOptions): TModalResult;
107 public
DoTestAllnull108 function DoTestAll: TModalResult;
109 constructor Create(TheOwner: TComponent); override;
110 destructor Destroy; override;
111 procedure Add(const Msg, CurDir: String; ProgressLine: boolean;
112 OriginalIndex: integer);
113 procedure AddMsg(const Msg, CurDir: String; OriginalIndex: integer);
114 procedure AddHint(const Msg: string);
115 procedure AddWarning(const Msg: string);
116 procedure AddMsg(const Level: TCompilerCheckMsgLvl; const Msg: string);
117 property IdleConnected: boolean read FIdleConnected write SetIdleConnected;
118 public
119 property Options: TCompilerOptions read FOptions write SetOptions;
120 property Test: TCompilerOptionsTest read FTest;
121 property MacroList: TTransferMacroList read FMacroList write SetMacroList;
122 end;
123
124 var
125 CheckCompilerOptsDlg: TCheckCompilerOptsDlg;
126
127 type
128 TCCOSpecialCharType = (
129 ccoscNonASCII,
130 ccoscWrongPathDelim,
131 ccoscUnusualChars,
132 ccoscSpecialChars,
133 ccoscNewLine
134 );
135 TCCOSpecialChars = set of TCCOSpecialCharType;
136
137 procedure FindSpecialCharsInPath(const Path: string; out HasChars: TCCOSpecialChars);
SpecialCharsToStrnull138 function SpecialCharsToStr(const HasChars: TCCOSpecialChars): string;
139
140
141 implementation
142
143 {$R *.lfm}
144
145 procedure FindSpecialCharsInPath(const Path: string; out HasChars: TCCOSpecialChars);
146 var
147 i: Integer;
148 begin
149 HasChars := [];
150 for i := 1 to length(Path) do
151 begin
152 case Path[i] of
153 #10,#13: Include(HasChars,ccoscNewLine);
154 #0..#9,#11,#12,#14..#31: Include(HasChars,ccoscSpecialChars);
155 '/','\': if Path[i]<>PathDelim then Include(HasChars,ccoscWrongPathDelim);
156 '@','#','$','&','*','(',')','[',']','+','<','>','?','|': Include(HasChars,ccoscUnusualChars);
157 #128..#255: Include(HasChars,ccoscNonASCII);
158 end;
159 end;
160 end;
161
SpecialCharsToStrnull162 function SpecialCharsToStr(const HasChars: TCCOSpecialChars): string;
163
164 procedure AddStr(var s: string; const Addition: string);
165 begin
166 if s='' then
167 s:=lisCCOContains
168 else
169 s:=s+', ';
170 s:=s+Addition;
171 end;
172
173 begin
174 Result:='';
175 if ccoscNonASCII in HasChars then AddStr(Result,lisCCONonASCII);
176 if ccoscWrongPathDelim in HasChars then AddStr(Result,lisCCOWrongPathDelimiter);
177 if ccoscUnusualChars in HasChars then AddStr(Result,lisCCOUnusualChars);
178
179 if ccoscSpecialChars in HasChars then AddStr(Result,lisCCOSpecialCharacters);
180 if ccoscNewLine in HasChars then AddStr(Result,lisCCOHasNewLine);
181 end;
182
183 { TCheckCompilerOptsDlg }
184
185 procedure TCheckCompilerOptsDlg.ApplicationOnIdle(Sender: TObject; var Done: Boolean);
186 begin
187 IdleConnected:=false;
188 DoTestAll;
189 end;
190
191 procedure TCheckCompilerOptsDlg.CopyOutputMenuItemClick(Sender: TObject);
192 var
193 s: String;
194 TVNode: TTreeNode;
195 begin
196 s:='';
197 for TVNode in OutputTreeView.Items do
198 s+=TVNode.Text+LineEnding;
199 Clipboard.AsText:=s;
200 end;
201
202 procedure TCheckCompilerOptsDlg.SetOptions(const AValue: TCompilerOptions);
203 begin
204 if FOptions=AValue then exit;
205 FOptions:=AValue;
206 end;
207
208 procedure TCheckCompilerOptsDlg.SetMsgDirectory(Index: integer;
209 const CurDir: string);
210 begin
211 if FDirectories=nil then FDirectories:=TStringList.Create;
212 while FDirectories.Count<=Index do FDirectories.Add('');
213 FDirectories[Index]:=CurDir;
214 end;
215
TCheckCompilerOptsDlg.CheckSpecialCharsInPathnull216 function TCheckCompilerOptsDlg.CheckSpecialCharsInPath(const Title, ExpandedPath: string
217 ): TModalResult;
218 var
219 Warning: String;
220 ErrorMsg: String;
221 HasChars: TCCOSpecialChars;
222 begin
223 FindSpecialCharsInPath(ExpandedPath, HasChars);
224 Warning := SpecialCharsToStr(HasChars * [ccoscNonASCII, ccoscWrongPathDelim, ccoscUnusualChars]);
225 ErrorMsg := SpecialCharsToStr(HasChars * [ccoscSpecialChars, ccoscNewLine]);
226
227 if Warning <> '' then
228 AddWarning(Title + ' ' + Warning);
229 if ErrorMsg <> '' then
230 begin
231 Result := IDEQuestionDialog(lisCCOInvalidSearchPath, Title + ' ' + ErrorMsg, mtError,
232 [mrIgnore, lisCCOSkip, mrAbort]);
233 end else
234 begin
235 if Warning = '' then
236 Result := mrOk
237 else
238 Result := mrIgnore;
239 end;
240 end;
241
CheckNonExistingSearchPathsnull242 function TCheckCompilerOptsDlg.CheckNonExistingSearchPaths(const Title,
243 ExpandedPath: string): TModalResult;
244 var
245 p: Integer;
246 CurPath: String;
247 begin
248 Result:=mrOk;
249 p:=1;
250 repeat
251 CurPath:=GetNextDirectoryInSearchPath(ExpandedPath,p);
252 if (CurPath<>'') and (not IDEMacros.StrHasMacros(CurPath))
253 and (FilenameIsAbsolute(CurPath)) then begin
254 if not DirPathExistsCached(CurPath) then begin
255 AddWarning(Format(lisDoesNotExists, [Title, CurPath]));
256 end;
257 end;
258 until p>length(ExpandedPath);
259 end;
260
TCheckCompilerOptsDlg.CheckCompilerExecutablenull261 function TCheckCompilerOptsDlg.CheckCompilerExecutable(
262 const CompilerFilename: string): TModalResult;
263 var
264 CompilerFiles: TStrings;
265 begin
266 FTest:=cotCheckCompilerExe;
267 LabelTest.Caption:=dlgCCOTestCheckingCompiler;
268 try
269 CheckIfFileIsExecutable(CompilerFilename);
270 except
271 on e: Exception do begin
272 Result:=IDEQuestionDialog(lisCCOInvalidCompiler,
273 Format(lisCCOCompilerNotAnExe,[CompilerFilename,LineEnding,E.Message]),
274 mtError,[mrIgnore,lisCCOSkip,mrAbort]);
275 exit;
276 end;
277 end;
278
279 // check if there are several compilers in path
280 CompilerFiles:=SearchAllFilesInPath(GetDefaultCompilerFilename,'',
281 GetEnvironmentVariableUTF8('PATH'),PathSeparator,[sffDontSearchInBasePath]);
282 try
283 ResolveLinksInFileList(CompilerFiles,false);
284 RemoveDoubles(CompilerFiles);
285 if (CompilerFiles<>nil) and (CompilerFiles.Count>1) then begin
286 Result:=MessageDlg(lisCCOAmbiguousCompiler,
287 Format(lisCCOSeveralCompilers,
288 [LineEnding+LineEnding,CompilerFiles.Text,LineEnding]),
289 mtWarning,[mbAbort,mbIgnore],0);
290 if Result<>mrIgnore then exit;
291 end;
292 finally
293 CompilerFiles.Free;
294 end;
295
296 Result:=mrOk;
297 end;
298
CheckCompileBogusFilenull299 function TCheckCompilerOptsDlg.CheckCompileBogusFile(
300 const CompilerFilename: string): TModalResult;
301 var
302 TestDir: String;
303 BogusFilename: String;
304 CmdLineParams, ErrMsg: String;
305 CompileTool: TAbstractExternalTool;
306 Kind: TPascalCompiler;
307 begin
308 // compile bogus file
309 FTest:=cotCompileBogusFiles;
310 LabelTest.Caption:=dlgCCOTestCompilingEmptyFile;
311
312 // get Test directory
313 TestDir:=AppendPathDelim(LazarusIDE.GetTestBuildDirectory);
314 if not DirPathExists(TestDir) then begin
315 IDEMessageDialog(lisCCOInvalidTestDir,
316 Format(lisCCOCheckTestDir,[LineEnding]),
317 mtError,[mbCancel]);
318 Result:=mrCancel;
319 exit;
320 end;
321 // create bogus file
322 BogusFilename:=CreateNonExistingFilename(TestDir+'testcompileroptions.pas');
323 if not CreateEmptyFile(BogusFilename) then begin
324 IDEMessageDialog(lisCCOUnableToCreateTestFile,
325 Format(lisCCOUnableToCreateTestPascalFile,[BogusFilename]),
326 mtError,[mbCancel]);
327 Result:=mrCancel;
328 exit;
329 end;
330 try
331 // create compiler command line options
332 CmdLineParams:=Options.MakeOptionsString(
333 [ccloAddVerboseAll,ccloDoNotAppendOutFileOption,ccloAbsolutePaths])
334 +' '+BogusFilename;
335 CompileTool:=ExternalToolList.Add(dlgCCOTestToolCompilingEmptyFile);
336 CompileTool.Reference(Self,ClassName);
337 try
338 if IsCompilerExecutable(CompilerFilename,ErrMsg,Kind,true) and (Kind=pcPas2js) then
339 CompileTool.AddParsers(SubToolPas2js)
340 else
341 CompileTool.AddParsers(SubToolFPC);
342 CompileTool.AddParsers(SubToolMake);
343 CompileTool.Process.CurrentDirectory:=TestDir;
344 CompileTool.Process.Executable:=CompilerFilename;
345 CompileTool.CmdLineParams:=CmdLineParams;
346 CompileTool.Execute;
347 CompileTool.WaitForExit;
348 finally
349 CompileTool.Release(Self);
350 end;
351 finally
352 DeleteFileUTF8(BogusFilename);
353 end;
354
355 Result:=mrOk;
356 end;
357
CheckPackagePathsIntersectionsnull358 function TCheckCompilerOptsDlg.CheckPackagePathsIntersections(
359 CurOptions: TCompilerOptions): TModalResult;
360 // check if the search paths contains source directories of used packages
361 // instead of only the output directories
362 var
363 CurProject: TProject;
364 CurPkg: TLazPackage;
365 FirstDependency: TPkgDependency;
366 PkgList: TFPList;
367 i: Integer;
368 UsedPkg: TLazPackage;
369 UnitPath: String;
370 OtherOutputDir: String;
371 OtherSrcPath: String;
372 p: Integer;
373 SrcDir: String;
374 begin
375 if CurOptions.BaseDirectory='' then exit(mrOk);
376
377 // get dependencies
378 CurProject:=nil;
379 CurPkg:=nil;
380 if CurOptions.Owner is TProject then begin
381 CurProject:=TProject(CurOptions.Owner);
382 FirstDependency:=CurProject.FirstRequiredDependency;
383 end;
384 if CurOptions.Owner is TLazPackage then begin
385 CurPkg:=TLazPackage(CurOptions.Owner);
386 FirstDependency:=CurPkg.FirstRequiredDependency;
387 end;
388 if FirstDependency=nil then exit(mrOK);
389 try
390 // get used packages
391 PackageGraph.GetAllRequiredPackages(nil,FirstDependency,PkgList,[pirSkipDesignTimeOnly]);
392 if PkgList=nil then exit(mrOk);
393
394 // get search path
395 UnitPath:=CurOptions.GetParsedPath(pcosUnitPath,icoNone,false,true);
396 // check each used package
397 for i:=0 to PkgList.Count-1 do begin
398 UsedPkg:=TLazPackage(PkgList[i]);
399 if UsedPkg.CompilerOptions.BaseDirectory='' then exit;
400 // get source directories of used package (excluding the output directory)
401 OtherSrcPath:=UsedPkg.CompilerOptions.GetParsedPath(pcosUnitPath,icoNone,false,true);
402 OtherOutputDir:=UsedPkg.CompilerOptions.GetUnitOutPath(false);
403 OtherSrcPath:=RemoveSearchPaths(OtherSrcPath,OtherOutputDir);
404 // find intersections
405 p:=1;
406 repeat
407 SrcDir:=GetNextDirectoryInSearchPath(UnitPath,p);
408 if SearchDirectoryInSearchPath(OtherSrcPath,SrcDir)>0 then
409 AddWarning(Format(lisTheUnitSearchPathOfContainsTheSourceDirectoryOfPac,
410 [CurOptions.GetOwnerName, SrcDir, UsedPkg.Name]));
411 until p>length(UnitPath);
412 end;
413 finally
414 PkgList.Free;
415 end;
416 Result:=mrOk;
417 end;
418
CheckCompilerConfignull419 function TCheckCompilerOptsDlg.CheckCompilerConfig(
420 CfgCache: TPCTargetConfigCache): TModalResult;
421 var
422 i: Integer;
423 CfgFile: TPCConfigFileState;
424 CfgCount: Integer;
425 begin
426 FTest:=cotCheckCompilerConfig;
427 LabelTest.Caption:=dlgCCOTestCheckingCompilerConfig;
428
429 CfgCount:=0;
430 for i:=0 to CfgCache.ConfigFiles.Count-1 do begin
431 CfgFile:=CfgCache.ConfigFiles[i];
432 if CfgFile.FileExists then inc(CfgCount);
433 end;
434 if CfgCount<0 then begin
435 // missing config file => warning
436 AddWarning(lisCCONoCfgFound);
437 end else if CfgCount=1 then begin
438 // exactly one config, sounds good, but might still the be wrong one
439 // => hint
440 for i:=0 to CfgCache.ConfigFiles.Count-1 do begin
441 CfgFile:=CfgCache.ConfigFiles[i];
442 if CfgFile.FileExists then begin
443 AddHint(Format(dlgCCOUsingConfigFile, [CfgFile.Filename]));
444 break;
445 end;
446 end;
447 end else if CfgCount>1 then begin
448 // multiple config files => warning
449 for i:=0 to CfgCache.ConfigFiles.Count-1 do begin
450 CfgFile:=CfgCache.ConfigFiles[i];
451 if CfgFile.FileExists then
452 AddWarning(lisCCOMultipleCfgFound+CfgFile.Filename);
453 end;
454 end;
455
456 Result:=mrOk;
457 end;
458
FindAllPPUFilesnull459 function TCheckCompilerOptsDlg.FindAllPPUFiles(const AnUnitPath: string
460 ): TStrings;
461 var
462 Directory: String;
463 p: Integer;
464 FileInfo: TSearchRec;
465 begin
466 Result:=TStringList.Create;
467
468 p:=1;
469 while p<=length(AnUnitPath) do begin
470 Directory:=TrimAndExpandDirectory(GetNextDirectoryInSearchPath(AnUnitPath,p));
471 if Directory<>'' then begin
472 if FindFirstUTF8(Directory+GetAllFilesMask,faAnyFile,FileInfo)=0
473 then begin
474 repeat
475 // check if special file
476 if (FileInfo.Name='.') or (FileInfo.Name='..') or (FileInfo.Name='') then
477 continue;
478 // check extension
479 if CompareFileExt(FileInfo.Name,'.ppu',
480 {$IFDEF MSWINDOWS}false{$ELSE}true{$ENDIF})=0 then
481 Result.Add(Directory+FileInfo.Name);
482 until FindNextUTF8(FileInfo)<>0;
483 end;
484 FindCloseUTF8(FileInfo);
485 end;
486 end;
487 end;
488
CheckRTLUnitsnull489 function TCheckCompilerOptsDlg.CheckRTLUnits(
490 CfgCache: TPCTargetConfigCache): TModalResult;
491
Checknull492 function Check(const TheUnitname: string; Severity: TCompilerCheckMsgLvl
493 ): Boolean;
494 var
495 CurUnitFile, Cfg: String;
496 begin
497 if (CfgCache.Units<>nil)
498 and (CfgCache.Units.Contains(TheUnitname)) then exit(true);
499 if CfgCache.Kind=pcPas2js then
500 begin
501 CurUnitFile:=TheUnitname+'.pas';
502 Cfg:='pas2js.cfg';
503 end
504 else begin
505 CurUnitFile:=TheUnitname+'.ppu';
506 Cfg:='fpc.cfg';
507 end;
508 AddMsg(Severity,Format(lisCCOMsgRTLUnitNotFound,[CurUnitFile]));
509 Result:=ord(Severity)>=ord(ccmlError);
510 if not Result then begin
511 if IDEMessageDialog(lisCCOMissingUnit,
512 Format(lisCCORTLUnitNotFoundDetailed,[CurUnitFile, LineEnding, Cfg]),
513 mtError,[mbIgnore,mbAbort])=mrIgnore then
514 Result:=true;
515 end;
516 end;
517
518 begin
519 FTest:=cotCheckRTLUnits;
520 LabelTest.Caption:=dlgCCOTestRTLUnits;
521
522 Result:=mrCancel;
523
524 if not Check('system',ccmlError) then exit;
525 if CfgCache.Kind=pcPas2js then
526 begin
527 if not Check('js',ccmlError) then exit;
528 if not Check('classes',ccmlError) then exit;
529 if not Check('sysutils',ccmlError) then exit;
530 end else begin
531 if not Check('objpas',ccmlError) then exit;
532 if CfgCache.TargetCPU='jvm' then begin
533 if not Check('uuchar',ccmlError) then exit;
534 end else begin
535 if not Check('sysutils',ccmlError) then exit;
536 if not Check('classes',ccmlError) then exit;
537 if not Check('avl_tree',ccmlError) then exit;
538 if not Check('zstream',ccmlError) then exit;
539 end;
540 end;
541
542 Result:=mrOk;
543 end;
544
TCheckCompilerOptsDlg.CheckCompilerDatenull545 function TCheckCompilerOptsDlg.CheckCompilerDate(CfgCache: TPCTargetConfigCache
546 ): TModalResult;
547 var
548 MinPPUDate: LongInt;
549 MaxPPUDate: LongInt;
550 CompilerDate: LongInt;
551 MinPPU: String;
552 MaxPPU: String;
553 Node: TAVLTreeNode;
554 Item: PStringToStringItem;
555
556 procedure CheckFileAge(const aFilename: string);
557 var
558 CurDate: LongInt;
559 begin
560 CurDate:=FileAgeCached(aFilename);
561 //DebugLn(['CheckFileAge ',aFilename,' ',CurDate]);
562 if (CurDate=-1) then exit;
563 if (MinPPUDate=-1) or (MinPPUDate>CurDate) then begin
564 MinPPUDate:=CurDate;
565 MinPPU:=aFilename;
566 end;
567 if (MaxPPUDate=-1) or (MaxPPUDate<CurDate) then begin
568 MaxPPUDate:=CurDate;
569 MaxPPU:=aFilename;
570 end;
571 end;
572
573 procedure CheckFileAgeOfUnit(const aUnitName: string);
574 var
575 Filename: string;
576 begin
577 Filename:=CfgCache.Units[aUnitName];
578 if Filename='' then exit;
579 CheckFileAge(Filename);
580 end;
581
582 begin
583 if CfgCache.Units=nil then exit(mrOK);
584
585 FTest:=cotCheckCompilerDate;
586 LabelTest.Caption:=dlgCCOTestCompilerDate;
587
588 Result:=mrCancel;
589
590 CompilerDate:=CfgCache.CompilerDate;
591
592 if CfgCache.Kind=pcFPC then
593 begin
594
595 // first check some rtl and fcl units
596 // They are normally installed in one step, so the dates should be nearly
597 // the same. If not, then probably two different installations are mixed up.
598 MinPPUDate:=-1;
599 MinPPU:='';
600 MaxPPUDate:=-1;
601 MaxPPU:='';
602 CheckFileAgeOfUnit('system');
603 CheckFileAgeOfUnit('sysutils');
604 CheckFileAgeOfUnit('classes');
605 CheckFileAgeOfUnit('base64');
606 CheckFileAgeOfUnit('avl_tree');
607 CheckFileAgeOfUnit('fpimage');
608
609 //DebugLn(['TCheckCompilerOptsDlg.CheckCompilerDate MinPPUDate=',MinPPUDate,' MaxPPUDate=',MaxPPUDate,' compdate=',CompilerDate]);
610
611 if MinPPU<>'' then begin
612 if MaxPPUDate-MinPPUDate>3600 then begin
613 // the FPC .ppu files dates differ more than one hour
614 Result:=MessageDlg(lisCCOWarningCaption,
615 Format(lisCCODatesDiffer,[LineEnding,LineEnding,MinPPU,LineEnding,MaxPPU]),
616 mtError,[mbIgnore,mbAbort],0);
617 if Result<>mrIgnore then
618 exit;
619 end;
620 end;
621
622 // check file dates of all .ppu
623 // if a .ppu is much older than the compiler itself, then the ppu is probably
624 // a) a leftover from a installation
625 // b) not updated
626 Node:=CfgCache.Units.Tree.FindLowest;
627 while Node<>nil do begin
628 Item:=PStringToStringItem(Node.Data);
629 if (Item^.Value<>'') and (CompareFileExt(Item^.Value,'.ppu',false)=0) then
630 CheckFileAge(Item^.Value);
631 Node:=CfgCache.Units.Tree.FindSuccessor(Node);
632 end;
633
634 if MinPPU<>'' then begin
635 if CompilerDate-MinPPUDate>300 then begin
636 // the compiler is more than 5 minutes newer than one of the ppu files
637 Result:=MessageDlg(lisCCOWarningCaption,
638 Format(lisCCOPPUOlderThanCompiler, [LineEnding, MinPPU]),
639 mtError,[mbIgnore,mbAbort],0);
640 if Result<>mrIgnore then
641 exit;
642 end;
643 end;
644 end;
645
646 Result:=mrOk;
647 end;
648
TCheckCompilerOptsDlg.CheckForAmbiguousPPUsnull649 function TCheckCompilerOptsDlg.CheckForAmbiguousPPUs(SearchForPPUs: TStrings;
650 SearchInPPUs: TStrings): TModalResult;
651 var
652 i: Integer;
653 j: Integer;
654 CurUnitName: String;
655 AnotherUnitName: String;
656 begin
657 if SearchInPPUs=nil then
658 SearchInPPUs:=SearchForPPUs;
659
660 // resolve links and remove doubles
661 ResolveLinksInFileList(SearchForPPUs,true);
662 RemoveDoubles(SearchForPPUs);
663 if SearchForPPUs<>SearchInPPUs then begin
664 ResolveLinksInFileList(SearchInPPUs,true);
665 RemoveDoubles(SearchInPPUs);
666 end;
667
668 for i:=1 to SearchForPPUs.Count-1 do begin
669 CurUnitName:=ExtractFileNameOnly(SearchForPPUs[i]);
670 if SearchForPPUs=SearchInPPUs then
671 j:=i-1
672 else
673 j:=SearchInPPUs.Count-1;
674 while j>=0 do begin
675 AnotherUnitName:=ExtractFileNameOnly(SearchInPPUs[j]);
676 if CompareText(AnotherUnitName,CurUnitName)=0 then begin
677 // unit exists twice
678 AddWarning(Format(lisCCOPPUExistsTwice,[SearchForPPUs[i],SearchInPPUs[j]]));
679 break;
680 end;
681 dec(j);
682 end;
683 end;
684 Result:=mrOk;
685 end;
686
TCheckCompilerOptsDlg.CheckFPCUnitPathsContainSourcesnull687 function TCheckCompilerOptsDlg.CheckFPCUnitPathsContainSources(
688 const FPCCfgUnitPath: string): TModalResult;
689 // The FPC standard unit path does not include source directories.
690 // If it contain source directories the user added these unit paths himself.
691 // This is probably a hack and has two disadvantages:
692 // 1. The IDE ignores these paths
693 // 2. The user risks to create various .ppu for these sources which leads to
694 // strange further compilation errors.
695 var
696 p: Integer;
697 Directory: String;
698 FileInfo: TSearchRec;
699 WarnedDirectories: TStringList;
700 begin
701 FTest:=cotCheckFPCUnitPathsContainSources;
702 LabelTest.Caption:=dlgCCOTestSrcInPPUPaths;
703
704 Result:=mrCancel;
705 WarnedDirectories:=TStringList.Create;
706 p:=1;
707 while p<=length(FPCCfgUnitPath) do begin
708 Directory:=TrimFilename(GetNextDirectoryInSearchPath(FPCCfgUnitPath,p));
709 if (Directory<>'') then begin
710 Directory:=TrimAndExpandDirectory(GetNextDirectoryInSearchPath(FPCCfgUnitPath,p));
711 if (Directory<>'') and (FilenameIsAbsolute(Directory))
712 and (WarnedDirectories.IndexOf(Directory)<0) then begin
713 //DebugLn(['TCheckCompilerOptsDlg.CheckFPCUnitPathsContainSources Directory="',Directory,'"']);
714 if FindFirstUTF8(Directory+GetAllFilesMask,faAnyFile,FileInfo)=0
715 then begin
716 repeat
717 // check if special file
718 if (FileInfo.Name='.') or (FileInfo.Name='..') or (FileInfo.Name='') then
719 continue;
720 // check extension
721 if FilenameIsPascalUnit(FileInfo.Name) then begin
722 AddWarning(lisCCOFPCUnitPathHasSource+Directory+FileInfo.Name);
723 WarnedDirectories.Add(Directory);
724 break;
725 end;
726 until FindNextUTF8(FileInfo)<>0;
727 end;
728 FindCloseUTF8(FileInfo);
729 end;
730 end;
731 end;
732 WarnedDirectories.Free;
733 Result:=mrOk;
734 end;
735
TCheckCompilerOptsDlg.CheckOutputPathInSourcePathsnull736 function TCheckCompilerOptsDlg.CheckOutputPathInSourcePaths(
737 CurOptions: TCompilerOptions): TModalResult;
738 var
739 OutputDir: String;
740 SrcPath: String;
741 begin
742 OutputDir:=CurOptions.GetUnitOutPath(false);
743 if OutputDir='' then begin
744 if CurOptions.Owner is TLazPackage then
745 AddWarning(CurOptions.GetOwnerName+' has no output directory set');
746 exit(mrOk);
747 end;
748 // check unit search path
749 SrcPath:=CurOptions.GetParsedPath(pcosUnitPath,icoNone,false);
750 if SearchDirectoryInSearchPath(SrcPath,OutputDir)>0 then begin
751 AddWarning(Format(lisTheOutputDirectoryOfIsListedInTheUnitSearchPathOf, [
752 CurOptions.GetOwnerName, CurOptions.GetOwnerName])
753 +lisTheOutputDirectoryShouldBeASeparateDirectoryAndNot);
754 end;
755 // check include search path
756 SrcPath:=CurOptions.GetParsedPath(pcosIncludePath,icoNone,false);
757 if SearchDirectoryInSearchPath(SrcPath,OutputDir)>0 then begin
758 AddWarning(Format(lisTheOutputDirectoryOfIsListedInTheIncludeSearchPath, [
759 CurOptions.GetOwnerName, CurOptions.GetOwnerName])
760 +lisTheOutputDirectoryShouldBeASeparateDirectoryAndNot);
761 end;
762 // check inherited unit search path
763 SrcPath:=CurOptions.GetParsedPath(pcosNone,icoUnitPath,false);
764 if SearchDirectoryInSearchPath(SrcPath,OutputDir)>0 then begin
765 AddWarning(Format(lisTheOutputDirectoryOfIsListedInTheInheritedUnitSear, [
766 CurOptions.GetOwnerName, CurOptions.GetOwnerName])
767 +lisTheOutputDirectoryShouldBeASeparateDirectoryAndNot);
768 end;
769 // check inherited include search path
770 SrcPath:=CurOptions.GetParsedPath(pcosNone,icoIncludePath,false);
771 if SearchDirectoryInSearchPath(SrcPath,OutputDir)>0 then begin
772 AddWarning(Format(lisTheOutputDirectoryOfIsListedInTheInheritedIncludeS, [
773 CurOptions.GetOwnerName, CurOptions.GetOwnerName])
774 +lisTheOutputDirectoryShouldBeASeparateDirectoryAndNot);
775 end;
776 Result:=mrOk;
777 end;
778
CheckOrphanedPPUsnull779 function TCheckCompilerOptsDlg.CheckOrphanedPPUs(CurOptions: TCompilerOptions
780 ): TModalResult;
781 // check for ppu and .o files that were not created from known .pas/.pp/.p files
782 var
783 FileInfo: TSearchRec;
784 PPUFiles: TStringList;
785 i: Integer;
786 OutputDir: String;
787 PPUFilename: string;
788 AUnitName: String;
789 SrcPath: String;
790 Directory: String;
791 CurProject: TLazProject;
792 ProjFile: TLazProjectFile;
793 begin
794 OutputDir:=CurOptions.GetUnitOutPath(false);
795 if OutputDir='' then exit(mrOk);
796
797 PPUFiles:=TStringList.Create;
798 try
799 // search .ppu and .o files in output directory
800 Directory:=AppendPathDelim(OutputDir);
801 if FindFirstUTF8(Directory+GetAllFilesMask,faAnyFile,FileInfo)=0 then
802 begin
803 repeat
804 // check if special file
805 if (FileInfo.Name='.') or (FileInfo.Name='..') or (FileInfo.Name='') then
806 continue;
807 // check extension
808 if (CompareFileExt(FileInfo.Name,'.ppu',
809 {$IFDEF MSWINDOWS}false{$ELSE}true{$ENDIF})<>0)
810 and (CompareFileExt(FileInfo.Name,'.o',
811 {$IFDEF MSWINDOWS}false{$ELSE}true{$ENDIF})<>0)
812 then
813 continue;
814 PPUFiles.Add(Directory+FileInfo.Name);
815 until FindNextUTF8(FileInfo)<>0;
816 end;
817 FindCloseUTF8(FileInfo);
818
819 // remove all .ppu/.o files with a unit source
820 SrcPath:=Options.GetParsedPath(pcosUnitPath,icoNone,false,true);
821 //DebugLn(['TCheckCompilerOptsDlg.CheckOrphanedPPUs SrcPath="',SrcPath,'" OutDir="',OutputDir,'"']);
822 for i:=PPUFiles.Count-1 downto 0 do begin
823 PPUFilename:=PPUFiles[i];
824 AUnitName:=ExtractFileNameOnly(PPUFilename);
825 // search .pas/.pp/.p file
826 if SearchPascalUnitInPath(AUnitName,'',SrcPath,';',ctsfcAllCase)<>'' then
827 PPUFiles.Delete(i)
828 // check for main source
829 else if (Options.Owner is TLazProject) then begin
830 CurProject:=TLazProject(Options.Owner);
831 if (CurProject.MainFileID>=0) then begin
832 ProjFile:=CurProject.MainFile;
833 if (SysUtils.CompareText(ExtractFileNameOnly(ProjFile.Filename),AUnitName)=0)
834 then
835 PPUFiles.Delete(i);
836 end;
837 end;
838 end;
839
840 // PPUFiles now contains all orphaned ppu/o files
841 PPUFiles.Sort;
842 for i:=0 to PPUFiles.Count-1 do
843 AddWarning(Format(dlgCCOOrphanedFileFound, [PPUFiles[i]]));
844 finally
845 PPUFiles.Free;
846 end;
847
848 Result:=mrOk;
849 end;
850
851 procedure TCheckCompilerOptsDlg.SetMacroList(const AValue: TTransferMacroList);
852 begin
853 if FMacroList=AValue then exit;
854 FMacroList:=AValue;
855 end;
856
857 procedure TCheckCompilerOptsDlg.SetIdleConnected(const AValue: boolean);
858 begin
859 if FIdleConnected=AValue then exit;
860 FIdleConnected:=AValue;
861 if FIdleConnected then
862 Application.AddOnIdleHandler(@ApplicationOnIdle)
863 else
864 Application.RemoveOnIdleHandler(@ApplicationOnIdle);
865 end;
866
TCheckCompilerOptsDlg.DoTestAllnull867 function TCheckCompilerOptsDlg.DoTestAll: TModalResult;
868 var
869 CompilerFilename: String;
870 CompileTool: TAbstractExternalTool;
871 CompilerFiles: TStrings;
872 FPCCfgUnitPath: string;
873 TargetUnitPath: String;
874 Target_PPUs: TStrings;
875 cp: TParsedCompilerOptString;
876 TargetCPU: String;
877 TargetOS: String;
878 CfgCache: TPCTargetConfigCache;
879 FPC_PPUs: TStrings;
880 begin
881 Result:=mrCancel;
882 if Test<>cotNone then exit;
883 CompileTool:=nil;
884 TestMemo.Lines.Clear;
885 CompilerFiles:=nil;
886 Target_PPUs:=nil;
887 FPC_PPUs:=nil;
888 IDEMessagesWindow.Clear;
889 Screen.Cursor:=crHourGlass;
890 try
891 // make sure there is no invalid cache due to bugs
892 InvalidateFileStateCache();
893
894 // check for special characters in search paths
895 for cp:=Low(TParsedCompilerOptString) to High(TParsedCompilerOptString) do
896 begin
897 if cp in ParsedCompilerSearchPaths then begin
898 Result:=CheckSpecialCharsInPath(copy(EnumToStr(cp),5,100),
899 Options.ParsedOpts.GetParsedValue(cp));
900 if not (Result in [mrOk,mrIgnore]) then exit;
901 end;
902 end;
903
904 // check for non existing paths
905 CheckNonExistingSearchPaths('include search path',Options.GetIncludePath(false));
906 CheckNonExistingSearchPaths('library search path',Options.GetLibraryPath(false));
907 CheckNonExistingSearchPaths('unit search path', Options.GetUnitPath(false));
908 CheckNonExistingSearchPaths('source search path', Options.GetSrcPath(false));
909
910 // fetch compiler filename
911 CompilerFilename:=Options.ParsedOpts.GetParsedValue(pcosCompilerPath);
912
913 // check compiler filename
914 Result:=CheckCompilerExecutable(CompilerFilename);
915 if not (Result in [mrOk,mrIgnore]) then exit;
916
917 TargetOS:=Options.TargetOS;
918 TargetCPU:=Options.TargetCPU;
919 CfgCache:=CodeToolBoss.CompilerDefinesCache.ConfigCaches.Find(CompilerFilename,
920 '',TargetOS,TargetCPU,true);
921 if CfgCache.NeedsUpdate then
922 CfgCache.Update(CodeToolBoss.CompilerDefinesCache.TestFilename,
923 CodeToolBoss.CompilerDefinesCache.ExtraOptions);
924
925 // check compiler config
926 Result:=CheckCompilerConfig(CfgCache);
927 if not (Result in [mrOk,mrIgnore]) then exit;
928
929 // check if compiler paths include base units
930 Result:=CheckRTLUnits(CfgCache);
931 if not (Result in [mrOk,mrIgnore]) then exit;
932
933 // check if compiler is older than fpc ppu
934 Result:=CheckCompilerDate(CfgCache);
935 if not (Result in [mrOk,mrIgnore]) then exit;
936
937 if CfgCache.Kind=pcFPC then
938 begin
939 // check if there are ambiguous fpc ppu
940 FPCCfgUnitPath:=CfgCache.GetUnitPaths;
941 FPC_PPUs:=FindAllPPUFiles(FPCCfgUnitPath);
942 Result:=CheckForAmbiguousPPUs(FPC_PPUs);
943 if not (Result in [mrOk,mrIgnore]) then exit;
944
945 // check if FPC unit paths contain sources
946 Result:=CheckFPCUnitPathsContainSources(FPCCfgUnitPath);
947 if not (Result in [mrOk,mrIgnore]) then exit;
948 end;
949
950 if Options is TPkgCompilerOptions then begin
951 // check if package has no separate output directory
952 Result:=CheckOutputPathInSourcePaths(Options);
953 if not (Result in [mrOk,mrIgnore]) then exit;
954 end;
955
956 if CfgCache.Kind=pcFPC then
957 begin
958 // gather PPUs in project/package unit search paths
959 TargetUnitPath:=Options.GetUnitPath(false);
960 Target_PPUs:=FindAllPPUFiles(TargetUnitPath);
961
962 // check if there are ambiguous ppu in project/package unit path
963 Result:=CheckForAmbiguousPPUs(Target_PPUs);
964 if not (Result in [mrOk,mrIgnore]) then exit;
965
966 // check if there are ambiguous ppu in fpc and project/package unit path
967 Result:=CheckForAmbiguousPPUs(FPC_PPUs,Target_PPUs);
968 if not (Result in [mrOk,mrIgnore]) then exit;
969
970 // check that all ppu in the output directory have sources in project/package
971 Result:=CheckOrphanedPPUs(Options);
972 if not (Result in [mrOk,mrIgnore]) then exit;
973 end;
974
975 // compile bogus file
976 Result:=CheckCompileBogusFile(CompilerFilename);
977 if not (Result in [mrOk,mrIgnore]) then exit;
978
979 // check if search paths of packages/projects intersects
980 Result:=CheckPackagePathsIntersections(Options);
981 if not (Result in [mrOk,mrIgnore]) then exit;
982
983 // ToDo: check ppu checksums and versions
984
985 if OutputTreeView.Items.Count=0 then
986 AddMsg(lisCCOTestsSuccess,'',-1);
987
988 finally
989 Screen.Cursor:=crDefault;
990 CompilerFiles.Free;
991 CompileTool.Free;
992 FTest:=cotNone;
993 LabelTest.Caption:=dlgCCOTest;
994 FPC_PPUs.Free;
995 Target_PPUs.Free;
996 end;
997 Result:=mrOk;
998 end;
999
1000 constructor TCheckCompilerOptsDlg.Create(TheOwner: TComponent);
1001 begin
1002 inherited Create(TheOwner);
1003 IdleConnected:=true;
1004 Caption:=dlgCCOCaption;
1005 LabelTest.Caption:=dlgCCOTest;
1006 LabelOutput.Caption:=dlgCCOResults;
1007 CopyOutputMenuItem.Caption:=lisCCOCopyOutputToCliboard;
1008 end;
1009
1010 destructor TCheckCompilerOptsDlg.Destroy;
1011 begin
1012 IdleConnected:=false;;
1013 FDirectories.Free;
1014 inherited Destroy;
1015 end;
1016
1017 procedure TCheckCompilerOptsDlg.Add(const Msg, CurDir: String;
1018 ProgressLine: boolean; OriginalIndex: integer);
1019 var
1020 i: Integer;
1021 begin
1022 if FLastLineIsProgress then begin
1023 OutputTreeView.Items[OutputTreeView.Items.Count-1].Text:=Msg;
1024 end else begin
1025 OutputTreeView.Items.Add(nil,Msg);
1026 end;
1027 FLastLineIsProgress:=ProgressLine;
1028 i:=OutputTreeView.Items.Count-1;
1029 SetMsgDirectory(i,CurDir);
1030 OutputTreeView.TopItem:=OutputTreeView.Items.GetLastNode;
1031 if OriginalIndex=0 then ;
1032 end;
1033
1034 procedure TCheckCompilerOptsDlg.AddMsg(const Msg, CurDir: String;
1035 OriginalIndex: integer);
1036 begin
1037 Add(Msg,CurDir,false,OriginalIndex);
1038 end;
1039
1040 procedure TCheckCompilerOptsDlg.AddHint(const Msg: string);
1041 begin
1042 AddMsg(ccmlHint,Msg);
1043 end;
1044
1045 procedure TCheckCompilerOptsDlg.AddWarning(const Msg: string);
1046 begin
1047 AddMsg(ccmlWarning,Msg);
1048 end;
1049
1050 procedure TCheckCompilerOptsDlg.AddMsg(const Level: TCompilerCheckMsgLvl;
1051 const Msg: string);
1052 begin
1053 case Level of
1054 ccmlWarning: Add(lisCCOWarningMsg+Msg,'',false,-1);
1055 ccmlHint: Add(lisCCOHintMsg+Msg,'',false,-1);
1056 else Add(lisCCOErrorMsg+Msg,'',false,-1);
1057 end;
1058 end;
1059
1060 end.
1061
1062