{ This file is part of the Free Component Library Copyright (c) 2024 by Michael Van Canneyt (michael@freepascal.org) Unit to parse and keep info about a package file. See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************} {$IFNDEF FPC_DOTTEDUNITS} unit dpkinfo; {$ENDIF} {$mode ObjFPC}{$H+} interface uses {$IFDEF FPC_DOTTEDUNITS} System.Classes, System.SysUtils, Pascal.Tree, Pascal.Parser, Xml.Dom, Xml.Writer; {$ELSE} Classes, SysUtils, pastree, pparser, dom, XMLWrite; {$ENDIF} Type { TPackageContainer } TInfoKind = (ikUnknown,ikRequires,ikFiles,ikPaths); TPackageContainer = class(TPasTreeContainer) Public function FindElement(const AName: String): TPasElement; override; function CreateElement(AClass: TPTreeElement; const AName: String; AParent: TPasElement; AVisibility: TPasMemberVisibility; const ASourceFilename: String; ASourceLinenumber: Integer): TPasElement;overload; override; end; { TPackageInfo } TPackageInfo = class(TComponent) private FDefines: TStrings; FKnownPackages: TStrings; FOutput: TStrings; FOutputFile: String; FUseAbsolute: Boolean; FPackageDir : String; class function IsAbsoluteWindowsFile(aFile: String): Boolean; procedure WriteFiles(Pack: TPasDynamicPackage); procedure WritePaths(Pack: TPasDynamicPackage); procedure WriteRequires(Pack: TPasDynamicPackage); Public Constructor Create(aOwner : TComponent); override; Destructor Destroy; override; Procedure ShowInfo(const aInputFile: String; aKind : TInfoKind); Property KnownPackages : TStrings Read FKnownPackages; Property Output : TStrings Read FOutput; Property Defines : TStrings Read FDefines; Property UseAbsolute : Boolean Read FUseAbsolute Write FUseAbsolute; end; { TSimpleParser } TSimpleParser = Class function ParseSource(AEngine: TPasTreeContainer; const FPCCommandLine : Array of String; Defines : TStrings): TPasModule; private procedure DoIt(Sender: TObject; const aFileName: String; aOptions: TStrings); end; implementation {$IFDEF FPC_DOTTEDUNITS} uses Pascal.Scanner; {$ELSE} uses pscanner; {$ENDIF} function TSimpleParser.ParseSource(AEngine: TPasTreeContainer; const FPCCommandLine : Array of String; Defines : TStrings): TPasModule; var FileResolver: TBaseFileResolver; Parser: TPasParser; Filename: String; Scanner: TPascalScanner; procedure ProcessCmdLinePart(S : String); var l,Len: Integer; begin if (S='') then exit; Len:=Length(S); if (s[1] = '-') and (len>1) then begin case s[2] of 'd': // -d define Scanner.AddDefine(UpperCase(Copy(s, 3, Len))); 'u': // -u undefine Scanner.RemoveDefine(UpperCase(Copy(s, 3, Len))); 'F': // -F if (len>2) and (s[3] = 'i') then // -Fi include path FileResolver.AddIncludePath(Copy(s, 4, Len)); 'I': // -I include path FileResolver.AddIncludePath(Copy(s, 3, Len)); 'S': // -S mode if (len>2) then begin l:=3; While L<=Len do begin case S[l] of 'c' : Scanner.Options:=Scanner.Options+[po_cassignments]; 'd' : Scanner.SetCompilerMode('DELPHI'); '2' : Scanner.SetCompilerMode('OBJFPC'); 'h' : ; // do nothing end; inc(l); end; end; 'M' : begin delete(S,1,2); Scanner.SetCompilerMode(S); end; end; end else if Filename <> '' then raise ENotSupportedException.Create(SErrMultipleSourceFiles) else Filename := s; end; var S: String; begin if DefaultFileResolverClass=Nil then raise ENotImplemented.Create(SErrFileSystemNotSupported); Result := nil; FileResolver := nil; Scanner := nil; Parser := nil; try FileResolver := DefaultFileResolverClass.Create; {$ifdef HasStreams} if FileResolver is TFileResolver then TFileResolver(FileResolver).UseStreams:=poUseStreams in Options; {$endif} Scanner := TPascalScanner.Create(FileResolver); Scanner.LogEvents:=AEngine.ScannerLogEvents; Scanner.OnLog:=AEngine.OnLog; Scanner.RegisterResourceHandler(['res'],@DoIt); For S in Defines do Scanner.AddDefine(S); Parser := TPasParser.Create(Scanner, FileResolver, AEngine); Parser.ImplicitUses.Clear; Filename := ''; Parser.LogEvents:=AEngine.ParserLogEvents; Parser.OnLog:=AEngine.OnLog; For S in FPCCommandLine do ProcessCmdLinePart(S); if Filename = '' then raise Exception.Create(SErrNoSourceGiven); {$IFDEF HASFS} FileResolver.AddIncludePath(ExtractFilePath(FileName)); {$ENDIF} Scanner.OpenFile(Filename); Parser.ParseMain(Result); finally Parser.Free; Scanner.Free; FileResolver.Free; end; end; procedure TSimpleParser.DoIt(Sender: TObject; const aFileName: String; aOptions: TStrings); begin // Do nothing end; { TPackageInfo } constructor TPackageInfo.Create(aOwner: TComponent); begin inherited Create(aOwner); FKnownPackages:=TStringList.Create; Foutput:=TStringList.Create; FDefines:=TStringList.Create; end; destructor TPackageInfo.Destroy; begin FreeAndNil(FKnownPackages); FreeAndNil(FOutput); FreeAndNil(FDefines); inherited Destroy; end; procedure TPackageInfo.WriteRequires(Pack : TPasDynamicPackage); var I : Integer; aPack : TPasRequiredPackage; begin For I:=0 to Pack.PackageSection.Requires.Count-1 do begin aPack:=TPasRequiredPackage(Pack.PackageSection.Requires[i]); if FKnownPackages.IndexOf(aPack.Name)=-1 then FOutput.Add(aPack.Name); end; end; class function TPackageInfo.IsAbsoluteWindowsFile(aFile: String): Boolean; begin Result:=(aFile<>'') and (aFile[2]=':') and (aFile[3]='\'); end; procedure TPackageInfo.WriteFiles(Pack : TPasDynamicPackage); var aUsed : TPasUsesUnit; aName,aFileName : String; isAbsolute : Boolean; begin For aUsed in Pack.PackageSection.UsesClause do begin aName:=aUsed.Name; if (aName='') then continue; if assigned(aUsed.InFileName) then begin aFileName:=aUsed.InFilename.Value; aFileName:=StringReplace(aFileName,'''','',[rfReplaceAll]); if IsAbsoluteWindowsFile(aFileName) then isAbsolute:=True else begin aFileName:=StringReplace(aFilename,'\','/',[rfReplaceAll]); isAbsolute:=aFileName[1]='/'; end end else begin aFileName:=aName+'.pas'; // Should not happen isAbsolute:=False; end; if (not IsAbsolute) and UseAbsolute then aFileName:=FPackageDir+aFileName; FOutput.Add(aFileName); end; end; procedure TPackageInfo.WritePaths(Pack : TPasDynamicPackage); var aUsed : TPasUsesUnit; aName,aFileName : String; isAbsolute : Boolean; Paths : TStrings; begin Paths:=TStringList.Create; For aUsed in Pack.PackageSection.UsesClause do begin aName:=aUsed.Name; if (aName='') then continue; if assigned(aUsed.InFileName) then begin aFileName:=aUsed.InFilename.Value; aFileName:=StringReplace(aFileName,'''','',[rfReplaceAll]); if IsAbsoluteWindowsFile(aFileName) then isAbsolute:=True else begin aFileName:=ExtractFilePath(StringReplace(aFilename,'\','/',[rfReplaceAll])); isAbsolute:=(aFileName<>'') and (aFileName[1]='/'); end end else begin aFileName:=''; // Should not happen isAbsolute:=False; end; if (not IsAbsolute) and UseAbsolute then aFileName:=FPackageDir+aFileName; if (aFileName<>'') and (Paths.IndexOf(aFileName)=-1) then begin FOutput.Add(aFileName); Paths.Add(aFileName); end; end; end; procedure TPackageInfo.ShowInfo(const aInputFile: String; aKind: TInfoKind); Var El : TPasElement; Pack : TPasDynamicPackage absolute El; C : TPackageContainer; Parser : TSimpleParser; begin Foutput.Clear; FPackageDir:=ExtractFilePath(ExpandFileName(aInputFile)); Parser:=nil; C:=TPackageContainer.Create; try Parser:=TSimpleParser.Create; El:=Parser.ParseSource(C,['-Sd',aInputFile],Defines); if not (El is TPasDynamicPackage) then Raise EPasTree.CreateFmt('%s is not a package source file. Got a %s instead.',[aInputFile,Pack.ClassName]); Case aKind of ikRequires : WriteRequires(Pack); ikPaths : WritePaths(Pack); ikFiles : WriteFiles(Pack); end; finally Parser.Free; El.Free; C.Free; end; end; { TPackageContainer } function TPackageContainer.FindElement(const AName: String): TPasElement; begin Result:=Nil; end; function TPackageContainer.CreateElement(AClass: TPTreeElement; const AName: String; AParent: TPasElement; AVisibility: TPasMemberVisibility; const ASourceFilename: String; ASourceLinenumber: Integer): TPasElement; begin Result:=aClass.Create(aName,aParent); Result.Visibility:=AVisibility; // ASourceFilename, ASourceLinenumber ? end; end.