lazarus/ide/etfpcmsgparser.pas

3132 lines
98 KiB
ObjectPascal

{
***************************************************************************
* *
* This source is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This code 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. See the GNU *
* General Public License for more details. *
* *
* A copy of the GNU General Public License is available on the World *
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
* obtain it by writing to the Free Software Foundation, *
* Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. *
* *
***************************************************************************
Author: Mattias Gaertner
Abstract:
Parser for Free Pascal Compiler output.
}
unit etFPCMsgParser;
{$mode objfpc}{$H+}
{ $DEFINE VerboseFPCMsgUnitNotFound}
interface
uses
// RTL
Classes, SysUtils, StrUtils, Math,
// CodeTools
KeywordFuncLists, CodeToolsFPCMsgs, CodeCache, FileProcs, CodeToolManager,
DirectoryCacher, BasicCodeTools, DefineTemplates, SourceLog, LinkScanner,
// LazUtils
LConvEncoding, LazUTF8, FileUtil, LazFileUtils, LazFileCache,
LazUtilities, AvgLvlTree,
// BuildIntf
IDEExternToolIntf, PackageIntf, ProjectIntf, MacroIntf,
// IDEIntf
LazIDEIntf, IDEUtils,
// IdeConfig
EnvironmentOpts, LazConf, IDECmdLine, SearchPathProcs,
etMakeMsgParser, etFPCMsgFilePool,
// IDE
LazarusIDEStrConsts;
const
FPCMsgIDCompiling = 3104;
FPCMsgIDLogo = 11023;
FPCMsgIDCantFindUnitUsedBy = 10022;
FPCMsgIDLinking = 9015;
FPCMsgIDErrorWhileLinking = 9013;
FPCMsgIDErrorWhileCompilingResources = 9029;
FPCMsgIDCallingResourceCompiler = 9028;
FPCMsgIDThereWereErrorsCompiling = 10026;
FPCMsgIDMethodIdentifierExpected = 3047;
FPCMsgIDIdentifierNotFound = 5000;
FPCMsgIDChecksumChanged = 10028;
FPCMsgIDUnitDeprecate = 5074; // Unit "$1" is deprecate
FPCMsgIDUnitDeprecated = 5075; // Unit "$1" is deprecated
FPCMsgIDUnitNotPortable = 5076; // Unit "$1" is not portable
FPCMsgIDUnitNotImplemented = 5078; // Unit "$1" is not implemented
FPCMsgIDUnitExperimental = 5079; // Unit "$1" is experimental
FPCMsgIDUnitNotUsed = 5023; // Unit "$1" not used in $2
FPCMsgIDCompilationAborted = 1018;
FPCMsgIDLinesCompiled = 1008;
FPCMsgAttrWorkerDirectory = 'WD';
FPCMsgAttrMissingUnit = 'MissingUnit';
FPCMsgAttrUsedByUnit = 'UsedByUnit';
type
{ TPatternToMsgID }
TPatternToMsgID = class
public
Pattern: string;
MsgID: integer;
PatternLine: integer; // line index in a multi line pattern, starting at 0
end;
PPatternToMsgID = ^TPatternToMsgID;
{ TPatternToMsgIDs }
TPatternToMsgIDs = class
private
fItems: array of TPatternToMsgID;
function IndexOf(Pattern: PChar; Insert: boolean): integer;
public
constructor Create;
destructor Destroy; override;
procedure Clear;
procedure Add(Pattern: string; MsgID: integer; PatternLine: integer = 0);
procedure AddLines(const Lines: string; MsgID: integer);
function LineToMsgID(p: PChar): integer; inline; // 0 = not found
function LineToPattern(p: PChar): PPatternToMsgID;
procedure WriteDebugReport;
procedure ConsistencyCheck;
end;
{ TIDEFPCParser }
TIDEFPCParser = class(TFPCParser)
private
fCurSource: TCodeBuffer;
fFileExists: TFilenameToPointerTree;
fIncludePath: string; // only valid if fIncludePathValidForWorkerDir=Tool.WorkerDirectory
fIncludePathValidForWorkerDir: string;
fUnitPath: string; // only valid if fUnitPathValidForWorkerDir=Tool.WorkerDirectory
fUnitPathValidForWorkerDir: string;
fLastWorkerImprovedMessage: array[TExtToolParserSyncPhase] of integer;
fLineToMsgID: TPatternToMsgIDs;
fMissingFPCMsgItem: TFPCMsgItem;
fMsgID: Integer; // current message id given by ReadLine (-vq)
fMsgIsStdErr: boolean;
fMsgItemCantFindUnitUsedBy: TFPCMsgItem;
fMsgItemCompilationAborted: TFPCMsgItem;
fMsgItemErrorWhileCompilingResources: TFPCMsgItem;
fMsgItemErrorWhileLinking: TFPCMsgItem;
fMsgItemMethodIdentifierExpected: TFPCMsgItem;
fMsgItemIdentifierNotFound: TFPCMsgItem;
fMsgItemThereWereErrorsCompiling: TFPCMsgItem;
fMsgItemChecksumChanged: TFPCMsgItem;
fMsgItemUnitNotUsed: TFPCMsgItem;
fMsgItemUnitIsDeprecate: TFPCMsgItem;
fMsgItemUnitIsDeprecated: TFPCMsgItem;
fMsgItemUnitIsExperimental: TFPCMsgItem;
fMsgItemUnitIsNotImplemented: TFPCMsgItem;
fMsgItemUnitIsNotPortable: TFPCMsgItem;
fOutputIndex: integer; // current OutputIndex given by ReadLine
procedure FetchIncludePath(aPhase: TExtToolParserSyncPhase; MsgWorkerDir: String);
procedure FetchUnitPath(aPhase: TExtToolParserSyncPhase; MsgWorkerDir: String);
function FileExists(const Filename: string; aSynchronized: boolean): boolean;
function CheckForMsgId(p: PChar): boolean; // (MsgId) message
function CheckFollowUpMessage(p: PChar): boolean;
function CheckForFileLineColMessage(p: PChar): boolean; // the normal messages: filename(y,x): Hint: ..
function CheckForGeneralMessage(p: PChar): boolean; // Fatal: .., Error: ..., Panic: ..
function CheckForInfos(p: PChar): boolean; // e.g. Free Pascal Compiler version 2.6.4 [2014/02/26] for i386
function CheckForCompilingState(p: PChar): boolean; // Compiling ..
function CheckForAssemblingState(p: PChar): boolean; // Assembling ..
function CheckForLinesCompiled(p: PChar): boolean; // ..lines compiled..
function CheckForExecutableInfo(p: PChar): boolean;
function CheckForLineProgress(p: PChar): boolean; // 600 206.521/231.648 Kb Used
function CheckForLoadFromUnit(p: PChar): Boolean;
function CheckForWindresErrors(p: PChar): boolean;
function CheckForLinkerErrors(p: PChar): boolean;
function CheckForAssemblerErrors(p: PChar): boolean;
function CheckForUnspecificStdErr(p: PChar): boolean;
function CreateMsgLine: TMessageLine;
procedure AddLinkingMessages;
procedure AddResourceMessages;
function NeedSource(aPhase: TExtToolParserSyncPhase; SourceOk: boolean): boolean;
procedure ImproveMsgHiddenByIDEDirective(aPhase: TExtToolParserSyncPhase;
MsgLine: TMessageLine; SourceOK: Boolean);
procedure ImproveMsgSenderNotUsed(aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine);
procedure ImproveMsgUnitTagged(aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine);
procedure ImproveMsgUnitNotUsed(aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine);
procedure ImproveMsgUnitNotFound(aPhase: TExtToolParserSyncPhase;
MsgLine: TMessageLine);
procedure ImproveMsgLinkerUndefinedReference(aPhase: TExtToolParserSyncPhase;
MsgLine: TMessageLine);
procedure ImproveMsgIdentifierPosition(aPhase: TExtToolParserSyncPhase;
MsgLine: TMessageLine; SourceOK: boolean);
function FindSrcViaPPU(aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine;
const PPUFilename: string): boolean;
procedure Translate(p: PChar; MsgItem, TranslatedItem: TFPCMsgItem;
out TranslatedMsg: String; var MsgType: TMessageLineUrgency);
function ReverseInstantFPCCacheDir(var aFilename: string; aSynchronized: boolean): boolean;
function ReverseTestBuildDir(MsgLine: TMessageLine; var aFilename: string): boolean;
function LongenFilename(MsgLine: TMessageLine; aFilename: string): string; // (worker thread)
protected
function GetDefaultPCFullVersion: LongWord; virtual;
function ToUTF8(const Line: string): string; virtual;
public
DirectoryStack: TStrings;
MsgFilename: string; // e.g. /path/to/fpcsrc/compiler/msg/errore.msg
MsgFile: TFPCMsgFilePoolItem;
TranslationFilename: string; // e.g. /path/to/fpcsrc/compiler/msg/errord.msg
TranslationFile: TFPCMsgFilePoolItem;
InstantFPCCache: string; // with trailing pathdelim
TestBuildDir: string; // with trailing pathdelim
VirtualProjectFiles: TFilenameToPointerTree;
PC_FullVersion: LongWord;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Init; override; // called after macros resolved, before starting thread (main thread)
procedure InitReading; override; // called when process started, before first line (worker thread)
procedure Done; override; // called after process stopped (worker thread)
procedure ReadLine(Line: string; OutputIndex: integer; IsStdErr: boolean;
var Handled: boolean); override;
procedure AddMsgLine(MsgLine: TMessageLine); override;
procedure ImproveMessages(aPhase: TExtToolParserSyncPhase); override;
function GetFPCMsgIDPattern(MsgID: integer): string; override;
function IsMsgID(MsgLine: TMessageLine; MsgID: integer;
var Item: TFPCMsgItem): boolean;
class function CanParseSubTool(const SubTool: string): boolean; override;
class function DefaultSubTool: string; override;
class function GetMsgPattern(SubTool: string; MsgID: integer;
out Urgency: TMessageLineUrgency): string; override;
class function GetMsgHint(SubTool: string; MsgID: integer): string;
override;
class function Priority: integer; override;
class function MsgLineIsId(Msg: TMessageLine; MsgId: integer;
out Value1, Value2: string): boolean; override;
class function GetFPCMsgPattern(Msg: TMessageLine): string; override;
class function GetFPCMsgValue1(Msg: TMessageLine): string; override;
class function GetFPCMsgValues(Msg: TMessageLine; out Value1, Value2: string): boolean; override;
class function MsgFilePool: TFPCMsgFilePool; virtual;
end;
// thread safe
//function FPCMsgFits(const Msg, Pattern: string;
//function TranslateFPCMsg(const Src, SrcPattern, TargetPattern: string): string;
// VarStarts: PPChar = nil; VarEnds: PPChar = nil // 10 PChars
// ): boolean;
function GetFPCMsgValueOne(const Src, Pattern: string; out Value1: string): boolean;
function GetFPCMsgValuesTwo(Src, Pattern: string; out Value1, Value2: string): boolean;
// not thread safe
function IsFileInIDESrcDir(Filename: string): boolean; // (main thread)
procedure RegisterFPCParser;
implementation
function IsFPCMsgVar(p: PChar): boolean; inline;
begin
Result:=(p^='$') and (p[1] in ['0'..'9']);
end;
function IsFPCMsgEndOrVar(p: PChar): boolean; inline;
begin
Result:=(p^=#0) or IsFPCMsgVar(p);
end;
function FPCMsgFits(const Msg, Pattern: string; VarStarts: PPChar;
VarEnds: PPChar): boolean;
{ for example:
Src='A lines compiled, B sec C'
SrcPattern='$1 lines compiled, $2 sec $3'
VarStarts and VarEnds can be nil.
If you need the boundaries of the parameters allocate VarStarts and VarEnds as
VarStarts:=GetMem(SizeOf(PChar)*10);
VarEnds:=GetMem(SizeOf(PChar)*10);
VarStarts[0] will be $0, VarStarts[1] will be $1 and so forth
}
var
MsgPos, PatPos: PChar;
MsgPos2, PatPos2: PChar;
i: Integer;
begin
Result:=false;
{$IFDEF VerboseFPCTranslate}
debugln(['FPCMsgFits Msg="',Msg,'" Pattern="',Pattern,'"']);
{$ENDIF}
if (Msg='') or (Pattern='') then exit;
MsgPos:=PChar(Msg);
PatPos:=PChar(Pattern);
// skip the characters of Msg copied from Pattern
while not IsFPCMsgEndOrVar(PatPos) do begin
if (MsgPos^<>PatPos^) then begin
// Pattern does not fit
{$IFDEF VerboseFPCTranslate}
debugln(['FPCMsgFits skipping start of Src and SrcPattern failed']);
{$ENDIF}
exit;
end;
inc(MsgPos);
inc(PatPos)
end;
{$IFDEF VerboseFPCTranslate}
debugln(['FPCMsgFits skipped start: SrcPos="',SrcPos,'" SrcPatPos="',SrcPatPos,'"']);
{$ENDIF}
if VarStarts<>nil then begin
FillByte(VarStarts^,SizeOf(PChar)*10,0);
FillByte(VarEnds^,SizeOf(PChar)*10,0);
end;
// find the parameters in Msg and store their boundaries in VarStarts, VarEnds
while (PatPos^<>#0) do begin
// read variable number
inc(PatPos);
i:=ord(PatPos^)-ord('0');
inc(PatPos);
if (VarEnds<>nil) and (VarEnds[i]=nil) then begin
VarStarts[i]:=MsgPos;
VarEnds[i]:=nil;
end;
// find the end of the parameter in Msg
// example: Pattern='$1 found' Msg='Ha found found'
repeat
if MsgPos^=PatPos^ then begin
{$IFDEF VerboseFPCTranslate}
debugln(['FPCMsgFits candidate for param ',i,' end: SrcPos="',SrcPos,'" SrcPatPos="',SrcPatPos,'"']);
{$ENDIF}
MsgPos2:=MsgPos;
PatPos2:=PatPos;
while (MsgPos2^=PatPos2^) and not IsFPCMsgEndOrVar(PatPos2) do begin
inc(MsgPos2);
inc(PatPos2);
end;
if IsFPCMsgEndOrVar(PatPos2) then begin
{$IFDEF VerboseFPCTranslate}
debugln(['FPCMsgFits param ',i,' end found: SrcPos2="',SrcPos2,'" SrcPatPos2="',SrcPatPos2,'"']);
{$ENDIF}
if (VarEnds<>nil) and (VarEnds[i]=nil) then
VarEnds[i]:=MsgPos;
MsgPos:=MsgPos2;
PatPos:=PatPos2;
break;
end;
{$IFDEF VerboseFPCTranslate}
debugln(['FPCMsgFits searching further...']);
{$ENDIF}
end else if MsgPos^=#0 then begin
if IsFPCMsgEndOrVar(PatPos) then begin
// empty parameter at end
if (VarEnds<>nil) and (VarEnds[i]=nil) then
VarEnds[i]:=MsgPos;
break;
end else begin
// Pattern does not fit Msg
{$IFDEF VerboseFPCTranslate}
debugln(['FPCMsgFits finding end of parameter ',i,' failed']);
{$ENDIF}
exit;
end;
end;
inc(MsgPos);
until false;
end;
Result:=true;
end;
function TranslateFPCMsg(const Src, SrcPattern, TargetPattern: string): string;
{ for example:
Src='A lines compiled, B sec C'
SrcPattern='$1 lines compiled, $2 sec $3'
TargetPattern='$1 Zeilen uebersetzt, $2 Sekunden $3'
Result='A Zeilen uebersetzt, B Sekunden C'
}
var
SrcPos: PChar;
TargetPatPos: PChar;
TargetPos: PChar;
SrcVarStarts, SrcVarEnds: array[0..9] of PChar;
VarUsed: array[0..9] of integer;
i: Integer;
begin
Result:='';
{$IFDEF VerboseFPCTranslate}
debugln(['TranslateFPCMsg Src="',Src,'" SrcPattern="',SrcPattern,'" TargetPattern="',TargetPattern,'"']);
{$ENDIF}
if (Src='') or (SrcPattern='') or (TargetPattern='') then exit;
if not FPCMsgFits(Src,SrcPattern,@SrcVarStarts[0],@SrcVarEnds[0]) then
exit;
for i:=Low(SrcVarStarts) to high(SrcVarStarts) do
VarUsed[i]:=0;
// create Target
SetLength(Result,length(TargetPattern)+length(Src));
TargetPatPos:=PChar(TargetPattern);
TargetPos:=PChar(Result);
while TargetPatPos^<>#0 do begin
//debugln(['TranslateFPCMsg Target ',dbgs(Pointer(TargetPatPos)),' ',ord(TargetPatPos^),' TargetPatPos="',TargetPatPos,'"']);
if IsFPCMsgVar(TargetPatPos) then begin
// insert variable
inc(TargetPatPos);
i:=ord(TargetPatPos^)-ord('0');
inc(TargetPatPos);
if SrcVarStarts[i]<>nil then begin
inc(VarUsed[i]);
if VarUsed[i]>1 then begin
// variable is used more than once => realloc result
dec(TargetPos,{%H-}PtrUInt(PChar(Result)));
SetLength(Result,length(Result)+SrcVarEnds[i]-SrcVarStarts[i]);
inc(TargetPos,{%H-}PtrUInt(PChar(Result)));
end;
SrcPos:=SrcVarStarts[i];
while SrcPos<SrcVarEnds[i] do begin
TargetPos^:=SrcPos^;
inc(TargetPos);
inc(SrcPos);
end;
end;
end else begin
// copy text from TargetPattern
TargetPos^:=TargetPatPos^;
inc(TargetPatPos);
inc(TargetPos);
end;
end;
SetLength(Result,TargetPos-PChar(Result));
if Result<>'' then
UTF8FixBroken(PChar(Result));
{$IFDEF VerboseFPCTranslate}
debugln(['TranslateFPCMsg Result="',Result,'"']);
{$ENDIF}
end;
function GetFPCMsgValueOne(const Src, Pattern: string; out Value1: string): boolean;
{ Pattern: 'Compiling $1'
Src: 'Compiling fcllaz.pas'
Value1: 'fcllaz.pas'
}
var
p: SizeInt;
l: SizeInt;
begin
Value1:='';
Result:=false;
if length(Src)<length(Pattern)-2 then exit;
p:=Pos('$1',Pattern);
if p<1 then exit;
// check start pattern
if (p>1) and (not CompareMem(Pointer(Src),Pointer(Pattern),p-1)) then exit;
// check end pattern
l:=length(Pattern)-p-2;
if (l>0)
and (not CompareMem(Pointer(Src)+length(Src)-l,Pointer(Pattern)+p+2,l)) then exit;
Value1:=copy(Src,p,length(Src)-length(Pattern)+2);
Result:=true;
end;
function GetFPCMsgValuesTwo(Src, Pattern: string; out Value1, Value2: string): boolean;
{ Pattern: 'Unit $1 was not found but $2 exists'
Src: 'Unit dialogprocs was not found but dialogpr exists'
Value1: 'dialogprocs'
Value1: 'dialogpr'
Not supported: '$1$2'
}
var
p1: SizeInt;
LastPattern: String;
p2: SizeInt;
MiddlePattern: String;
SrcP1Behind: Integer;
SrcP2: Integer;
begin
Result:=false;
Value1:='';
Value2:='';
p1:=Pos('$1',Pattern);
if p1<1 then exit;
p2:=Pos('$2',Pattern);
if p2<=p1+2 then exit;
if LeftStr(Pattern,p1-1)<>LeftStr(Src,p1-1) then exit;
LastPattern:=RightStr(Pattern,length(Pattern)-p2-1);
if RightStr(Src,length(LastPattern))<>LastPattern then exit;
MiddlePattern:=copy(Pattern,p1+2,p2-p1-2);
SrcP1Behind:=PosEx(MiddlePattern,Src,p1+2);
if SrcP1Behind<1 then exit;
Value1:=copy(Src,p1,SrcP1Behind-p1);
SrcP2:=SrcP1Behind+length(MiddlePattern);
Value2:=copy(Src,SrcP2,length(Src)-SrcP2-length(LastPattern)+1);
Result:=true;
end;
function IsFileInIDESrcDir(Filename: string): boolean;
var
LazDir: String;
begin
Filename:=TrimFilename(Filename);
if not FilenameIsAbsolute(Filename) then exit(false);
LazDir:=AppendPathDelim(EnvironmentOptions.GetParsedLazarusDirectory);
Result:=FileIsInPath(Filename,LazDir+'ide')
or FileIsInPath(Filename,LazDir+'debugger')
or FileIsInPath(Filename,LazDir+'packager')
or FileIsInPath(Filename,LazDir+'converter')
or FileIsInPath(Filename,LazDir+'designer');
end;
procedure RegisterFPCParser;
begin
ExternalToolList.RegisterParser(TIDEFPCParser);
end;
{ TPatternToMsgIDs }
function TPatternToMsgIDs.IndexOf(Pattern: PChar; Insert: boolean): integer;
var
l: Integer;
r: Integer;
m: Integer;
ItemP: PChar;
FindP: PChar;
cmp: Integer;
begin
Result:=-1;
l:=0;
r:=length(fItems)-1;
cmp:=0;
m:=0;
while (l<=r) do begin
m:=(l+r) div 2;
ItemP:=PChar(fItems[m].Pattern);
FindP:=Pattern;
while (ItemP^=FindP^) do begin
if ItemP^=#0 then
exit(m); // exact match
inc(ItemP);
inc(FindP);
end;
if ItemP^ in [#0,'$'] then begin
// Pattern longer than Item
if not Insert then begin
if (Result<0) or (length(fItems[m].Pattern)>length(fItems[Result].Pattern))
then
Result:=m;
end;
end;
cmp:=ord(ItemP^)-ord(FindP^);
if cmp<0 then
l:=m+1
else
r:=m-1;
end;
if Insert then begin
if cmp<0 then
Result:=m+1
else
Result:=m;
end;
end;
constructor TPatternToMsgIDs.Create;
begin
end;
destructor TPatternToMsgIDs.Destroy;
begin
Clear;
inherited Destroy;
end;
procedure TPatternToMsgIDs.Clear;
var
i: Integer;
begin
for i:=0 to length(fItems)-1 do
fItems[i].Free;
SetLength(fItems,0);
end;
procedure TPatternToMsgIDs.Add(Pattern: string; MsgID: integer;
PatternLine: integer);
procedure RaiseInvalidMsgID;
begin
raise Exception.Create('invalid MsgID: '+IntToStr(MsgID));
end;
var
i: Integer;
Item: TPatternToMsgID;
Cnt: Integer;
begin
if MsgID=0 then
RaiseInvalidMsgID;
Pattern:=Trim(Pattern);
if (Pattern='') or (Pattern[1]='$') then exit;
i:=IndexOf(PChar(Pattern),true);
Cnt:=length(fItems);
SetLength(fItems,Cnt+1);
if Cnt-i>0 then
Move(fItems[i],fItems[i+1],SizeOf(TPatternToMsgID)*(Cnt-i));
Item:=TPatternToMsgID.Create;
fItems[i]:=Item;
Item.Pattern:=Pattern;
Item.MsgID:=MsgID;
Item.PatternLine:=PatternLine;
end;
procedure TPatternToMsgIDs.AddLines(const Lines: string; MsgID: integer);
var
StartPos: PChar;
p: PChar;
PatternLine: Integer;
begin
PatternLine:=0;
p:=PChar(Lines);
while p^<>#0 do begin
StartPos:=p;
while not (p^ in [#0,#10,#13]) do inc(p);
if p>StartPos then begin
Add(copy(Lines,StartPos-PChar(Lines)+1,p-StartPos),MsgID,PatternLine);
inc(PatternLine);
end;
while p^ in [#10,#13] do inc(p);
end;
end;
function TPatternToMsgIDs.LineToMsgID(p: PChar): integer;
var
Item: PPatternToMsgID;
begin
Item:=LineToPattern(p);
if Item=nil then
Result:=0
else
Result:=Item^.MsgID;
end;
function TPatternToMsgIDs.LineToPattern(p: PChar): PPatternToMsgID;
var
i: Integer;
begin
while p^ in [' ',#9,#10,#13] do inc(p);
i:=IndexOf(p,false);
if i<0 then
Result:=nil
else
Result:=@fItems[i];
end;
procedure TPatternToMsgIDs.WriteDebugReport;
var
i: Integer;
begin
debugln(['TLineStartToMsgIDs.WriteDebugReport Count=',length(fItems)]);
for i:=0 to Length(fItems)-1 do begin
debugln([' ID=',fItems[i].MsgID,'="',fItems[i].Pattern,'"']);
end;
ConsistencyCheck;
end;
procedure TPatternToMsgIDs.ConsistencyCheck;
procedure E(Msg: string);
begin
raise Exception.Create(Msg);
end;
var
i: Integer;
Item: TPatternToMsgID;
begin
for i:=0 to Length(fItems)-1 do begin
Item:=fItems[i];
if Item.MsgID<=0 then
E('Item.MsgID<=0');
if Item.Pattern='' then
E('Item.Pattern empty');
if IndexOf(PChar(Item.Pattern),false)<>i then
E('IndexOf '+dbgs(i)+' "'+Item.Pattern+'" IndexOf='+dbgs(IndexOf(PChar(Item.Pattern),false)));
end;
end;
{ TIDEFPCParser }
destructor TIDEFPCParser.Destroy;
begin
FreeAndNil(VirtualProjectFiles);
FreeAndNil(FFilesToIgnoreUnitNotUsed);
FreeAndNil(fFileExists);
FreeAndNil(fCurSource);
if TranslationFile<>nil then
MsgFilePool.UnloadFile(TranslationFile);
if MsgFile<>nil then
MsgFilePool.UnloadFile(MsgFile);
FreeAndNil(DirectoryStack);
FreeAndNil(fLineToMsgID);
inherited Destroy;
end;
procedure TIDEFPCParser.Init;
procedure LoadMsgFile(aFilename: string; var List: TFPCMsgFilePoolItem);
begin
//debugln(['TFPCParser.Init load Msg filename=',aFilename]);
if aFilename='' then
debugln(['WARNING: TFPCParser.Init missing msg file'])
else if (aFilename<>'') and (List=nil) then begin
try
List:=MsgFilePool.LoadFile(aFilename,true,nil);
{$IFDEF VerboseExtToolThread}
debugln(['LoadMsgFile successfully read ',aFilename]);
{$ENDIF}
except
on E: Exception do begin
debugln(['WARNING: TFPCParser.Init failed to load file '+aFilename+': '+E.Message]);
end;
end;
end;
end;
var
i: Integer;
Param: String;
p: PChar;
aTargetOS: String;
aTargetCPU: String;
aProject: TLazProject;
aProjFile: TLazProjectFile;
begin
inherited Init;
PC_FullVersion:=GetDefaultPCFullVersion;
if MsgFilePool<>nil then begin
aTargetOS:='';
aTargetCPU:='';
for i:=0 to Tool.Process.Parameters.Count-1 do begin
Param:=Tool.Process.Parameters[i];
if Param='' then continue;
p:=PChar(Param);
if p^<>'-' then continue;
if p[1]='T' then
aTargetOS:=copy(Param,3,255)
else if p[1]='P' then
aTargetCPU:=copy(Param,3,255);
end;
MsgFilePool.GetMsgFileNames(Tool.Process.Executable,aTargetOS,aTargetCPU,
MsgFilename,TranslationFilename);
end;
LoadMsgFile(MsgFilename,MsgFile);
if TranslationFilename<>'' then
LoadMsgFile(TranslationFilename,TranslationFile);
// get include search path
fIncludePathValidForWorkerDir:=Tool.WorkerDirectory;
fIncludePath:=CodeToolBoss.GetIncludePathForDirectory(
ChompPathDelim(fIncludePathValidForWorkerDir));
// get unit search path
fUnitPathValidForWorkerDir:=Tool.WorkerDirectory;
fUnitPath:=CodeToolBoss.GetUnitPathForDirectory(
ChompPathDelim(fUnitPathValidForWorkerDir));
// get instantfpc cache directory
InstantFPCCache:='$(InstantFPCCache)';
if IDEMacros.SubstituteMacros(InstantFPCCache) then
InstantFPCCache:=AppendPathDelim(InstantFPCCache)
else
InstantFPCCache:='';
// get TestBuildDir
if Tool.CurrentDirectoryIsTestDir then begin
// source filenames in CurrentDirectory must be reversed back
// -> store the list of virtual filenames (needed by worker thread)
TestBuildDir:=AppendPathDelim(ResolveDots(Tool.Process.CurrentDirectory));
if VirtualProjectFiles=nil then
VirtualProjectFiles:=TFilenameToPointerTree.Create(true);
aProject:=LazarusIDE.ActiveProject;
for i:=0 to aProject.FileCount-1 do begin
aProjFile:=aProject.Files[i];
if aProjFile.IsPartOfProject and (not FilenameIsAbsolute(aProjFile.Filename)) then
VirtualProjectFiles[aProjFile.Filename]:=Tool;
end;
end else
TestBuildDir:='';
end;
procedure TIDEFPCParser.InitReading;
procedure AddPatternItem(MsgID: integer);
var
Item: TFPCMsgItem;
begin
Item:=MsgFile.GetMsg(MsgID);
if Item<>nil then
fLineToMsgID.AddLines(Item.Pattern,Item.ID);
end;
var
p: TExtToolParserSyncPhase;
begin
inherited InitReading;
fLineToMsgID.Clear;
AddPatternItem(FPCMsgIDLogo);
AddPatternItem(FPCMsgIDLinking);
AddPatternItem(FPCMsgIDCallingResourceCompiler);
//fLineToMsgID.WriteDebugReport;
for p:=low(fLastWorkerImprovedMessage) to high(fLastWorkerImprovedMessage) do
fLastWorkerImprovedMessage[p]:=-1;
FreeAndNil(DirectoryStack);
end;
procedure TIDEFPCParser.Done;
begin
FreeAndNil(fCurSource);
inherited Done;
end;
function TIDEFPCParser.CheckForCompilingState(p: PChar): boolean;
var
OldP: PChar;
AFilename: string;
aDir: String;
MsgLine: TMessageLine;
NewFilename: String;
begin
OldP:=p;
// for example 'Compiling ./subdir/unit1.pas'
if fMsgID=0 then begin
if not ReadString(p,'Compiling ') then exit(false);
fMsgID:=FPCMsgIDCompiling;
Result:=true;
end else if fMsgID=FPCMsgIDCompiling then begin
Result:=true;
if not ReadString(p,'Compiling ') then exit;
end else begin
exit(false);
end;
// add path to history
if (p^='.') and (p[1]=PathDelim) then
inc(p,2); // skip ./
AFilename:=TrimFilename(p);
aDir:=ExtractFilePath(AFilename);
if aDir<>'' then begin
// make absolute
if (not FilenameIsAbsolute(aDir)) and (Tool.WorkerDirectory<>'') then begin
aDir:=TrimFilename(AppendPathDelim(Tool.WorkerDirectory)+aDir);
AFilename:=aDir+ExtractFileName(AFilename);
end;
// reverse instantfpc cache
if (InstantFPCCache<>'') and (Tool.WorkerDirectory<>'')
and (FilenameIsAbsolute(aDir))
and (CompareFilenames(InstantFPCCache,aDir)=0) then
begin
NewFilename:=AppendPathDelim(Tool.WorkerDirectory)+ExtractFileName(AFilename);
if FileExists(NewFilename,false) then begin
AFilename:=NewFilename;
aDir:=Tool.WorkerDirectory;
end;
end;
// store directory
if DirectoryStack=nil then DirectoryStack:=TStringList.Create;
if (DirectoryStack.Count=0)
or (DirectoryStack[DirectoryStack.Count-1]<>aDir) then
DirectoryStack.Add(aDir);
end;
MsgLine:=CreateMsgLine;
MsgLine.Urgency:=mluProgress;
MsgLine.SubTool:=DefaultSubTool;
MsgLine.Filename:=AFilename;
MsgLine.Msg:=OldP;
inherited AddMsgLine(MsgLine);
Result:=true;
end;
function TIDEFPCParser.CheckForAssemblingState(p: PChar): boolean;
var
MsgLine: TMessageLine;
OldP: PChar;
begin
Result:=fMsgID=9001;
if (not Result) and (fMsgID>0) then exit;
OldP:=p;
if (not Result) and (not CompStr('Assembling ',p)) then exit;
MsgLine:=CreateMsgLine;
MsgLine.Urgency:=mluProgress;
MsgLine.SubTool:=DefaultSubTool;
MsgLine.Msg:=OldP;
inherited AddMsgLine(MsgLine);
Result:=true;
end;
function TIDEFPCParser.CheckForGeneralMessage(p: PChar): boolean;
{ check for
Fatal: message
Hint: (11030) Start of reading config file /etc/fpc.cfg
Error: /usr/bin/ppc386 returned an error exitcode
}
const
FrontEndFPCExitCodeError = 'returned an error exitcode';
var
MsgLine: TMessageLine;
MsgType: TMessageLineUrgency;
p2: PChar;
i: Integer;
TranslatedItem: TFPCMsgItem;
MsgItem: TFPCMsgItem;
TranslatedMsg: String;
procedure CheckFinalNote;
// check if there was already an error message
// if yes, then downgrade this message to a mluVerbose
var
u: TMessageLineUrgency;
begin
for u:=mluError to high(TMessageLineUrgency) do
if Tool.WorkerMessages.UrgencyCounts[u]>0 then
begin
MsgType:=mluVerbose;
exit;
end;
end;
begin
Result:=false;
MsgType:=mluNone;
if ReadString(p,'Fatal: ') then begin
MsgType:=mluFatal;
// check for "Fatal: compilation aborted"
if fMsgItemCompilationAborted=nil then begin
fMsgItemCompilationAborted:=MsgFile.GetMsg(FPCMsgIDCompilationAborted);
if fMsgItemCompilationAborted=nil then
fMsgItemCompilationAborted:=fMissingFPCMsgItem;
end;
p2:=p;
if (fMsgItemCompilationAborted<>fMissingFPCMsgItem)
and ReadString(p2,fMsgItemCompilationAborted.Pattern) then
CheckFinalNote;
end
else if ReadString(p,'Panic') then
MsgType:=mluPanic
else if ReadString(p,'Error: ') then begin
// check for fpc frontend message "Error: /usr/bin/ppc386 returned an error exitcode"
TranslatedMsg:=p;
MsgType:=mluError;
if Pos(FrontEndFPCExitCodeError,TranslatedMsg)>0 then begin
fMsgID:=FPCMsgIDCompilationAborted;
CheckFinalNote;
end;
end
else if ReadString(p,'Warn: ') or ReadString(p,'Warning:') then
MsgType:=mluWarning
else if ReadString(p,'Note: ') then
MsgType:=mluNote
else if ReadString(p,'Hint: ') then
MsgType:=mluHint
else if ReadString(p,'Debug: ') then
MsgType:=mluDebug
else begin
exit;
end;
if MsgType=mluNone then exit;
Result:=true;
while p^ in [' ',#9] do inc(p);
TranslatedMsg:='';
if (p^='(') and (p[1] in ['0'..'9']) then begin
p2:=p;
inc(p2);
i:=0;
while (p2^ in ['0'..'9']) and (i<1000000) do begin
i:=i*10+ord(p2^)-ord('0');
inc(p2);
end;
if p2^=')' then begin
fMsgID:=i;
p:=p2+1;
while p^ in [' ',#9] do inc(p);
//if Pos('reading',String(p))>0 then
// debugln(['TFPCParser.CheckForGeneralMessage ID=',fMsgID,' Msg=',p]);
if (fMsgID>0) then begin
TranslatedItem:=nil;
MsgItem:=nil;
if (MsgFile<>nil) then
MsgItem:=MsgFile.GetMsg(fMsgID);
if (TranslationFile<>nil) then
TranslatedItem:=TranslationFile.GetMsg(fMsgID);
Translate(p,MsgItem,TranslatedItem,TranslatedMsg,MsgType);
if (TranslatedItem=nil) and (MsgItem=nil) then begin
if ConsoleVerbosity>=1 then
debugln(['TFPCParser.CheckForGeneralMessage msgid not found: ',fMsgID]);
end;
end;
end;
end;
if (MsgType>=mluError) and (fMsgID=FPCMsgIDCompilationAborted) // fatal: Compilation aborted
then begin
CheckFinalNote;
end;
MsgLine:=CreateMsgLine;
MsgLine.Urgency:=MsgType;
MsgLine.SubTool:=DefaultSubTool;
MsgLine.Msg:=p;
MsgLine.TranslatedMsg:=TranslatedMsg;
AddMsgLine(MsgLine);
end;
function TIDEFPCParser.CheckForLineProgress(p: PChar): boolean;
// for example: 600 206.521/231.648 Kb Used
var
OldP: PChar;
MsgLine: TMessageLine;
begin
Result:=false;
OldP:=p;
if not ReadNumberWithThousandSep(p) then exit;
if not ReadChar(p,' ') then exit;
if not ReadNumberWithThousandSep(p) then exit;
if not ReadChar(p,'/') then exit;
if not ReadNumberWithThousandSep(p) then exit;
if not ReadChar(p,' ') then exit;
MsgLine:=CreateMsgLine;
MsgLine.SubTool:=DefaultSubTool;
MsgLine.Urgency:=mluProgress;
MsgLine.Msg:=OldP;
inherited AddMsgLine(MsgLine);
Result:=true;
end;
function TIDEFPCParser.CheckForLinesCompiled(p: PChar): boolean;
var
OldStart: PChar;
MsgLine: TMessageLine;
begin
Result:=fMsgID=FPCMsgIDLinesCompiled;
if (not Result) and (fMsgID>0) then exit;
OldStart:=p;
if not Result then begin
if not ReadNumberWithThousandSep(p) then exit;
if not ReadString(p,' lines compiled, ') then exit;
if not ReadNumberWithThousandSep(p) then exit;
end;
Result:=true;
MsgLine:=CreateMsgLine;
MsgLine.SubTool:=DefaultSubTool;
if EnvironmentOptions.MsgViewShowFPCMsgLinesCompiled then
MsgLine.Urgency:=mluImportant
else
MsgLine.Urgency:=mluVerbose;
MsgLine.Msg:=OldStart;
inherited AddMsgLine(MsgLine);
end;
function TIDEFPCParser.CheckForExecutableInfo(p: PChar): boolean;
{ For example:
Size of Code: 1184256 bytes
Size of initialized data: 519168 bytes
Size of uninitialized data: 83968 bytes
Stack space reserved: 262144 bytes
Stack space commited: 4096 bytes
}
var
OldStart: PChar;
MsgLine: TMessageLine;
begin
Result:=(fMsgID>=9130) and (fMsgID<=9140);
if (not Result) and (fMsgID>0) then exit;
OldStart:=p;
if (not Result) then begin
if not (ReadString(p,'Size of Code: ') or
ReadString(p,'Size of initialized data: ') or
ReadString(p,'Size of uninitialized data: ') or
ReadString(p,'Stack space reserved: ') or
ReadString(p,'Stack space commited: ') or // message contains typo
ReadString(p,'Stack space committed: ')) then exit;
if not ReadNumberWithThousandSep(p) then exit;
if not ReadString(p,' bytes') then exit;
end;
Result:=true;
MsgLine:=CreateMsgLine;
MsgLine.SubTool:=DefaultSubTool;
MsgLine.Urgency:=mluProgress;
MsgLine.Msg:=OldStart;
inherited AddMsgLine(MsgLine);
end;
function TIDEFPCParser.CheckForWindresErrors(p: PChar): boolean;
// example: ...\windres.exe: warning: ...
var
MsgLine: TMessageLine;
WPos: PChar;
begin
Result := false;
WPos:=FindSubStrI('windres',p);
if WPos=nil then exit;
Result:=true;
MsgLine:=CreateMsgLine;
MsgLine.SubTool:=SubToolFPCWindRes;
MsgLine.Urgency:=mluWarning;
p := wPos + 7;
if CompStr('.exe', p) then
inc(p, 4);
MsgLine.Msg:='windres' + p;
AddMsgLine(MsgLine);
end;
function TIDEFPCParser.CheckForLinkerErrors(p: PChar): boolean;
const
patUndefinedSymbol: String = 'Undefined symbols for architecture';
patLD: String = '/usr/bin/ld';
function FindLeadingFilename(MinP, FileEndP: PChar; out FileStartP: PChar): boolean;
begin
FileStartP:=FileEndP;
while FileStartP>MinP do
begin
dec(FileStartP);
if FileStartP^=':' then
begin
if FileStartP[1]=' ' then begin
// e.g. "/usr/bin/ld: filename"
inc(FileStartP,2);
exit(FileStartP<FileEndP);
end else if (FileStartP>MinP) and (FileStartP[-1] in ['a'..'z','A'..'Z'])
and ((FileStartP-1=MinP)
or (FileStartP[-2] in [':',' ']))
and (FileStartP[1] in ['/','\']) then
begin
// e.g C:\filename
dec(FileStartP,2);
exit(true);
end else begin
inc(FileStartP);
exit(FileStartP<FileEndP);
end;
end;
end;
Result:=true;
end;
function FindFileLineNumberMsg(StartP: PChar; out FileStartP, FileEndP: PChar;
out LineNumber: Integer; out MsgStartP: PChar): boolean;
var
CurP: PChar;
begin
Result:=false;
FileStartP:=nil;
FileEndP:=nil;
LineNumber:=0;
MsgStartP:=nil;
CurP:=StartP;
while CurP^<>#0 do
begin
if (CurP^=':') and (CurP[1] in ['0'..'9']) then
begin
FileEndP:=CurP;
inc(CurP);
while (CurP^ in ['0'..'9']) do
begin
LineNumber:=LineNumber*10+ord(CurP^)-ord('0');
if LineNumber>1000000 then break;
inc(CurP);
end;
if (LineNumber>0) and (CurP^=':') and (CurP[1]=' ')
and FindLeadingFilename(p,FileEndP,FileStartP) then
begin
MsgStartP:=CurP+2;
exit(true);
end;
end else
inc(CurP);
end;
end;
var
MsgLine: TMessageLine;
Urgency: TMessageLineUrgency;
s: string;
FileStartP, FileEndP, MsgStartP: PChar;
LineNumber: Integer;
begin
if CompareMem(PChar(patUndefinedSymbol),p,length(patUndefinedSymbol)) then
begin
MsgLine:=CreateMsgLine;
MsgLine.MsgID:=0;
MsgLine.SubTool:=SubToolFPCLinker;
MsgLine.Urgency:=mluError;
MsgLine.Msg:='linker: '+p;
inherited AddMsgLine(MsgLine);
exit(true);
end;
// check for "filename:linenumber: error message"
if FindFileLineNumberMsg(p,FileStartP,FileEndP,LineNumber,MsgStartP) then
begin
MsgLine:=CreateMsgLine;
MsgLine.MsgID:=0;
MsgLine.SubTool:=SubToolFPCLinker;
MsgLine.Urgency:=mluError;
MsgLine.Filename:=GetString(FileStartP,FileEndP-FileStartP);
MsgLine.Line:=LineNumber;
MsgLine.Msg:='linker: '+MsgStartP;
inherited AddMsgLine(MsgLine);
exit(true);
end;
if CompareMem(PChar(patLD),p,length(patLD)) then
begin
MsgLine:=CreateMsgLine;
MsgLine.MsgID:=0;
MsgLine.SubTool:=SubToolFPCLinker;
s:=p;
Urgency:=mluHint;
if fMsgIsStdErr then
begin
Urgency:=mluWarning;
if (Pos('link.res',s)>0) and (Pos(' -T',s)>0) then
// /usr/bin/ld: warning: /path/link.res contains output sections; did you forget -T?
Urgency:=mluVerbose;
end;
MsgLine.Urgency:=Urgency;
MsgLine.Msg:='linker: '+s;
inherited AddMsgLine(MsgLine);
exit(true);
end;
Result:=false;
end;
function TIDEFPCParser.CheckForAssemblerErrors(p: PChar): boolean;
// example:
// <stdin>:227:9: error: unsupported directive '.stabs'
var
APos: PChar;
s: string;
MsgLine: TMessageLine;
begin
Result:=false;
APos:=FindSubStrI('error: unsupported directive',p);
if APos=nil then exit;
Result:=true;
MsgLine:=CreateMsgLine;
MsgLine.SubTool:=SubToolFPCWindRes;
MsgLine.Urgency:=mluError;
s:=APos;
if Pos('.stabs',s)>0 then
s+='. Hint: Use another type of debug info.';
MsgLine.Msg:='assembler: '+s;
AddMsgLine(MsgLine);
end;
function TIDEFPCParser.CheckForUnspecificStdErr(p: PChar): boolean;
var
MsgLine: TMessageLine;
begin
if not fMsgIsStdErr then exit(false);
Result:=true;
MsgLine:=CreateMsgLine;
MsgLine.SubTool:=SubToolFPC;
if FindSubStrI('warning',p)<>nil then
MsgLine.Urgency:=mluWarning
else
MsgLine.Urgency:=mluError;
MsgLine.Msg:=p;
AddMsgLine(MsgLine);
end;
function TIDEFPCParser.CheckForInfos(p: PChar): boolean;
function ReadFPCLogo(PatternItem: PPatternToMsgID;
out FPCVersionAsInt: LongWord): boolean;
var
Line: string;
Ranges: TFPCMsgRanges;
aRange: PFPCMsgRange;
i: SizeInt;
aFPCFullVersion: String;
FPCVersion: integer;
FPCRelease: integer;
FPCPatch: integer;
begin
Result:=false;
FPCVersionAsInt:=0;
i:=Pos('$FPCFULLVERSION',PatternItem^.Pattern);
if i<1 then exit;
Line:=p;
Ranges:=nil;
try
ExtractFPCMsgParameters(PatternItem^.Pattern,Line,Ranges);
if Ranges.Count>0 then begin
// first is $FPCFULLVERSION
aRange:=@Ranges.Ranges[0];
aFPCFullVersion:=copy(Line,aRange^.StartPos+1,aRange^.EndPos-aRange^.StartPos);
SplitFPCVersion(aFPCFullVersion,FPCVersion,FPCRelease,FPCPatch);
FPCVersionAsInt:=FPCVersion*10000+FPCRelease*100+FPCPatch;
Result:=FPCVersionAsInt>0;
end;
// second is $FPCDATE
// third is $FPCCPU
finally
Ranges.Free;
end;
end;
var
MsgItem: TFPCMsgItem;
MsgLine: TMessageLine;
MsgType: TMessageLineUrgency;
PatternItem: PPatternToMsgID;
aFPCVersion: LongWord;
begin
Result:=false;
PatternItem:=fLineToMsgID.LineToPattern(p);
if PatternItem=nil then exit;
fMsgID:=PatternItem^.MsgID;
if (fMsgID=FPCMsgIDLogo) and (DirectoryStack<>nil) then begin
// a new call of the compiler (e.g. when compiling via make)
// => clear stack
FreeAndNil(DirectoryStack);
end;
MsgItem:=MsgFile.GetMsg(fMsgID);
if MsgItem=nil then exit;
Result:=true;
MsgType:=FPCMsgToMsgUrgency(MsgItem);
if MsgType=mluNone then
MsgType:=mluVerbose;
MsgLine:=CreateMsgLine;
MsgLine.SubTool:=DefaultSubTool;
MsgLine.Urgency:=MsgType;
if (fMsgID=FPCMsgIDLogo) and ReadFPCLogo(PatternItem,aFPCVersion) then begin
if aFPCVersion<>PC_FullVersion then begin
// unexpected FPC version => always show
MsgLine.Urgency:=mluImportant;
PC_FullVersion:=aFPCVersion;
end;
end;
AddMsgLine(MsgLine);
end;
function TIDEFPCParser.CreateMsgLine: TMessageLine;
begin
Result:=inherited CreateMsgLine(fOutputIndex);
Result.MsgID:=fMsgID;
if fMsgIsStdErr then
Result.Flags:=Result.Flags+[mlfStdErr];
end;
procedure TIDEFPCParser.AddLinkingMessages;
{ Add messages for all output between "Linking ..." and the
current line "Error while linking"
For example:
Linking /home/user/project1
/usr/bin/ld: warning: /home/user/link.res contains output sections; did you forget -T?
/usr/bin/ld: cannot find -la52
project1.lpr(20,1) Error: Error while linking
Examples for linking errors:
linkerror.o(.text$_main+0x9):linkerror.pas: undefined reference to `NonExistingFunction'
/path/lib/x86_64-linux/blaunit.o: In function `FORMCREATE':
/path//blaunit.pas:45: undefined reference to `BLAUNIT_BLABLA'
Closing script ppas.sh
Mac OS X linker example:
ld: framework not found Cocoas
Note: this comes in stderr, so it might be some lines after corresponding stdout
Multiline Mac OS X linker example:
Undefined symbols:
"_exterfunc", referenced from:
_PASCALMAIN in testld.o
"_exterfunc2", referenced from:
_PASCALMAIN in testld.o
ld: symbol(s) not found
Linking project1
Undefined symbols for architecture x86_64:
"_GetCurrentEventButtonState", referenced from:
_COCOAINT_TCOCOAWIDGETSET_$__GETKEYSTATE$LONGINT$$SMALLINT in cocoaint.o
ld: symbol(s) not found for architecture x86_64
An error occurred while linking
Linking IDE:
(9015) Linking ../lazarus
/usr/bin/ld: cannot find -lGL
make[2]: *** [lazarus] Error 1
make[1]: *** [ide] Error 2
make: *** [ide] Error 2
/home/mattias/pascal/wichtig/lazarus/ide/lazarus.pp(161,1) Error: (9013) Error while linking
}
var
i: Integer;
MsgLine: TMessageLine;
begin
// change all low urgency messages in front of the last message to Important
i:=Tool.WorkerMessages.Count-1;
while i>=0 do begin
MsgLine:=Tool.WorkerMessages[i];
//debugln(['TIDEFPCParser.AddLinkingMessages ',i,' ',dbgs(MsgLine.Urgency),' ',MsgLine.Msg]);
if MsgLine.Urgency<mluHint then
MsgLine.Urgency:=mluImportant
else
break;
dec(i);
end;
// add all skipped lines in front of the linking error
i:=Tool.WorkerMessages.Count-1;
if i<0 then exit;
MsgLine:=Tool.WorkerMessages[i];
//debugln(['TIDEFPCParser.AddLinkingMessages MsgLine.OutputIndex=',MsgLine.OutputIndex,' fOutputIndex=',fOutputIndex]);
for i:=MsgLine.OutputIndex+1 to fOutputIndex-1 do begin
MsgLine:=inherited CreateMsgLine(i);
MsgLine.MsgID:=0;
MsgLine.SubTool:=SubToolFPCLinker;
if MsgLine.Msg<>'' then
MsgLine.Urgency:=mluImportant
else
MsgLine.Urgency:=mluVerbose2;
inherited AddMsgLine(MsgLine);
end;
end;
procedure TIDEFPCParser.AddResourceMessages;
{ Add messages for all output between "Calling resource compiler " and the
current line "Error while compiling resources"
For example:
Calling resource compiler "/usr/bin/fpcres" with "-o /home/user/project1.or -a x86_64 -of elf -v "@/home/user/project1.reslst"" as command line
Debug: parsing command line parameters
...
Error: Error while compiling resources
}
var
i: Integer;
MsgLine: TMessageLine;
begin
// find message "Calling resource compiler ..."
i:=Tool.WorkerMessages.Count-1;
while (i>=0) and (Tool.WorkerMessages[i].MsgID<>FPCMsgIDCallingResourceCompiler) do
dec(i);
if i<0 then exit;
MsgLine:=Tool.WorkerMessages[i];
for i:=MsgLine.OutputIndex+1 to fOutputIndex-1 do begin
MsgLine:=inherited CreateMsgLine(i);
MsgLine.MsgID:=0;
MsgLine.SubTool:=SubToolFPCRes;
if MsgLine.Msg<>'' then
MsgLine.Urgency:=mluHint
else
MsgLine.Urgency:=mluVerbose2;
inherited AddMsgLine(MsgLine);
end;
end;
function TIDEFPCParser.NeedSource(aPhase: TExtToolParserSyncPhase;
SourceOk: boolean): boolean;
begin
if SourceOk then exit(false);
case aPhase of
etpspAfterReadLine: NeedSynchronize:=true;
etpspSynchronized: NeedAfterSync:=true;
end;
Result:=true;
end;
function TIDEFPCParser.IsMsgID(MsgLine: TMessageLine; MsgID: integer;
var Item: TFPCMsgItem): boolean;
begin
if MsgLine.MsgID=MsgID then exit(true);
Result:=false;
if MsgLine.MsgID<>0 then exit;
if MsgLine.SubTool<>DefaultSubTool then exit;
if Item=nil then begin
Item:=MsgFile.GetMsg(MsgID);
if Item=nil then
Item:=fMissingFPCMsgItem;
end;
if Item=fMissingFPCMsgItem then exit;
if Item.PatternFits(MsgLine.Msg)<0 then exit;
MsgLine.MsgID:=MsgID;
Result:=true;
end;
procedure TIDEFPCParser.ImproveMsgHiddenByIDEDirective(
aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine; SourceOK: Boolean);
// check for {%H-}
function IsH(p: PChar): boolean; inline;
begin
Result:=(p^='{') and (p[1]='%') and (p[2]='H') and (p[3]='-');
end;
var
p: PChar;
X: Integer;
Y: Integer;
HasDirective: Boolean;
AbsPos: Integer; // 0-based
OtherPos: Integer;
AtomEnd: integer;
begin
if MsgLine.Urgency>=mluError then exit;
if mlfHiddenByIDEDirectiveValid in MsgLine.Flags then exit;
if NeedSource(aPhase,SourceOK) then
exit;
X:=MsgLine.Column;
Y:=MsgLine.Line;
if (y<=fCurSource.LineCount) and (x-1<=fCurSource.GetLineLength(y-1))
then begin
HasDirective:=false;
AbsPos:=fCurSource.GetLineStart(y-1)+x-2; // 0-based
p:=PChar(fCurSource.Source)+AbsPos;
//debugln(['TFPCParser.ImproveMsgHiddenByIDEDirective ',MsgLine.Filename,' ',Y,',',X,' ',copy(fCurSource.GetLine(y-1),1,x-1),'|',copy(fCurSource.GetLine(y-1),x,100),' p=',p[0],p[1],p[2]]);
if IsH(p) then
// directive beginning at cursor
HasDirective:=true
else if (x>5) and IsH(p-5) then
// directive ending at cursor
HasDirective:=true
else begin
// different compiler versions report some message positions differently.
// They changed some message positions from start to end of token.
// => check other end of token
//debugln(['TIDEFPCParser.ImproveMsgHiddenByIDEDirective mlfLeftToken=',mlfLeftToken in MsgLine.Flags]);
if mlfLeftToken in MsgLine.Flags then begin
if IsIdentChar[p[-1]] then begin
OtherPos:=AbsPos+1;
ReadPriorPascalAtom(fCurSource.Source,OtherPos,AtomEnd);
if (OtherPos>5) and (AtomEnd=AbsPos+1)
and IsH(@fCurSource.Source[OtherPos-5]) then begin
// for example: {%H-}identifier|
HasDirective:=true;
end;
end;
end else begin
if IsIdentStartChar[p^] then begin
inc(p,GetIdentLen(p));
if IsH(p) then
// for example: |identifier{%H-}
HasDirective:=true;
end;
end;
end;
if HasDirective then begin
MsgLine.Flags:=MsgLine.Flags+[mlfHiddenByIDEDirective,
mlfHiddenByIDEDirectiveValid];
exit;
end;
end;
MsgLine.Flags:=MsgLine.Flags+[mlfHiddenByIDEDirectiveValid];
end;
procedure TIDEFPCParser.ImproveMsgSenderNotUsed(
aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine);
// FPCMsgIDParameterNotUsed = 5024; Parameter "$1" not used
begin
if aPhase<>etpspAfterReadLine then exit;
if (MsgLine.Urgency<=mluVerbose) then exit;
// check for Sender not used
if HideHintsSenderNotUsed
and (MsgLine.Msg='Parameter "Sender" not used') then begin
MsgLine.Urgency:=mluVerbose;
end;
end;
procedure TIDEFPCParser.ImproveMsgUnitTagged(aPhase: TExtToolParserSyncPhase;
MsgLine: TMessageLine);
// check for Unit experimental/deprecated message
// and change urgency to merely 'verbose'
begin
if aPhase<>etpspAfterReadLine then exit;
if (MsgLine.Urgency<=mluVerbose) then exit;
if not IsMsgID(MsgLine,FPCMsgIDUnitDeprecate,fMsgItemUnitIsDeprecate) then exit;
if not IsMsgID(MsgLine,FPCMsgIDUnitDeprecated,fMsgItemUnitIsDeprecated) then exit;
if not IsMsgID(MsgLine,FPCMsgIDUnitNotPortable,fMsgItemUnitIsNotPortable) then exit;
if not IsMsgID(MsgLine,FPCMsgIDUnitNotImplemented,fMsgItemUnitIsNotImplemented) then exit;
if not IsMsgID(MsgLine,FPCMsgIDUnitExperimental,fMsgItemUnitIsExperimental) then exit;
//debugln(['TIDEFPCParser.ImproveMsgUnitTagged ',aPhase=etpspSynchronized,' ',MsgLine.Msg]);
// unit tagged
if IndexInStringList(FilesToIgnoreUnitNotUsed,cstFilename,MsgLine.Filename)>=0 then
begin
MsgLine.Urgency:=mluVerbose;
end;
end;
procedure TIDEFPCParser.ImproveMsgUnitNotUsed(aPhase: TExtToolParserSyncPhase;
MsgLine: TMessageLine);
// check for Unit not used message in main sources
// and change urgency to merely 'verbose'
begin
if aPhase<>etpspAfterReadLine then exit;
if (MsgLine.Urgency<=mluVerbose) then exit;
if not IsMsgID(MsgLine,FPCMsgIDUnitNotUsed,fMsgItemUnitNotUsed) then exit;
//debugln(['TIDEFPCParser.ImproveMsgUnitNotUsed ',aPhase=etpspSynchronized,' ',MsgLine.Msg]);
// unit not used
if IndexInStringList(FilesToIgnoreUnitNotUsed,cstFilename,MsgLine.Filename)>=0 then
begin
MsgLine.Urgency:=mluVerbose;
end else if HideHintsUnitNotUsedInMainSource then begin
if FilenameExtIs(MsgLine.Filename, 'lpr', false) then
// a lpr does not use a unit => not important
MsgLine.Urgency:=mluVerbose
else if FilenameIsAbsolute(MsgLine.Filename)
and FileExists(ChangeFileExt(MsgLine.Filename, '.lpk'), aPhase=etpspSynchronized)
then begin
// a lpk does not use a unit => not important
MsgLine.Urgency:=mluVerbose;
end;
end;
end;
procedure TIDEFPCParser.ImproveMsgUnitNotFound(aPhase: TExtToolParserSyncPhase;
MsgLine: TMessageLine);
procedure FixSourcePos(CodeBuf: TCodeBuffer; MissingUnitname: string);
var
InPos: Integer;
NamePos: Integer;
Tool: TCodeTool;
Caret: TCodeXYPosition;
NewFilename: String;
begin
{$IFDEF VerboseFPCMsgUnitNotFound}
debugln(['TIDEFPCParser.ImproveMsgUnitNotFound File=',CodeBuf.Filename]);
{$ENDIF}
LazarusIDE.SaveSourceEditorChangesToCodeCache(nil);
if not CodeToolBoss.FindUnitInAllUsesSections(CodeBuf,MissingUnitname,NamePos,InPos)
then begin
DebugLn('TIDEFPCParser.ImproveMsgUnitNotFound FindUnitInAllUsesSections failed due to syntax errors or '+MissingUnitname+' is not used in '+CodeBuf.Filename);
exit;
end;
Tool:=CodeToolBoss.CurCodeTool;
if Tool=nil then exit;
if not Tool.CleanPosToCaret(NamePos,Caret) then exit;
if (Caret.X>0) and (Caret.Y>0) then begin
//DebugLn('QuickFixUnitNotFoundPosition Line=',dbgs(Line),' Col=',dbgs(Col));
NewFilename:=Caret.Code.Filename;
MsgLine.SetSourcePosition(NewFilename,Caret.Y,Caret.X);
end;
end;
procedure FindPPUFiles(MissingUnitname: string; PkgList: TFPList;
PPUFiles: TStringList // Strings:PPUFilename, Objects:TIDEPackage
);
var
i: Integer;
Pkg: TIDEPackage;
DirCache: TCTDirectoryCache;
PPUFilename: String;
UnitOutDir: String;
begin
if PkgList=nil then exit;
for i:=0 to PkgList.Count-1 do begin
Pkg:=TIDEPackage(PkgList[i]);
UnitOutDir:=Pkg.LazCompilerOptions.GetUnitOutputDirectory(false);
//debugln(['TQuickFixUnitNotFoundPosition.Execute ',Pkg.Name,' UnitOutDir=',UnitOutDir]);
if not FilenameIsAbsolute(UnitOutDir) then continue;
DirCache:=CodeToolBoss.DirectoryCachePool.GetCache(UnitOutDir,true,false);
PPUFilename:=DirCache.FindFile(MissingUnitname+'.ppu',ctsfcLoUpCase);
//debugln(['TQuickFixUnitNotFoundPosition.Execute ShortPPU=',PPUFilename]);
if PPUFilename='' then continue;
PPUFilename:=AppendPathDelim(DirCache.Directory)+PPUFilename;
PPUFiles.AddObject(PPUFilename,Pkg);
end;
end;
procedure FindPPUInInstalledPkgs(MissingUnitname: string;
PPUFiles: TStringList // Strings:PPUFilename, Objects:TIDEPackage
);
var
i: Integer;
Pkg: TIDEPackage;
PkgList: TFPList;
begin
// search ppu in installed packages
PkgList:=TFPList.Create;
try
for i:=0 to PackageEditingInterface.GetPackageCount-1 do begin
Pkg:=PackageEditingInterface.GetPackages(i);
if Pkg.AutoInstall=pitNope then continue;
PkgList.Add(Pkg);
end;
FindPPUFiles(MissingUnitname,PkgList,PPUFiles);
finally
PkgList.Free;
end;
end;
procedure FindPPUInModuleAndDeps(MissingUnitname: string; Module: TObject;
PPUFiles: TStringList // Strings:PPUFilename, Objects:TIDEPackage
);
var
PkgList: TFPList;
begin
PkgList:=nil;
try
PackageEditingInterface.GetRequiredPackages(Module,PkgList);
if (Module is TIDEPackage) then begin
if PkgList=nil then
PkgList:=TFPList.Create;
if PkgList.IndexOf(Module)<0 then
PkgList.Add(Module);
end;
FindPPUFiles(MissingUnitname,PkgList,PPUFiles);
finally
PkgList.Free;
end;
end;
procedure FindPackage(MissingUnitname: string; OnlyInstalled: boolean;
out Pkg: TIDEPackage; out PkgName: string; out PkgFile: TLazPackageFile);
var
i: Integer;
j: Integer;
aFile: TLazPackageFile;
CurPkg: TIDEPackage;
begin
PkgName:='';
PkgFile:=nil;
Pkg:=nil;
// search unit in packages
for i:=0 to PackageEditingInterface.GetPackageCount-1 do begin
CurPkg:=PackageEditingInterface.GetPackages(i);
if OnlyInstalled and (CurPkg.AutoInstall=pitNope) then
continue;
if CompareTextCT(CurPkg.Name,MissingUnitname)=0 then begin
PkgName:=CurPkg.Name;
Pkg:=CurPkg;
break;
end;
for j:=0 to CurPkg.FileCount-1 do begin
aFile:=CurPkg.Files[j];
if not (aFile.FileType in PkgFileRealUnitTypes) then
continue;
if CompareTextCT(ExtractFileNameOnly(aFile.Filename),MissingUnitname)<>0
then continue;
if (PkgFile=nil) or (aFile.InUses and not PkgFile.InUses) then
begin
// a better file was found
PkgFile:=aFile;
PkgName:=CurPkg.Name;
Pkg:=CurPkg;
end;
end;
end;
end;
var
MissingUnitName: string;
UsedByUnit: string;
Filename: String;
NewFilename: String;
CodeBuf: TCodeBuffer;
Owners: TFPList;
UsedByOwner: TObject;
UsedByPkg: TIDEPackage;
PPUFilename: String;
OnlyInstalled: Boolean;
s: String;
PPUFiles: TStringList; // Strings:PPUFilename, Objects:TIDEPackage
i: Integer;
DepOwner: TObject;
TheOwner: TObject;
MissingPkg: TIDEPackage;
MissingPkgName: String;
MissingPkgFile: TLazPackageFile;
FPCUnitFilename: String;
begin
if MsgLine.Urgency<mluError then exit;
if not IsMsgID(MsgLine,FPCMsgIDCantFindUnitUsedBy,fMsgItemCantFindUnitUsedBy)
then // Can't find unit $1 used by $2
exit;
case aPhase of
etpspAfterReadLine:
begin
NeedSynchronize:=true;
exit;
end;
etpspSynchronized: ;
etpspAfterSync: exit;
end;
// in main thread
if not GetFPCMsgValues(MsgLine,MissingUnitName,UsedByUnit) then
exit;
MsgLine.Attribute[FPCMsgAttrMissingUnit]:=MissingUnitName;
MsgLine.Attribute[FPCMsgAttrUsedByUnit]:=UsedByUnit;
{$IFDEF VerboseFPCMsgUnitNotFound}
debugln(['TIDEFPCParser.ImproveMsgUnitNotFound Missing="',MissingUnitname,'" used by "',UsedByUnit,'"']);
{$ENDIF}
CodeBuf:=nil;
Filename:=MsgLine.GetFullFilename;
if (CompareFilenames(ExtractFileName(Filename),'staticpackages.inc')=0)
and ((ExtractFilePath(Filename)='')
or (CompareFilenames(ExtractFilePath(Filename),AppendPathDelim(GetPrimaryConfigPath))=0))
then begin
// common case: when building the IDE a package unit is missing
// staticpackages.inc(1,1) Fatal: Can't find unit sqldblaz used by Lazarus
// change to lazarus.pp(1,1)
Filename:=AppendPathDelim(EnvironmentOptions.GetParsedLazarusDirectory)+'ide'+PathDelim+'lazarus.pp';
MsgLine.SetSourcePosition(Filename,1,1);
MsgLine.Msg:=Format(lisCanTFindAValidPpu, [MissingUnitname]);
end else if SysUtils.CompareText(ExtractFileNameOnly(Filename),UsedByUnit)<>0
then begin
// the message belongs to another unit
NewFilename:='';
if FilenameIsAbsolute(Filename) then
begin
// For example: /path/laz/main.pp(1,1) Fatal: Can't find unit lazreport used by lazarus
// => search source 'lazarus' in directory
NewFilename:=CodeToolBoss.DirectoryCachePool.FindUnitInDirectory(
ExtractFilePath(Filename),UsedByUnit,true);
end;
if NewFilename='' then begin
TheOwner:=nil;
if Tool.Data is TIDEExternalToolData then begin
TheOwner:=ExternalToolList.GetIDEObject(TIDEExternalToolData(Tool.Data));
end else if Tool.Data=nil then begin
{$IFDEF VerboseFPCMsgUnitNotFound}
debugln(['TIDEFPCParser.ImproveMsgUnitNotFound Tool.Data=nil, ProcDir=',Tool.Process.CurrentDirectory]);
{$ENDIF}
end;
NewFilename:=LazarusIDE.FindUnitFile(UsedByUnit,TheOwner);
if NewFilename='' then begin
{$IFDEF VerboseFPCMsgUnitNotFound}
debugln(['TIDEFPCParser.ImproveMsgUnitNotFound unit not found: ',UsedByUnit]);
{$ENDIF}
end;
end;
if NewFilename<>'' then
Filename:=NewFilename;
end;
if FilenameIsAbsolute(Filename) or (mlfTestBuildFile in MsgLine.Flags) then begin
CodeBuf:=CodeToolBoss.LoadFile(Filename,false,false);
if CodeBuf=nil then begin
{$IFDEF VerboseFPCMsgUnitNotFound}
debugln(['TIDEFPCParser.ImproveMsgUnitNotFound unable to load unit: ',Filename]);
{$ENDIF}
end;
end else begin
{$IFDEF VerboseFPCMsgUnitNotFound}
debugln(['TIDEFPCParser.ImproveMsgUnitNotFound unable to locate UsedByUnit: ',UsedByUnit,' Filename="',MsgLine.Filename,'" Attr[',FPCMsgAttrWorkerDirectory,']=',MsgLine.Attribute[FPCMsgAttrWorkerDirectory],' Tool.WorkerDirectory=',Tool.WorkerDirectory]);
{$ENDIF}
end;
// fix line and column
Owners:=nil;
PPUFiles:=TStringList.Create;
try
UsedByOwner:=nil;
UsedByPkg:=nil;
if CodeBuf<>nil then begin
FixSourcePos(CodeBuf,MissingUnitname);
Owners:=PackageEditingInterface.GetOwnersOfUnit(CodeBuf.Filename);
if (Owners<>nil) and (Owners.Count>0) then begin
UsedByOwner:=TObject(Owners[0]);
if UsedByOwner is TIDEPackage then
UsedByPkg:=TIDEPackage(UsedByOwner);
end;
end;
// if the ppu exists then improve the message
if (CodeBuf<>nil) and FilenameIsAbsolute(CodeBuf.Filename) then begin
{$IFDEF VerboseFPCMsgUnitNotFound}
debugln(['TIDEFPCParser.ImproveMsgUnitNotFound Filename=',CodeBuf.Filename]);
{$ENDIF}
PPUFilename:=CodeToolBoss.DirectoryCachePool.FindCompiledUnitInCompletePath(
ExtractFilePath(CodeBuf.Filename),MissingUnitname);
if (PPUFilename<>'') then begin
FPCUnitFilename:=CodeToolBoss.DirectoryCachePool.FindUnitInUnitSet(
ExtractFilePath(CodeBuf.Filename),MissingUnitName);
end else
FPCUnitFilename:='';
{$IFDEF VerboseFPCMsgUnitNotFound}
debugln(['TIDEFPCParser.ImproveMsgUnitNotFound PPUFilename=',PPUFilename,' IsFileInIDESrcDir=',IsFileInIDESrcDir(CodeBuf.Filename)]);
{$ENDIF}
OnlyInstalled:=IsFileInIDESrcDir(CodeBuf.Filename);
if OnlyInstalled then begin
FindPPUInInstalledPkgs(MissingUnitname,PPUFiles);
end else if UsedByOwner<>nil then
FindPPUInModuleAndDeps(MissingUnitName,UsedByOwner,PPUFiles);
{$IFDEF VerboseFPCMsgUnitNotFound}
debugln(['TIDEFPCParser.ImproveMsgUnitNotFound PPUFiles in PPU path=',PPUFiles.Count]);
{$ENDIF}
FindPackage(MissingUnitname,OnlyInstalled,MissingPkg,MissingPkgName,MissingPkgFile);
{$IFDEF VerboseFPCMsgUnitNotFound}
debugln(['TIDEFPCParser.ImproveMsgUnitNotFound MissingUnitPkg=',MissingPkgName]);
{$ENDIF}
s:=Format(lisCannotFind, [MissingUnitname]);
if UsedByUnit<>'' then
s+=Format(lisUsedBy, [UsedByUnit]);
if PPUFiles.Count>0 then begin
// there is a ppu file in a package output directory, but the compiler
// didn't like it => change message
if PPUFilename='' then
PPUFilename:=PPUFiles[0];
s+=Format(lisIncompatiblePpu, [PPUFilename]);
if PPUFiles.Count=1 then
s+=Format(lisPackage3, [TIDEPackage(PPUFiles.Objects[0]).Name])
else begin
s+=lisMultiplePack;
for i:=0 to PPUFiles.Count-1 do begin
if i>0 then
s+=', ';
s+=TIDEPackage(PPUFiles.Objects[i]).Name;
end;
end;
end else if PPUFilename<>'' then begin
if CompareFilenames(PPUFilename,FPCUnitFilename)=0 then begin
// there is ppu in the FPC units, but the compiler does not like it
// => a) using a wrong compiler version (wrong fpc.cfg)
// b) user units in fpc.cfg
// c) fpc units not compiled with -Ur
// d) wrong target platform
s+=', ppu='+PPUFilename+', check your fpc.cfg';
end else begin
// there is a ppu file in the source path
if (MissingPkg<>nil) and (MissingPkg.LazCompilerOptions.UnitOutputDirectory='')
then
s+='. '+lisPackageNeedsAnOutputDirectory
else
s+='. '+lisMakeSureAllPpuFilesOfAPackageAreInItsOutputDirecto;
s+=' '+Format(lisPpuInWrongDirectory, [PPUFilename]);
if MissingPkgName<>'' then
s+=' '+Format(lisCleanUpPackage, [MissingPkgName]);
s+='.';
end;
end
else if (UsedByPkg<>nil) and (CompareTextCT(UsedByPkg.Name,MissingPkgName)=0)
then begin
// two units of a package cannot find each other
s+=Format(lisCheckSearchPathPackageTryACleanRebuildCheckImpleme, [
UsedByPkg.Name]);
s+='.';
end else if (MissingPkgName<>'')
and (OnlyInstalled
or ((UsedByOwner<>nil)
and PackageEditingInterface.IsOwnerDependingOnPkg(UsedByOwner,MissingPkgName,DepOwner)))
then begin
// ppu file of an used package is missing
if (MissingPkgFile<>nil) and (not MissingPkgFile.InUses) then
s+=Format(lisEnableFlagUseUnitOfUnitInPackage, [MissingUnitName, MissingPkgName])
else
s+=Format(lisCheckIfPackageCreatesPpuCheckNothingDeletesThisFil, [
MissingPkgName, MissingUnitName]);
s+='.';
end else begin
if MissingPkgName<>'' then
s+=Format(lisCheckIfPackageIsInTheDependencies, [MissingPkgName]);
if UsedByOwner is TLazProject then
s+=lisOfTheProjectInspector
else if UsedByPkg<>nil then
s+=Format(lisOfPackage, [UsedByPkg.Name]);
s+='.';
end;
MsgLine.Msg:=s;
{$IFDEF VerboseFPCMsgUnitNotFound}
debugln(['TIDEFPCParser.ImproveMsgUnitNotFound Msg.Msg="',MsgLine.Msg,'"']);
{$ENDIF}
end;
finally
PPUFiles.Free;
Owners.Free;
end;
end;
procedure TIDEFPCParser.ImproveMsgLinkerUndefinedReference(
aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine);
function CheckForLinuxLDFileAndLineNumber: boolean;
{ For example:
/path/lib/x86_64-linux/blaunit.o: In function `FORMCREATE':
/path//blaunit.pas:45: undefined reference to `BLAUNIT_BLABLA'
}
var
p: PChar;
Msg: String;
aFilename: String;
LineNumber: Integer;
i: SizeInt;
begin
Result:=false;
if aPhase<>etpspAfterReadLine then exit;
if MsgLine.HasSourcePosition then exit;
Msg:=MsgLine.Msg;
p:=PChar(Msg);
// check for "filename:decimals: message"
// or unit1.o(.text+0x3a):unit1.pas:48: undefined reference to `DoesNotExist'
// read filename
repeat
if p^=#0 then exit;
inc(p);
until (p^=':') and (p[1] in ['0'..'9']);
aFilename:=LeftStr(Msg,p-PChar(Msg));
// check for something):filename
i:=Pos('):',aFilename);
if i>0 then
Delete(aFilename,1,i+1);
aFilename:=TrimFilename(aFilename);
// read line number
inc(p);
LineNumber:=0;
while p^ in ['0'..'9'] do begin
LineNumber:=LineNumber*10+ord(p^)-ord('0');
if LineNumber>9999999 then exit;
inc(p);
end;
if p^<>':' then exit;
inc(p);
while p^ in [' '] do inc(p);
Result:=true;
MsgLine.Msg:=copy(Msg,p-PChar(Msg)+1,length(Msg));
MsgLine.SetSourcePosition(aFilename,LineNumber,1);
MsgLine.Urgency:=mluError;
end;
function CheckForDarwinLDReferencedFrom: boolean;
{ For example:
"_UNIT1_GIBTESNICHT", referenced from:
}
var
MangledName: string;
aComplete: boolean;
aErrorMsg: string;
NewCode: TCodeBuffer;
NewX: integer;
NewY: integer;
NewTopLine: integer;
begin
Result:=false;
if MsgLine.HasSourcePosition then exit;
// check for ' "_FPC-Mangled-Identifier", referenced from:
if not GetFPCMsgValueOne(MsgLine.Msg,' "_$1", referenced from:',MangledName)
then exit;
Result:=true;
case aPhase of
etpspAfterReadLine:
begin
NeedSynchronize:=true;
exit;
end;
etpspAfterSync: exit;
end;
// in main thread
CodeToolBoss.FindFPCMangledIdentifier(MangledName,aComplete,aErrorMsg,
nil,NewCode,NewX,NewY,NewTopLine);
if NewCode=nil then exit;
Result:=true;
MsgLine.SetSourcePosition(NewCode.Filename,NewY,NewX);
MsgLine.Urgency:=mluError;
end;
function CheckForDarwinLDMangledInO: boolean;
{ For example:
_UNIT1_TFORM1_$__FORMCREATE$TOBJECT in unit1.o
}
var
MangledName: string;
aUnitName: string;
aComplete: boolean;
aErrorMsg: string;
NewCode: TCodeBuffer;
NewX: integer;
NewY: integer;
NewTopLine: integer;
begin
Result:=false;
if MsgLine.HasSourcePosition then exit;
if not etFPCMsgParser.GetFPCMsgValuesTwo(MsgLine.Msg,' _$1 in $2.o',
MangledName,aUnitName)
then exit;
Result:=true;
case aPhase of
etpspAfterReadLine:
begin
NeedSynchronize:=true;
exit;
end;
etpspAfterSync: exit;
end;
// in main thread
CodeToolBoss.FindFPCMangledIdentifier(MangledName,aComplete,aErrorMsg,
nil,NewCode,NewX,NewY,NewTopLine);
if NewCode=nil then exit;
Result:=true;
MsgLine.SetSourcePosition(NewCode.Filename,NewY,NewX);
MsgLine.Urgency:=mluError;
end;
begin
if MsgLine.SubTool<>SubToolFPCLinker then exit;
if CheckForLinuxLDFileAndLineNumber then exit;
if CheckForDarwinLDReferencedFrom then exit;
if CheckForDarwinLDMangledInO then exit;
end;
procedure TIDEFPCParser.ImproveMsgIdentifierPosition(
aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine; SourceOK: boolean);
{ FPC sometimes reports the token after the identifier
=> fix the position
Examples:
" i :="
unit1.pas(42,5) Error: (5000) Identifier not found "i"
"procedure TMyClass.DoIt ;"
test.pas(7,26) Error: (3047) method identifier expected
}
const
AttrPosChecked = 'PosChecked';
var
LineRange: TLineRange;
Line, Col: Integer;
p, AtomEnd: integer;
Src: String;
Identifier: String;
NewP: Integer;
begin
Col:=MsgLine.Column;
Line:=MsgLine.Line;
if (Col<1) or (Line<1) then
exit;
if (Line=1) and (Col=1) then exit;
if MsgLine.SubTool<>SubToolFPC then exit;
if MsgLine.MsgID=0 then begin
// maybe not compiled with -vq: search patterns of common messages
if (not IsMsgID(MsgLine,FPCMsgIDIdentifierNotFound,fMsgItemIdentifierNotFound))
and (not IsMsgID(MsgLine,FPCMsgIDMethodIdentifierExpected,fMsgItemMethodIdentifierExpected))
then
exit;
end;
if MsgLine.MsgID=FPCMsgIDMethodIdentifierExpected then
Identifier:=''
else begin
Identifier:=GetFPCMsgValue1(MsgLine);
if not IsValidIdent(Identifier) then exit;
end;
if MsgLine.Attribute[AttrPosChecked]<>'' then exit;
if NeedSource(aPhase,SourceOK) then
exit;
MsgLine.Attribute[AttrPosChecked]:=ClassName;
//DebuglnThreadLog(['Old Line=',Line,' ',MsgLine.Column]);
if Line>=fCurSource.LineCount then exit;
fCurSource.GetLineRange(Line-1,LineRange);
//DebuglnThreadLog(['Old Range=',LineRange.StartPos,'-',LineRange.EndPos,' Str="',copy(fCurSource.Source,LineRange.StartPos,LineRange.EndPos-LineRange.StartPos),'"']);
Col:=Min(Col,LineRange.EndPos-LineRange.StartPos+1);
p:=LineRange.StartPos+Col-1;
Src:=fCurSource.Source;
if Identifier<>'' then begin
// message is about a specific identifier
if CompareIdentifiers(PChar(Identifier),@Src[p])=0 then begin
// already pointing at the start of the identifier
exit;
end;
end else begin
// message is about any one identifier
if IsIdentStartChar[Src[p]] then begin
// already pointing at an identifier
exit;
end;
end;
// go to prior token
//DebuglnThreadLog(['New Line=',Line,' Col=',Col,' p=',p]);
NewP:=p;
ReadPriorPascalAtom(Src,NewP,AtomEnd,false);
if NewP<1 then exit;
if Identifier<>'' then begin
// message is about a specific identifier
if CompareIdentifiers(PChar(Identifier),@Src[NewP])<>0 then begin
// the prior token is not the identifier neither
// => don't know
exit;
end;
end else begin
// message is about any one identifier
if not IsIdentStartChar[Src[NewP]] then begin
// the prior token is not an identifier neither
// => don't know
exit;
end;
end;
fCurSource.AbsoluteToLineCol(NewP,Line,Col);
//DebuglnThreadLog(['New Line=',Line,' Col=',Col,' p=',NewP]);
if (Line<1) or (Col<1) then exit;
if MsgLine.Urgency>=mluError then begin
// position errors at start of wrong identifier, nicer for identifier completion
MsgLine.SetSourcePosition(MsgLine.Filename,Line,Col);
MsgLine.Flags:=MsgLine.Flags-[mlfLeftToken];
end else begin
// position hints at end of identifier, nicer for {%H-}
MsgLine.SetSourcePosition(MsgLine.Filename,Line,Col+length(Identifier));
MsgLine.Flags:=MsgLine.Flags+[mlfLeftToken];
end;
end;
function TIDEFPCParser.FindSrcViaPPU(aPhase: TExtToolParserSyncPhase;
MsgLine: TMessageLine; const PPUFilename: string): boolean;
{ in main thread
for example:
/usr/lib/fpc/3.1.1/units/x86_64-linux/rtl/sysutils.ppu:filutil.inc(481,10) Error: (5088) ...
PPUFilename=/usr/lib/fpc/3.1.1/units/x86_64-linux/rtl/sysutils.ppu
Filename=filutil.inc
}
var
i: Integer;
PrevMsgLine: TMessageLine;
aFilename: String;
MsgWorkerDir: String;
UnitSrcFilename: String;
IncPath: String;
Dir: String;
ShortFilename: String;
IncFilename: String;
AnUnitName: String;
InFilename: String;
begin
case aPhase of
etpspAfterReadLine: exit(false);
etpspSynchronized: ;
etpspAfterSync: exit(true);
end;
Result:=true;
// in main thread
i:=MsgLine.Index;
aFilename:=MsgLine.Filename;
//debugln(['TIDEFPCParser.FindSrcViaPPU i=',i,' PPUFilename="',PPUFilename,'" Filename="',aFilename,'"']);
if (i>0) then begin
PrevMsgLine:=Tool.WorkerMessages[i-1];
if (PrevMsgLine.SubTool=DefaultSubTool)
and (CompareFilenames(PPUFilename,PrevMsgLine.Attribute['PPU'])=0)
and FilenameIsAbsolute(PrevMsgLine.Filename)
and (CompareFilenames(ExtractFilename(PrevMsgLine.Filename),ExtractFilename(aFilename))=0)
then begin
// same file as previous message => use it
MsgLine.Filename:=PrevMsgLine.Filename;
exit;
end;
end;
if not FilenameIsAbsolute(PPUFilename) then
begin
exit;
end;
ShortFilename:=ExtractFilename(aFilename);
MsgWorkerDir:=MsgLine.Attribute[FPCMsgAttrWorkerDirectory];
AnUnitName:=ExtractFilenameOnly(PPUFilename);
InFilename:='';
UnitSrcFilename:=CodeToolBoss.DirectoryCachePool.FindUnitSourceInCompletePath(
MsgWorkerDir,AnUnitName,InFilename);
//debugln(['TIDEFPCParser.FindSrcViaPPU MsgWorkerDir="',MsgWorkerDir,'" UnitSrcFilename="',UnitSrcFilename,'"']);
if UnitSrcFilename<>'' then begin
if CompareFilenames(ExtractFilename(UnitSrcFilename),ShortFilename)=0 then
begin
MsgLine.Filename:=UnitSrcFilename;
exit;
end;
Dir:=ChompPathDelim(TrimFilename(ExtractFilePath(UnitSrcFilename)));
IncPath:=CodeToolBoss.GetIncludePathForDirectory(Dir);
IncFilename:=SearchFileInSearchPath(ShortFilename,Dir,IncPath);
//debugln(['TIDEFPCParser.FindSrcViaPPU Dir="',Dir,'" IncPath="',IncPath,'" ShortFilename="',ShortFilename,'" IncFilename="',IncFilename,'"']);
if IncFilename<>'' then begin
MsgLine.Filename:=IncFilename;
exit;
end;
end;
end;
procedure TIDEFPCParser.Translate(p: PChar; MsgItem, TranslatedItem: TFPCMsgItem;
out TranslatedMsg: String; var MsgType: TMessageLineUrgency);
begin
TranslatedMsg:='';
if (MsgType=mluNone) or UseTranslationUrgency then begin
if (TranslatedItem<>nil) then
MsgType:=FPCMsgToMsgUrgency(TranslatedItem);
if (MsgType=mluNone) and (MsgItem<>nil) then
MsgType:=FPCMsgToMsgUrgency(MsgItem);
end;
if TranslatedItem<>nil then begin
if System.Pos('$',TranslatedItem.Pattern)<1 then begin
TranslatedMsg:=TranslatedItem.Pattern;
UTF8FixBroken(TranslatedMsg);
end
else if MsgItem<>nil then
TranslatedMsg:=TranslateFPCMsg(p,MsgItem.Pattern,TranslatedItem.Pattern);
//debugln(['TFPCParser.Translate Translation="',TranslatedMsg,'"']);
end;
end;
function TIDEFPCParser.ReverseInstantFPCCacheDir(var aFilename: string;
aSynchronized: boolean): boolean;
var
Reversed: String;
begin
Result:=false;
if (InstantFPCCache='') then exit;
if (CompareFilenames(ExtractFilePath(aFilename),InstantFPCCache)=0) then begin
Reversed:=AppendPathDelim(Tool.WorkerDirectory)+ExtractFilename(aFilename);
if FileExists(Reversed,aSynchronized) then begin
aFilename:=Reversed;
Result:=true;
end;
end;
end;
function TIDEFPCParser.ReverseTestBuildDir(MsgLine: TMessageLine;
var aFilename: string): boolean;
var
Reversed: String;
l: Integer;
begin
Result:=false;
if not Tool.CurrentDirectoryIsTestDir then exit;
l:=length(TestBuildDir); // Note: TestBuildDir includes trailing PathDelim
if (length(aFilename)>l) and (aFilename[l]=PathDelim)
and (CompareFilenames(LeftStr(aFilename,l),TestBuildDir)=0) then begin
Reversed:=copy(aFilename,l+1,length(aFilename));
if VirtualProjectFiles.Contains(Reversed) then begin
MsgLine.Flags:=MsgLine.Flags+[mlfTestBuildFile];
MsgLine.Attribute[MsgAttrDiskFilename]:=aFilename;
aFilename:=Reversed;
Result:=true;
end
end;
end;
constructor TIDEFPCParser.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
fMissingFPCMsgItem:=TFPCMsgItem(Pointer(1));
fLineToMsgID:=TPatternToMsgIDs.Create;
fFileExists:=TFilenameToPointerTree.Create(false);
FFilesToIgnoreUnitNotUsed:=TStringList.Create;
HideHintsSenderNotUsed:=true;
HideHintsUnitNotUsedInMainSource:=true;
UseTranslationUrgency:=true;
PC_FullVersion:=GetCompiledFPCVersion;
end;
function TIDEFPCParser.FileExists(const Filename: string; aSynchronized: boolean
): boolean;
var
p: Pointer;
begin
// check internal cache
p:=fFileExists[Filename];
if p=Pointer(Self) then
Result:=true
else if p=Pointer(fFileExists) then
Result:=false
else begin
// check disk
if aSynchronized then
Result:=FileExistsCached(Filename)
else
Result:=FileExistsUTF8(Filename);
// save result
if Result then
fFileExists[Filename]:=Pointer(Self)
else
fFileExists[Filename]:=Pointer(fFileExists);
end;
end;
procedure TIDEFPCParser.FetchIncludePath(aPhase: TExtToolParserSyncPhase;
MsgWorkerDir: String);
begin
if MsgWorkerDir='' then
MsgWorkerDir:=Tool.WorkerDirectory;
if fIncludePathValidForWorkerDir<>MsgWorkerDir then begin
// fetch include path from IDE
case aPhase of
etpspAfterReadLine:
NeedSynchronize:=true;
etpspSynchronized:
begin
fIncludePathValidForWorkerDir:=MsgWorkerDir;
fIncludePath:=CodeToolBoss.GetIncludePathForDirectory(
ChompPathDelim(MsgWorkerDir));
{$IFDEF VerboseFPCMsgUnitNotFound}
debugln(['TIDEFPCParser.FetchIncludePath ',fIncludePath]);
{$ENDIF}
NeedAfterSync:=true;
end;
end;
end;
end;
procedure TIDEFPCParser.FetchUnitPath(aPhase: TExtToolParserSyncPhase;
MsgWorkerDir: String);
begin
if MsgWorkerDir='' then
MsgWorkerDir:=Tool.WorkerDirectory;
if fUnitPathValidForWorkerDir<>MsgWorkerDir then begin
// fetch unit path from IDE
case aPhase of
etpspAfterReadLine:
NeedSynchronize:=true;
etpspSynchronized:
begin
fUnitPathValidForWorkerDir:=MsgWorkerDir;
fUnitPath:=CodeToolBoss.GetUnitPathForDirectory(
ChompPathDelim(MsgWorkerDir));
NeedAfterSync:=true;
end;
end;
end;
end;
function TIDEFPCParser.CheckForMsgId(p: PChar): boolean;
var
MsgItem: TFPCMsgItem;
TranslatedItem: TFPCMsgItem;
MsgLine: TMessageLine;
TranslatedMsg: String;
MsgUrgency: TMessageLineUrgency;
Msg: string;
begin
Result:=false;
if (fMsgID<1) or (MsgFile=nil) then exit;
MsgItem:=MsgFile.GetMsg(fMsgID);
if MsgItem=nil then exit;
Result:=true;
TranslatedItem:=nil;
if (TranslationFile<>nil) then
TranslatedItem:=TranslationFile.GetMsg(fMsgID);
MsgUrgency:=mluNone;
Translate(p,MsgItem,TranslatedItem,TranslatedMsg,MsgUrgency);
Msg:=p;
case fMsgID of
FPCMsgIDThereWereErrorsCompiling: // There were $1 errors compiling module, stopping
MsgUrgency:=mluVerbose;
FPCMsgIDLinesCompiled: // n lines compiled, m sec
if EnvironmentOptions.MsgViewShowFPCMsgLinesCompiled then
MsgUrgency:=mluImportant;
end;
MsgLine:=CreateMsgLine;
MsgLine.SubTool:=DefaultSubTool;
MsgLine.Urgency:=MsgUrgency;
MsgLine.Msg:=Msg;
MsgLine.TranslatedMsg:=TranslatedMsg;
AddMsgLine(MsgLine);
end;
function TIDEFPCParser.CheckFollowUpMessage(p: PChar): boolean;
var
i: Integer;
LastMsgLine, MsgLine: TMessageLine;
begin
Result:=false;
if (p^=' ') then begin
i:=Tool.WorkerMessages.Count-1;
if i<0 then exit;
LastMsgLine:=Tool.WorkerMessages[i];
if LastMsgLine.SubTool=SubToolFPCLinker then begin
// a follow up line of the linker output
Result:=true;
MsgLine:=CreateMsgLine;
MsgLine.MsgID:=0;
MsgLine.SubTool:=SubToolFPCLinker;
MsgLine.Urgency:=LastMsgLine.Urgency;
MsgLine.Msg:='linker: '+p;
inherited AddMsgLine(MsgLine);
end;
end;
end;
function TIDEFPCParser.CheckForFileLineColMessage(p: PChar): boolean;
{ filename(line,column) Hint: message
filename(line,column) Hint: (msgid) message
filename(line) Hint: (msgid) message
B:\file(3)name(line,column) Hint: (msgid) message
/usr/lib/fpc/3.1.1/units/x86_64-linux/rtl/sysutils.ppu:filutil.inc(481,10) Error: (5088) ...
}
var
FileStartPos: PChar;
FileEndPos: PChar;
LineStartPos: PChar;
ColStartPos: PChar;
MsgType: TMessageLineUrgency;
MsgLine: TMessageLine;
p2: PChar;
i: Integer;
TranslatedItem: TFPCMsgItem;
MsgItem: TFPCMsgItem;
TranslatedMsg: String;
aFilename: String;
Column: Integer;
PPUFileStartPos: PChar;
PPUFileEndPos: PChar;
begin
Result:=false;
FileStartPos:=p;
FileEndPos:=nil;
PPUFileStartPos:=nil;
PPUFileEndPos:=nil;
// search colon and last ( in front of colon
while true do begin
case p^ of
#0: exit;
'(': FileEndPos:=p;
':':
if (p-FileStartPos>5) and (p[-4]='.') and (p[-3] in ['p','P'])
and (p[-2] in ['p','P']) and (p[-1] in ['u','U']) then begin
// e.g. /usr/lib/fpc/3.1.1/units/x86_64-linux/rtl/sysutils.ppu:filutil.inc(481,10) Error: (5088) ...
if PPUFileStartPos<>nil then exit;
PPUFileStartPos:=FileStartPos;
PPUFileEndPos:=p;
FileStartPos:=p+1;
end
else if (DriveSeparator='') or (p-FileStartPos>1) then
break;
end;
inc(p);
end;
if (FileEndPos=nil) or (FileEndPos-FileStartPos=0) or (FileEndPos[-1]=' ') then exit;
p:=FileEndPos;
inc(p); // skip bracket
LineStartPos:=p;
if not ReadDecimal(p) then exit;
if p^=',' then begin
if not ReadChar(p,',') then exit;
ColStartPos:=p;
if not ReadDecimal(p) then exit;
end else
ColStartPos:=nil;
if not ReadChar(p,')') then exit;
if not ReadChar(p,' ') then exit;
MsgType:=mluNote;
if ReadString(p,'Info:') then begin
MsgType:=mluVerbose;
end else if ReadString(p,'Hint:') then begin
MsgType:=mluHint;
end else if ReadString(p,'Note:') then begin
MsgType:=mluNote;
end else if ReadString(p,'Warn:') or ReadString(p,'Warning: ') then begin
MsgType:=mluWarning;
end else if ReadString(p,'Error:') then begin
MsgType:=mluError;
end else if ReadString(p,'Fatal:') then begin
MsgType:=mluError;
end else begin
p2:=p;
while not (p2^ in [':',#0,' ']) do inc(p2);
if p2^=':' then begin
// unknown type (maybe a translation?)
p:=p2+1;
end;
end;
while p^ in [' ',#9] do inc(p);
Result:=true;
TranslatedMsg:='';
if (p^='(') and (p[1] in ['0'..'9']) then begin
// (msgid)
p2:=p;
inc(p2);
i:=0;
while (p2^ in ['0'..'9']) and (i<1000000) do begin
i:=i*10+ord(p2^)-ord('0');
inc(p2);
end;
if p2^=')' then begin
fMsgID:=i;
p:=p2+1;
while p^ in [' ',#9] do inc(p);
//debugln(['TFPCParser.CheckForFileLineColMessage ID=',fMsgID,' Msg=',FileStartPos]);
if (fMsgID>0) then begin
TranslatedItem:=nil;
MsgItem:=nil;
if (TranslationFile<>nil) then
TranslatedItem:=TranslationFile.GetMsg(fMsgID);
if (MsgFile<>nil) then
MsgItem:=MsgFile.GetMsg(fMsgID);
if (TranslatedItem=nil) and (MsgItem=nil) then begin
if ConsoleVerbosity>=1 then
debugln(['TFPCParser.CheckForFileLineColMessage msgid not found: ',fMsgID]);
end else begin
Translate(p,MsgItem,TranslatedItem,TranslatedMsg,MsgType);
if MsgType=mluNone then begin
if ConsoleVerbosity>=1 then
debugln(['TFPCParser.CheckForFileLineColMessage msgid has no type: ',fMsgID]);
end;
end;
end;
end;
end;
if ColStartPos<>nil then
Column:=Str2Integer(ColStartPos,0)
else
Column:=0;
MsgLine:=CreateMsgLine;
MsgLine.SubTool:=DefaultSubTool;
MsgLine.Urgency:=MsgType;
aFilename:=GetString(FileStartPos,FileEndPos-FileStartPos);
if PPUFileStartPos<>nil then
MsgLine.Attribute['PPU']:=GetString(PPUFileStartPos,PPUFileEndPos-PPUFileStartPos);
MsgLine.Filename:=LongenFilename(MsgLine,aFilename);
MsgLine.Line:=Str2Integer(LineStartPos,0);
MsgLine.Column:=Column;
MsgLine.Msg:=p;
MsgLine.TranslatedMsg:=TranslatedMsg;
//debugln(['TFPCParser.CheckForFileLineColMessage ',dbgs(MsgLine.Urgency)]);
AddMsgLine(MsgLine);
end;
function TIDEFPCParser.CheckForLoadFromUnit(p: PChar): Boolean;
var
OldP: PChar;
MsgLine: TMessageLine;
begin
Result:=fMsgID=10027;
if (not Result) and (fMsgID>0) then exit;
OldP:=p;
if not Result then begin
if not ReadString(p,'Load from ') then exit;
while not (p^ in ['(',#0]) do inc(p);
if p^<>'(' then exit;
while not (p^ in [')',#0]) do inc(p);
if p^<>')' then exit;
if not ReadString(p,') unit ') then exit;
end;
MsgLine:=CreateMsgLine;
MsgLine.SubTool:=DefaultSubTool;
MsgLine.Urgency:=mluProgress;
MsgLine.Msg:=OldP;
AddMsgLine(MsgLine);
Result:=true;
end;
procedure TIDEFPCParser.ReadLine(Line: string; OutputIndex: integer;
IsStdErr: boolean; var Handled: boolean);
{ returns true, if it is a compiler message
Examples for freepascal compiler messages:
Compiling <filename>
Assembling <filename>
Fatal: <some text>
Fatal: (message id) <some text>
(message id) <some text>
<filename>(123,45) <ErrorType>: <some text>
<filename>(123) <ErrorType>: <some text>
<filename>(456) <ErrorType>: <some text> in line (123)
[0.000] (3101) Macro defined: CPUAMD64
<filename>(12,34) <ErrorType>: (5024) <some text>
}
var
p: PChar;
begin
if Line='' then exit;
Line:=ToUTF8(Line);
p:=PChar(Line);
fOutputIndex:=OutputIndex;
fMsgID:=0;
fMsgIsStdErr:=IsStdErr;
// skip time [0.000]
if (p^='[') and (p[1] in ['0'..'9']) then begin
inc(p,2);
while p^ in ['0'..'9','.'] do inc(p);
if p^<>']' then exit; // not a fpc message
inc(p);
while p^ in [' '] do inc(p);
end;
// read message ID (000)
if (p^='(') and (p[1] in ['0'..'9']) then begin
inc(p);
while p^ in ['0'..'9','.'] do begin
if fMsgID>1000000 then exit; // not a fpc message
fMsgID:=fMsgID*10+ord(p^)-ord('0');
inc(p);
end;
if p^<>')' then exit; // not a fpc message
inc(p);
while p^=' ' do inc(p);
end;
if p^ in [#0..#31,' '] then begin
CheckFollowUpMessage(p);
exit; // not a fpc message
end;
Handled:=true;
//debugln(['TIDEFPCParser.ReadLine ',IsStdErr,' ',Line]);
// check for (msgid) message
if CheckForMsgId(p) then exit;
// check for 'filename(line,column) Error: message'
if CheckForFileLineColMessage(p) then exit;
// check for 'Compiling <filename>'
if CheckForCompilingState(p) then exit;
// check for 'Assembling <filename>'
if CheckForAssemblingState(p) then exit;
// check for 'Fatal: ', 'Panic: ', 'Error: ', ...
if CheckForGeneralMessage(p) then exit;
// check for '<line> <kb>/<kb>'...
if CheckForLineProgress(p) then exit;
// check for '<int> Lines compiled, <int>.<int> sec'
if CheckForLinesCompiled(p) then exit;
// check for infos (logo, Linking <Progname>)
if CheckForInfos(p) then exit;
// check for -vx output
if CheckForExecutableInfo(p) then exit;
// check for Load from unit
if CheckForLoadFromUnit(p) then exit;
// check for windres errors
if CheckForWindresErrors(p) then exit;
// check for linker errors
if CheckForLinkerErrors(p) then exit;
// check for assembler errors
if CheckForAssemblerErrors(p) then exit;
// last: check for unknown std error
if CheckForUnspecificStdErr(p) then exit;
{$IFDEF VerboseFPCParser}
debugln('TFPCParser.ReadLine UNKNOWN: ',Line);
{$ENDIF}
Handled:=false;
end;
procedure TIDEFPCParser.AddMsgLine(MsgLine: TMessageLine);
begin
if IsMsgID(MsgLine,FPCMsgIDErrorWhileCompilingResources,
fMsgItemErrorWhileCompilingResources)
then begin
// Error while compiling resources
AddResourceMessages;
MsgLine.Msg:=MsgLine.Msg+' -> '+'Compile with -vd for more details. Check for duplicates.';
MsgLine.TranslatedMsg:=MsgLine.TranslatedMsg+' -> '+lisCompileWithVdForMoreDetailsCheckForDuplicates;
end
else if IsMsgID(MsgLine,FPCMsgIDErrorWhileLinking,fMsgItemErrorWhileLinking) then
AddLinkingMessages
else if IsMsgID(MsgLine,FPCMsgIDChecksumChanged,fMsgItemChecksumChanged) then
MsgLine.Urgency:=mluWarning
else if IsMsgID(MsgLine,FPCMsgIDThereWereErrorsCompiling,
fMsgItemThereWereErrorsCompiling)
then
MsgLine.Urgency:=mluVerbose;
inherited AddMsgLine(MsgLine);
end;
function TIDEFPCParser.LongenFilename(MsgLine: TMessageLine; aFilename: string
): string;
var
ShortFilename: String;
i: Integer;
LastMsgLine: TMessageLine;
LastFilename: String;
begin
Result:=TrimFilename(aFilename);
if FilenameIsAbsolute(Result) then begin
if ReverseInstantFPCCacheDir(Result,false) then exit;
if ReverseTestBuildDir(MsgLine,Result) then exit;
exit;
end;
if MsgLine.Attribute['PPU']<>'' then begin
MsgLine.Attribute[FPCMsgAttrWorkerDirectory]:=Tool.WorkerDirectory;
exit;
end;
ShortFilename:=Result;
// check last message line
LastMsgLine:=Tool.WorkerMessages.GetLastLine;
if (LastMsgLine<>nil) then begin
if mlfTestBuildFile in LastMsgLine.Flags then
LastFilename:=LastMsgLine.Attribute[MsgAttrDiskFilename]
else
LastFilename:=LastMsgLine.Filename;
if FilenameIsAbsolute(LastFilename) then begin
if (length(LastFilename)>length(ShortFilename))
and (LastFilename[length(LastFilename)-length(ShortFilename)] in AllowDirectorySeparators)
and (CompareFilenames(RightStr(LastFilename,length(ShortFilename)),ShortFilename)=0)
then begin
if mlfTestBuildFile in LastMsgLine.Flags then begin
MsgLine.Attribute[MsgAttrDiskFilename]:=LastFilename;
MsgLine.Flags:=MsgLine.Flags+[mlfTestBuildFile];
Result:=LastMsgLine.Filename;
end else begin
Result:=LastFilename;
ReverseTestBuildDir(MsgLine,Result);
end;
exit;
end;
end;
end;
// search file in the last compiling directories
if DirectoryStack<>nil then begin
for i:=DirectoryStack.Count-1 downto 0 do begin
Result:=AppendPathDelim(DirectoryStack[i])+ShortFilename;
if FileExists(Result,false) then begin
ReverseTestBuildDir(MsgLine,Result);
exit;
end;
end;
end;
// search file in worker directory
if Tool.WorkerDirectory<>'' then begin
Result:=AppendPathDelim(Tool.WorkerDirectory)+ShortFilename;
if FileExists(Result,false) then begin
ReverseTestBuildDir(MsgLine,Result);
exit;
end;
end;
// file not found
Result:=ShortFilename;
// save Tool.WorkerDirectory for ImproveMessage
MsgLine.Attribute[FPCMsgAttrWorkerDirectory]:=Tool.WorkerDirectory;
end;
function TIDEFPCParser.GetDefaultPCFullVersion: LongWord;
var
Kind: TPascalCompiler;
begin
// get compiler version
Result:=LongWord(CodeToolBoss.GetPCVersionForDirectory(Tool.WorkerDirectory,Kind));
if Kind=pcFPC then ;
end;
function TIDEFPCParser.ToUTF8(const Line: string): string;
begin
if PC_FullVersion>=20701 then
Result:=ConsoleToUTF8(Line)
else begin
{$IFDEF MSWINDOWS}
Result:=WinCPToUTF8(Line);
{$ELSE}
Result:=SysToUTF8(Line);
{$ENDIF}
end;
end;
procedure TIDEFPCParser.ImproveMessages(aPhase: TExtToolParserSyncPhase);
var
i: Integer;
MsgLine: TMessageLine;
aFilename: String;
Y: Integer;
X: Integer;
Code: TCodeBuffer;
SourceOK: Boolean;
MsgWorkerDir: String;
PrevMsgLine: TMessageLine;
CmdLineParams: String;
SrcFilename: String;
PPUFilename: String;
begin
//debugln(['TIDEFPCParser.ImproveMessages START ',aSynchronized,' Last=',fLastWorkerImprovedMessage[aSynchronized],' Now=',Tool.WorkerMessages.Count]);
for i:=fLastWorkerImprovedMessage[aPhase]+1 to Tool.WorkerMessages.Count-1 do
begin
MsgLine:=Tool.WorkerMessages[i];
Y:=MsgLine.Line;
X:=MsgLine.Column;
if (Y>0) and (X>0)
and (MsgLine.SubTool=DefaultSubTool) and (MsgLine.Filename<>'')
then begin
if mlfTestBuildFile in MsgLine.Flags then
aFilename:=MsgLine.Attribute[MsgAttrDiskFilename]
else
aFilename:=MsgLine.Filename;
PPUFilename:='';
if (not FilenameIsAbsolute(aFilename)) then begin
PPUFilename:=MsgLine.Attribute['PPU'];
if PPUFilename<>'' then begin
// compiler gave ppu file and relative source file
if not FindSrcViaPPU(aPhase,MsgLine,PPUFilename) then continue;
end;
end;
if (not FilenameIsAbsolute(aFilename)) then begin
// short file name => 1. search the full file name in previous message
if i>0 then begin
PrevMsgLine:=Tool.WorkerMessages[i-1];
if (PrevMsgLine.SubTool=DefaultSubTool)
and FilenameIsAbsolute(PrevMsgLine.Filename)
and (CompareFilenames(ExtractFilename(PrevMsgLine.Filename),ExtractFilename(aFilename))=0)
then begin
// same file as previous message => use it
aFilename:=PrevMsgLine.Filename;
MsgLine.Filename:=aFilename;
end;
end;
end;
if (not FilenameIsAbsolute(aFilename)) then begin
// short file name => 2. search in include path
MsgWorkerDir:=MsgLine.Attribute[FPCMsgAttrWorkerDirectory];
FetchIncludePath(aPhase,MsgWorkerDir); // needs Phase etpspAfterReadLine+etpspSynchronized
{$IFDEF VerboseFPCMsgUnitNotFound}
if aPhase=etpspSynchronized then
debugln(['TIDEFPCParser.ImproveMessages IncPath="',fIncludePath,'" aFilename="',aFilename,'" MsgWorkerDir="',MsgWorkerDir,'"']);
{$ENDIF}
if (aPhase in [etpspAfterReadLine,etpspAfterSync])
and (fIncludePathValidForWorkerDir=MsgWorkerDir) then begin
// include path is valid and in worker thread
// -> search file (todo: needs a thread safe function for star directories)
aFilename:=FileUtil.SearchFileInPath(aFilename,MsgWorkerDir,fIncludePath,';',
[FileUtil.sffSearchLoUpCase,sffFile]);
if aFilename<>'' then
MsgLine.Filename:=aFilename;
end;
end;
if (not FilenameIsAbsolute(aFilename)) and (aPhase=etpspAfterReadLine)
then begin
CmdLineParams:=Tool.CmdLineParams;
if Pos(CmdLineParams,PathDelim+'fpc'+ExeExt+' ')>0 then begin
// short file name => 3. check the cmd line param source file
SrcFilename:=GetFPCParameterSrcFile(Tool.CmdLineParams);
if (SrcFilename<>'')
and ((CompareFilenames(ExtractFilename(SrcFilename),aFilename)=0)
or (CompareFilenames(ExtractFileNameOnly(SrcFilename),aFilename)=0))
then begin
if not FilenameIsAbsolute(SrcFilename) then begin
MsgWorkerDir:=MsgLine.Attribute[FPCMsgAttrWorkerDirectory];
SrcFilename:=ResolveDots(AppendPathDelim(MsgWorkerDir)+SrcFilename);
end;
if FilenameIsAbsolute(SrcFilename) then
MsgLine.Filename:=SrcFilename;
end;
end;
end;
// get source
SourceOK:=false;
aFilename:=MsgLine.Filename;
if FilenameIsAbsolute(aFilename) or (mlfTestBuildFile in MsgLine.Flags)
then begin
if (fCurSource<>nil)
and (CompareFilenames(aFilename,fCurSource.Filename)=0) then begin
SourceOK:=true;
end else begin
// need source
case aPhase of
etpspAfterReadLine:
NeedSynchronize:=true;
etpspSynchronized:
begin
// load source file
//debugln(['TFPCParser.ImproveMessages loading ',aFilename]);
Code:=CodeToolBoss.LoadFile(aFilename,true,false);
if Code<>nil then begin
if fCurSource=nil then
fCurSource:=TCodeBuffer.Create;
fCurSource.Filename:=aFilename;
if Code.FileOnDiskNeedsUpdate then begin
// IDE buffer contains changes that are not yet saved to disk
// The compiler messages are about the disk file
// => load the file
fCurSource.LoadFromFile(aFilename);
end else begin
// IDE buffer valid => just copy
fCurSource.Source:=Code.Source;
end;
SourceOK:=true;
NeedAfterSync:=true;
end;
end;
end;
end;
end;
ImproveMsgIdentifierPosition(aPhase, MsgLine, SourceOK);
ImproveMsgHiddenByIDEDirective(aPhase, MsgLine, SourceOK);
ImproveMsgUnitNotUsed(aPhase, MsgLine);
ImproveMsgUnitTagged(aPhase, MsgLine);
ImproveMsgSenderNotUsed(aPhase, MsgLine);
end else if MsgLine.SubTool=SubToolFPCLinker then begin
ImproveMsgLinkerUndefinedReference(aPhase, MsgLine);
end;
ImproveMsgUnitNotFound(aPhase, MsgLine);
end;
fLastWorkerImprovedMessage[aPhase]:=Tool.WorkerMessages.Count-1;
end;
class function TIDEFPCParser.CanParseSubTool(const SubTool: string): boolean;
begin
Result:=(CompareText(SubTool,SubToolFPC)=0)
or (CompareText(SubTool,SubToolFPCLinker)=0)
or (CompareText(SubTool,SubToolFPCRes)=0);
end;
class function TIDEFPCParser.DefaultSubTool: string;
begin
Result:=SubToolFPC;
end;
class function TIDEFPCParser.GetMsgHint(SubTool: string; MsgID: integer): string;
var
CurMsgFile: TFPCMsgFilePoolItem;
MsgItem: TFPCMsgItem;
begin
Result:='';
if CompareText(SubTool,DefaultSubTool)=0 then begin
CurMsgFile:=MsgFilePool.LoadCurrentEnglishFile(false,nil);
if CurMsgFile=nil then exit;
try
MsgItem:=CurMsgFile.GetMsg(MsgID);
if MsgItem=nil then exit;
Result:=MsgItem.GetTrimmedComment(false,true);
finally
MsgFilePool.UnloadFile(CurMsgFile);
end;
end;
end;
class function TIDEFPCParser.GetMsgPattern(SubTool: string; MsgID: integer; out
Urgency: TMessageLineUrgency): string;
var
CurMsgFile: TFPCMsgFilePoolItem;
MsgItem: TFPCMsgItem;
begin
Result:='';
Urgency:=mluNone;
if CompareText(SubTool,DefaultSubTool)=0 then begin
if MsgFilePool=nil then exit;
CurMsgFile:=MsgFilePool.LoadCurrentEnglishFile(false,nil);
if CurMsgFile=nil then exit;
try
MsgItem:=CurMsgFile.GetMsg(MsgID);
if MsgItem=nil then exit;
Result:=MsgItem.Pattern;
Urgency:=FPCMsgToMsgUrgency(MsgItem);
finally
MsgFilePool.UnloadFile(CurMsgFile);
end;
end;
end;
class function TIDEFPCParser.Priority: integer;
begin
Result:=SubToolFPCPriority;
end;
class function TIDEFPCParser.MsgLineIsId(Msg: TMessageLine; MsgId: integer; out
Value1, Value2: string): boolean;
function GetStr(FromPos, ToPos: PChar): string;
begin
if (FromPos=nil) or (FromPos=ToPos) then
Result:=''
else begin
SetLength(Result,ToPos-FromPos);
Move(FromPos^,Result[1],ToPos-FromPos);
end;
end;
var
aFPCParser: TFPCParser;
Pattern: String;
VarStarts: PPChar;
VarEnds: PPChar;
s: String;
begin
Value1:='';
Value2:='';
if Msg=nil then exit(false);
if Msg.SubTool<>DefaultSubTool then exit(false);
if (Msg.MsgID<>MsgId)
and (Msg.MsgID<>0) then exit(false);
Result:=true;
aFPCParser:=GetFPCParser(Msg);
if aFPCParser=nil then exit;
Pattern:=aFPCParser.GetFPCMsgIDPattern(MsgId);
VarStarts:=GetMem(SizeOf(PChar)*10);
VarEnds:=GetMem(SizeOf(PChar)*10);
s:=Msg.Msg;
Result:=FPCMsgFits(s,Pattern,VarStarts,VarEnds);
if Result then begin
Value1:=GetStr(VarStarts[1],VarEnds[1]);
Value2:=GetStr(VarStarts[2],VarEnds[2]);
end;
Freemem(VarStarts);
Freemem(VarEnds);
end;
function TIDEFPCParser.GetFPCMsgIDPattern(MsgID: integer): string;
var
MsgItem: TFPCMsgItem;
begin
Result:='';
if MsgID<=0 then exit;
if MsgFile=nil then exit;
MsgItem:=MsgFile.GetMsg(MsgID);
if MsgItem=nil then exit;
Result:=MsgItem.Pattern;
end;
class function TIDEFPCParser.GetFPCMsgPattern(Msg: TMessageLine): string;
var
aFPCParser: TFPCParser;
begin
Result:='';
if Msg.MsgID<=0 then exit;
aFPCParser:=GetFPCParser(Msg);
if aFPCParser=nil then exit;
Result:=aFPCParser.GetFPCMsgIDPattern(Msg.MsgID);
end;
class function TIDEFPCParser.GetFPCMsgValue1(Msg: TMessageLine): string;
begin
Result:='';
if Msg.MsgID<=0 then exit;
if Msg.SubTool<>DefaultSubTool then exit;
if not GetFPCMsgValueOne(Msg.Msg,GetFPCMsgPattern(Msg),Result) then
Result:='';
end;
class function TIDEFPCParser.GetFPCMsgValues(Msg: TMessageLine; out Value1,
Value2: string): boolean;
begin
Result:=false;
if Msg.MsgID<=0 then exit;
if Msg.SubTool<>DefaultSubTool then exit;
Result:=etFPCMsgParser.GetFPCMsgValuesTwo(Msg.Msg,GetFPCMsgPattern(Msg),Value1,Value2);
end;
class function TIDEFPCParser.MsgFilePool: TFPCMsgFilePool;
begin
Result:=FPCMsgFilePool;
end;
initialization
IDEFPCParser:=TIDEFPCParser;
finalization
FreeAndNil(FpcMsgFilePool);
end.