mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-05 18:38:01 +02:00
1846 lines
53 KiB
ObjectPascal
1846 lines
53 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:
|
|
Running external programs and parsing their output lines.
|
|
}
|
|
unit ExtTools;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
{off $DEFINE VerboseExtToolErrors}
|
|
{off $DEFINE VerboseExtToolAddOutputLines}
|
|
{off $DEFINE VerboseExtToolThread}
|
|
|
|
interface
|
|
|
|
uses
|
|
// RTL + FCL
|
|
Classes, SysUtils, Math, process, Pipes, AVL_Tree, System.UITypes,
|
|
// LazUtils
|
|
FileUtil, LazFileUtils, LazUtilities, LazLoggerBase, UTF8Process, LazUTF8,
|
|
AvgLvlTree,
|
|
// BuildIntf
|
|
IDEExternToolIntf, BaseIDEIntf, MacroIntf, LazMsgWorker,
|
|
// IdeUtils
|
|
IdeUtilsPkgStrConsts,
|
|
// IdeConfig
|
|
TransferMacros,
|
|
// IDE
|
|
LazarusIDEStrConsts;
|
|
|
|
type
|
|
TLMVToolState = (
|
|
lmvtsRunning,
|
|
lmvtsSuccess,
|
|
lmvtsFailed
|
|
);
|
|
TLMVToolStates = set of TLMVToolState;
|
|
|
|
{ TLazExtToolView }
|
|
|
|
TLazExtToolView = class(TExtToolView)
|
|
private
|
|
FToolState: TLMVToolState;
|
|
protected
|
|
procedure SetToolState(AValue: TLMVToolState); virtual;
|
|
public
|
|
property ToolState: TLMVToolState read FToolState write SetToolState;
|
|
end;
|
|
|
|
TExternalTool = class;
|
|
|
|
{ TExternalToolThread }
|
|
|
|
TExternalToolThread = class(TThread)
|
|
private
|
|
fLines: TStringList;
|
|
FTool: TExternalTool;
|
|
procedure SetTool(AValue: TExternalTool);
|
|
public
|
|
property Tool: TExternalTool read FTool write SetTool;
|
|
procedure Execute; override;
|
|
procedure DebuglnThreadLog(const Args: array of string);
|
|
destructor Destroy; override; // (main thread)
|
|
end;
|
|
|
|
{ TExternalTool }
|
|
|
|
TExternalTool = class(TAbstractExternalTool)
|
|
private
|
|
FThread: TExternalToolThread;
|
|
fExecuteAfter: TFPList; // list of TExternalTool
|
|
fExecuteBefore: TFPList; // list of TExternalTool
|
|
fNeedAfterSync: boolean;
|
|
fOutputCountNotified: integer;
|
|
procedure ProcessRunning; // (worker thread) after Process.Execute
|
|
procedure ProcessStopped; // (worker thread) when process stopped
|
|
procedure NotifyHandlerStopped; // (main thread) called by ProcessStopped
|
|
procedure NotifyHandlerNewOutput; // (main thread) called by AddOutputLines
|
|
procedure SetThread(AValue: TExternalToolThread); // main or worker thread
|
|
procedure SynchronizedImproveMessages; // (main thread) called by AddOutputLines
|
|
procedure DoTerminate; // (main thread)
|
|
protected
|
|
procedure DoExecute; override; // (main thread)
|
|
procedure DoStart; // (main thread)
|
|
procedure CreateView; virtual; abstract; // (main thread)
|
|
function GetExecuteAfter(Index: integer): TAbstractExternalTool; override;
|
|
function GetExecuteBefore(Index: integer): TAbstractExternalTool; override;
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
function CanFree: boolean; override;
|
|
procedure QueueAsyncAutoFree; virtual; abstract;
|
|
public
|
|
constructor Create(aOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
property Thread: TExternalToolThread read FThread write SetThread;
|
|
procedure Execute; override; // (main thread)
|
|
procedure Terminate; override; // (main thread)
|
|
procedure WaitForExit; override; // (main thread)
|
|
function ResolveMacros: boolean; override; // (main thread)
|
|
|
|
function ExecuteAfterCount: integer; override;
|
|
function ExecuteBeforeCount: integer; override;
|
|
procedure RemoveExecuteBefore(Tool: TAbstractExternalTool); override;
|
|
function IsExecutedBefore(Tool: TAbstractExternalTool): Boolean; override;
|
|
procedure AddExecuteBefore(Tool: TAbstractExternalTool); override;
|
|
function CanStart: boolean;
|
|
function GetLongestEstimatedLoad: int64;
|
|
|
|
procedure UserThreadRunning; override; // (worker thread) when thread Execute
|
|
procedure UserThreadStopped; override; // (worker thread) when thread stopped
|
|
procedure AddOutputLines(Lines: TStringList); override; // (worker thread) when new output arrived
|
|
|
|
function InitParsers: boolean; override;
|
|
end;
|
|
|
|
TExternalToolClass = class of TExternalTool;
|
|
|
|
{ TExternalTools }
|
|
|
|
TExternalTools = class(TExternalToolsBase)
|
|
private
|
|
FCritSec: TRTLCriticalSection;
|
|
fRunning: TFPList; // list of TExternalTool, needs Enter/LeaveCriticalSection
|
|
fOldThreads: TFPList; // list of TExternalToolThread, needs Enter/LeaveCriticalSection
|
|
FMaxProcessCount: integer;
|
|
fParsers: TFPList; // list of TExtToolParserClass
|
|
procedure AddOldThread(aThread: TExternalToolThread); // (main thread)
|
|
function GetRunningTools(Index: integer): TExternalTool;
|
|
procedure AddRunningTool(Tool: TExternalTool); // (worker thread)
|
|
procedure RemoveRunningTool(Tool: TExternalTool); // (worker thread)
|
|
function RunExtToolHandler(ToolOptions: TIDEExternalToolOptions): boolean; // (main thread)
|
|
function RunToolAndDetach(ToolOptions: TIDEExternalToolOptions): boolean; // (main thread)
|
|
function RunToolWithParsers(ToolOptions: TIDEExternalToolOptions): boolean; // (main thread)
|
|
procedure FreeFinishedThreads; // (main thread)
|
|
procedure OnThreadTerminate(Sender: TObject); // (main thread)
|
|
protected
|
|
FToolClass: TExternalToolClass;
|
|
function GetParsers(Index: integer): TExtToolParserClass; override;
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
public
|
|
constructor Create(aOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
function Add(Title: string): TAbstractExternalTool; override;
|
|
function IndexOf(Tool: TAbstractExternalTool): integer; override;
|
|
property MaxProcessCount: integer read FMaxProcessCount write FMaxProcessCount;
|
|
procedure Work;
|
|
function FindNextToolToStart: TExternalTool;
|
|
procedure Terminate(Tool: TExternalTool);
|
|
procedure TerminateAll; override;
|
|
procedure Clear; override;
|
|
function RunningCount: integer;
|
|
property RunningTools[Index: integer]: TExternalTool read GetRunningTools;
|
|
procedure EnterCriticalSection; override;
|
|
procedure LeaveCriticalSection; override;
|
|
// parsers
|
|
function ParserCount: integer; override;
|
|
procedure RegisterParser(Parser: TExtToolParserClass); override;
|
|
procedure UnregisterParser(Parser: TExtToolParserClass); override;
|
|
function FindParserForTool(const SubTool: string): TExtToolParserClass; override;
|
|
function FindParserWithName(const ParserName: string): TExtToolParserClass; override;
|
|
function GetMsgTool(Msg: TMessageLine): TAbstractExternalTool; override;
|
|
end;
|
|
|
|
TExternalToolsClass = class of TExternalTools;
|
|
|
|
function ExternalToolsRef: TExternalTools;
|
|
|
|
|
|
implementation
|
|
|
|
function ExternalToolsRef: TExternalTools;
|
|
begin
|
|
Result := ExternalToolList as TExternalTools;
|
|
end;
|
|
|
|
{$IF defined(VerboseExtToolErrors) or defined(VerboseExtToolThread) or defined(VerboseExtToolAddOutputLines)}
|
|
function ArgsToString(Args: array of const): string;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := '';
|
|
for i:=Low(Args) to High(Args) do begin
|
|
case Args[i].VType of
|
|
vtInteger: Result := Result + dbgs(Args[i].vinteger);
|
|
vtInt64: Result := Result + dbgs(Args[i].VInt64^);
|
|
vtQWord: Result := Result + dbgs(Args[i].VQWord^);
|
|
vtBoolean: Result := Result + dbgs(Args[i].vboolean);
|
|
vtExtended: Result := Result + dbgs(Args[i].VExtended^);
|
|
vtString: Result := Result + Args[i].VString^;
|
|
vtAnsiString: Result := Result + AnsiString(Args[i].VAnsiString);
|
|
vtChar: Result := Result + Args[i].VChar;
|
|
vtPChar: Result := Result + Args[i].VPChar;
|
|
vtPWideChar: Result := {%H-}Result {%H-}+ Args[i].VPWideChar;
|
|
vtWideChar: Result := Result + AnsiString(Args[i].VWideChar);
|
|
vtWidestring: Result := Result + AnsiString(WideString(Args[i].VWideString));
|
|
vtObject: Result := Result + DbgSName(Args[i].VObject);
|
|
vtClass: Result := Result + DbgSName(Args[i].VClass);
|
|
vtPointer: Result := Result + Dbgs(Args[i].VPointer);
|
|
else Result := Result + '?unknown variant?';
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure DebuglnThreadLog(const args: array of const);
|
|
var
|
|
s, Filename: string;
|
|
fs: TFileStream;
|
|
begin
|
|
if GetCurrentThreadId=MainThreadID then
|
|
debugln(args)
|
|
else
|
|
begin
|
|
s:=ArgsToString(args)+sLineBreak;
|
|
Filename:='lazdbg'+IntToStr(GetCurrentThreadId)+'.log';
|
|
if FileExistsUTF8(Filename) then
|
|
fs:=TFileStream.Create(Filename,fmOpenWrite or fmShareDenyNone)
|
|
else
|
|
fs:=TFileStream.Create(Filename,fmCreate);
|
|
try
|
|
try
|
|
fs.Seek(0,soEnd);
|
|
fs.Write(s[1],length(s));
|
|
except
|
|
end;
|
|
finally
|
|
fs.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
{ TLazExtToolView }
|
|
|
|
procedure TLazExtToolView.SetToolState(AValue: TLMVToolState);
|
|
begin
|
|
if FToolState=AValue then Exit;
|
|
FToolState:=AValue;
|
|
end;
|
|
|
|
{ TExternalTool }
|
|
|
|
procedure TExternalTool.ProcessRunning;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
EnterCriticalSection;
|
|
try
|
|
if FStage<>etsStarting then exit;
|
|
FStage:=etsRunning;
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
for i:=0 to ParserCount-1 do
|
|
Parsers[i].InitReading;
|
|
end;
|
|
|
|
procedure TExternalTool.ProcessStopped;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
{$IFDEF VerboseExtToolErrors}
|
|
if ErrorMessage<>'' then
|
|
DebuglnThreadLog(['TExternalTool.ThreadStopped ',Title,' ErrorMessage=',ErrorMessage]);
|
|
{$ENDIF}
|
|
if Thread<>nil then
|
|
Thread.Tool:=nil;
|
|
EnterCriticalSection;
|
|
try
|
|
if (not Terminated) and (ErrorMessage='') then
|
|
begin
|
|
if ExitCode<>0 then
|
|
ErrorMessage:=Format(lisExitCode, [IntToStr(ExitCode)])
|
|
else if ExitStatus<>0 then
|
|
ErrorMessage:='ExitStatus '+IntToStr(ExitStatus);
|
|
end;
|
|
if FStage>=etsStopped then exit;
|
|
FStage:=etsStopped;
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
for i:=0 to ParserCount-1 do begin
|
|
try
|
|
Parsers[i].Done;
|
|
except
|
|
on E: Exception do begin
|
|
{$IFDEF VerboseExtToolErrors}
|
|
DebuglnThreadLog(['TExternalTool.ProcessStopped ',Title,' Error in ',DbgSName(Parsers[i]),': ',E.Message]);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
end;
|
|
if Tools<>nil then
|
|
TExternalTools(Tools).RemoveRunningTool(Self);
|
|
TThread.Synchronize(nil,@NotifyHandlerStopped);
|
|
end;
|
|
|
|
procedure TExternalTool.AddOutputLines(Lines: TStringList);
|
|
var
|
|
i: Integer;
|
|
Handled: Boolean;
|
|
Line: LongInt;
|
|
OldOutputCount: LongInt;
|
|
OldMsgCount: LongInt;
|
|
Parser: TExtToolParser;
|
|
NeedSynchronize, IsStdErr: Boolean;
|
|
MsgLine: TMessageLine;
|
|
LineStr: String;
|
|
begin
|
|
{$IFDEF VerboseExtToolAddOutputLines}
|
|
DebuglnThreadLog(['TExternalTool.AddOutputLines ',Title,' Tick=',IntToStr(GetTickCount64),' Lines=',Lines.Count]);
|
|
{$ENDIF}
|
|
if (Lines=nil) or (Lines.Count=0) then exit;
|
|
NeedSynchronize:=false;
|
|
EnterCriticalSection;
|
|
try
|
|
OldOutputCount:=WorkerOutput.Count;
|
|
OldMsgCount:=WorkerMessages.Count;
|
|
WorkerOutput.AddStrings(Lines);
|
|
for i:=0 to ParserCount-1 do
|
|
Parsers[i].NeedSynchronize:=false;
|
|
|
|
// feed new lines into all parsers, converting raw lines into messages
|
|
for Line:=OldOutputCount to WorkerOutput.Count-1 do begin
|
|
Handled:=false;
|
|
LineStr:=WorkerOutput[Line];
|
|
IsStdErr:=WorkerOutput.Objects[Line]<>nil;
|
|
for i:=0 to ParserCount-1 do begin
|
|
{$IFDEF VerboseExtToolAddOutputLines}
|
|
DebuglnThreadLog(['TExternalTool.AddOutputLines ',DbgSName(Parsers[i]),' Line="',WorkerOutput[Line],'" READLINE ...']);
|
|
{$ENDIF}
|
|
Parsers[i].ReadLine(LineStr,Line,IsStdErr,Handled);
|
|
if Handled then break;
|
|
end;
|
|
if (not Handled) then begin
|
|
MsgLine:=WorkerMessages.CreateLine(Line);
|
|
MsgLine.Msg:=LineStr; // use raw output as default msg
|
|
if ParserCount=0 then
|
|
MsgLine.Urgency:=mluImportant
|
|
else
|
|
MsgLine.Urgency:=mluDebug;
|
|
if IsStdErr then
|
|
MsgLine.Flags:=MsgLine.Flags+[mlfStdErr];
|
|
WorkerMessages.Add(MsgLine);
|
|
end;
|
|
end;
|
|
|
|
// let all parsers improve the new messages in worker thread before synchronized
|
|
if OldMsgCount<WorkerMessages.Count then begin
|
|
for i:=0 to ParserCount-1 do begin
|
|
Parser:=Parsers[i];
|
|
Parser.NeedSynchronize:=false;
|
|
Parser.NeedAfterSync:=false;
|
|
{$IFDEF VerboseExtToolAddOutputLines}
|
|
DebuglnThreadLog(['TExternalTool.AddOutputLines ',DbgSName(Parser),' IMPROVE after ReadLine ...']);
|
|
{$ENDIF}
|
|
Parser.ImproveMessages(etpspAfterReadLine);
|
|
if Parser.NeedSynchronize then
|
|
NeedSynchronize:=true;
|
|
end;
|
|
end;
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
|
|
// let all parsers improve the new messages in main thread
|
|
if NeedSynchronize then begin
|
|
{$IFDEF VerboseExtToolAddOutputLines}
|
|
DebuglnThreadLog(['TExternalTool.AddOutputLines SynchronizedImproveMessages ...']);
|
|
{$ENDIF}
|
|
if Thread<>nil then
|
|
Thread.Synchronize(Thread,@SynchronizedImproveMessages)
|
|
else if UserThread<>nil then
|
|
TThread.Synchronize(UserThread,@SynchronizedImproveMessages)
|
|
end;
|
|
|
|
EnterCriticalSection;
|
|
try
|
|
// let all parsers improve the new messages in worker thread after synchronized
|
|
if fNeedAfterSync then begin
|
|
for i:=0 to ParserCount-1 do begin
|
|
Parser:=Parsers[i];
|
|
if not Parser.NeedAfterSync then continue;
|
|
{$IFDEF VerboseExtToolAddOutputLines}
|
|
DebuglnThreadLog(['TExternalTool.AddOutputLines ',DbgSName(Parser),' IMPROVE after sync ...']);
|
|
{$ENDIF}
|
|
Parser.ImproveMessages(etpspAfterSync);
|
|
end;
|
|
end;
|
|
|
|
// feed new messages into all viewers
|
|
if OldMsgCount<WorkerMessages.Count then begin
|
|
for i:=0 to ViewCount-1 do begin
|
|
{$IFDEF VerboseExtToolAddOutputLines}
|
|
DebuglnThreadLog(['TExternalTool.AddOutputLines ',DbgSName(Views[i]),' "',Views[i].Caption,'" ProcessNewMessages ...']);
|
|
{$ENDIF}
|
|
Views[i].ProcessNewMessages(Thread);
|
|
end;
|
|
end;
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
|
|
// notify main thread handlers for new output
|
|
// Note: The IDE itself does not set such a handler
|
|
if {$IFDEF VerboseExtToolAddOutputLines}true{$ELSE}FHandlers[ethNewOutput].Count>0{$ENDIF}
|
|
then begin
|
|
{$IFDEF VerboseExtToolAddOutputLines}
|
|
DebuglnThreadLog(['TExternalTool.AddOutputLines NotifyHandlerNewOutput ...']);
|
|
{$ENDIF}
|
|
Thread.Synchronize(Thread,@NotifyHandlerNewOutput);
|
|
end;
|
|
fOutputCountNotified:=WorkerOutput.Count;
|
|
{$IFDEF VerboseExtToolAddOutputLines}
|
|
DebuglnThreadLog(['TExternalTool.AddOutputLines END']);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TExternalTool.InitParsers: boolean;
|
|
var
|
|
i: Integer;
|
|
aParser: TExtToolParser;
|
|
begin
|
|
for i:=0 to ParserCount-1 do begin
|
|
aParser:=Parsers[i];
|
|
try
|
|
aParser.Init;
|
|
except
|
|
on E: Exception do begin
|
|
ErrorMessage:=Format(lisParser, [DbgSName(aParser), E.Message]);
|
|
if (FStage>=etsStopped) then exit(true);
|
|
debugln(['Error: (lazarus) [TExternalTool.InitParsers] Error=',ErrorMessage]);
|
|
EnterCriticalSection;
|
|
try
|
|
if FStage>=etsStopped then exit(true);
|
|
FStage:=etsStopped;
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
procedure TExternalTool.NotifyHandlerStopped;
|
|
var
|
|
i: Integer;
|
|
View: TExtToolView;
|
|
begin
|
|
DoCallNotifyHandler(ethStopped);
|
|
|
|
EnterCriticalSection;
|
|
try
|
|
for i:=ViewCount-1 downto 0 do begin
|
|
if i>=ViewCount then continue;
|
|
View:=Views[i];
|
|
if ErrorMessage<>'' then
|
|
View.SummaryMsg:=ErrorMessage
|
|
else
|
|
View.SummaryMsg:=lisSuccess;
|
|
View.InputClosed; // this might delete the view
|
|
end;
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
|
|
if Group<>nil then
|
|
Group.ToolExited(Self);
|
|
|
|
// process stopped => start next
|
|
if Tools<>nil then
|
|
TExternalTools(Tools).Work;
|
|
|
|
// free tool if not used
|
|
AutoFree;
|
|
end;
|
|
|
|
procedure TExternalTool.NotifyHandlerNewOutput;
|
|
var
|
|
i: integer;
|
|
begin
|
|
if fOutputCountNotified>=WorkerOutput.Count then exit;
|
|
{$IFDEF VerboseExtToolAddOutputLines}
|
|
for i:=fOutputCountNotified to WorkerOutput.Count-1 do
|
|
debugln('IDE-DEBUG: ',WorkerOutput[i]);
|
|
{$ENDIF}
|
|
i:=FHandlers[ethNewOutput].Count;
|
|
while FHandlers[ethNewOutput].NextDownIndex(i) do
|
|
TExternalToolNewOutputEvent(FHandlers[ethNewOutput][i])(Self,fOutputCountNotified);
|
|
end;
|
|
|
|
procedure TExternalTool.SetThread(AValue: TExternalToolThread);
|
|
var
|
|
OldThread: TExternalToolThread;
|
|
begin
|
|
if FThread=AValue then Exit;
|
|
OldThread:=FThread;
|
|
FThread:=AValue;
|
|
if OldThread<>nil then
|
|
OldThread.Tool:=nil;
|
|
if FThread<>nil then
|
|
FThread.Tool:=Self;
|
|
end;
|
|
|
|
procedure TExternalTool.SynchronizedImproveMessages;
|
|
var
|
|
i: Integer;
|
|
Parser: TExtToolParser;
|
|
begin
|
|
EnterCriticalSection;
|
|
try
|
|
fNeedAfterSync:=false;
|
|
for i:=0 to ParserCount-1 do begin
|
|
Parser:=Parsers[i];
|
|
if not Parser.NeedSynchronize then continue;
|
|
{$IFDEF VerboseExtToolAddOutputLines}
|
|
//debugln(['TExternalTool.SynchronizedImproveMessages ',DbgSName(Parser),' ...']);
|
|
{$ENDIF}
|
|
Parser.ImproveMessages(etpspSynchronized);
|
|
Parser.NeedSynchronize:=false;
|
|
if Parser.NeedAfterSync then
|
|
fNeedAfterSync:=true;
|
|
end;
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
end;
|
|
|
|
constructor TExternalTool.Create(aOwner: TComponent);
|
|
begin
|
|
inherited Create(aOwner);
|
|
FWorkerOutput:=TStringList.Create;
|
|
FProcess:=TProcessUTF8.Create(nil);
|
|
FProcess.Options:= [poUsePipes{$IFDEF Windows},poStderrToOutPut{$ENDIF}];
|
|
FProcess.ShowWindow := swoHide;
|
|
fExecuteBefore:=TFPList.Create;
|
|
fExecuteAfter:=TFPList.Create;
|
|
end;
|
|
|
|
destructor TExternalTool.Destroy;
|
|
var
|
|
OldThread: TExternalToolThread;
|
|
begin
|
|
//debugln(['TExternalTool.Destroy ',Title]);
|
|
EnterCriticalSection;
|
|
try
|
|
FStage:=etsDestroying;
|
|
if Thread is TExternalToolThread then
|
|
begin
|
|
OldThread:=TExternalToolThread(Thread);
|
|
fThread:=nil;
|
|
OldThread.Tool:=nil;
|
|
end;
|
|
FreeAndNil(FProcess);
|
|
FreeAndNil(FWorkerOutput);
|
|
FreeAndNil(fExecuteBefore);
|
|
FreeAndNil(fExecuteAfter);
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TExternalTool.DoExecute;
|
|
// in main thread
|
|
|
|
function CheckError: boolean;
|
|
begin
|
|
if (FStage>=etsStopped) then exit(true);
|
|
if (ErrorMessage='') then exit(false);
|
|
debugln(['Error: (lazarus) [TExternalTool.DoExecute.CheckError] Error=',ErrorMessage]);
|
|
EnterCriticalSection;
|
|
try
|
|
if FStage>=etsStopped then exit(true);
|
|
FStage:=etsStopped;
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
CreateView;
|
|
NotifyHandlerStopped;
|
|
|
|
Result:=true;
|
|
end;
|
|
|
|
var
|
|
ExeFile: String;
|
|
i: Integer;
|
|
aParser: TExtToolParser;
|
|
begin
|
|
if Terminated then exit;
|
|
|
|
// set Stage to etsInitializing
|
|
EnterCriticalSection;
|
|
try
|
|
if Stage<>etsInit then
|
|
raise Exception.Create('TExternalTool.Execute: already initialized');
|
|
FStage:=etsInitializing;
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
|
|
// resolve macros
|
|
if ResolveMacrosOnExecute then
|
|
begin
|
|
if not ResolveMacros then begin
|
|
if ErrorMessage='' then
|
|
ErrorMessage:=lisFailedToResolveMacros;
|
|
if CheckError then exit;
|
|
end;
|
|
end;
|
|
|
|
// init CurrentDirectory
|
|
Process.CurrentDirectory:=TrimFilename(Process.CurrentDirectory);
|
|
if not FilenameIsAbsolute(Process.CurrentDirectory) then
|
|
Process.CurrentDirectory:=AppendPathDelim(GetCurrentDirUTF8)+Process.CurrentDirectory;
|
|
|
|
// init Executable
|
|
Process.Executable:=TrimFilename(Process.Executable);
|
|
{$IFDEF VerboseExtToolThread}
|
|
debugln(['TExternalTool.DoExecute Exe=',Process.Executable]);
|
|
{$ENDIF}
|
|
if not FilenameIsAbsolute(Process.Executable) then begin
|
|
if ExtractFilePath(Process.Executable)<>'' then
|
|
Process.Executable:=AppendPathDelim(GetCurrentDirUTF8)+Process.Executable
|
|
else if Process.Executable='' then begin
|
|
ErrorMessage:=Format(lisToolHasNoExecutable, [Title]);
|
|
CheckError;
|
|
exit;
|
|
end else begin
|
|
ExeFile:=FindDefaultExecutablePath(Process.Executable,GetCurrentDirUTF8);
|
|
if ExeFile='' then begin
|
|
ErrorMessage:=Format(lisCanNotFindExecutable, [Process.Executable]);
|
|
CheckError;
|
|
exit;
|
|
end;
|
|
Process.Executable:=ExeFile;
|
|
end;
|
|
end;
|
|
ExeFile:=Process.Executable;
|
|
if not FileExistsUTF8(ExeFile) then begin
|
|
ErrorMessage:=Format(lisMissingExecutable, [ExeFile]);
|
|
CheckError;
|
|
exit;
|
|
end;
|
|
if DirectoryExistsUTF8(ExeFile) then begin
|
|
ErrorMessage:=Format(lisExecutableIsADirectory, [ExeFile]);
|
|
CheckError;
|
|
exit;
|
|
end;
|
|
if not FileIsExecutable(ExeFile) then begin
|
|
ErrorMessage:=Format(lisExecutableLacksThePermissionToRun, [ExeFile]);
|
|
CheckError;
|
|
exit;
|
|
end;
|
|
|
|
// init misc
|
|
WorkerMessages.BaseDirectory:=Process.CurrentDirectory;
|
|
WorkerDirectory:=WorkerMessages.BaseDirectory;
|
|
if EnvironmentOverrides.Count>0 then
|
|
AssignEnvironmentTo(Process.Environment,EnvironmentOverrides);
|
|
|
|
// init parsers
|
|
for i:=0 to ParserCount-1 do begin
|
|
aParser:=Parsers[i];
|
|
try
|
|
aParser.Init;
|
|
except
|
|
on E: Exception do begin
|
|
ErrorMessage:=Format(lisParser, [DbgSName(aParser), E.Message]);
|
|
CheckError;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// set Stage to etsWaitingForStart
|
|
EnterCriticalSection;
|
|
try
|
|
if Stage<>etsInitializing then
|
|
raise Exception.Create('TExternalTool.Execute: bug in initialization');
|
|
FStage:=etsWaitingForStart;
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
end;
|
|
|
|
procedure TExternalTool.DoStart;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
// set Stage to etsStarting
|
|
EnterCriticalSection;
|
|
try
|
|
if Stage<>etsWaitingForStart then
|
|
raise Exception.Create('TExternalTool.Execute: already started');
|
|
FStage:=etsStarting;
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
|
|
CreateView;
|
|
|
|
// mark running
|
|
if Tools<>nil then
|
|
TExternalTools(Tools).AddRunningTool(Self);
|
|
|
|
// start thread
|
|
if Thread=nil then begin
|
|
FThread:=TExternalToolThread.Create(true);
|
|
Thread.Tool:=Self;
|
|
FThread.FreeOnTerminate:=false;
|
|
FThread.OnTerminate:=@TExternalTools(Tools).OnThreadTerminate;
|
|
end;
|
|
if ConsoleVerbosity>=0 then begin
|
|
debugln(['Info: (lazarus) Execute Title="',Title,'"']);
|
|
debugln(['Info: (lazarus) Working Directory="',Process.CurrentDirectory,'"']);
|
|
debugln(['Info: (lazarus) Executable="',Process.Executable,'"']);
|
|
for i:=0 to Process.Parameters.Count-1 do
|
|
debugln(['Info: (lazarus) Param[',i,']="',Process.Parameters[i],'"']);
|
|
for i:=0 to EnvironmentOverrides.Count-1 do
|
|
debugln(['Info: (lazarus) Env[',i,']="',LeftStr(DbgStr(EnvironmentOverrides[i]),100),'"']);
|
|
end;
|
|
Thread.Start;
|
|
end;
|
|
|
|
function TExternalTool.ExecuteBeforeCount: integer;
|
|
begin
|
|
Result:=fExecuteBefore.Count;
|
|
end;
|
|
|
|
function TExternalTool.ExecuteAfterCount: integer;
|
|
begin
|
|
Result:=fExecuteAfter.Count;
|
|
end;
|
|
|
|
function TExternalTool.GetExecuteAfter(Index: integer): TAbstractExternalTool;
|
|
begin
|
|
Result:=TAbstractExternalTool(fExecuteAfter[Index]);
|
|
end;
|
|
|
|
function TExternalTool.GetExecuteBefore(Index: integer): TAbstractExternalTool;
|
|
begin
|
|
Result:=TAbstractExternalTool(fExecuteBefore[Index]);
|
|
end;
|
|
|
|
procedure TExternalTool.DoTerminate;
|
|
var
|
|
NeedProcTerminate: Boolean;
|
|
begin
|
|
NeedProcTerminate:=false;
|
|
EnterCriticalSection;
|
|
try
|
|
if ConsoleVerbosity>0 then
|
|
DebugLn(['Info: (lazarus) TExternalTool.DoTerminate ',Title,', Terminated=',Terminated,', Stage=',dbgs(Stage)]);
|
|
if Terminated then exit;
|
|
if Stage=etsStopped then exit;
|
|
|
|
if ErrorMessage='' then
|
|
ErrorMessage:=lisAborted;
|
|
fTerminated:=true;
|
|
if Stage=etsRunning then
|
|
NeedProcTerminate:=true;
|
|
if Stage<etsStarting then
|
|
FStage:=etsStopped
|
|
else if Stage<=etsRunning then
|
|
FStage:=etsWaitingForStop;
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
if NeedProcTerminate and (Process<>nil) then begin
|
|
if ConsoleVerbosity>0 then
|
|
DebugLn(['Info: (lazarus) TExternalTool.DoTerminate ',Title,'. Terminating the process.']);
|
|
Process.Terminate(AbortedExitCode);
|
|
end;
|
|
end;
|
|
|
|
procedure TExternalTool.Notification(AComponent: TComponent; Operation: TOperation);
|
|
begin
|
|
inherited Notification(AComponent, Operation);
|
|
if Operation=opRemove then begin
|
|
if fExecuteBefore<>nil then
|
|
fExecuteBefore.Remove(AComponent);
|
|
if fExecuteAfter<>nil then
|
|
fExecuteAfter.Remove(AComponent);
|
|
end;
|
|
end;
|
|
|
|
function TExternalTool.CanFree: boolean;
|
|
begin
|
|
Result:=(FThread=nil) and inherited CanFree;
|
|
end;
|
|
|
|
function TExternalTool.IsExecutedBefore(Tool: TAbstractExternalTool): Boolean;
|
|
var
|
|
Visited: TFPList;
|
|
|
|
function Search(CurTool: TAbstractExternalTool): Boolean;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if CurTool=Tool then exit(true);
|
|
if Visited.IndexOf(CurTool)>=0 then exit(false);
|
|
Visited.Add(CurTool);
|
|
for i:=0 to CurTool.ExecuteBeforeCount-1 do
|
|
if Search(CurTool.ExecuteBefore[i]) then exit(true);
|
|
Result:=false;
|
|
end;
|
|
|
|
begin
|
|
Result:=false;
|
|
if Tool=Self then exit;
|
|
Visited:=TFPList.Create;
|
|
try
|
|
Result:=Search(Self);
|
|
finally
|
|
Visited.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TExternalTool.AddExecuteBefore(Tool: TAbstractExternalTool);
|
|
begin
|
|
//debugln(['TExternalTool.AddExecuteBefore Self=',Title,' Tool=',Tool.Title]);
|
|
if (Tool=Self) or (Tool.IsExecutedBefore(Self)) then
|
|
raise Exception.Create('TExternalTool.AddExecuteBefore: that would create a circle');
|
|
if (fExecuteBefore<>nil) and (fExecuteBefore.IndexOf(Tool)<0) then
|
|
fExecuteBefore.Add(Tool);
|
|
if (TExternalTool(Tool).fExecuteAfter<>nil)
|
|
and (TExternalTool(Tool).fExecuteAfter.IndexOf(Self)<=0) then
|
|
TExternalTool(Tool).fExecuteAfter.Add(Self);
|
|
end;
|
|
|
|
function TExternalTool.CanStart: boolean;
|
|
var
|
|
i: Integer;
|
|
ExecBefore: TAbstractExternalTool;
|
|
begin
|
|
Result:=false;
|
|
//debugln(['TExternalTool.CanStart ',Title,' ',dbgs(Stage)]);
|
|
if Stage<>etsWaitingForStart then exit;
|
|
if Terminated then exit;
|
|
for i:=0 to ExecuteBeforeCount-1 do begin
|
|
ExecBefore:=ExecuteBefore[i];
|
|
if ord(ExecBefore.Stage)<ord(etsStopped) then exit;
|
|
if ExecBefore.ErrorMessage<>'' then exit;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function TExternalTool.GetLongestEstimatedLoad: int64;
|
|
type
|
|
TInfo = record
|
|
Load: int64;
|
|
end;
|
|
PInfo = ^TInfo;
|
|
var
|
|
ToolToInfo: TPointerToPointerTree;
|
|
|
|
function GetLoad(Tool: TExternalTool): int64;
|
|
var
|
|
Info: PInfo;
|
|
i: Integer;
|
|
begin
|
|
Info:=PInfo(ToolToInfo[Tool]);
|
|
if Info<>nil then
|
|
Result:=Info^.Load
|
|
else begin
|
|
New(Info);
|
|
Info^.Load:=1;
|
|
ToolToInfo[Tool]:=Info;
|
|
Result:=0;
|
|
for i:=0 to Tool.ExecuteAfterCount-1 do
|
|
Result:=Max(Result,GetLoad(TExternalTool(Tool.ExecuteAfter[i])));
|
|
inc(Result,Tool.EstimatedLoad);
|
|
Info^.Load:=Result;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
Node: TAvlTreeNode;
|
|
Item: PPointerToPointerItem;
|
|
Info: PInfo;
|
|
begin
|
|
ToolToInfo:=TPointerToPointerTree.Create;
|
|
try
|
|
Result:=GetLoad(Self);
|
|
finally
|
|
Node:=ToolToInfo.Tree.FindLowest;
|
|
while Node<>nil do begin
|
|
Item:=PPointerToPointerItem(Node.Data);
|
|
Info:=PInfo(Item^.Value);
|
|
Dispose(Info);
|
|
Node:=ToolToInfo.Tree.FindSuccessor(Node);
|
|
end;
|
|
ToolToInfo.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TExternalTool.UserThreadRunning;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
EnterCriticalSection;
|
|
try
|
|
if FStage>etsRunning then exit;
|
|
FStage:=etsRunning;
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
for i:=0 to ParserCount-1 do
|
|
Parsers[i].InitReading;
|
|
end;
|
|
|
|
procedure TExternalTool.UserThreadStopped;
|
|
begin
|
|
ProcessStopped;
|
|
end;
|
|
|
|
procedure TExternalTool.Execute;
|
|
begin
|
|
if Stage<>etsInit then
|
|
raise Exception.Create('TExternalTool.Execute "'+Title+'" already started');
|
|
DoExecute;
|
|
if Stage<>etsWaitingForStart then
|
|
exit;
|
|
|
|
if Tools<>nil then
|
|
TExternalTools(Tools).Work
|
|
else
|
|
DoStart;
|
|
end;
|
|
|
|
procedure TExternalTool.Terminate;
|
|
begin
|
|
if Tools<>nil then
|
|
TExternalTools(Tools).Terminate(Self)
|
|
else
|
|
DoTerminate;
|
|
end;
|
|
|
|
procedure TExternalTool.WaitForExit;
|
|
var
|
|
MyTools: TExternalToolsBase;
|
|
begin
|
|
MyTools:=Tools;
|
|
repeat
|
|
EnterCriticalSection;
|
|
try
|
|
if Stage=etsDestroying then exit;
|
|
if (Stage=etsStopped) and (FindUnfinishedView=nil) then exit;
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
// call synchronized tasks, this might free this tool
|
|
if MainThreadID=ThreadID then
|
|
begin
|
|
Assert(Owner is TExternalToolsBase, 'TExternalTool.WaitForExit: Owner is not TExternalToolsBase.');
|
|
TExternalToolsBase(Owner).HandleMessages;
|
|
end;
|
|
Assert(Assigned(ExternalToolList), 'TExternalTool.WaitForExit: ExternalToolList=Nil.');
|
|
// check if this tool still exists
|
|
if MyTools.IndexOf(Self)<0 then exit;
|
|
// still running => wait
|
|
Sleep(50);
|
|
until false;
|
|
end;
|
|
|
|
function TExternalTool.ResolveMacros: boolean;
|
|
|
|
function Resolve(const aValue: string; out NewValue: string): boolean;
|
|
begin
|
|
NewValue:=aValue;
|
|
Result:=IDEMacros.SubstituteMacros(NewValue);
|
|
if Result then exit;
|
|
if ErrorMessage='' then
|
|
ErrorMessage:=Format(lisInvalidMacrosIn, [aValue]);
|
|
LazMessageWorker(lisCCOErrorCaption, Format(lisInvalidMacrosInExternalTool,
|
|
[aValue, Title]),
|
|
mtError,[mbCancel]);
|
|
end;
|
|
|
|
var
|
|
i: Integer;
|
|
s: string;
|
|
begin
|
|
if IDEMacros=nil then exit(true);
|
|
Result:=false;
|
|
|
|
if not Resolve(Process.CurrentDirectory,s) then exit;
|
|
Process.CurrentDirectory:=s;
|
|
|
|
if not Resolve(Process.Executable,s) then exit;
|
|
Process.Executable:=s;
|
|
|
|
for i:=0 to Process.Parameters.Count-1 do begin
|
|
if not Resolve(Process.Parameters[i],s) then exit;
|
|
Process.Parameters[i]:=s;
|
|
end;
|
|
|
|
for i:=0 to EnvironmentOverrides.Count-1 do begin
|
|
if not Resolve(EnvironmentOverrides[i],s) then exit;
|
|
EnvironmentOverrides[i]:=s;
|
|
end;
|
|
|
|
Result:=true;
|
|
end;
|
|
|
|
procedure TExternalTool.RemoveExecuteBefore(Tool: TAbstractExternalTool);
|
|
begin
|
|
if fExecuteBefore<>nil then
|
|
fExecuteBefore.Remove(Tool);
|
|
if TExternalTool(Tool).fExecuteAfter<>nil then
|
|
TExternalTool(Tool).fExecuteAfter.Remove(Self);
|
|
end;
|
|
|
|
{ TExternalTools }
|
|
|
|
function TExternalTools.RunExtToolHandler(ToolOptions: TIDEExternalToolOptions): boolean;
|
|
begin
|
|
{$IFDEF VerboseExtToolThread}
|
|
debugln(['TExternalTools.RunExtToolHandler ',ToolOptions.Title,
|
|
' exe="',ToolOptions.Executable,'" params="',ToolOptions.CmdLineParams,'"']);
|
|
{$ENDIF}
|
|
if ToolOptions.Parsers.Count=0 then
|
|
Result := RunToolAndDetach(ToolOptions)
|
|
else
|
|
Result := RunToolWithParsers(ToolOptions)
|
|
end;
|
|
|
|
function TExternalTools.RunToolAndDetach(ToolOptions: TIDEExternalToolOptions): boolean;
|
|
// simply run and detach
|
|
|
|
procedure Err(Msg: string);
|
|
begin
|
|
debugln(['Error: (lazarus) [TExternalTools.RunToolAndDetach] ',ToolOptions.Title,' failed: ',Msg]);
|
|
// ToDo: show user
|
|
end;
|
|
|
|
var
|
|
i: Integer;
|
|
Proc: TProcessUTF8;
|
|
sl: TStringList;
|
|
s, Path: String;
|
|
begin
|
|
Result:=false;
|
|
Proc:=TProcessUTF8.Create(nil);
|
|
try
|
|
Proc.InheritHandles:=false;
|
|
// working directory
|
|
s:=ToolOptions.WorkingDirectory;
|
|
if ToolOptions.ResolveMacros then begin
|
|
if not GlobalMacroList.SubstituteStr(s) then begin
|
|
Err('macros of WorkingDirectory: "'+ToolOptions.WorkingDirectory+'"');
|
|
exit;
|
|
end;
|
|
end;
|
|
s:=ChompPathDelim(CleanAndExpandDirectory(s));
|
|
if not DirectoryExistsUTF8(s) then begin
|
|
Err('missing directory "'+s+'"');
|
|
exit;
|
|
end;
|
|
Proc.CurrentDirectory:=s;
|
|
|
|
// environment
|
|
if ToolOptions.EnvironmentOverrides.Count>0 then
|
|
AssignEnvironmentTo(Proc.Environment,ToolOptions.EnvironmentOverrides);
|
|
if ToolOptions.ResolveMacros then begin
|
|
for i:=0 to Proc.Environment.Count-1 do begin
|
|
s:=Proc.Environment[i];
|
|
if not GlobalMacroList.SubstituteStr(s) then begin
|
|
Err('environment override "'+Proc.Environment[i]+'"');
|
|
exit;
|
|
end;
|
|
Proc.Environment[i]:=s;
|
|
end;
|
|
end;
|
|
|
|
// executable
|
|
s:=ToolOptions.Executable;
|
|
if ToolOptions.ResolveMacros then begin
|
|
if not GlobalMacroList.SubstituteStr(s) then begin
|
|
Err('invalid macros in Executable: "'+ToolOptions.Executable+'"');
|
|
exit;
|
|
end;
|
|
end;
|
|
if not FilenameIsAbsolute(s) then begin
|
|
// search in PATH
|
|
if Proc.Environment.Count>0 then
|
|
Path:=Proc.Environment.Values['PATH']
|
|
else
|
|
Path:=GetEnvironmentVariableUTF8('PATH');
|
|
s:=SearchFileInPath(s,Proc.CurrentDirectory,
|
|
Path, PathSeparator, sffFindProgramInPath);
|
|
{$IFDEF Windows}
|
|
if (s='') and (ExtractFileExt(s)='') then begin
|
|
s:=SearchFileInPath(s+'.exe',Proc.CurrentDirectory,
|
|
Path, PathSeparator,
|
|
sffFindProgramInPath);
|
|
end;
|
|
{$ENDIF}
|
|
if s='' then begin
|
|
Err('missing executable "'+ToolOptions.Executable+'"');
|
|
exit;
|
|
end;
|
|
end;
|
|
if not FilenameIsAbsolute(s) then begin
|
|
Err('missing executable: "'+s+'"');
|
|
exit;
|
|
end;
|
|
if not FileExistsUTF8(s) then begin
|
|
Err('missing executable: "'+s+'"');
|
|
exit;
|
|
end;
|
|
if DirectoryExistsUTF8(s) {$IFDEF DARWIN}and (ExtractFileExt(s)<>'.app'){$ENDIF} then begin
|
|
Err('executable is a directory: "'+s+'"');
|
|
exit;
|
|
end;
|
|
if {$IFDEF DARWIN}(ExtractFileExt(s)<>'.app') and{$ENDIF} not FileIsExecutable(s) then begin
|
|
Err('executable lacks permission to run: "'+s+'"');
|
|
exit;
|
|
end;
|
|
|
|
{$IFDEF DARWIN}
|
|
if DirectoryExistsUTF8(s) then
|
|
begin
|
|
Proc.Executable:='/usr/bin/open';
|
|
s:=s+LineEnding+ToolOptions.CmdLineParams;
|
|
end
|
|
else
|
|
{$ENDIF}
|
|
begin
|
|
Proc.Executable:=s;
|
|
s:=ToolOptions.CmdLineParams;
|
|
end;
|
|
|
|
// params
|
|
if ToolOptions.ResolveMacros and not GlobalMacroList.SubstituteStr(s) then begin
|
|
Err('invalid macros in cmd line params "'+ToolOptions.CmdLineParams+'"');
|
|
exit;
|
|
end;
|
|
sl:=TStringList.Create;
|
|
try
|
|
SplitCmdLineParams(s,sl);
|
|
Proc.Parameters:=sl;
|
|
finally
|
|
sl.Free;
|
|
end;
|
|
|
|
// run and detach
|
|
if ToolOptions.ShowConsole then
|
|
Proc.Options:=Proc.Options+[poNewConsole]-[poNoConsole]
|
|
else
|
|
Proc.Options:=Proc.Options-[poNewConsole]+[poNoConsole];
|
|
if ToolOptions.HideWindow then
|
|
Proc.ShowWindow:=swoHide
|
|
else
|
|
Proc.ShowWindow:=swoShow;
|
|
try
|
|
Proc.Execute;
|
|
Result:=true;
|
|
except
|
|
end;
|
|
finally
|
|
Proc.Free;
|
|
end;
|
|
end;
|
|
|
|
function TExternalTools.RunToolWithParsers(ToolOptions: TIDEExternalToolOptions): boolean;
|
|
// run with parsers and messages
|
|
var
|
|
Tool: TAbstractExternalTool;
|
|
i: Integer;
|
|
begin
|
|
{$IFDEF VerboseExtToolThread}
|
|
debugln(['TExternalTools.RunToolWithParsers run with scanners ...']);
|
|
{$ENDIF}
|
|
Result:=false;
|
|
Tool:=Add(ToolOptions.Title);
|
|
Tool.Reference(Self,ClassName);
|
|
try
|
|
Tool.Hint:=ToolOptions.Hint;
|
|
Tool.Process.CurrentDirectory:=ToolOptions.WorkingDirectory;
|
|
Tool.Process.Executable:=ToolOptions.Executable;
|
|
Tool.CmdLineParams:=ToolOptions.CmdLineParams;
|
|
Tool.EnvironmentOverrides:=ToolOptions.EnvironmentOverrides;
|
|
Assert(Assigned(ToolOptions.Parsers), 'TExternalTools.RunToolWithParsers: Parsers=Nil.');
|
|
for i:=0 to ToolOptions.Parsers.Count-1 do
|
|
Tool.AddParsers(ToolOptions.Parsers[i]);
|
|
if ToolOptions.ShowConsole then
|
|
Tool.Process.Options:=Tool.Process.Options+[poNewConsole]-[poNoConsole]
|
|
else
|
|
Tool.Process.Options:=Tool.Process.Options-[poNewConsole]+[poNoConsole];
|
|
if ToolOptions.HideWindow then
|
|
Tool.Process.ShowWindow:=swoHide
|
|
else
|
|
Tool.Process.ShowWindow:=swoShow;
|
|
Tool.MaxIdleInMS:=ToolOptions.MaxIdleInMS;
|
|
if ToolOptions.ResolveMacros and not Tool.ResolveMacros then begin
|
|
debugln(['Error: (lazarus) [TExternalTools.RunToolWithParsers] failed to resolve macros']);
|
|
exit;
|
|
end;
|
|
{$IFDEF VerboseExtToolThread}
|
|
debugln(['TExternalTools.RunToolWithParsers Execute ',Tool.Title,' WD="',Tool.Process.CurrentDirectory,'" Exe="',Tool.Process.Executable,'" Params="',Tool.CmdLineParams,'" ...']);
|
|
{$ENDIF}
|
|
Tool.Execute;
|
|
{$IFDEF VerboseExtToolThread}
|
|
debugln(['TExternalTools.RunToolWithParsers WaitForExit ',Tool.Title,' ...']);
|
|
{$ENDIF}
|
|
Tool.WaitForExit;
|
|
{$IFDEF VerboseExtToolThread}
|
|
debugln(['TExternalTools.RunToolWithParsers Done ',Tool.Title]);
|
|
{$ENDIF}
|
|
Result:=(Tool.ErrorMessage='') and (not Tool.Terminated) and (Tool.ExitStatus=0);
|
|
finally
|
|
Tool.Release(Self);
|
|
end;
|
|
end;
|
|
|
|
procedure TExternalTools.FreeFinishedThreads;
|
|
var
|
|
i: Integer;
|
|
aThread: TExternalToolThread;
|
|
begin
|
|
for i:=fOldThreads.Count-1 downto 0 do
|
|
begin
|
|
aThread:=TExternalToolThread(fOldThreads[i]);
|
|
if aThread.Finished then
|
|
begin
|
|
fOldThreads.Delete(i);
|
|
aThread.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TExternalTools.OnThreadTerminate(Sender: TObject);
|
|
begin
|
|
AddOldThread(TExternalToolThread(Sender));
|
|
end;
|
|
|
|
procedure TExternalTools.AddOldThread(aThread: TExternalToolThread);
|
|
var
|
|
OldTool: TExternalTool;
|
|
begin
|
|
OldTool:=aThread.Tool;
|
|
aThread.Tool:=nil;
|
|
if fOldThreads.IndexOf(aThread)<0 then
|
|
fOldThreads.Add(aThread);
|
|
|
|
if OldTool<>nil then
|
|
OldTool.AutoFree;
|
|
end;
|
|
|
|
function TExternalTools.GetRunningTools(Index: integer): TExternalTool;
|
|
begin
|
|
EnterCriticalSection;
|
|
try
|
|
Result:=TExternalTool(fRunning[Index]);
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
end;
|
|
|
|
procedure TExternalTools.AddRunningTool(Tool: TExternalTool);
|
|
begin
|
|
EnterCriticalSection;
|
|
try
|
|
if fRunning.IndexOf(Tool)<0 then
|
|
fRunning.Add(Tool);
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
end;
|
|
|
|
procedure TExternalTools.RemoveRunningTool(Tool: TExternalTool);
|
|
begin
|
|
EnterCriticalSection;
|
|
try
|
|
fRunning.Remove(Tool);
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
end;
|
|
|
|
function TExternalTools.GetParsers(Index: integer): TExtToolParserClass;
|
|
begin
|
|
Result:=TExtToolParserClass(fParsers[Index]);
|
|
end;
|
|
|
|
procedure TExternalTools.Notification(AComponent: TComponent; Operation: TOperation);
|
|
begin
|
|
inherited Notification(AComponent, Operation);
|
|
if Operation=opRemove then begin
|
|
EnterCriticalSection;
|
|
try
|
|
if fItems<>nil then
|
|
fItems.Remove(AComponent);
|
|
if fRunning<>nil then
|
|
fRunning.Remove(AComponent);
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
constructor TExternalTools.Create(aOwner: TComponent);
|
|
begin
|
|
inherited Create(aOwner);
|
|
InitCriticalSection(FCritSec);
|
|
fRunning:=TFPList.Create;
|
|
fParsers:=TFPList.Create;
|
|
fOldThreads:=TFPList.Create;
|
|
MaxProcessCount:=DefaultMaxProcessCount;
|
|
RunExternalTool := @RunExtToolHandler;
|
|
end;
|
|
|
|
destructor TExternalTools.Destroy;
|
|
begin
|
|
RunExternalTool:=nil;
|
|
TerminateAll;
|
|
EnterCriticalSection;
|
|
try
|
|
if fRunning.Count>0 then
|
|
raise Exception.Create('TExternalTools.Destroy some tools still running');
|
|
inherited Destroy;
|
|
FreeAndNil(fRunning);
|
|
FreeAndNil(fParsers);
|
|
FreeAndNil(fOldThreads);
|
|
finally
|
|
LeaveCriticalSection;
|
|
end;
|
|
DoneCriticalsection(FCritSec);
|
|
end;
|
|
|
|
function TExternalTools.Add(Title: string): TAbstractExternalTool;
|
|
begin
|
|
Result:=FToolClass.Create(Self);
|
|
Result.Title:=Title;
|
|
fItems.Add(Result);
|
|
end;
|
|
|
|
function TExternalTools.IndexOf(Tool: TAbstractExternalTool): integer;
|
|
begin
|
|
Result:=fItems.IndexOf(Tool);
|
|
end;
|
|
|
|
function TExternalTools.ParserCount: integer;
|
|
begin
|
|
Result:=fParsers.Count;
|
|
end;
|
|
|
|
procedure TExternalTools.Work;
|
|
var
|
|
Tool: TExternalTool;
|
|
begin
|
|
while RunningCount<MaxProcessCount do begin
|
|
Tool:=FindNextToolToStart;
|
|
if Tool=nil then
|
|
break;
|
|
Tool.DoStart;
|
|
end;
|
|
FreeFinishedThreads;
|
|
end;
|
|
|
|
function TExternalTools.FindNextToolToStart: TExternalTool;
|
|
var
|
|
Tool: TExternalTool;
|
|
CurLoad: Int64;
|
|
Load: Int64;
|
|
i: Integer;
|
|
begin
|
|
Result:=nil;
|
|
Load:=0;
|
|
for i:=0 to Count-1 do begin
|
|
Tool:=TExternalTool(Items[i]);
|
|
//debugln(['TExternalTools.FindNextToolToExec ',Tool.Title,' ',Tool.CanStart]);
|
|
if not Tool.CanStart then continue;
|
|
CurLoad:=Tool.GetLongestEstimatedLoad;
|
|
if (Result<>nil) and (Load>=CurLoad) then Continue;
|
|
Result:=Tool;
|
|
Load:=CurLoad;
|
|
end;
|
|
end;
|
|
|
|
procedure TExternalTools.Terminate(Tool: TExternalTool);
|
|
begin
|
|
if Tool=nil then exit;
|
|
Tool.DoTerminate;
|
|
end;
|
|
|
|
procedure TExternalTools.TerminateAll;
|
|
// terminate all current tools
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i:=Count-1 downto 0 do
|
|
Terminate(Items[i] as TExternalTool);
|
|
FreeFinishedThreads;
|
|
end;
|
|
|
|
procedure TExternalTools.Clear;
|
|
begin
|
|
TerminateAll;
|
|
while Count>0 do
|
|
Items[0].Free;
|
|
end;
|
|
|
|
function TExternalTools.RunningCount: integer;
|
|
begin
|
|
Result:=fRunning.Count;
|
|
end;
|
|
|
|
procedure TExternalTools.EnterCriticalSection;
|
|
begin
|
|
System.EnterCriticalsection(FCritSec);
|
|
end;
|
|
|
|
procedure TExternalTools.LeaveCriticalSection;
|
|
begin
|
|
System.LeaveCriticalsection(FCritSec);
|
|
end;
|
|
|
|
procedure TExternalTools.RegisterParser(Parser: TExtToolParserClass);
|
|
begin
|
|
if fParsers.IndexOf(Parser)>=0 then exit;
|
|
fParsers.Add(Parser);
|
|
end;
|
|
|
|
procedure TExternalTools.UnregisterParser(Parser: TExtToolParserClass);
|
|
begin
|
|
if fParsers=nil then exit;
|
|
fParsers.Remove(Parser);
|
|
end;
|
|
|
|
function TExternalTools.FindParserForTool(const SubTool: string): TExtToolParserClass;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i:=0 to fParsers.Count-1 do begin
|
|
Result:=TExtToolParserClass(fParsers[i]);
|
|
if Result.CanParseSubTool(SubTool) then exit;
|
|
end;
|
|
Result:=nil;
|
|
end;
|
|
|
|
function TExternalTools.FindParserWithName(const ParserName: string): TExtToolParserClass;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i:=0 to fParsers.Count-1 do begin
|
|
Result:=TExtToolParserClass(fParsers[i]);
|
|
if SameText(Result.GetParserName,ParserName) then exit;
|
|
end;
|
|
Result:=nil;
|
|
end;
|
|
|
|
function TExternalTools.GetMsgTool(Msg: TMessageLine): TAbstractExternalTool;
|
|
var
|
|
CurOwner: TObject;
|
|
View: TExtToolView;
|
|
begin
|
|
Result:=nil;
|
|
if (Msg=nil) or (Msg.Lines=nil) then exit;
|
|
CurOwner:=Msg.Lines.Owner;
|
|
if CurOwner=nil then exit;
|
|
if CurOwner is TAbstractExternalTool then
|
|
Result:=TAbstractExternalTool(CurOwner)
|
|
else if CurOwner is TExtToolView then begin
|
|
View:=TExtToolView(CurOwner);
|
|
Result:=View.Tool;
|
|
end;
|
|
end;
|
|
|
|
{ TExternalToolThread }
|
|
|
|
procedure TExternalToolThread.SetTool(AValue: TExternalTool);
|
|
var
|
|
OldTool: TExternalTool;
|
|
begin
|
|
if FTool=AValue then Exit;
|
|
OldTool:=FTool;
|
|
FTool:=nil;
|
|
if OldTool<>nil then
|
|
OldTool.Thread:=nil;
|
|
if AValue<>nil then
|
|
begin
|
|
FTool:=AValue;
|
|
if FTool<>nil then
|
|
FTool.Thread:=Self;
|
|
end;
|
|
end;
|
|
|
|
procedure TExternalToolThread.Execute;
|
|
type
|
|
TErrorFrame = record
|
|
Addr: Pointer;
|
|
Line: shortstring;
|
|
end;
|
|
PErrorFrame = ^TErrorFrame;
|
|
|
|
var
|
|
ErrorFrames: array[0..30] of TErrorFrame;
|
|
ErrorFrameCount: integer;
|
|
|
|
function GetExceptionStackTrace: string;
|
|
var
|
|
FrameCount: LongInt;
|
|
Frames: PPointer;
|
|
Cnt: LongInt;
|
|
f: PErrorFrame;
|
|
i: Integer;
|
|
begin
|
|
Result:='';
|
|
FrameCount:=ExceptFrameCount;
|
|
Frames:=ExceptFrames;
|
|
ErrorFrames[0].Addr:=ExceptAddr;
|
|
ErrorFrames[0].Line:='';
|
|
ErrorFrameCount:=1;
|
|
Cnt:=FrameCount;
|
|
for i:=1 to Cnt do begin
|
|
ErrorFrames[i].Addr:=Frames[i-1];
|
|
ErrorFrames[i].Line:='';
|
|
ErrorFrameCount:=i+1;
|
|
end;
|
|
for i:=0 to ErrorFrameCount-1 do begin
|
|
f:=@ErrorFrames[i];
|
|
try
|
|
f^.Line:=copy(BackTraceStrFunc(f^.Addr),1,255);
|
|
except
|
|
f^.Line:=copy(SysBackTraceStr(f^.Addr),1,255);
|
|
end;
|
|
end;
|
|
for i:=0 to ErrorFrameCount-1 do begin
|
|
Result+=ErrorFrames[i].Line+LineEnding;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
Buf: string;
|
|
|
|
function ReadInputPipe(aStream: TInputPipeStream; var LineBuf: string;
|
|
IsStdErr: boolean): boolean;
|
|
// true if some bytes have been read
|
|
var
|
|
Count: DWord;
|
|
StartPos: Integer;
|
|
i: DWord;
|
|
begin
|
|
Result:=false;
|
|
if aStream=nil then exit;
|
|
Count:=aStream.NumBytesAvailable;
|
|
if Count=0 then exit;
|
|
Count:=aStream.Read(Buf[1],Min(length(Buf),Count));
|
|
if Count=0 then exit;
|
|
Result:=true;
|
|
StartPos:=1;
|
|
i:=1;
|
|
while i<=Count do begin
|
|
if Buf[i] in [#10,#13] then begin
|
|
LineBuf:=LineBuf+copy(Buf,StartPos,i-StartPos);
|
|
if IsStdErr then
|
|
fLines.AddObject(LineBuf,fLines)
|
|
else
|
|
fLines.Add(LineBuf);
|
|
LineBuf:='';
|
|
if (i<Count) and (Buf[i+1] in [#10,#13]) and (Buf[i]<>Buf[i+1])
|
|
then
|
|
inc(i);
|
|
StartPos:=i+1;
|
|
end;
|
|
inc(i);
|
|
end;
|
|
LineBuf:=LineBuf+copy(Buf,StartPos,Count-StartPos+1);
|
|
end;
|
|
|
|
const
|
|
UpdateTimeDiff = 1000 div 5; // update five times a second, even if there is still work
|
|
var
|
|
{$IFDEF VerboseExtToolThread}
|
|
Title: String;
|
|
{$ENDIF}
|
|
OutputLine, StdErrLine: String;
|
|
LastUpdate: QWord;
|
|
ErrMsg: String;
|
|
ok: Boolean;
|
|
HasOutput: Boolean;
|
|
IdleCount: Integer;
|
|
begin
|
|
{$IFDEF VerboseExtToolThread}
|
|
Title:=Tool.Title;
|
|
{$ENDIF}
|
|
if Tool.Thread<>Self then
|
|
raise Exception.Create('');
|
|
SetLength(Buf{%H-},4096);
|
|
ErrorFrameCount:=0;
|
|
fLines:=TStringList.Create;
|
|
try
|
|
try
|
|
if Tool.Stage<>etsStarting then begin
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' Tool.Stage=',dbgs(Tool.Stage),' aborting']);
|
|
{$ENDIF}
|
|
exit;
|
|
end;
|
|
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' check executing "',Tool.Process.Executable,'" ...']);
|
|
{$ENDIF}
|
|
|
|
if not FileIsExecutable(Tool.Process.Executable) then begin
|
|
Tool.ErrorMessage:=Format(lisCanNotExecute, [Tool.Process.Executable]);
|
|
Tool.ProcessStopped;
|
|
exit;
|
|
end;
|
|
if not DirectoryExistsUTF8(ChompPathDelim(Tool.Process.CurrentDirectory))
|
|
then begin
|
|
Tool.ErrorMessage:=Format(lisMissingDirectory, [Tool.Process.
|
|
CurrentDirectory]);
|
|
Tool.ProcessStopped;
|
|
exit;
|
|
end;
|
|
|
|
// Under Unix TProcess uses fpFork, which means the current thread is
|
|
// duplicated. One is the old thread and one runs fpExecve.
|
|
// If fpExecve runs, then it will not return.
|
|
// If fpExecve fails it returns via an exception and this thread runs twice.
|
|
ok:=false;
|
|
try
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' execute ...']);
|
|
{$ENDIF}
|
|
// now execute
|
|
Tool.Process.PipeBufferSize:=Max(Tool.Process.PipeBufferSize,64*1024);
|
|
Tool.Process.Execute;
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' executing ...']);
|
|
{$ENDIF}
|
|
ok:=true;
|
|
except
|
|
on E: Exception do begin
|
|
// BEWARE: we are now either in the normal thread or in the failed forked thread
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' execute failed: ',E.Message]);
|
|
{$ENDIF}
|
|
if Tool.ErrorMessage='' then
|
|
Tool.ErrorMessage:=Format(lisUnableToExecute, [E.Message]);
|
|
end;
|
|
end;
|
|
// BEWARE: we are now either in the normal thread or in the failed forked thread
|
|
if not ok then begin
|
|
Tool.ProcessStopped;
|
|
exit;
|
|
end;
|
|
// we are now in the normal thread
|
|
if Tool.Stage>=etsStopped then
|
|
exit;
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' ProcessRunning ...']);
|
|
{$ENDIF}
|
|
Tool.ProcessRunning;
|
|
if Tool.Stage>=etsStopped then
|
|
exit;
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' reading ...']);
|
|
{$ENDIF}
|
|
|
|
OutputLine:='';
|
|
StdErrLine:='';
|
|
LastUpdate:=GetTickCount64;
|
|
IdleCount:=0;
|
|
while (Tool<>nil) and (Tool.Stage=etsRunning) do begin
|
|
if Tool.ReadStdOutBeforeErr then begin
|
|
HasOutput:=ReadInputPipe(Tool.Process.Output,OutputLine,false)
|
|
or ReadInputPipe(Tool.Process.Stderr,StdErrLine,true);
|
|
end else begin
|
|
HasOutput:=ReadInputPipe(Tool.Process.Stderr,StdErrLine,true)
|
|
or ReadInputPipe(Tool.Process.Output,OutputLine,false);
|
|
end;
|
|
if HasOutput then
|
|
IdleCount:=0
|
|
else begin
|
|
// no more pending output
|
|
if not Tool.Process.Running then break;
|
|
end;
|
|
if (fLines.Count>0)
|
|
and (Abs(int64(GetTickCount64)-LastUpdate)>UpdateTimeDiff) then begin
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' ',TimeToStr(Now),' ',IntToStr(GetTickCount64),' AddOutputLines ...']);
|
|
{$ENDIF}
|
|
Tool.AddOutputLines(fLines);
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' AddOutputLines ok']);
|
|
{$ENDIF}
|
|
fLines.Clear;
|
|
LastUpdate:=GetTickCount64;
|
|
end;
|
|
if (not HasOutput) then begin
|
|
// no more pending output and process is still running
|
|
// => tool needs some time
|
|
if Tool.MaxIdleInMS>0 then begin
|
|
if IdleCount>Tool.MaxIdleInMS then break;
|
|
Sleep(20);
|
|
inc(IdleCount,20);
|
|
end else begin
|
|
Sleep(50);
|
|
end;
|
|
end;
|
|
end;
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' end reading']);
|
|
{$ENDIF}
|
|
// add rest of output
|
|
if (OutputLine<>'') then
|
|
fLines.Add(OutputLine);
|
|
if (StdErrLine<>'') then
|
|
fLines.Add(StdErrLine);
|
|
if (Tool<>nil) and (fLines.Count>0) then begin
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' final AddOutputLines ...']);
|
|
{$ENDIF}
|
|
Tool.AddOutputLines(fLines);
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' final AddOutputLines ok']);
|
|
{$ENDIF}
|
|
fLines.Clear;
|
|
end;
|
|
try
|
|
if Tool.Stage>=etsStopped then begin
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' not reading exit status, because already stopped']);
|
|
{$ENDIF}
|
|
exit;
|
|
end;
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' reading exit status ...']);
|
|
{$ENDIF}
|
|
if (Tool.MaxIdleInMS<1) or (IdleCount<Tool.MaxIdleInMS) then
|
|
begin
|
|
Tool.ExitStatus:=Tool.Process.ExitStatus;
|
|
Tool.ExitCode:=Tool.Process.ExitCode;
|
|
end;
|
|
{$IFDEF VerboseExtToolThread}
|
|
if Tool.ExitStatus<>0 then
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' exit status=',Tool.ExitStatus,' ExitCode=',Tool.ExitCode]);
|
|
{$ENDIF}
|
|
except
|
|
Tool.ErrorMessage:=lisUnableToReadProcessExitStatus;
|
|
end;
|
|
except
|
|
on E: Exception do begin
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' run: ',E.Message]);
|
|
{$ENDIF}
|
|
if (Tool<>nil) and (Tool.ErrorMessage='') then begin
|
|
Tool.ErrorMessage:=E.Message;
|
|
ErrMsg:=GetExceptionStackTrace;
|
|
{$IFDEF VerboseExtToolErrors}
|
|
DebuglnThreadLog(ErrMsg);
|
|
{$ENDIF}
|
|
Tool.ErrorMessage:=E.Message+LineEnding+ErrMsg;
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
{$IFDEF VerboseExtToolThread}
|
|
if fLines<>nil then
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' cleaning up']);
|
|
{$ENDIF}
|
|
// clean up
|
|
try
|
|
FreeAndNil(fLines);
|
|
except
|
|
on E: Exception do begin
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute adding pending messages: ',E.Message]);
|
|
{$ENDIF}
|
|
if Tool<>nil then
|
|
Tool.ErrorMessage:=Format(lisFreeingBufferLines, [E.Message]);
|
|
end;
|
|
end;
|
|
end;
|
|
if Tool.Stage>=etsStopped then begin
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' not cleaning up']);
|
|
{$ENDIF}
|
|
exit;
|
|
end;
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' ProcessStopped ...']);
|
|
{$ENDIF}
|
|
Tool.ProcessStopped;
|
|
{$IFDEF VerboseExtToolThread}
|
|
DebuglnThreadLog(['TExternalToolThread.Execute ',Title,' Thread END']);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TExternalToolThread.DebuglnThreadLog(const Args: array of string);
|
|
var
|
|
s, aFilename: String;
|
|
fs: TFileStream;
|
|
i: Integer;
|
|
begin
|
|
s:='['+IntToStr(PtrUint(GetThreadID))+'] ';
|
|
for i:=low(Args) to High(Args) do
|
|
s:=s+Args[i];
|
|
s:=s+sLineBreak;
|
|
aFilename:='TExternalToolThread-DebuglnThreadLog.txt';
|
|
if FileExists(aFilename) then
|
|
fs:=TFileStream.Create(aFilename,fmOpenWrite or fmShareDenyNone)
|
|
else
|
|
fs:=TFileStream.Create(aFilename,fmCreate or fmShareDenyNone);
|
|
try
|
|
fs.Seek(0,soEnd);
|
|
fs.Write(s[1],length(s));
|
|
finally
|
|
fs.Free;
|
|
end;
|
|
end;
|
|
|
|
destructor TExternalToolThread.Destroy;
|
|
begin
|
|
Tool:=nil;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
end.
|
|
|