mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-05 15:18:09 +02:00
1470 lines
46 KiB
ObjectPascal
1470 lines
46 KiB
ObjectPascal
{
|
|
/***************************************************************************
|
|
compiler.pp - Lazarus IDE unit
|
|
-------------------------------------
|
|
TCompiler is responsible for configuration and running
|
|
the Free Pascal Compiler.
|
|
|
|
|
|
Initial Revision : Sun Mar 28 23:15:32 CST 1999
|
|
|
|
|
|
***************************************************************************/
|
|
|
|
***************************************************************************
|
|
* *
|
|
* 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. *
|
|
* *
|
|
***************************************************************************
|
|
}
|
|
unit Compiler;
|
|
|
|
{$mode objfpc}
|
|
{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, Contnrs, StrUtils,
|
|
{$IF FPC_FULLVERSION >= 30200}System.{$ENDIF}UITypes,
|
|
// LazUtils
|
|
FPCAdds, LazUTF8, LazFileUtils, LazUtilities, LazLoggerBase,
|
|
// Codetools
|
|
DefineTemplates, LinkScanner, CodeToolManager,
|
|
// BuildIntf
|
|
IDEExternToolIntf,
|
|
// IdeIntf
|
|
IDEMsgIntf, LazIDEIntf,
|
|
// IdeConfig
|
|
TransferMacros,
|
|
// IDE
|
|
IDECmdLine, LazarusIDEStrConsts, CompilerOptions, Project;
|
|
|
|
type
|
|
TOnCmdLineCreate = procedure(var CmdLine: string; var Abort:boolean) of object;
|
|
|
|
{ TCompiler }
|
|
|
|
TCompiler = class(TObject)
|
|
private
|
|
FOnCmdLineCreate : TOnCmdLineCreate;
|
|
procedure WriteError(const Msg: string);
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
function Compile(AProject: TProject;
|
|
const WorkingDir, CompilerFilename, CompilerParams: string;
|
|
BuildAll, SkipLinking, SkipAssembler, CurrentDirectoryIsTestDir: boolean;
|
|
const aCompileHint: string): TModalResult;
|
|
end;
|
|
|
|
// Following classes are for compiler options parsed from "fpc -h" and "fpc -i".
|
|
|
|
TCompilerOptEditKind = (
|
|
oeGroup, // A header for a group
|
|
oeSet, // A header for a set
|
|
oeSetElem, // One char element of a set, use CheckBox
|
|
oeSetNumber, // Number element of a set, use Edit
|
|
oeBoolean, // True/False, typically use CheckBox
|
|
oeText, // Textual value
|
|
oeNumber, // Numeric value
|
|
oeList // Pre-defined list of choices
|
|
);
|
|
|
|
TCompilerOptGroup = class;
|
|
|
|
{ TCompilerOpt }
|
|
|
|
TCompilerOpt = class
|
|
private
|
|
fOwnerGroup: TCompilerOptGroup;
|
|
fId: integer; // Identification.
|
|
fOption: string; // Option with the leading '-'.
|
|
fSuffix: string; // <x> or similar suffix of option.
|
|
fValue: string; // Data entered by user, 'True' for Boolean.
|
|
fOrigLine: integer; // Original line in the input data.
|
|
fEditKind: TCompilerOptEditKind;
|
|
fDescription: string;
|
|
fIndentation: integer; // Indentation level in "fpc -h" output.
|
|
fVisible: Boolean; // Used for filtering.
|
|
fIgnored: Boolean; // Pretend this option does not exist.
|
|
fChoices: TStrings; // Choices got from "fpc -i"
|
|
procedure AddChoicesByOptOld;
|
|
function Comment: string;
|
|
procedure Filter(aFilter: string; aOnlySelected: Boolean);
|
|
function GenerateOptValue(aUseComments: Boolean): string;
|
|
procedure SetValue(aValue: string; aOrigLine: integer);
|
|
protected
|
|
procedure ParseEditKind; virtual;
|
|
function ParseOption(aDescr: string; aIndent: integer): Boolean; virtual;
|
|
public
|
|
constructor Create(aOwnerGroup: TCompilerOptGroup);
|
|
destructor Destroy; override;
|
|
function CalcLeft(aDefaultLeft, aLimit: integer): integer;
|
|
public
|
|
property Id: integer read fId;
|
|
property Option: string read fOption;
|
|
property Suffix: string read fSuffix;
|
|
property Value: string read fValue write fValue;
|
|
property EditKind: TCompilerOptEditKind read fEditKind;
|
|
property Description: string read fDescription;
|
|
property Indentation: integer read fIndentation;
|
|
property Visible: Boolean read fVisible write fVisible;
|
|
property Ignored: Boolean read fIgnored write fIgnored;
|
|
property Choices: TStrings read fChoices;
|
|
end;
|
|
|
|
TCompilerOptList = TObjectList;
|
|
TCompilerOptReader = class; // Forward reference
|
|
|
|
{ TCompilerOptGroup }
|
|
|
|
// Group with explanation header. Actual options are not defined here.
|
|
TCompilerOptGroup = class(TCompilerOpt)
|
|
private
|
|
fOwnerReader: TCompilerOptReader;
|
|
// List of options belonging to this group.
|
|
fCompilerOpts: TCompilerOptList;
|
|
fIncludeNegativeOpt: Boolean; // Each option has a variation with "NO" appended.
|
|
function OneCharOptions(aOptAndValue: string): TCompilerOpt;
|
|
protected
|
|
procedure ParseEditKind; override;
|
|
function ParseOption(aDescr: string; aIndent: integer): Boolean; override;
|
|
public
|
|
constructor Create(aOwnerReader: TCompilerOptReader; aOwnerGroup: TCompilerOptGroup);
|
|
destructor Destroy; override;
|
|
procedure Clear;
|
|
function FindOption(aOptStr: string): TCompilerOpt;
|
|
function FindOptionById(aId: integer): TCompilerOpt;
|
|
function SelectOption(aOptAndValue: string): Boolean;
|
|
procedure DeselectAll;
|
|
public
|
|
property CompilerOpts: TCompilerOptList read fCompilerOpts;
|
|
end;
|
|
|
|
{ TCompilerOptSet }
|
|
|
|
// A set of options. A combination of chars or numbers following the option char.
|
|
TCompilerOptSet = class(TCompilerOptGroup)
|
|
private
|
|
fCommonIndent: integer; // Common indentation for this group fixed during parse.
|
|
function SetNumberOpt(aValue: string): Boolean;
|
|
function SetBooleanOpt(aValue: string): Boolean;
|
|
protected
|
|
procedure AddOptions(aDescr: string; aIndent: integer);
|
|
procedure ParseEditKind; override;
|
|
public
|
|
constructor Create(aOwnerReader: TCompilerOptReader;
|
|
aOwnerGroup: TCompilerOptGroup; aCommonIndent: integer);
|
|
destructor Destroy; override;
|
|
function CollectSelectedOptions(aUseComments: Boolean): string;
|
|
procedure SelectOptions(aOptStr: string);
|
|
property CommonIndent: integer read fCommonIndent write fCommonIndent;
|
|
end;
|
|
|
|
{ TCompilerOptReader }
|
|
|
|
TCompilerOptReader = class
|
|
private
|
|
fCurOrigLine: integer; // Current line num when parsing original data.
|
|
// Defines (-d...) are separated from custom options and stored here.
|
|
fDefines: TStringList;
|
|
// Options not accepted by parser. They may still be valid (a macro maybe)
|
|
fInvalidOptions: TStringList; // and will be included in output.
|
|
// List of categories parsed from "fpc -i". Contains category names,
|
|
// Objects[] contains another StringList for the selection list.
|
|
fSupportedCategories: TStringListUTF8Fast;
|
|
// Hierarchy of options parsed from "fpc -h".
|
|
fRootOptGroup: TCompilerOptGroup;
|
|
fCompilerExecutable: string; // Compiler path must be set by caller.
|
|
fFpcVersion: string; // Parsed from "fpc -h".
|
|
fIsNewFpc: Boolean;
|
|
fParsedTarget: String;
|
|
fErrorMsg: String;
|
|
fGeneratedOptions: TStringList; // Options generated from GUI.
|
|
fUseComments: Boolean; // Add option's description into generated data.
|
|
function AddChoicesNew(aOpt: string): TStrings;
|
|
function AddNewCategory(aCategoryName: String): TStringList;
|
|
function AddOptInLowestOrigLine(OutStrings: TStrings): Boolean;
|
|
procedure CopyOptions(aRoot: TCompilerOpt);
|
|
function FindLowestOrigLine(aStrings: TStrings; out aOrigLine: Integer): integer;
|
|
function IsGroup(aOpt: string; var aCategoryList: TStrings): Boolean;
|
|
function ReadCategorySelections(aChar: Char): TStringList;
|
|
function ReadVersion(s: string): Boolean;
|
|
procedure CreateNewGroupItem(aGroup: TCompilerOptGroup; aTxt: string);
|
|
procedure AddGroupItems(aGroup: TCompilerOptGroup; aItems: TStrings);
|
|
function ParseI(aLines: TStringList): TModalResult;
|
|
function ParseH(aLines: TStringList): TModalResult;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Clear;
|
|
function UpdateTargetParam: Boolean;
|
|
function ReadAndParseOptions: TModalResult;
|
|
function FilterOptions(aFilter: string; aOnlySelected: Boolean): Boolean;
|
|
function FindOptionById(aId: integer): TCompilerOpt;
|
|
function FromCustomOptions(aStrings: TStrings): TModalResult;
|
|
function ToCustomOptions(aStrings: TStrings; aUseComments: Boolean): TModalResult;
|
|
public
|
|
property Defines: TStringList read fDefines;
|
|
//property SupportedCategories: TStringList read fSupportedCategories;
|
|
property RootOptGroup: TCompilerOptGroup read fRootOptGroup;
|
|
property CompilerExecutable: string read fCompilerExecutable write fCompilerExecutable;
|
|
property ParsedTarget: String read fParsedTarget write fParsedTarget;
|
|
property ErrorMsg: String read fErrorMsg write fErrorMsg;
|
|
end;
|
|
|
|
{ TCompilerOptThread - thread for reading 'fpc -h' output }
|
|
|
|
TCompilerOptThread = class(TThread)
|
|
private
|
|
fReader: TCompilerOptReader;
|
|
fReadTime: TDateTime;
|
|
fStartedOnce: boolean;
|
|
function GetErrorMsg: string;
|
|
procedure Clear; // (main thread)
|
|
protected
|
|
procedure Execute; override;
|
|
public
|
|
constructor Create(aReader: TCompilerOptReader);
|
|
destructor Destroy; override;
|
|
procedure StartParsing; // (main thread)
|
|
procedure EndParsing; // (main thread)
|
|
public
|
|
property ReadTime: TDateTime read fReadTime;
|
|
property ErrorMsg: string read GetErrorMsg;
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TCompiler }
|
|
|
|
{------------------------------------------------------------------------------
|
|
TCompiler Constructor
|
|
------------------------------------------------------------------------------}
|
|
|
|
constructor TCompiler.Create;
|
|
begin
|
|
inherited Create;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TCompiler Destructor
|
|
------------------------------------------------------------------------------}
|
|
destructor TCompiler.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TCompiler Compile
|
|
------------------------------------------------------------------------------}
|
|
function TCompiler.Compile(AProject: TProject; const WorkingDir,
|
|
CompilerFilename, CompilerParams: string; BuildAll, SkipLinking,
|
|
SkipAssembler, CurrentDirectoryIsTestDir: boolean; const aCompileHint: string
|
|
): TModalResult;
|
|
var
|
|
CmdLine : String;
|
|
Abort : Boolean;
|
|
Tool: TAbstractExternalTool;
|
|
FPCParser: TFPCParser;
|
|
Title: String;
|
|
TargetOS: String;
|
|
TargetCPU: String;
|
|
TargetFilename, SubTool: String;
|
|
CompilerKind: TPascalCompiler;
|
|
begin
|
|
Result:=mrCancel;
|
|
if ConsoleVerbosity>=1 then
|
|
DebugLn('TCompiler.Compile WorkingDir="',WorkingDir,'" CompilerFilename="',CompilerFilename,'" CompilerParams="',CompilerParams,'"');
|
|
|
|
try
|
|
CheckIfFileIsExecutable(CompilerFilename);
|
|
except
|
|
on E: Exception do begin
|
|
WriteError(Format(lisCompilerErrorInvalidCompiler, [E.Message]));
|
|
if CompilerFilename='' then begin
|
|
WriteError(lisCompilerHintYouCanSetTheCompilerPath);
|
|
end;
|
|
exit;
|
|
end;
|
|
end;
|
|
CmdLine := '';
|
|
if BuildAll then
|
|
CmdLine := CmdLine+' -B';
|
|
if SkipLinking and SkipAssembler then
|
|
CmdLine := CmdLine+' -s'
|
|
else if SkipLinking then
|
|
CmdLine := CmdLine+' -Cn';
|
|
|
|
if CompilerParams<>'' then
|
|
CmdLine := CmdLine+' '+CompilerParams;
|
|
if Assigned(FOnCmdLineCreate) then begin
|
|
Abort:=false;
|
|
FOnCmdLineCreate(CmdLine,Abort);
|
|
if Abort then
|
|
exit(mrAbort);
|
|
end;
|
|
if ConsoleVerbosity>=0 then
|
|
DebugLn('[TCompiler.Compile] CmdLine="',CompilerFilename+CmdLine,'"');
|
|
|
|
Title:=lisCompileProject;
|
|
if AProject.BuildModes.Count>1 then
|
|
Title+=Format(lisMode, [AProject.ActiveBuildMode.Identifier]);
|
|
TargetOS:=AProject.CompilerOptions.GetEffectiveTargetOS;
|
|
if TargetOS<>FPCAdds.GetCompiledTargetOS then
|
|
Title+=Format(lisOS, [TargetOS]);
|
|
TargetCPU:=AProject.CompilerOptions.GetEffectiveTargetCPU;
|
|
if TargetCPU<>FPCAdds.GetCompiledTargetCPU then
|
|
Title+=Format(lisCPU, [TargetCPU]);
|
|
TargetFilename:=AProject.CompilerOptions.CreateTargetFilename;
|
|
if TargetFilename<>'' then
|
|
Title+=Format(lisTarget2, [TargetFilename]);
|
|
|
|
Tool:=ExternalToolList.Add(Title);
|
|
Tool.Reference(Self,ClassName);
|
|
try
|
|
Tool.Data:=TIDEExternalToolData.Create(IDEToolCompileProject,'',AProject.ProjectInfoFile);
|
|
Tool.FreeData:=true;
|
|
Tool.Hint:=aCompileHint;
|
|
Tool.Process.Executable:=CompilerFilename;
|
|
Tool.CmdLineParams:=CmdLine;
|
|
Tool.Process.CurrentDirectory:=WorkingDir;
|
|
Tool.CurrentDirectoryIsTestDir:=CurrentDirectoryIsTestDir;
|
|
SubTool:=SubToolFPC;
|
|
CompilerKind:=CodeToolBoss.GetPascalCompilerForDirectory(WorkingDir);
|
|
if CompilerKind=pcPas2js then
|
|
SubTool:=SubToolPas2js;
|
|
FPCParser:=TFPCParser(Tool.AddParsers(SubTool));
|
|
FPCParser.HideHintsSenderNotUsed:=not AProject.CompilerOptions.ShowHintsForSenderNotUsed;
|
|
FPCParser.HideHintsUnitNotUsedInMainSource:=not AProject.CompilerOptions.ShowHintsForUnusedUnitsInMainSrc;
|
|
if (not AProject.CompilerOptions.ShowHintsForUnusedUnitsInMainSrc)
|
|
and (AProject.MainFilename<>'') then
|
|
FPCParser.FilesToIgnoreUnitNotUsed.Add(AProject.MainFilename);
|
|
Tool.AddParsers(SubToolMake);
|
|
Tool.Execute;
|
|
Tool.WaitForExit;
|
|
if Tool.ErrorMessage='' then
|
|
Result:=mrOK;
|
|
finally
|
|
Tool.Release(Self);
|
|
end;
|
|
if ConsoleVerbosity>=0 then
|
|
DebugLn('[TCompiler.Compile] end');
|
|
end;
|
|
|
|
procedure TCompiler.WriteError(const Msg: string);
|
|
begin
|
|
DebugLn(Msg,' [TCompiler.WriteError]');
|
|
if IDEMessagesWindow<>nil then
|
|
IDEMessagesWindow.AddCustomMessage(mluError,Msg);
|
|
end;
|
|
|
|
// Compiler options parsed from "fpc -h" and "fpc -i".
|
|
|
|
var
|
|
OptionIdCounter: integer;
|
|
|
|
|
|
function NextOptionId: integer;
|
|
begin
|
|
Result := OptionIdCounter;
|
|
Inc(OptionIdCounter);
|
|
end;
|
|
|
|
function CalcIndentation(s: string): integer;
|
|
begin
|
|
Result := 0;
|
|
while (Result < Length(s)) and (s[Result+1] = ' ') do
|
|
Inc(Result);
|
|
end;
|
|
|
|
function IsIgnoredOption(aOpt: string): Boolean;
|
|
begin
|
|
if Length(aOpt) < 2 then Exit(False);
|
|
// Ignore : * information
|
|
// * all file names and paths
|
|
// * executable path
|
|
// * change name of produced executable
|
|
// * define and undefine
|
|
// * set language mode
|
|
// * target operating system
|
|
Result := aOpt[2] in ['i', 'F', 'e', 'o', 'd', 'u', 'M', 'T'];
|
|
end;
|
|
|
|
|
|
{ TCompilerOpt }
|
|
|
|
constructor TCompilerOpt.Create(aOwnerGroup: TCompilerOptGroup);
|
|
begin
|
|
inherited Create;
|
|
fOwnerGroup := aOwnerGroup;
|
|
if Assigned(aOwnerGroup) then
|
|
aOwnerGroup.fCompilerOpts.Add(Self);
|
|
fId := NextOptionId;
|
|
fOrigLine := -1;
|
|
end;
|
|
|
|
destructor TCompilerOpt.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TCompilerOpt.AddChoicesByOptOld;
|
|
// From FPC 2.6.x output
|
|
|
|
procedure AddChoices(aCategory: string);
|
|
// Add selection choices for this option. Data originates from "fpc -i".
|
|
var
|
|
i: Integer;
|
|
begin
|
|
with fOwnerGroup.fOwnerReader do
|
|
if fSupportedCategories.Find(aCategory, i) then
|
|
fChoices := fSupportedCategories.Objects[i] as TStrings
|
|
else
|
|
raise Exception.CreateFmt('No selection list for "%s" found.', [aCategory]);
|
|
end;
|
|
|
|
begin
|
|
if Pos('fpc -i', fDescription) = 0 then Exit;
|
|
fEditKind := oeList; // Values will be got later.
|
|
case fOption of
|
|
'-Ca': AddChoices('ABI targets:');
|
|
'-Cf': AddChoices('FPU instruction sets:');
|
|
'-Cp': AddChoices('CPU instruction sets:');
|
|
// '-Oo', '-Oo[NO]': AddChoices('Optimizations:');
|
|
'-Op': AddChoices('CPU instruction sets:');
|
|
// '-OW': AddChoices('Whole Program Optimizations:');
|
|
// '-Ow': AddChoices('Whole Program Optimizations:');
|
|
else
|
|
raise Exception.Create('Don''t know where to get selection list for option '+fOption);
|
|
end;
|
|
end;
|
|
|
|
procedure TCompilerOpt.ParseEditKind;
|
|
begin
|
|
// Guess whether this option can be edited and what is the EditKind
|
|
fEditKind := oeBoolean; // Default kind
|
|
if (Length(fSuffix) = 3) and (fSuffix[1] = '<') and (fSuffix[3] = '>') then
|
|
case fSuffix[2] of
|
|
'x': fEditKind:=oeText; // <x>
|
|
'n': fEditKind:=oeNumber; // <n>
|
|
end;
|
|
if fOwnerGroup.fOwnerReader.fIsNewFpc then begin
|
|
fChoices := fOwnerGroup.fOwnerReader.AddChoicesNew(fDescription);
|
|
if Assigned(fChoices) then
|
|
fEditKind := oeList;
|
|
end
|
|
else
|
|
AddChoicesByOptOld;
|
|
end;
|
|
|
|
function TCompilerOpt.ParseOption(aDescr: string; aIndent: integer): Boolean;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := True;
|
|
fIndentation := aIndent;
|
|
if aDescr[1] <> '-' then Exit(False); // Skip free text explanations.
|
|
// Separate the actual option and description from each other
|
|
i := 1;
|
|
while (i <= Length(aDescr)) and (aDescr[i] <> ' ') do
|
|
Inc(i);
|
|
fOption := Copy(aDescr, 1, i-1);
|
|
while (i <= Length(aDescr)) and (aDescr[i] = ' ') do
|
|
Inc(i);
|
|
fDescription := Copy(aDescr, i, Length(aDescr));
|
|
i := Length(fOption);
|
|
if (i > 3) and (fOption[i-2] = '<') and (fOption[i] = '>') then
|
|
begin
|
|
// Move <x> in the end to Suffix. We need the pure option later.
|
|
fSuffix := Copy(fOption, i-2, i);
|
|
fOption := Copy(fOption, 1, i-3);
|
|
end;
|
|
if fOwnerGroup.fIgnored or IsIgnoredOption(fOption) then
|
|
fIgnored := True;
|
|
ParseEditKind;
|
|
end;
|
|
|
|
procedure TCompilerOpt.Filter(aFilter: string; aOnlySelected: Boolean);
|
|
var
|
|
//iOpt, iDes: SizeInt;
|
|
HideNonSelected: Boolean;
|
|
begin
|
|
HideNonSelected := (fValue='') and aOnlySelected;
|
|
Visible := not (fIgnored or HideNonSelected)
|
|
and ( (aFilter='') or (Pos(aFilter,UTF8LowerCase(fOption))>0)
|
|
or (Pos(aFilter,UTF8LowerCase(fDescription))>0) );
|
|
{
|
|
if aFilter = '' then
|
|
Visible := not (fIgnored or HideNonSelected)
|
|
else begin
|
|
iOpt := Pos(aFilter,UTF8LowerCase(fOption));
|
|
iDes := Pos(aFilter,UTF8LowerCase(fDescription));
|
|
Visible := not (fIgnored or HideNonSelected) and ( (iOpt>0) or (iDes>0) );
|
|
if Visible then
|
|
DebugLn(['TCompilerOpt.Filter match "', aFilter, '": iOpt=', iOpt,
|
|
', iDes=', iDes, ', Ignore=', fIgnored, ', aOnlySelected=', aOnlySelected,
|
|
', Opt'=fOption, ', Descr=', fDescription]);
|
|
end;
|
|
}
|
|
end;
|
|
|
|
const
|
|
CommentId = '-dLazIdeComment_';
|
|
|
|
function TCompilerOpt.Comment: string;
|
|
begin
|
|
Result := ' ' + CommentId + StringReplace(fDescription,' ','_',[rfReplaceAll]);
|
|
end;
|
|
|
|
function TCompilerOpt.GenerateOptValue(aUseComments: Boolean): string;
|
|
begin
|
|
if fValue = '' then Exit('');
|
|
if fValue = 'True' then // Boolean
|
|
Result := fOption
|
|
else // or value of other kind
|
|
Result := fOption + StrToCmdLineParam(Value);
|
|
// ToDo: Show "//" comment in editor and change to a define when storing.
|
|
// Result := ' // ' + aOpt.Description
|
|
if aUseComments then // Replace illegal characters with '_' in comment
|
|
Result := Result + Comment;
|
|
end;
|
|
|
|
procedure TCompilerOpt.SetValue(aValue: string; aOrigLine: integer);
|
|
begin
|
|
fValue := aValue;
|
|
fOrigLine := aOrigLine;
|
|
end;
|
|
|
|
function TCompilerOpt.CalcLeft(aDefaultLeft, aLimit: integer): integer;
|
|
var
|
|
Len: Integer;
|
|
begin
|
|
Len := (fIndentation div 2) + Length(fOption); // Approximation
|
|
if Len > aLimit then
|
|
Result := aDefaultLeft + (Len-aLimit)*8
|
|
else
|
|
Result := aDefaultLeft;
|
|
end;
|
|
|
|
{ TCompilerOptGroup }
|
|
|
|
constructor TCompilerOptGroup.Create(aOwnerReader: TCompilerOptReader; aOwnerGroup: TCompilerOptGroup);
|
|
begin
|
|
inherited Create(aOwnerGroup);
|
|
fOwnerReader := aOwnerReader;
|
|
fCompilerOpts := TCompilerOptList.Create;
|
|
end;
|
|
|
|
destructor TCompilerOptGroup.Destroy;
|
|
begin
|
|
fCompilerOpts.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TCompilerOptGroup.Clear;
|
|
begin
|
|
fCompilerOpts.Clear;
|
|
end;
|
|
|
|
function TCompilerOptGroup.FindOption(aOptStr: string): TCompilerOpt;
|
|
|
|
function FindOptionSub(aRoot: TCompilerOpt): TCompilerOpt;
|
|
var
|
|
Children: TCompilerOptList;
|
|
i: Integer;
|
|
begin
|
|
Result := Nil;
|
|
if aRoot is TCompilerOptGroup then
|
|
begin
|
|
Children := TCompilerOptGroup(aRoot).CompilerOpts;
|
|
if aRoot is TCompilerOptSet then
|
|
begin // TCompilerOptSet
|
|
if AnsiStartsStr(aRoot.Option, aOptStr) then
|
|
begin
|
|
with TCompilerOptSet(aRoot) do
|
|
SelectOptions(Copy(aOptStr, Length(aRoot.Option)+1, Length(aOptStr)));
|
|
Result := aRoot;
|
|
end;
|
|
end
|
|
else begin // TCompilerOptGroup
|
|
for i := 0 to Children.Count-1 do // Recursive call for children.
|
|
begin
|
|
Result := FindOptionSub(TCompilerOpt(Children[i]));
|
|
if Assigned(Result) then Break;
|
|
end;
|
|
end;
|
|
end
|
|
else begin // TCompilerOpt
|
|
if aRoot.Option = aOptStr then
|
|
Result := aRoot
|
|
else if (aRoot.EditKind = oeText) and AnsiStartsStr(aRoot.Option, aOptStr) then
|
|
begin
|
|
aRoot.SetValue(Copy(aOptStr, Length(aRoot.Option)+1, Length(aOptStr)),
|
|
fOwnerReader.fCurOrigLine);
|
|
Result := aRoot;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Result := FindOptionSub(Self);
|
|
end;
|
|
|
|
function TCompilerOptGroup.FindOptionById(aId: integer): TCompilerOpt;
|
|
|
|
function FindOptionSub(aRoot: TCompilerOpt): TCompilerOpt;
|
|
var
|
|
Children: TCompilerOptList;
|
|
i: Integer;
|
|
begin
|
|
Result := Nil;
|
|
if aRoot is TCompilerOptGroup then
|
|
begin
|
|
Children := TCompilerOptGroup(aRoot).CompilerOpts;
|
|
for i := 0 to Children.Count-1 do // Recursive call for children.
|
|
begin
|
|
Result := FindOptionSub(TCompilerOpt(Children[i]));
|
|
if Assigned(Result) then Break;
|
|
end;
|
|
end
|
|
else begin // TCompilerOpt
|
|
if aRoot.fId = aId then
|
|
Result := aRoot;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Result := FindOptionSub(Self);
|
|
end;
|
|
|
|
function TCompilerOptGroup.OneCharOptions(aOptAndValue: string): TCompilerOpt;
|
|
// Split and select option characters like in -Criot.
|
|
// Returns reference to the last option object if all characters were valid opts.
|
|
var
|
|
i: Integer;
|
|
OptBase: String;
|
|
List: TList;
|
|
begin
|
|
List := TList.Create;
|
|
try
|
|
OptBase := Copy(aOptAndValue, 1, 2);
|
|
// First check if all options are valid. Change them only if they are valid.
|
|
for i := 3 to Length(aOptAndValue) do
|
|
begin
|
|
Result := FindOption(OptBase + aOptAndValue[i]);
|
|
if Assigned(Result) then
|
|
List.Add(Result)
|
|
else
|
|
Break;
|
|
end;
|
|
// Set boolean options but only if they all are valid.
|
|
if Assigned(Result) then
|
|
for i := 0 to List.Count-1 do
|
|
TCompilerOpt(List[i]).SetValue('True', fOwnerReader.fCurOrigLine);
|
|
finally
|
|
List.Free;
|
|
end;
|
|
end;
|
|
|
|
function TCompilerOptGroup.SelectOption(aOptAndValue: string): Boolean;
|
|
var
|
|
Opt: TCompilerOpt;
|
|
Param: string;
|
|
OptLen, ParamLen: integer;
|
|
begin
|
|
Opt := FindOption(aOptAndValue);
|
|
if Assigned(Opt) then
|
|
begin
|
|
// Found. Set boolean option, other type of options are already set.
|
|
if Opt.EditKind = oeBoolean then
|
|
Opt.SetValue('True', fOwnerReader.fCurOrigLine);
|
|
end
|
|
else begin
|
|
// Option was not found, try separating the parameter.
|
|
// ToDo: figure out the length in a more clever way.
|
|
if (Length(aOptAndValue) < 3) or (aOptAndValue[1] <> '-') then
|
|
Exit(False);
|
|
if aOptAndValue[2] in ['e', 'u', 'I', 'k', 'o'] then
|
|
OptLen := 2
|
|
else
|
|
OptLen := 3;
|
|
ParamLen := Length(aOptAndValue) - OptLen;
|
|
Opt := Nil;
|
|
if (ParamLen > 1)
|
|
and (aOptAndValue[OptLen+1] in ['''', '"'])
|
|
and (aOptAndValue[Length(aOptAndValue)] in ['''', '"']) then
|
|
Param := Copy(aOptAndValue, OptLen+2, ParamLen-2) // Strip quotes
|
|
else begin
|
|
Param := Copy(aOptAndValue, OptLen+1, ParamLen);
|
|
if OptLen = 3 then // Can contain one char options like -Criot. Can be combined.
|
|
Opt := OneCharOptions(aOptAndValue);
|
|
end;
|
|
if Opt = Nil then
|
|
begin
|
|
Opt := FindOption(Copy(aOptAndValue, 1, OptLen));
|
|
if Assigned(Opt) then
|
|
begin
|
|
Assert(Opt.Value='', 'TCompilerOptGroup.SelectOption: Opt.Value is already set.');
|
|
Opt.SetValue(Param, fOwnerReader.fCurOrigLine)
|
|
end;
|
|
end;
|
|
end;
|
|
Result := Assigned(Opt);
|
|
end;
|
|
|
|
procedure TCompilerOptGroup.DeselectAll;
|
|
|
|
procedure DeselectSub(aRoot: TCompilerOpt);
|
|
var
|
|
Children: TCompilerOptList;
|
|
i: Integer;
|
|
begin
|
|
if aRoot is TCompilerOptGroup then
|
|
begin
|
|
Children := TCompilerOptGroup(aRoot).CompilerOpts;
|
|
for i := 0 to Children.Count-1 do // Recursive call for children.
|
|
DeselectSub(TCompilerOpt(Children[i]));
|
|
end
|
|
else
|
|
aRoot.SetValue('', -1); // TCompilerOpt
|
|
end;
|
|
|
|
begin
|
|
DeselectSub(Self);
|
|
end;
|
|
|
|
procedure TCompilerOptGroup.ParseEditKind;
|
|
begin
|
|
fEditKind := oeGroup;
|
|
end;
|
|
|
|
function TCompilerOptGroup.ParseOption(aDescr: string; aIndent: integer): Boolean;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := inherited ParseOption(aDescr, aIndent);
|
|
if not Result then Exit;
|
|
i := Length(fOption);
|
|
fIncludeNegativeOpt := Copy(fOption, i-3, 4) = '[NO]';
|
|
if fIncludeNegativeOpt then
|
|
SetLength(fOption, i-4);
|
|
end;
|
|
|
|
{ TCompilerOptSet }
|
|
|
|
constructor TCompilerOptSet.Create(aOwnerReader: TCompilerOptReader;
|
|
aOwnerGroup: TCompilerOptGroup; aCommonIndent: integer);
|
|
begin
|
|
inherited Create(aOwnerReader, aOwnerGroup);
|
|
fCommonIndent := aCommonIndent;
|
|
end;
|
|
|
|
destructor TCompilerOptSet.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TCompilerOptSet.CollectSelectedOptions(aUseComments: Boolean): string;
|
|
// Collect subitems of a set to one option.
|
|
var
|
|
Opt: TCompilerOpt;
|
|
i: Integer;
|
|
s: string;
|
|
begin
|
|
s := '';
|
|
for i := 0 to fCompilerOpts.Count-1 do
|
|
begin
|
|
Opt := TCompilerOpt(fCompilerOpts[i]);
|
|
if Opt.Value <> '' then
|
|
case Opt.EditKind of
|
|
oeSetElem : s := s + Opt.Option;
|
|
oeSetNumber: s := s + Opt.Value;
|
|
end;
|
|
end;
|
|
if s <> '' then begin
|
|
Result := Option + s;
|
|
if aUseComments then
|
|
Result := Result + Comment;
|
|
end
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TCompilerOptSet.SetNumberOpt(aValue: string): Boolean;
|
|
// Find a numeric value in the set and update its value. Return True on success.
|
|
var
|
|
i: Integer;
|
|
Opt: TCompilerOpt;
|
|
begin
|
|
for i := 0 to fCompilerOpts.Count-1 do
|
|
begin
|
|
Opt := TCompilerOpt(fCompilerOpts[i]);
|
|
if Opt.EditKind = oeSetNumber then
|
|
begin
|
|
Opt.SetValue(aValue, fOwnerReader.fCurOrigLine);
|
|
Exit(True); // Found and updated.
|
|
end;
|
|
end;
|
|
Result := False; // Not found.
|
|
end;
|
|
|
|
function TCompilerOptSet.SetBooleanOpt(aValue: string): Boolean;
|
|
// Find a single letter value in the set and update its value. Return True on success.
|
|
var
|
|
i: Integer;
|
|
Opt: TCompilerOpt;
|
|
begin
|
|
for i := 0 to fCompilerOpts.Count-1 do
|
|
begin
|
|
Opt := TCompilerOpt(fCompilerOpts[i]);
|
|
if (Opt.EditKind = oeSetElem) and (Opt.Option = aValue) then
|
|
begin
|
|
Opt.SetValue('True', fOwnerReader.fCurOrigLine);
|
|
Exit(True); // Found and updated.
|
|
end;
|
|
end;
|
|
Result := False; // Not found.
|
|
end;
|
|
|
|
procedure TCompilerOptSet.SelectOptions(aOptStr: string);
|
|
// Select options in this set based on the given characters.
|
|
var
|
|
i, Start: Integer;
|
|
OneOpt: string;
|
|
OptOk: Boolean;
|
|
begin
|
|
i := 1;
|
|
while i <= Length(aOptStr) do
|
|
begin
|
|
Start := i;
|
|
if aOptStr[i] in ['0'..'9'] then
|
|
while (i <= Length(aOptStr)) and (aOptStr[i] in ['0'..'9']) do
|
|
Inc(i)
|
|
else
|
|
Inc(i);
|
|
OneOpt := Copy(aOptStr, Start, i-Start);
|
|
if OneOpt[1] in ['0'..'9'] then
|
|
OptOk := SetNumberOpt(OneOpt)
|
|
else
|
|
OptOk := False;
|
|
if not (OptOk or SetBooleanOpt(OneOpt)) then
|
|
raise Exception.CreateFmt('Option %s is not found in set %s.', [OneOpt, fOption]);
|
|
end;
|
|
end;
|
|
|
|
procedure TCompilerOptSet.AddOptions(aDescr: string; aIndent: integer);
|
|
// Set can have one letter options and <n> for numbers
|
|
|
|
procedure NewSetNumber(aDescr: string);
|
|
var
|
|
OptSet: TCompilerOpt;
|
|
begin
|
|
OptSet := TCompilerOpt.Create(Self); // Add it under a group
|
|
OptSet.fIndentation := aIndent;
|
|
OptSet.fOption := 'Number';
|
|
OptSet.fDescription := aDescr;
|
|
OptSet.fEditKind := oeSetNumber;
|
|
end;
|
|
|
|
procedure NewSetElem(aDescr: string);
|
|
var
|
|
OptSet: TCompilerOpt;
|
|
begin
|
|
// Ignore -vl and -vs
|
|
if (fOption = '-v') and (aDescr[1] in ['l', 's']) then Exit;
|
|
OptSet := TCompilerOpt.Create(Self); // Add it under a group
|
|
OptSet.fIndentation := aIndent;
|
|
OptSet.fOption := aDescr[1];
|
|
OptSet.fDescription := Copy(aDescr, 2, Length(aDescr));
|
|
OptSet.fEditKind := oeSetElem;
|
|
end;
|
|
|
|
var
|
|
Opt1, Opt2: string;
|
|
i: Integer;
|
|
begin
|
|
if AnsiStartsStr('<n>', aDescr) then
|
|
NewSetNumber(aDescr)
|
|
else begin
|
|
i := PosEx(':', aDescr, 4);
|
|
if (i > 0) and (aDescr[i-1]=' ') and (aDescr[i-2]<>' ') and (aDescr[i-3]=' ') then
|
|
begin
|
|
// Found another option on the same line, like ' a :'
|
|
Opt2 := Copy(aDescr, i-2, Length(aDescr));
|
|
if aDescr[3] = ':' then
|
|
Opt1 := TrimRight(Copy(aDescr, 1, i-3))
|
|
else
|
|
Opt1 := '';
|
|
end
|
|
else begin
|
|
Opt2 := '';
|
|
Opt1 := aDescr;
|
|
end;
|
|
if Opt1 <> '' then // Can be empty when line in help output is split.
|
|
NewSetElem(Opt1)
|
|
else if fCompilerOpts.Count > 0 then
|
|
aIndent := TCompilerOpt(fCompilerOpts[0]).Indentation;
|
|
if Opt2 <> '' then
|
|
NewSetElem(Opt2);
|
|
end;
|
|
end;
|
|
|
|
procedure TCompilerOptSet.ParseEditKind;
|
|
begin
|
|
fEditKind := oeSet;
|
|
end;
|
|
|
|
|
|
{ TCompilerOptReader }
|
|
|
|
constructor TCompilerOptReader.Create;
|
|
begin
|
|
inherited Create;
|
|
fDefines := TStringList.Create;
|
|
fInvalidOptions := TStringList.Create;
|
|
fSupportedCategories := TStringListUTF8Fast.Create;
|
|
fSupportedCategories.Sorted := True;
|
|
fGeneratedOptions := TStringList.Create;
|
|
fRootOptGroup := TCompilerOptGroup.Create(Self, Nil);
|
|
end;
|
|
|
|
destructor TCompilerOptReader.Destroy;
|
|
begin
|
|
Clear;
|
|
fRootOptGroup.Free;
|
|
fGeneratedOptions.Free;
|
|
fSupportedCategories.Free;
|
|
fInvalidOptions.Free;
|
|
fDefines.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TCompilerOptReader.Clear;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
fRootOptGroup.Clear;
|
|
for i := 0 to fSupportedCategories.Count-1 do
|
|
fSupportedCategories.Objects[i].Free;
|
|
fSupportedCategories.Clear;
|
|
end;
|
|
|
|
function TCompilerOptReader.AddChoicesNew(aOpt: string): TStrings;
|
|
// From FPC 2.7.1+ output
|
|
const
|
|
FpcIStart = 'see fpc -i or fpc -i';
|
|
var
|
|
ch: Char;
|
|
i: integer;
|
|
begin
|
|
Result := Nil;
|
|
i := Pos(FpcIStart, aOpt);
|
|
if i = 0 then Exit;
|
|
Assert(Length(aOpt) >= i+Length(FpcIStart));
|
|
ch := aOpt[i+Length(FpcIStart)]; // Pick the next char from description.
|
|
if fSupportedCategories.Find(ch, i) then
|
|
Result := fSupportedCategories.Objects[i] as TStrings
|
|
else begin
|
|
Result := ReadCategorySelections(ch);
|
|
Result.Insert(0, ''); // First an empty string. Allows removing selection.
|
|
fSupportedCategories.AddObject(ch, Result);
|
|
end;
|
|
end;
|
|
|
|
function TCompilerOptReader.IsGroup(aOpt: string; var aCategoryList: TStrings): Boolean;
|
|
// This option should be a group instead of a selection list.
|
|
// The information is not available in fpc -h output.
|
|
var
|
|
i: Integer;
|
|
CategoryName: string;
|
|
begin
|
|
Result := False;
|
|
if fIsNewFpc then
|
|
begin
|
|
// FPC 2.7.1+
|
|
if AnsiStartsStr('-Oo', aOpt)
|
|
or AnsiStartsStr('-OW', aOpt)
|
|
or AnsiStartsStr('-Ow', aOpt) then
|
|
begin
|
|
aCategoryList := AddChoicesNew(aOpt);
|
|
Result := Assigned(aCategoryList);
|
|
end;
|
|
end
|
|
else begin
|
|
// FPC 2.6.x
|
|
CategoryName := '';
|
|
if AnsiStartsStr('-Oo', aOpt) then
|
|
CategoryName := 'Optimizations:'
|
|
else if AnsiStartsStr('-OW', aOpt) or AnsiStartsStr('-Ow', aOpt) then
|
|
CategoryName := 'Whole Program Optimizations:';
|
|
Result := CategoryName <> '';
|
|
if Result then
|
|
if fSupportedCategories.Find(CategoryName, i) then
|
|
aCategoryList := fSupportedCategories.Objects[i] as TStrings
|
|
else
|
|
raise Exception.CreateFmt('No list of options found for "%s".', [CategoryName]);
|
|
end;
|
|
end;
|
|
|
|
function TCompilerOptReader.AddNewCategory(aCategoryName: String): TStringList;
|
|
begin
|
|
Result := TStringList.Create;
|
|
Result.Add(''); // First an empty string. Allows removing selection.
|
|
fSupportedCategories.AddObject(aCategoryName, Result);
|
|
end;
|
|
|
|
function TCompilerOptReader.ParseI(aLines: TStringList): TModalResult;
|
|
const
|
|
Supported = 'Supported ';
|
|
var
|
|
i, j: Integer;
|
|
Line, TrimmedLine: String;
|
|
Category, sl: TStringList;
|
|
begin
|
|
Result := mrOK;
|
|
Category := Nil;
|
|
sl := TStringList.Create;
|
|
try
|
|
sl.StrictDelimiter := True;
|
|
sl.Delimiter := ',';
|
|
for i := 0 to aLines.Count-1 do
|
|
begin
|
|
Line := aLines[i];
|
|
TrimmedLine := Trim(Line);
|
|
if Assigned(Category) then
|
|
begin
|
|
if TrimmedLine = '' then
|
|
Category := Nil // End of category.
|
|
else begin
|
|
if Line[1] <> ' ' then
|
|
raise Exception.Create('TCompilerReader.ParseI: Line should start with a space.');
|
|
sl.Clear;
|
|
// Some old FPC versions had a comma separated list.
|
|
sl.DelimitedText := Trim(Line);
|
|
for j := 0 to sl.Count-1 do
|
|
Category.Add(sl[j]);
|
|
end;
|
|
end
|
|
else if AnsiStartsStr(Supported, Line) then
|
|
Category := AddNewCategory(Copy(Line, Length(Supported)+1, Length(Line)));
|
|
end;
|
|
finally
|
|
sl.Free;
|
|
end;
|
|
end;
|
|
|
|
function TCompilerOptReader.ReadVersion(s: string): Boolean;
|
|
const
|
|
VersBegin = 'Free Pascal Compiler version ';
|
|
var
|
|
Start, V1, V2: Integer;
|
|
OutputI: TStringList; // fpc -Fr$(FPCMsgFile) -i
|
|
begin
|
|
Result := AnsiStartsStr(VersBegin, s);
|
|
if Result then
|
|
begin
|
|
fIsNewFpc := False;
|
|
Start := Length(VersBegin)+1;
|
|
V1 := PosEx(' ', s, Start);
|
|
if V1 > 0 then
|
|
begin
|
|
fFpcVersion := Copy(s, Start, V1-Start);
|
|
if (Length(fFpcVersion)>2) then begin
|
|
V1 := StrToIntDef(fFpcVersion[1], 0);
|
|
V2 := StrToIntDef(fFpcVersion[3], 0);
|
|
fIsNewFpc := ((V1=2) and (V2>=7)) or (V1>2);
|
|
end;
|
|
// The rest 2 fields are date and target CPU.
|
|
end;
|
|
if not fIsNewFpc then
|
|
begin
|
|
// Get categories with FPC -i, once we know the version is old (2.6.x).
|
|
OutputI := RunTool(fCompilerExecutable, fParsedTarget + ' -i');
|
|
if OutputI = Nil then Exit(False);
|
|
try
|
|
Result := ParseI(OutputI) = mrOK;
|
|
finally
|
|
OutputI.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCompilerOptReader.CreateNewGroupItem(aGroup: TCompilerOptGroup; aTxt: string);
|
|
var
|
|
Opt: TCompilerOpt;
|
|
begin
|
|
Opt := TCompilerOpt.Create(aGroup); // Add it under a group
|
|
Opt.fOption := aGroup.Option + aTxt;
|
|
Opt.fIndentation := aGroup.Indentation+4;
|
|
Opt.fEditKind := oeBoolean;
|
|
end;
|
|
|
|
procedure TCompilerOptReader.AddGroupItems(aGroup: TCompilerOptGroup; aItems: TStrings);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 1 to aItems.Count-1 do // Skip the first empty item.
|
|
begin
|
|
CreateNewGroupItem(aGroup, aItems[i]);
|
|
if aGroup.fIncludeNegativeOpt then
|
|
CreateNewGroupItem(aGroup, 'NO'+aItems[i]);
|
|
end;
|
|
end;
|
|
|
|
function TCompilerOptReader.ParseH(aLines: TStringList): TModalResult;
|
|
const
|
|
OptSetId = 'a combination of';
|
|
var
|
|
i, ThisInd, NextInd, OptSetInd: Integer;
|
|
ThisLine: String;
|
|
Opt: TCompilerOpt;
|
|
LastGroup, SubGroup: TCompilerOptGroup;
|
|
GroupItems: TStrings;
|
|
begin
|
|
Result := mrOK;
|
|
LastGroup := fRootOptGroup;
|
|
GroupItems:=nil;
|
|
for i := 0 to aLines.Count-1 do
|
|
begin
|
|
ThisLine := StringReplace(aLines[i],'-Agas-darwinAssemble','-Agas-darwin Assemble',[]);
|
|
ThisInd := CalcIndentation(ThisLine);
|
|
ThisLine := Trim(ThisLine);
|
|
if LastGroup is TCompilerOptSet then
|
|
begin // Fix strangely split line indents in options groups.
|
|
OptSetInd := TCompilerOptSet(LastGroup).CommonIndent;
|
|
if (ThisLine[1] <> '-') and (ThisInd > OptSetInd) then
|
|
ThisInd := OptSetInd;
|
|
end;
|
|
// Top header line for compiler version, check only once.
|
|
if (fFpcVersion = '') and ReadVersion(ThisLine) then Continue;
|
|
if ThisInd < 2 then Continue;
|
|
if (ThisLine = '') or (ThisInd > 30)
|
|
or (ThisLine[1] = '@')
|
|
or (Pos('-? ', ThisLine) > 0)
|
|
or (Pos('-h ', ThisLine) > 0) then Continue;
|
|
if i < aLines.Count-1 then
|
|
NextInd := CalcIndentation(aLines[i+1])
|
|
else
|
|
NextInd := -1;
|
|
if NextInd > ThisInd then
|
|
begin
|
|
if LastGroup is TCompilerOptSet then
|
|
NextInd := TCompilerOptSet(LastGroup).CommonIndent
|
|
else begin
|
|
if Pos(OptSetId, ThisLine) > 0 then // Header for sets
|
|
// Hard-code indent to NextInd, for strangely split lines later in help output.
|
|
LastGroup := TCompilerOptSet.Create(Self, LastGroup, NextInd)
|
|
else // Group header for options
|
|
LastGroup := TCompilerOptGroup.Create(Self, LastGroup);
|
|
LastGroup.ParseOption(ThisLine, ThisInd);
|
|
end;
|
|
end;
|
|
if NextInd <= ThisInd then
|
|
begin
|
|
// This is an option
|
|
if LastGroup is TCompilerOptSet then // Add it to a set (may add many)
|
|
TCompilerOptSet(LastGroup).AddOptions(ThisLine, ThisInd)
|
|
else begin
|
|
if IsGroup(ThisLine, GroupItems) then
|
|
begin
|
|
SubGroup := TCompilerOptGroup.Create(Self, LastGroup);
|
|
SubGroup.ParseOption(ThisLine, ThisInd);
|
|
AddGroupItems(SubGroup, GroupItems);
|
|
end
|
|
else begin
|
|
Opt := TCompilerOpt.Create(LastGroup); // Add it under a group
|
|
Opt.ParseOption(ThisLine, ThisInd);
|
|
end;
|
|
end;
|
|
if (NextInd <> -1) and (NextInd < ThisInd) then
|
|
LastGroup := LastGroup.fOwnerGroup; // Return to a previous group
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TCompilerOptReader.UpdateTargetParam: Boolean;
|
|
// Updates target OS and CPU parameter using global macros.
|
|
// Returns true if the value has changed since last time.
|
|
var
|
|
NewTarget: string;
|
|
begin
|
|
NewTarget := '-T$(TargetOS) -P$(TargetCPU)';
|
|
if not GlobalMacroList.SubstituteStr(NewTarget) then
|
|
raise Exception.CreateFmt('UpdateTargetParam: Cannot substitute macros "%s".',
|
|
[NewTarget]);
|
|
Result := fParsedTarget <> NewTarget;
|
|
if Result then
|
|
fParsedTarget := NewTarget; // fParsedTarget is used as a param for FPC.
|
|
end;
|
|
|
|
function TCompilerOptReader.ReadCategorySelections(aChar: Char): TStringList;
|
|
// Get the selection list for a category using "fpc -i+char", for new FPC versions.
|
|
begin
|
|
Result:=RunTool(fCompilerExecutable, fParsedTarget + ' -i' + aChar);
|
|
Result.Sort;
|
|
end;
|
|
|
|
function TCompilerOptReader.ReadAndParseOptions: TModalResult;
|
|
// fpc -Fr$(FPCMsgFile) -h
|
|
var
|
|
OutputH: TStringList;
|
|
begin
|
|
if fCompilerExecutable = '' then
|
|
fCompilerExecutable := 'fpc'; // Let's hope "fpc" is found in PATH.
|
|
OptionIdCounter := 0;
|
|
fErrorMsg := '';
|
|
try
|
|
// FPC with option -h
|
|
OutputH := RunTool(fCompilerExecutable, fParsedTarget + ' -h');
|
|
if OutputH = Nil then Exit(mrCancel);
|
|
Result := ParseH(OutputH);
|
|
finally
|
|
OutputH.Free;
|
|
end;
|
|
end;
|
|
|
|
function TCompilerOptReader.FilterOptions(aFilter: string; aOnlySelected: Boolean): Boolean;
|
|
// Filter all options recursively, setting their Visible flag as needed.
|
|
// Returns True if Option(group) or child options have visible items.
|
|
|
|
function FilterOptionsSub(aRoot: TCompilerOpt): Boolean;
|
|
var
|
|
Children: TCompilerOptList;
|
|
i: Integer;
|
|
begin
|
|
// Filter the root item
|
|
aRoot.Filter(aFilter, aOnlySelected); // Sets Visible flag
|
|
// Filter children in a group
|
|
if aRoot is TCompilerOptGroup then
|
|
begin
|
|
Children := TCompilerOptGroup(aRoot).CompilerOpts;
|
|
for i := 0 to Children.Count-1 do // Recursive call for children.
|
|
aRoot.Visible := FilterOptionsSub(TCompilerOpt(Children[i])) or aRoot.Visible;
|
|
end;
|
|
Result := aRoot.Visible;
|
|
end;
|
|
|
|
begin
|
|
Result := FilterOptionsSub(fRootOptGroup);
|
|
end;
|
|
|
|
function TCompilerOptReader.FindOptionById(aId: integer): TCompilerOpt;
|
|
begin
|
|
Result := fRootOptGroup.FindOptionById(aId);
|
|
end;
|
|
|
|
function TCompilerOptReader.FromCustomOptions(aStrings: TStrings): TModalResult;
|
|
// Example: $(IDEBuildOptions) -dCR -dgc -Criot
|
|
var
|
|
i, j: Integer;
|
|
s: String;
|
|
sl: TStringList;
|
|
begin
|
|
Result := mrOK;
|
|
fCurOrigLine := 0;
|
|
fRootOptGroup.DeselectAll;
|
|
fDefines.Clear;
|
|
fInvalidOptions.Clear;
|
|
sl := TStringList.Create;
|
|
try
|
|
// Separate options that are on one line.
|
|
for i := 0 to aStrings.Count-1 do
|
|
begin
|
|
s := Trim(aStrings[i]);
|
|
if s = '' then Continue;
|
|
sl.Clear;
|
|
SplitCmdLineParams(s, sl);
|
|
for j := 0 to sl.Count-1 do begin
|
|
s := sl[j];
|
|
// Put the option into fDefines or fInvalidOptions, or set in options collection.
|
|
if AnsiStartsStr('-d', s) and (Length(s) > 2) then
|
|
begin
|
|
if not AnsiStartsStr(CommentId, s) then // Skip a generated comment.
|
|
fDefines.Add(s)
|
|
end
|
|
else
|
|
if not fRootOptGroup.SelectOption(s) then
|
|
fInvalidOptions.AddObject(s, TObject({%H-}Pointer(PtrUInt(i))));
|
|
Inc(fCurOrigLine);
|
|
end;
|
|
end;
|
|
finally
|
|
sl.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCompilerOptReader.CopyOptions(aRoot: TCompilerOpt);
|
|
// Collect non-default options from GUI to fGeneratedOptions
|
|
var
|
|
Children: TCompilerOptList;
|
|
i: Integer;
|
|
s: string;
|
|
begin
|
|
if aRoot is TCompilerOptGroup then
|
|
begin
|
|
Children := TCompilerOptGroup(aRoot).CompilerOpts;
|
|
if aRoot is TCompilerOptSet then
|
|
begin // TCompilerOptSet
|
|
s := TCompilerOptSet(aRoot).CollectSelectedOptions(fUseComments);
|
|
if s <> '' then
|
|
fGeneratedOptions.AddObject(s, TObject({%H-}Pointer(PtrUInt(aRoot.fOrigLine))));
|
|
end
|
|
else begin // TCompilerOptGroup
|
|
for i := 0 to Children.Count-1 do
|
|
CopyOptions(TCompilerOpt(Children[i])); // Recursive call for children.
|
|
end;
|
|
end
|
|
else if aRoot.Value <> '' then // TCompilerOpt
|
|
fGeneratedOptions.AddObject(aRoot.GenerateOptValue(fUseComments),
|
|
TObject({%H-}Pointer(PtrUINt(aRoot.fOrigLine))));
|
|
end;
|
|
|
|
function TCompilerOptReader.FindLowestOrigLine(aStrings: TStrings;
|
|
out aOrigLine: Integer): integer;
|
|
// Return index in aStrings for an option that has the lowest original line number.
|
|
// aOrigLine returns the original line number.
|
|
var
|
|
i, OriLine, MinOrigLine: Integer;
|
|
begin
|
|
Result := -1;
|
|
aOrigLine := -1;
|
|
MinOrigLine := MaxInt;
|
|
for i := 0 to aStrings.Count-1 do
|
|
begin
|
|
OriLine := Integer({%H-}PtrUInt(aStrings.Objects[i]));
|
|
if (OriLine > -1) and (OriLine < MinOrigLine) then
|
|
begin
|
|
MinOrigLine := OriLine;
|
|
aOrigLine := OriLine;
|
|
Result := i;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TCompilerOptReader.AddOptInLowestOrigLine(OutStrings: TStrings): Boolean;
|
|
// Copy an option that had the lowest original line number.
|
|
// Returns True if options from original data was found.
|
|
var
|
|
iGen, iInv: Integer;
|
|
iGenOrig, iInvOrig: Integer;
|
|
begin
|
|
// Find lowest lines from both generated and invalid options
|
|
iGen := FindLowestOrigLine(fGeneratedOptions, iGenOrig);
|
|
iInv := FindLowestOrigLine(fInvalidOptions, iInvOrig);
|
|
// then add the one that is lower.
|
|
if (iGenOrig = -1) and (iInvOrig = -1) then Exit(False);
|
|
Result := True;
|
|
if ( (iGenOrig > -1) and (iInvOrig > -1) and (iGenOrig <= iInvOrig) )
|
|
or ( (iGenOrig > -1) and (iInvOrig = -1) ) then
|
|
begin
|
|
OutStrings.Add(fGeneratedOptions[iGen]);
|
|
fGeneratedOptions[iGen] := '';
|
|
fGeneratedOptions.Objects[iGen] := TObject(Pointer(-1)); // Mark as processed.
|
|
end
|
|
else begin
|
|
OutStrings.Add(fInvalidOptions[iInv]);
|
|
fInvalidOptions[iInv] := '';
|
|
fInvalidOptions.Objects[iInv] := TObject(Pointer(-1));
|
|
end;
|
|
end;
|
|
|
|
function TCompilerOptReader.ToCustomOptions(aStrings: TStrings;
|
|
aUseComments: Boolean): TModalResult;
|
|
// Copy options to a list if they have a non-default value (True for boolean).
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := mrOK;
|
|
fUseComments := aUseComments;
|
|
fGeneratedOptions.Clear;
|
|
CopyOptions(fRootOptGroup);
|
|
// Options are now in fGeneratedOptions. Move them to aStrings in a right order.
|
|
aStrings.Clear;
|
|
// First collect options that were in the original list.
|
|
while AddOptInLowestOrigLine(aStrings) do ;
|
|
// Then add all the rest.
|
|
for i := 0 to fGeneratedOptions.Count-1 do
|
|
if fGeneratedOptions[i] <> '' then
|
|
aStrings.Add(fGeneratedOptions[i]);
|
|
// Then defines
|
|
aStrings.AddStrings(fDefines);
|
|
end;
|
|
|
|
{ TCompilerOptThread }
|
|
|
|
constructor TCompilerOptThread.Create(aReader: TCompilerOptReader);
|
|
begin
|
|
inherited Create(True);
|
|
//FreeOnTerminate:=True;
|
|
fStartedOnce:=false;
|
|
fReader:=aReader;
|
|
end;
|
|
|
|
destructor TCompilerOptThread.Destroy;
|
|
begin
|
|
if fStartedOnce then
|
|
WaitFor;
|
|
Clear;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TCompilerOptThread.GetErrorMsg: string;
|
|
begin
|
|
Result := fReader.ErrorMsg;
|
|
end;
|
|
|
|
procedure TCompilerOptThread.Clear;
|
|
begin
|
|
;
|
|
end;
|
|
|
|
procedure TCompilerOptThread.StartParsing;
|
|
begin
|
|
if fStartedOnce then
|
|
WaitFor;
|
|
fReader.CompilerExecutable:=LazarusIDE.GetCompilerFilename;
|
|
fReader.UpdateTargetParam;
|
|
Start;
|
|
fStartedOnce:=true;
|
|
end;
|
|
|
|
procedure TCompilerOptThread.EndParsing;
|
|
begin
|
|
if fStartedOnce then
|
|
WaitFor;
|
|
end;
|
|
|
|
procedure TCompilerOptThread.Execute;
|
|
var
|
|
StartTime: TDateTime;
|
|
begin
|
|
StartTime := Now;
|
|
try
|
|
fReader.ReadAndParseOptions;
|
|
except
|
|
on E: Exception do
|
|
fReader.ErrorMsg := 'Error reading compiler: '+E.Message;
|
|
end;
|
|
fReadTime := Now-StartTime;
|
|
end;
|
|
|
|
|
|
end.
|
|
|