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