mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-16 10:41:19 +02:00
552 lines
15 KiB
ObjectPascal
552 lines
15 KiB
ObjectPascal
unit namespacetool;
|
|
|
|
{$mode ObjFPC}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, types, prefixer;
|
|
|
|
Const
|
|
DefaultSubdir = 'namespaced';
|
|
DefaultDoneList = 'done.lst';
|
|
|
|
type
|
|
|
|
{ TNamespaceCreation }
|
|
TSubDirMode = (
|
|
sdmAppend, // append dirmap result to subdir
|
|
sdmReplace // replace directory part with result of dirmap
|
|
);
|
|
TNamespaceToolLogEvent = procedure(Sender : TObject; EventType : TEventType; Const Msg : String) of object;
|
|
TChangeFPMakeResult = (cmrFailed,cmrAlreadyDone,cmrOK);
|
|
|
|
{ TNamespaceTool }
|
|
|
|
TNamespaceTool = class(TComponent)
|
|
Private
|
|
FDoneFileName : string;
|
|
FDirMapFileName: string;
|
|
FOnLog: TNamespaceToolLogEvent;
|
|
FPrefixesFileName: string;
|
|
FDefaultPrefix: string;
|
|
FFPMakeNameSpaceFile : String;
|
|
FSubDir : String;
|
|
FCasedFiles,
|
|
FUpdate,
|
|
FDryRun,
|
|
FWritePrefixes,
|
|
FBackup: Boolean;
|
|
FSubdirMode: TSubDirMode;
|
|
FFPMakeMap : TStrings;
|
|
FDirmap : TStrings;
|
|
FKnownPrefixes : TStrings;
|
|
FRestart : Boolean;
|
|
FLastOpts: TStringDynArray;
|
|
FLastRule,
|
|
FLastDir : String;
|
|
FForcedExt : String;
|
|
procedure DoPrefixLog(Sender: TObject; aType: TEventType; const aMsg: String
|
|
);
|
|
procedure SetForcedExt(AValue: String);
|
|
procedure SetSubdir(AValue: String);
|
|
Protected
|
|
|
|
procedure DoMsg(const aFmt: String; const aArgs: array of const;
|
|
EventType: TEventType=etInfo); overload;
|
|
procedure DoMsg(const aMessage: String; EventType: TEventType=etInfo); overload;
|
|
// Add code to initialize namespace to fpmake in filename.
|
|
function AddNamespaceNameToFpMake(const aFileName: string): TChangeFPMakeResult;
|
|
// add file to FPMake namespaces file
|
|
procedure AddToFPMakeMap(const aSrcFileName, aDestFileName: string);
|
|
// Create directory if not dryrun
|
|
procedure CreateDestDir(const aDestDir: string);
|
|
// Actual HandleFileList
|
|
procedure DoHandleFileList(const aFileName: String);
|
|
// Return name of package dir from filename (first level of dir tree).
|
|
function GetPackageDir(const aFileName: string): string;
|
|
// Return unit name from file name.
|
|
function GetUnitNameFromFile(aFile: String): string;
|
|
// Split line into
|
|
procedure SplitLine(aLine: String; out aFileName, aRule: String;
|
|
var aOpts: TStringDynArray);
|
|
// Write FPMake Namespaces file.
|
|
procedure WritePackageNameSpaceFile(aDir: String; aList: TStrings; DoClear: Boolean=True);
|
|
Public
|
|
class procedure SplitRuleLine(aLine: String; out aFileName, aRule: String;
|
|
var AlastDir, aLastRule: String; var aOpts, aLastOpts: TStringDynArray);
|
|
Public
|
|
Constructor Create(aOwner : TComponent); override;
|
|
Destructor Destroy; override;
|
|
// Initialize (load config files)
|
|
Procedure Init;
|
|
// Actual actions
|
|
// Apply rule to a single unit file
|
|
procedure HandleFile(const aFileName: String; aRule: string; aOptions: array of String);
|
|
// Load file list and call handlefile for each
|
|
procedure HandleFileList(const aFileName: String);
|
|
// Create a 'known prefixes' file with the names of the files
|
|
procedure CreateKnown(const aFileName: String);
|
|
|
|
|
|
Property OnLog : TNamespaceToolLogEvent Read FOnLog Write FOnLog;
|
|
Property ForcedExt : String Read FForcedExt Write SetForcedExt;
|
|
Property DirMapFileName : String Read FDirMapFileName Write FDirMapFileName;
|
|
Property PrefixesFileName : String Read FPrefixesFileName Write FPrefixesFileName;
|
|
Property DefaultPrefix : String Read FDefaultPrefix Write FDefaultPrefix;
|
|
Property Subdir : String Read FSubdir Write SetSubdir;
|
|
Property SubdirMode : TSubDirMode Read FSubdirMode Write FSubdirMode;
|
|
Property Backup : Boolean Read FBackup Write FBackup;
|
|
Property Update : Boolean Read FUpdate Write FUpdate;
|
|
Property DryRun : Boolean Read FDryRun Write FDryRun;
|
|
Property Restart : Boolean Read FRestart Write FRestart;
|
|
Property CasedFiles : Boolean Read FCasedFiles Write FCasedFiles;
|
|
Property FPMakeNameSpaceFile : String Read FFPMakeNameSpaceFile Write FFPMakeNameSpaceFile;
|
|
Property KnownPrefixes : TStrings Read FKnownPrefixes;
|
|
Property DirMap : Tstrings Read FDirmap;
|
|
end;
|
|
|
|
implementation
|
|
|
|
procedure TNamespaceTool.CreateDestDir(const aDestDir : string);
|
|
|
|
begin
|
|
if not DirectoryExists(aDestDir) then
|
|
begin
|
|
DoMsg('Creating destination directory: %s',[aDestDir]);
|
|
if not FDryRun then
|
|
if not ForceDirectories(aDestDir) then
|
|
Raise Exception.Create('Could not create destination directory '+aDestDir);
|
|
end;
|
|
end;
|
|
|
|
procedure TNamespaceTool.DoMsg(const aFmt: String; const aArgs: array of const; EventType : TEventType = etInfo);
|
|
begin
|
|
DoMsg(Format(aFmt,aArgs),EventType);
|
|
end;
|
|
|
|
procedure TNamespaceTool.DoMsg(const aMessage: String; EventType : TEventType = etInfo);
|
|
begin
|
|
if assigned(OnLog) then
|
|
OnLog(Self,EventType, aMessage);
|
|
end;
|
|
|
|
procedure TNamespaceTool.AddToFPMakeMap(const aSrcFileName,aDestFileName : string);
|
|
|
|
Var
|
|
Src,Dest,aDir,aRule : String;
|
|
|
|
begin
|
|
Src:=aSrcFileName;
|
|
Dest:=aDestFileName;
|
|
// Strip package dir
|
|
aDir:=GetPackageDir(aSrcFileName);
|
|
if Pos(aDir,Src)=1 then
|
|
Delete(Src,1,Length(aDir));
|
|
if Pos(aDir,Dest)=1 then
|
|
Delete(Dest,1,Length(aDir));
|
|
// Map file itself.
|
|
FFPMakeMap.Values[Src]:=Dest;
|
|
aDir:=ExtractFilePath(Src);
|
|
// Map source directory to namespaced
|
|
aRule:='{s*:'+aDir+'}';
|
|
FFPMakeMap.Values[aRule]:=ExtractFilePath(Dest);
|
|
// Add original to include directory
|
|
aRule:='{i+:'+aDir+'}';
|
|
if FFPMakeMap.IndexOf(aRule)=-1 then
|
|
FFPMakeMap.Add(aRule);
|
|
end;
|
|
|
|
function TNamespaceTool.GetUnitNameFromFile(aFile : String) : string;
|
|
|
|
begin
|
|
Result:=ExtractFileName(ChangeFileExt(aFile,''))
|
|
end;
|
|
|
|
procedure TNamespaceTool.SetForcedExt(AValue: String);
|
|
begin
|
|
if FForcedExt=AValue then Exit;
|
|
if (aValue<>'') and (aValue[1]<>'.') then
|
|
aValue:='.'+aValue;
|
|
FForcedExt:=AValue;
|
|
end;
|
|
|
|
procedure TNamespaceTool.DoPrefixLog(Sender: TObject; aType: TEventType;
|
|
const aMsg: String);
|
|
begin
|
|
DoMsg(aMsg,aType);
|
|
end;
|
|
|
|
procedure TNamespaceTool.SetSubdir(AValue: String);
|
|
begin
|
|
if FSubdir=AValue then Exit;
|
|
FSubdir:=AValue;
|
|
if FSubDir<>'' then
|
|
FSubDir:=IncludeTrailingPathDelimiter(FSubDir);
|
|
end;
|
|
|
|
procedure TNamespaceTool.HandleFile(const aFileName: String; aRule : string; aOptions: array of String);
|
|
|
|
Var
|
|
aNewUnitName,aNewUnitFile,Ext,SrcDir,aUnitName,DestDir,aDummy,DestFN : String;
|
|
P : TPrefixer;
|
|
NeedUpdate : Boolean;
|
|
Idx : Integer;
|
|
|
|
begin
|
|
NeedUpdate:=False;
|
|
Ext:=FForcedExt;
|
|
if Ext='' then
|
|
Ext:=ExtractFileExt(aFileName);
|
|
// Construct File name
|
|
aUnitName:=GetUnitNameFromFile(aFilename);
|
|
// Construct destination dir.
|
|
SrcDir:=ExtractFilePath(aFileName);
|
|
DestDir:=FDirMap.Values[aUnitName];
|
|
if DestDir='' then
|
|
DestDir:=FDirMap.Values[ExcludeTrailingBackslash(SrcDir)];
|
|
if DestDir='' then
|
|
DestDir:=SrcDir;
|
|
case SubDirMode of
|
|
sdmAppend : DestDir:=FSubDir+DestDir;
|
|
sdmReplace : ; // do nothing
|
|
end;
|
|
DestDir:=IncludeTrailingPathDelimiter(DestDir);
|
|
// No rule, see if there is a filename rule in known prefixes
|
|
if aRule='' then
|
|
begin
|
|
Idx:=FKnownPrefixes.IndexOfName(aUnitName);
|
|
if Idx<>-1 then
|
|
FKnownPrefixes.GetNameValue(Idx,aDummy,aRule);
|
|
end;
|
|
aNewUnitFile:=TPrefixer.ApplyRule(aFileName,aDummy,aRule,FCasedFiles and (aRule<>''));
|
|
aNewUnitName:=GetUnitNameFromFile(aNewUnitFile);
|
|
if SameText(aNewUnitName,aUnitName) then
|
|
begin
|
|
DoMsg('Rule for %s does not result in different unit name, skipping.',[aFileName],etWarning);
|
|
exit;
|
|
end;
|
|
DestFN:=DestDir+aNewUnitName+Ext;
|
|
// Add new file to FPMake map.
|
|
AddToFPMakeMap(aFileName,DestFN);
|
|
if FileExists(DestFN) then
|
|
DoMsg('File %s already exists, skipping generation',[DestFN]);
|
|
// Create directory.
|
|
CreateDestDir(DestDir);
|
|
DoMsg('Converting %s to %s',[aFileName,DestFN]);
|
|
if not FDryRun then
|
|
begin
|
|
P:=TPrefixer.Create(Self);
|
|
try
|
|
P.OnLog:=@DoPrefixLog;
|
|
P.UnitFileMode:=fmInclude;
|
|
P.IncludeUnitNameMode:=inmIfndef;
|
|
P.FileName:=aFileName;
|
|
P.NameSpace:=TPrefixer.ExtractPrefix(aRule);
|
|
P.KnownNameSpaces.AddStrings(FKnownPrefixes);
|
|
P.SkipDestFileName:=FileExists(DestFN);
|
|
P.DestFileName:=DestFN;
|
|
P.CreateBackups:=FBackup;
|
|
P.CasedFileNames:=FCasedFiles;
|
|
P.Params.AddStrings(aOptions);
|
|
P.Params.Add('-Fi'+ExtractFilePath(aFileName));
|
|
P.Execute;
|
|
finally
|
|
P.Free;
|
|
end;
|
|
end;
|
|
If NeedUpdate then
|
|
begin
|
|
FKnownPrefixes.Values[aUnitName]:='*'+aNewUnitName;
|
|
FWritePrefixes:=True;
|
|
end;
|
|
|
|
end;
|
|
|
|
Function TNamespaceTool.AddNamespaceNameToFpMake(const aFileName : string) : TChangeFPMakeResult;
|
|
|
|
const
|
|
namespacelist = 'namespaces.lst';
|
|
|
|
Var
|
|
aFile : TStringList;
|
|
I : Integer;
|
|
aLine : string;
|
|
|
|
begin
|
|
Result:=cmrFailed;
|
|
aFile:=TStringList.Create;
|
|
try
|
|
aFile.LoadFromFile(aFileName);
|
|
i:=aFile.Count-1;
|
|
while (I>=0) and (Result=cmrFailed) do
|
|
begin
|
|
if Pos('p.namespacemap',LowerCase(aFile[i]))>0 then
|
|
result:=cmrAlreadyDone;
|
|
Dec(I);
|
|
end;
|
|
i:=aFile.Count-1;
|
|
while (I>=0) and (Result=cmrFailed) do
|
|
begin
|
|
aLine:=aFile[i];
|
|
if pos('{$ifndef ALLPACKAGES}',aLine)>0 then
|
|
if Pos('run',Lowercase(aFile[i+1]))>0 then
|
|
begin
|
|
aFile.Insert(I,'');
|
|
aFile.Insert(I,Format(' P.NamespaceMap:=''%s'';',[namespacelist]));
|
|
aFile.Insert(I,'');
|
|
Result:=cmrOK;
|
|
end;
|
|
Dec(I);
|
|
end;
|
|
if Result=cmrOK then
|
|
aFile.SaveToFile(aFileName);
|
|
finally
|
|
aFile.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TNamespaceTool.WritePackageNameSpaceFile(aDir : String; aList : TStrings; DoClear : Boolean = True);
|
|
|
|
Var
|
|
FN : String;
|
|
|
|
begin
|
|
if aDir<>'' then
|
|
aDir:=IncludeTrailingPathDelimiter(aDir);
|
|
if (FFPMakeNameSpaceFile='') or (FFPMakeMap.Count=0) then
|
|
exit;
|
|
FN:=aDir+FFPMakeNameSpaceFile;
|
|
DoMsg('Writing fpmake map file to %s, writing %d rules',[FN,FFPMakeMap.Count]);
|
|
FFPMakeMap.SaveToFile(FN);
|
|
if DoClear then
|
|
FFPMakeMap.Clear;
|
|
if FileExists(aDir+'fpmake.pp') then
|
|
Case AddNamespaceNameToFpMake(aDir+'fpmake.pp') of
|
|
cmrFailed : DoMsg('Failed to set NamespaceMap to file "%s"',[FN],etError);
|
|
cmrAlreadyDone : DoMsg('NamespaceMap already set in "%s"',[FN],etWarning);
|
|
cmrOK : DoMsg('Added NamespaceMap to file "%s"',[FN],etInfo);
|
|
end
|
|
|
|
end;
|
|
|
|
constructor TNamespaceTool.Create(aOwner: TComponent);
|
|
begin
|
|
inherited Create(aOwner);
|
|
FDirmap:=TStringList.Create;
|
|
FKnownPrefixes:=TStringList.Create;
|
|
FFPMakeMap:=TStringList.Create;
|
|
FDoneFileName:=DefaultDoneList;
|
|
end;
|
|
|
|
destructor TNamespaceTool.Destroy;
|
|
begin
|
|
FreeAndNil(FDirmap);
|
|
FreeAndNil(FKnownPrefixes);
|
|
FreeAndNil(FFPMakeMap);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TNamespaceTool.Init;
|
|
begin
|
|
if (PrefixesFileName<>'') then
|
|
begin
|
|
KnownPrefixes.LoadFromFile(PrefixesFileName);
|
|
DoMsg('Load of %s results in %d known prefixes',[PrefixesFileName,KnownPrefixes.Count]);
|
|
end;
|
|
if (DirMapFileName<>'') then
|
|
begin
|
|
Dirmap.LoadFromFile(DirMapFileName);
|
|
DoMsg('Load of %s results in %d directory mappings',[DirMapFileName,DirMap.Count]);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TNamespaceTool.SplitLine(aLine: String; out aFileName, aRule: String;
|
|
var aOpts: TStringDynArray);
|
|
|
|
|
|
begin
|
|
SplitRuleLine(aLine,aFileName,aRule,FLastDir,FLastRule,aOpts,FLastOpts);
|
|
end;
|
|
|
|
Class procedure TNamespaceTool.SplitRuleLine(aLine: String; out aFileName, aRule: String; var AlastDir, aLastRule : String; var aOpts, aLastOpts: TStringDynArray);
|
|
|
|
var
|
|
I,P : Integer;
|
|
aDir,FN,Opt : String;
|
|
|
|
begin
|
|
aRule:='';
|
|
aFileName:='';
|
|
aOpts:=[];
|
|
P:=Pos(';',aLine);
|
|
if P=0 then
|
|
begin
|
|
FN:=aLine;
|
|
SetLength(aOpts,0);
|
|
end
|
|
else
|
|
begin
|
|
FN:=Copy(aLine,1,P-1);
|
|
Opt:=Trim(Copy(aLine,P+1));
|
|
SetLength(aOpts,Length(Opt));
|
|
I:=0;
|
|
Repeat
|
|
P:=Pos(' ',Opt);
|
|
if P=0 then
|
|
P:=Length(Opt)+1;
|
|
if p>1 then
|
|
begin
|
|
aOpts[I]:=Copy(Opt,1,P-1);
|
|
Opt:=Trim(Copy(Opt,P+1));
|
|
inc(I);
|
|
end;
|
|
until (Opt='');
|
|
SetLength(aOpts,I);
|
|
end;
|
|
P:=Pos('=',FN);
|
|
if P<>0 then
|
|
begin
|
|
aRule:=Copy(FN,P+1);
|
|
FN:=Copy(FN,1,P-1);
|
|
end;
|
|
aFileName:=FN;
|
|
// Use previous rule ?
|
|
aDir:=ExtractFilePath(FN);
|
|
if aDir=aLastDir then
|
|
begin
|
|
if (aRule='') then
|
|
aRule:=aLastRule;
|
|
if Length(aOpts)=0 then
|
|
aOpts:=aLastOpts;
|
|
end;
|
|
aLastDir:=aDir;
|
|
aLastRule:=aRule;
|
|
aLastOpts:=aOpts;
|
|
end;
|
|
|
|
function TNamespaceTool.GetPackageDir(const aFileName : string) : string;
|
|
|
|
Var
|
|
P : Integer;
|
|
|
|
begin
|
|
Result:='';
|
|
if aFileName='' then
|
|
exit;
|
|
P:=Pos('/',aFileName,2);
|
|
if P=0 then
|
|
exit;
|
|
Result:=Copy(aFileName,1,P);
|
|
If Result[1]='/' then
|
|
Delete(Result,1,1);
|
|
end;
|
|
|
|
procedure TNamespaceTool.HandleFileList(const aFileName : String);
|
|
|
|
begin
|
|
DoHandleFileList(aFileName);
|
|
if FWritePrefixes and Update then
|
|
begin
|
|
DoMsg('Updating known prefixes file: %s ',[PrefixesFileName]);
|
|
if not FDryRun then
|
|
FKnownPrefixes.SaveToFile(FPrefixesFileName);
|
|
end;
|
|
end;
|
|
|
|
procedure TNamespaceTool.DoHandleFileList(const aFileName : String);
|
|
|
|
Var
|
|
List,Done : TStringList;
|
|
aLine,FN,FNDir, LastPackageDir,aRule : String;
|
|
aOpts : TStringDynArray;
|
|
|
|
begin
|
|
aOpts:=[];
|
|
Done:=Nil;
|
|
LastPackageDir:='';
|
|
List:=TStringList.Create;
|
|
try
|
|
Done:=TStringList.Create;
|
|
if (not FRestart) and fileExists(FDoneFileName) then
|
|
Done.LoadFromFile(FDoneFileName);
|
|
List.LoadFromFile(aFileName);
|
|
For aLine in List do
|
|
begin
|
|
// Lines have 3 parts
|
|
// FileName=Rule;Compile Options
|
|
SplitLine(aLine,FN,aRule,aOpts);
|
|
FNDir:=GetPackageDir(FN);
|
|
if (LastPackageDir<>FNDir) then
|
|
begin
|
|
if (LastPackageDir<>'') and (FFPMakeNameSpaceFile<>'') then
|
|
WritePackageNameSpaceFile(LastPackageDir,List);
|
|
LastPackageDir:=FNDir;
|
|
end;
|
|
if Done.indexOf(FN)=-1 then
|
|
begin
|
|
try
|
|
HandleFile(FN,aRule,aOpts);
|
|
Done.Add(FN);
|
|
except
|
|
On E : Exception do
|
|
DoMsg('Error %s while handling file %s : %s',[E.ClassName,FN,E.Message],etError);
|
|
end;
|
|
end;
|
|
end;
|
|
if (LastPackageDir<>'') and (FFPMakeNameSpaceFile<>'') then
|
|
WritePackageNameSpaceFile(LastPackageDir,List);
|
|
finally
|
|
Done.SaveToFile(FDoneFileName);
|
|
List.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TNamespaceTool.CreateKnown(const aFileName: String);
|
|
|
|
Var
|
|
List,Done : TStringList;
|
|
aRule,aLine,FN,aUnit,aNewUnit : String;
|
|
aOpts : TStringDynArray;
|
|
|
|
begin
|
|
Done:=Nil;
|
|
FLastDir:='';
|
|
FLastRule:='';
|
|
aOpts:=[];
|
|
if FPrefixesFileName='' then
|
|
FPrefixesFileName:=ChangeFileExt(aFileName,'.map');
|
|
List:=TStringList.Create;
|
|
try
|
|
Done:=TStringList.Create;
|
|
if FileExists(FPrefixesFileName) then
|
|
Done.LoadFromFile(FPrefixesFileName);
|
|
List.LoadFromFile(aFileName);
|
|
// Lines have 3 parts
|
|
// FileName=Rule;Compile Options
|
|
For aLine in List do
|
|
begin
|
|
SplitLine(aLine,FN,aRule,aOpts);
|
|
aUnit:=ChangeFileExt(ExtractFileName(FN),'');
|
|
aNewUnit:=ChangeFileExt(ExtractFileName(TPrefixer.ApplyRule(FN,aUnit,aRule,FCasedFiles)),'');
|
|
Done.Values[aUnit]:='*'+aNewUnit;
|
|
end;
|
|
if FDryRun then
|
|
begin
|
|
for aLine in Done do
|
|
DoMsg(aLine)
|
|
end
|
|
else
|
|
Done.SaveToFile(FPrefixesFileName);
|
|
finally
|
|
Done.SaveToFile('done.tmp');
|
|
Done.Free;
|
|
List.Free;
|
|
end;
|
|
|
|
end;
|
|
|
|
end.
|
|
|