mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-06 18:32:37 +02:00
545 lines
15 KiB
ObjectPascal
545 lines
15 KiB
ObjectPascal
unit delphitool;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, System.UITypes, dialogs, CompOptsIntf, IDEExternToolIntf, MacroDefIntf, MacroIntf, RegExpr, strdelphitool;
|
|
|
|
|
|
Const
|
|
SubToolDelphiPriority = SubToolFPCPriority-10;
|
|
|
|
type
|
|
|
|
{ TWinePathConverter }
|
|
|
|
TWinePathConverter = Class
|
|
Private
|
|
FMap : TStrings;
|
|
Protected
|
|
Procedure FillMap; virtual;
|
|
Public
|
|
Constructor Create;
|
|
Destructor Destroy; override;
|
|
Function UnixToWindows(Const aFileName : String) : String; virtual;
|
|
function WindowsToUnix(Const aFileName : String) : String; virtual;
|
|
end;
|
|
|
|
{ TDelphiCompilerParser }
|
|
|
|
TDelphiCompilerParser = class(TExtToolParser)
|
|
private
|
|
protected
|
|
FRegExprFilenameLineIDMsg: TRegExpr;
|
|
FRegExprFilenameLineUrgencyIDMsg: TRegExpr;
|
|
FConverter : TWinePathConverter;
|
|
Function CreateConverter : TWinePathConverter;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure ReadLine(Line: string; OutputIndex: integer; IsStdErr: boolean;
|
|
var Handled: boolean); override; // (worker thread)
|
|
class function DefaultSubTool: string; override;
|
|
class function GetParserName: string; override;
|
|
class function GetLocalizedParserName: string; override;
|
|
class function Priority: integer; override;
|
|
end;
|
|
TDelphiCompilerParserClass = class of TDelphiCompilerParser;
|
|
|
|
{ TDelphiTool }
|
|
TDelphiMacro = (dmCompiler,dmConfigFileName,dmAdditionalArgs,dmCompileCommand);
|
|
TDelphiTool = class
|
|
private
|
|
class var _instance: TDelphiTool;
|
|
Private
|
|
FMacros : Array[TDelphiMacro] of TTransferMacro;
|
|
Protected
|
|
function GetCompileCommand(const s: string; const Data: PtrInt; var Abort: boolean): string; virtual;
|
|
function GetCompilerArgs(const s: string; const Data: PtrInt; var Abort: boolean): string; virtual;
|
|
function GetCompilerPath(const s: string; const Data: PtrInt; var Abort: boolean): string; virtual;
|
|
function GetConfigPath(const s: string; const Data: PtrInt; var Abort: boolean): string; virtual;
|
|
function OnProjectBuilding(Sender: TObject): TModalResult; virtual;
|
|
function FPCToDelphiOpts(Opts: TLazCompilerOptions; aDelphiOpts: TStrings): Integer; virtual;
|
|
function GenerateConfigFilename(const aFilename: String): Boolean; virtual;
|
|
Public
|
|
Destructor Destroy; override;
|
|
Class Constructor Init;
|
|
Class Destructor Done;
|
|
Procedure Hook; virtual;
|
|
Procedure UnHook; virtual;
|
|
Function GetParsedCompilerFilename : String;
|
|
Function GetCurrentConfigFileName(PrependAt: Boolean = true) : String;
|
|
Function GetCompilerArguments: string;
|
|
Function GetCompileCommand: string;
|
|
Class Property Instance: TDelphiTool read _Instance;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
{$IFDEF UNIX} baseunix, {$ENDIF}
|
|
LazIDEIntf, LazLoggerBase, LazUtilities, delphioptions, fileutil, lazfileutils;
|
|
|
|
{ TWinePathConverter }
|
|
|
|
{$IFDEF UNIX}
|
|
procedure TWinePathConverter.FillMap;
|
|
|
|
Var
|
|
DriveLink,DevicesDir : String;
|
|
drive : Char;
|
|
|
|
begin
|
|
|
|
DevicesDir:=GetUserDir+'.wine/dosdevices/';
|
|
if not DirectoryExists(DevicesDir,True) then
|
|
exit;
|
|
for drive:='a' to 'z' do
|
|
begin
|
|
DriveLink:=DevicesDir+Drive+':';
|
|
if DirectoryExists(DriveLink) then
|
|
FMap.Add(Drive+':='+IncludeTrailingPathDelimiter(fpReadLink(DriveLink)));
|
|
end;
|
|
end;
|
|
{$ELSE}
|
|
procedure TWinePathConverter.FillMap;
|
|
begin
|
|
// Do nothing
|
|
end;
|
|
{$ENDIF}
|
|
|
|
constructor TWinePathConverter.Create;
|
|
begin
|
|
FMap:=TStringList.Create;
|
|
end;
|
|
|
|
destructor TWinePathConverter.Destroy;
|
|
begin
|
|
FreeAndNil(FMap);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TWinePathConverter.UnixToWindows(const aFileName: String): String;
|
|
|
|
function SubDirOf(const aBaseDir,aDestDir : string) : Boolean;
|
|
|
|
Var
|
|
relPath : String;
|
|
|
|
begin
|
|
relPath:=ExtractRelativePath(aBaseDir,aFileName);
|
|
Result:=Copy(relPath,1,3)<>'../';
|
|
end;
|
|
|
|
function FileBelowDir(const aBaseDir,aDestFile : String) : Boolean;
|
|
|
|
Var
|
|
relPath : String;
|
|
|
|
begin
|
|
relPath:=ExtractRelativePath(aBaseDir,aDestFile);
|
|
Result:=Copy(relPath,1,3)<>'../';
|
|
end;
|
|
|
|
|
|
Var
|
|
relPath,Drive,DriveDir,CurDrive,CurDir : String;
|
|
i : Integer;
|
|
|
|
begin
|
|
if FMap.Count=0 then
|
|
FillMap;
|
|
CurDrive:='';
|
|
CurDir:='';
|
|
for I:=0 to FMap.Count-1 do
|
|
begin
|
|
FMap.GetNameValue(I,Drive,DriveDir);
|
|
if FileBelowDir(DriveDir,aFileName) then
|
|
begin
|
|
if (CurDir='') or (SubDirOf(CurDir,DriveDir)) then
|
|
begin
|
|
CurDrive:=Drive;
|
|
CurDir:=DriveDir
|
|
end;
|
|
end;
|
|
end;
|
|
if CurDrive<>'' then
|
|
begin
|
|
relPath:=ExtractRelativePath(DriveDir,aFileName);
|
|
Result:=UpperCase(CurDrive)+StringReplace(relPath,'/','\',[rfReplaceAll]);
|
|
end
|
|
else
|
|
Result:=StringReplace(relPath,'/','\',[rfReplaceAll]);
|
|
end;
|
|
|
|
function TWinePathConverter.WindowsToUnix(const aFileName: String): String;
|
|
|
|
var
|
|
S : String;
|
|
|
|
begin
|
|
Result:='';
|
|
if aFileName='' then exit;
|
|
if (Length(aFileName)<2) or (aFileName[2]<>':') then
|
|
Exit(aFileName);
|
|
if FMap.Count=0 then
|
|
FillMap;
|
|
S:=LowerCase(aFileName[1]+':');
|
|
S:=FMap.Values[S];
|
|
if S<>'' then
|
|
Result:=S+SetDirSeparators(Copy(aFileName,4))
|
|
else
|
|
Result:=SetDirSeparators(aFileName);
|
|
Result:=StringReplace(Result,'\','/',[rfReplaceAll]);
|
|
end;
|
|
|
|
{ TDelphiCompilerParser }
|
|
|
|
function TDelphiCompilerParser.CreateConverter: TWinePathConverter;
|
|
begin
|
|
Result:=TWinePathConverter.Create;
|
|
end;
|
|
|
|
constructor TDelphiCompilerParser.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FConverter:=CreateConverter;
|
|
|
|
// filename(linenumber): E2003 Undeclared identifier: 'foo'
|
|
FRegExprFilenameLineIDMsg:=TRegExpr.Create;
|
|
FRegExprFilenameLineIDMsg.ModifierStr:='I';
|
|
FRegExprFilenameLineIDMsg.Expression:='^(.*)\(([0-9]+)\): ([HNWEF])([0-9]+) (.*)$';
|
|
|
|
// filename(linenumber): Fatal: F2613 Unit 'Unit3' not found.
|
|
FRegExprFilenameLineUrgencyIDMsg:=TRegExpr.Create;
|
|
FRegExprFilenameLineUrgencyIDMsg.ModifierStr:='I';
|
|
FRegExprFilenameLineUrgencyIDMsg.Expression:='^(.*)\(([0-9]+)\) ([a-zA-Z]+): ([HNWEF])([0-9]+) (.*)$';
|
|
end;
|
|
|
|
destructor TDelphiCompilerParser.Destroy;
|
|
begin
|
|
FreeAndNil(FRegExprFilenameLineIDMsg);
|
|
FreeAndNil(FRegExprFilenameLineUrgencyIDMsg);
|
|
FreeAndNil(FConverter);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TDelphiCompilerParser.ReadLine(Line: string; OutputIndex: integer;
|
|
IsStdErr: boolean; var Handled: boolean);
|
|
|
|
procedure Add(const aFilename, LineNoStr, UrgencyLetter, IDStr, MsgStr: String);
|
|
var
|
|
MsgLine: TMessageLine;
|
|
begin
|
|
MsgLine:=CreateMsgLine(OutputIndex);
|
|
case UrgencyLetter of
|
|
'H': MsgLine.Urgency:=mluHint;
|
|
'N': MsgLine.Urgency:=mluNote;
|
|
'W': MsgLine.Urgency:=mluWarning;
|
|
'E': MsgLine.Urgency:=mluError;
|
|
'F': MsgLine.Urgency:=mluFatal;
|
|
else MsgLine.Urgency:=mluImportant;
|
|
end;
|
|
if DelphiToolOptions.ConvertPathsToUnix then
|
|
MsgLine.Filename:=FConverter.WindowsToUnix(aFilename)
|
|
else
|
|
MsgLine.Filename:=aFilename;
|
|
MsgLine.Line:=StrToIntDef(LineNoStr,0);
|
|
MsgLine.MsgID:=StrToIntDef(IDStr,0);
|
|
MsgLine.Msg:=MsgStr;
|
|
if IsStdErr then
|
|
MsgLine.Flags:=MsgLine.Flags+[mlfStdErr];
|
|
AddMsgLine(MsgLine);
|
|
end;
|
|
|
|
procedure AddFilenameLineIDMsg;
|
|
var
|
|
RE: TRegExpr;
|
|
aFilename, LineNoStr, UrgencyLetter, IDStr, MsgStr: String;
|
|
begin
|
|
RE:=FRegExprFilenameLineIDMsg;
|
|
aFilename:=RE.Match[1];
|
|
LineNoStr:=RE.Match[2];
|
|
UrgencyLetter:=RE.Match[3];
|
|
IDStr:=RE.Match[4];
|
|
MsgStr:=RE.Match[5];
|
|
Add(aFilename,LineNoStr,UrgencyLetter,IDStr,MsgStr);
|
|
end;
|
|
|
|
procedure AddFilenameLineUrgencyIDMsg;
|
|
var
|
|
RE: TRegExpr;
|
|
aFilename, LineNoStr, UrgencyLetter, IDStr, MsgStr: String;
|
|
begin
|
|
RE:=FRegExprFilenameLineUrgencyIDMsg;
|
|
aFilename:=RE.Match[1];
|
|
LineNoStr:=RE.Match[2];
|
|
//UrgencyStr:=RE.Match[3];
|
|
UrgencyLetter:=RE.Match[4];
|
|
IDStr:=RE.Match[5];
|
|
MsgStr:=RE.Match[6];
|
|
Add(aFilename,LineNoStr,UrgencyLetter,IDStr,MsgStr);
|
|
end;
|
|
|
|
procedure AddOtherLine;
|
|
var
|
|
MsgLine: TMessageLine;
|
|
begin
|
|
MsgLine:=CreateMsgLine(OutputIndex);
|
|
MsgLine.MsgID:=0;
|
|
MsgLine.SubTool:=SSubToolDelphi;
|
|
if MsgLine.Msg<>'' then
|
|
MsgLine.Urgency:=mluImportant
|
|
else
|
|
MsgLine.Urgency:=mluVerbose2;
|
|
if IsStdErr then
|
|
MsgLine.Flags:=MsgLine.Flags+[mlfStdErr];
|
|
AddMsgLine(MsgLine);
|
|
end;
|
|
|
|
begin
|
|
FRegExprFilenameLineIDMsg.InputString:=Line;
|
|
if FRegExprFilenameLineIDMsg.ExecPos(1) then
|
|
begin
|
|
AddFilenameLineIDMsg;
|
|
exit;
|
|
end;
|
|
|
|
FRegExprFilenameLineUrgencyIDMsg.InputString:=Line;
|
|
if FRegExprFilenameLineUrgencyIDMsg.ExecPos(1) then
|
|
begin
|
|
AddFilenameLineUrgencyIDMsg;
|
|
exit;
|
|
end;
|
|
|
|
AddOtherLine;
|
|
|
|
Handled:=true;
|
|
end;
|
|
|
|
class function TDelphiCompilerParser.DefaultSubTool: string;
|
|
begin
|
|
Result:=SDelphiToolName;
|
|
end;
|
|
|
|
class function TDelphiCompilerParser.GetParserName: string;
|
|
begin
|
|
Result:=SDelphiParserName;
|
|
end;
|
|
|
|
class function TDelphiCompilerParser.GetLocalizedParserName: string;
|
|
begin
|
|
Result:=SDelphiLocalizedParserName;
|
|
end;
|
|
|
|
|
|
class function TDelphiCompilerParser.Priority: integer;
|
|
begin
|
|
Result:=SubToolDelphiPriority;
|
|
end;
|
|
|
|
{ TDelphiTool }
|
|
|
|
function TDelphiTool.GetCompilerPath(const s: string; const Data: PtrInt;
|
|
var Abort: boolean): string;
|
|
begin
|
|
Abort:=False;
|
|
if (s<>'') and (ConsoleVerbosity>=0) then
|
|
debugln(['Hint: (lazarus) [TDelphiTool.GetCompilerPath] ignoring macro DCC parameter "',s,'"']);
|
|
Result:=GetParsedCompilerFilename;
|
|
if Result='' then
|
|
Result:='dcc32.exe'; // always return something to get nicer error messages
|
|
debugln(['macro DCC parameter: "',Result,'"']);
|
|
end;
|
|
|
|
function TDelphiTool.GetCompileCommand(const s: string; const Data: PtrInt;
|
|
var Abort: boolean): string;
|
|
begin
|
|
if (s<>'') and (ConsoleVerbosity>=0) then
|
|
debugln(['Hint: (lazarus) [TDelphiTool.GetCompilerPath] ignoring macro DELPHICOMPILE parameter "',s,'"']);
|
|
Result:=GetCompileCommand();
|
|
// debugln(['macro DELPHICOMPILE parameter: "',Result,'"']);
|
|
end;
|
|
|
|
function TDelphiTool.GetCompilerArgs(const s: string; const Data: PtrInt;
|
|
var Abort: boolean): string;
|
|
|
|
|
|
begin
|
|
|
|
if (s<>'') and (ConsoleVerbosity>=0) then
|
|
debugln(['Hint: (lazarus) [TDelphiTool.GetCompilerPath] ignoring macro DCCARGS parameter "',s,'"']);
|
|
Result:=GetCompilerArguments;
|
|
// debugln(['macro DCCARGS parameter: "',Result,'"']);
|
|
end;
|
|
|
|
|
|
function TDelphiTool.GetConfigPath(const s: string; const Data: PtrInt;
|
|
var Abort: boolean): string;
|
|
begin
|
|
Abort:=False;
|
|
if (s<>'') and (ConsoleVerbosity>=0) then
|
|
debugln(['Hint: (lazarus) [TDelphiTool.GetConfigPath] ignoring macro DCCCONFIG parameter "',s,'"']);
|
|
Result:=GetCurrentConfigFilename;
|
|
if (Result='') then
|
|
Result:='Project1'+DelphiOptions.DefaultConfigExtension;
|
|
// debugln(['macro DCCCONFIG parameter: "',Result,'"']);
|
|
end;
|
|
|
|
destructor TDelphiTool.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
end;
|
|
|
|
class constructor TDelphiTool.Init;
|
|
begin
|
|
_instance:=TDelphiTool.Create;
|
|
end;
|
|
|
|
class destructor TDelphiTool.Done;
|
|
begin
|
|
FreeAndNil(_instance);
|
|
end;
|
|
|
|
function TDelphiTool.FPCToDelphiOpts(Opts : TLazCompilerOptions; aDelphiOpts: TStrings) : Integer;
|
|
|
|
Procedure AddFlag(SetFlag : Boolean; const FlagName : String);
|
|
|
|
begin
|
|
aDelphiOpts.Add('-$'+FlagName+PlusMinus[SetFlag]);
|
|
Inc(Result);
|
|
end;
|
|
|
|
procedure AddOption(DoAdd : Boolean; const Option : String);
|
|
|
|
begin
|
|
if DoAdd then
|
|
begin
|
|
aDelphiOpts.Add('-'+Option);
|
|
Inc(Result);
|
|
end;
|
|
end;
|
|
|
|
procedure AddOption(aValue : String; const FlagName : String);
|
|
|
|
begin
|
|
AddOption(aValue<>'',FlagName+aValue);
|
|
end;
|
|
|
|
begin
|
|
// Options
|
|
AddOption(Opts.GetUnitPath(False,coptParsed,True),'U');
|
|
AddOPtion(Opts.GetIncludePath(False,coptParsed,True),'I');
|
|
AddOption(Opts.GenerateDebugInfo,'V');
|
|
AddOption(Opts.UnitOutputDirectory,'NU');
|
|
AddOption(Opts.UnitOutputDirectory,'NO');
|
|
AddOption(Opts.GetObjectPath(True,coptParsed,True),'O');
|
|
AddOption(Opts.ShowWarn,'W');
|
|
AddOption(Opts.ShowHints,'H');
|
|
AddOption(Opts.DontUseConfigFile,'-no-config');
|
|
AddOption(Opts.TargetFileExt,'TX');
|
|
// Flags
|
|
AddFlag(Opts.RangeChecks,'R');
|
|
AddFlag(Opts.IOChecks,'I');
|
|
AddFlag(Opts.IncludeAssertionCode,'C');
|
|
AddFlag(Opts.UseAnsiStrings,'H');
|
|
AddFlag(Opts.OptimizationLevel>0,'O');
|
|
AddFlag(Opts.OverflowChecks,'Q');
|
|
AddFlag(Opts.GenerateDebugInfo,'L');
|
|
AddFlag(Opts.GenerateDebugInfo,'W');
|
|
AddFlag(Opts.GenerateDebugInfo,'Y');
|
|
// We can maybe check for -Sy in custom options ?
|
|
end;
|
|
|
|
function TDelphiTool.GenerateConfigFilename(const aFilename : String) : Boolean;
|
|
|
|
var
|
|
Opts : TLazCompilerOptions;
|
|
BuildID: String;
|
|
Idx : Integer;
|
|
L : TStrings;
|
|
|
|
begin
|
|
debugln(['Generating delphi project configuration file: "',aFileName,'"']);
|
|
BuildID:=LazarusIDE.ActiveProject.ActiveBuildModeID;
|
|
Idx:=LazarusIDE.ActiveProject.LazBuildModes.IndexOf(BuildID);
|
|
if Idx<0 then
|
|
Idx:=0;
|
|
Opts:=LazarusIDE.ActiveProject.LazBuildModes.BuildModes[Idx].LazCompilerOptions;
|
|
L:=TstringList.Create;
|
|
try
|
|
Result:=FPCToDelphiOpts(Opts,L)>0;
|
|
L.SaveToFile(aFileName);
|
|
debugln(['Generated delphi project configuration file: "',aFileName,'"']);
|
|
finally
|
|
L.Free;
|
|
end;
|
|
end;
|
|
|
|
function TDelphiTool.OnProjectBuilding(Sender: TObject): TModalResult;
|
|
|
|
begin
|
|
Result:=mrOK;
|
|
FMacros[dmCompiler].LazbuildValue:=GetCompileCommand();
|
|
FMacros[dmConfigFileName].LazbuildValue:=GetCurrentConfigFileName;
|
|
FMacros[dmCompileCommand].LazbuildValue:=GetCompileCommand;
|
|
FMacros[dmAdditionalArgs].LazbuildValue:=GetCompilerArguments;
|
|
if DelphiToolOptions.GenerateConfigFile and assigned(LazarusIDE.ActiveProject) then
|
|
GenerateConfigFilename(GetCurrentConfigFileName(False));
|
|
end;
|
|
|
|
procedure TDelphiTool.Hook;
|
|
begin
|
|
FMacros[dmCompiler]:=IDEMacros.Add('DCC', '', SDelphiCompilerFileNameCaption, @GetCompilerPath, [tmfLazbuild]);
|
|
FMacros[dmConfigFileName]:=IDEMacros.Add('DCCCONFIG', '', SDelphiCompilerConfigFileName, @GetConfigPath, [tmfLazbuild]);
|
|
FMacros[dmCompileCommand]:=IDEMacros.Add('DELPHICOMPILE', '', SDelphiCompileCommand, @GetCompileCommand, [tmfLazbuild]);
|
|
FMacros[dmAdditionalArgs]:=IDEMacros.Add('DCCARGS', '', SDelphiCompilerArgs, @GetCompilerArgs, [tmfLazbuild]);
|
|
LazarusIDE.AddHandlerOnProjectBuilding(@OnProjectBuilding);
|
|
end;
|
|
|
|
procedure TDelphiTool.UnHook;
|
|
begin
|
|
//
|
|
end;
|
|
|
|
function TDelphiTool.GetParsedCompilerFilename: String;
|
|
|
|
begin
|
|
Result:=DelphiToolOptions.CompilerFileName;
|
|
IDEMacros.SubstituteMacros(Result);
|
|
if not FilenameIsAbsolute(Result) then
|
|
Result:=FindDefaultExecutablePath(Result);
|
|
end;
|
|
|
|
function TDelphiTool.GetCurrentConfigFileName(PrependAt: Boolean = True): String;
|
|
begin
|
|
if Assigned(LazarusIDE.ActiveProject) and DelphiToolOptions.GenerateConfigFile then
|
|
begin
|
|
Result:=ChangeFileExt(LazarusIDE.ActiveProject.ProjectInfoFile,DelphiOptions.DefaultConfigExtension);
|
|
if PrependAt then
|
|
Result:='@'+Result;
|
|
end
|
|
else
|
|
Result:='';
|
|
end;
|
|
|
|
function TDelphiTool.GetCompilerArguments: string;
|
|
begin
|
|
Result:=DelphiToolOptions.AdditionalOptions;
|
|
IDEMacros.SubstituteMacros(Result);
|
|
end;
|
|
|
|
|
|
function TDelphiTool.GetCompileCommand: string;
|
|
|
|
begin
|
|
Result:='$(DCC) $(DCCARGS) $(DCCCONFIG)';
|
|
IDEMacros.SubstituteMacros(Result);
|
|
end;
|
|
|
|
|
|
end.
|
|
|