lazarus/ide/ideinfoneedbuild.pas

441 lines
13 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:
IDE dialog showing packages needing (re)build.
}
unit IDEInfoNeedBuild;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Laz_AVL_Tree,
// LCL
Forms, Controls, StdCtrls, ButtonPanel, LCLType,
// LazUtils
LazUtilities, LazUTF8,
// IdeIntf
IDEWindowIntf, LazIDEIntf, ProjectIntf, PackageIntf, InputHistory,
// IDE
LazarusIDEStrConsts, PackageDefs, PackageSystem, Project,
EnvironmentOpts, IDEProcs, BuildManager;
type
TINeedBuild = (
inbNone,
inbNo,
inbNormal,
inbClean
);
TInfoNeedBuildItem = class
public
Target: TObject; // TProject, TLazPackage, LazarusIDE
Caption: string;
Filename: string;
NeedBuild: TINeedBuild;
Note: string;
end;
{ TIDEInfoNeedBuildDlg }
TIDEInfoNeedBuildDlg = class(TForm)
ButtonPanel1: TButtonPanel;
MainMemo: TMemo;
TargetComboBox: TComboBox;
TargetLabel: TLabel;
procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
procedure OnIdle(Sender: TObject; var {%H-}Done: Boolean);
procedure TargetComboBoxChange(Sender: TObject);
private
FIdleConnected: boolean;
FMainTarget: TObject;
FSkipDesignTimePackages: boolean;
FTargetToItem: TAvlTree; // tree of TInfoNeedBuildItem sorted for Target
FTargets: TFPList; // topologically sorted list of TInfoNeedBuildItem, last=main
procedure FillTargets;
function ProjectAsTarget(AProject: TProject): string;
function IDEAsTarget: string;
procedure SetIdleConnected(AValue: boolean);
function CheckNeedBuild(All: boolean): boolean;// true = complete
function GetTargets(Target: string): TFPList;
function HaveSameTargets(BuildItems, Targets: TFPList): boolean;
procedure ClearTargets;
procedure SetMainTarget(AValue: TObject);
public
property IdleConnected: boolean read FIdleConnected write SetIdleConnected;
property MainTarget: TObject read FMainTarget write SetMainTarget;
property SkipDesignTimePackages: boolean read FSkipDesignTimePackages
write FSkipDesignTimePackages;
end;
procedure ShowNeedBuildDialog;
function CompareInfoNeedBuildItemWithTargets(Info1, Info2: Pointer): integer;
function CompareTargetWithInfoNeedBuildItem(Target, Info: Pointer): integer;
implementation
{$R *.lfm}
procedure ShowNeedBuildDialog;
var
IDEInfoNeedBuildDlg: TIDEInfoNeedBuildDlg;
begin
IDEInfoNeedBuildDlg:=TIDEInfoNeedBuildDlg.Create(nil);
try
IDEInfoNeedBuildDlg.ShowModal;
finally
IDEInfoNeedBuildDlg.Free;
end;
end;
function CompareInfoNeedBuildItemWithTargets(Info1, Info2: Pointer): integer;
var
Item1: TInfoNeedBuildItem absolute Info1;
Item2: TInfoNeedBuildItem absolute Info2;
begin
Result:=ComparePointers(Item1.Target,Item2.Target);
end;
function CompareTargetWithInfoNeedBuildItem(Target, Info: Pointer): integer;
var
Item: TInfoNeedBuildItem absolute Info;
begin
Result:=ComparePointers(Target,Item.Target);
end;
{ TIDEInfoNeedBuildDlg }
procedure TIDEInfoNeedBuildDlg.FormCreate(Sender: TObject);
var
Target: String;
begin
FTargetToItem:=TAvlTree.Create(@CompareInfoNeedBuildItemWithTargets);
FTargets:=TFPList.Create;
Caption:=lisWhatNeedsBuilding;
TargetLabel.Caption:=lisTarget;
FillTargets;
Target:=InputHistories.ViewNeedBuildTarget;
if (Target<>'') and (TargetComboBox.Items.IndexOf(Target)>=0) then
TargetComboBox.Text:=Target
else
TargetComboBox.Text:=TargetComboBox.Items[0];
IDEDialogLayoutList.ApplyLayout(Self);
IdleConnected:=true;
TargetComboBox.DropDownCount:=EnvironmentOptions.DropDownCount;
end;
procedure TIDEInfoNeedBuildDlg.FormDestroy(Sender: TObject);
begin
IdleConnected:=false;
ClearTargets;
FreeAndNil(FTargetToItem);
FreeAndNil(FTargets);
MainTarget:=nil;
end;
procedure TIDEInfoNeedBuildDlg.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key=VK_ESCAPE then
ModalResult:=mrCancel;
end;
procedure TIDEInfoNeedBuildDlg.OnIdle(Sender: TObject; var Done: Boolean);
begin
if CheckNeedBuild(false) then
IdleConnected:=false;
end;
procedure TIDEInfoNeedBuildDlg.TargetComboBoxChange(Sender: TObject);
begin
IdleConnected:=true;
end;
procedure TIDEInfoNeedBuildDlg.FormClose(Sender: TObject;
var CloseAction: TCloseAction);
var
Target: TCaption;
begin
IdleConnected:=false;
IDEDialogLayoutList.SaveLayout(Self);
Target:=TargetComboBox.Text;
if Target=ProjectAsTarget(Project1) then
Target:='';
InputHistories.ViewNeedBuildTarget:=Target;
end;
procedure TIDEInfoNeedBuildDlg.FillTargets;
var
sl: TStringListUTF8Fast;
i: Integer;
begin
sl:=TStringListUTF8Fast.Create;
try
for i:=0 to PackageGraph.Count-1 do
sl.Add(PackageGraph[i].Name);
sl.Sort;
sl.Insert(0,IDEAsTarget);
if Project1<>nil then
sl.Insert(0,ProjectAsTarget(Project1));
TargetComboBox.Items.Assign(sl);
finally
sl.Free;
end;
end;
function TIDEInfoNeedBuildDlg.ProjectAsTarget(AProject: TProject): string;
begin
if AProject=nil then
Result:=''
else
Result:=Format(lisProject, [AProject.Title]);
end;
function TIDEInfoNeedBuildDlg.IDEAsTarget: string;
begin
Result:=lisLazarusIDE;
end;
procedure TIDEInfoNeedBuildDlg.SetIdleConnected(AValue: boolean);
begin
if FIdleConnected=AValue then Exit;
if AValue and (ComponentState*[csDestroying,csLoading]<>[]) then exit;
FIdleConnected:=AValue;
if IdleConnected then
Application.AddOnIdleHandler(@OnIdle)
else
Application.RemoveOnIdleHandler(@OnIdle);
end;
function TIDEInfoNeedBuildDlg.CheckNeedBuild(All: boolean): boolean;
var
NewTargets: TFPList;
i: Integer;
Item: TInfoNeedBuildItem;
s: String;
Pkg: TLazPackage;
AProject: TProject;
NeedBuildAll: boolean;
NeedBuild: TModalResult;
begin
SkipDesignTimePackages:=false;
NewTargets:=GetTargets(TargetComboBox.Text);
try
if not HaveSameTargets(FTargets,NewTargets) then begin
// targets have changed
ClearTargets;
if NewTargets=nil then exit(true);
for i:=0 to NewTargets.Count-1 do begin
Item:=TInfoNeedBuildItem.Create;
Item.Target:=TObject(NewTargets[i]);
if Item.Target=LazarusIDE then begin
Item.Caption:=IDEAsTarget;
Item.Filename:=EnvironmentOptions.LazarusDirectory;
end
else if Item.Target is TProject then begin
AProject:=TProject(Item.Target);
Item.Caption:=ProjectAsTarget(AProject);
Item.Filename:=AProject.ProjectInfoFile;
SkipDesignTimePackages:=not (pfUseDesignTimePackages in AProject.Flags);
end
else if Item.Target is TLazPackage then begin
Pkg:=TLazPackage(Item.Target);
Pkg.Flags:=Pkg.Flags-[lpfNeedGroupCompile];
Item.Caption:=Pkg.IDAsString;
Item.Filename:=Pkg.Filename;
end;
FTargetToItem.Add(Item);
FTargets.Add(Item);
end;
end;
finally
NewTargets.Free;
end;
Result:=true;
// check
if (FTargets.Count>0) then
MainTarget:=TInfoNeedBuildItem(FTargets.Last).Target
else
MainTarget:=nil;
i:=0;
while i<FTargets.Count do begin
Item:=TInfoNeedBuildItem(FTargets[i]);
if Item.NeedBuild=inbNone then begin
Item.NeedBuild:=inbNo;
if Item.Target=LazarusIDE then begin
// no check available
end
else if Item.Target is TProject then begin
AProject:=TProject(Item.Target);
Item.Note:='';
NeedBuild:=MainBuildBoss.DoCheckIfProjectNeedsCompilation(AProject,
NeedBuildAll,Item.Note);
if NeedBuild=mrYes then begin
if NeedBuildAll then
Item.NeedBuild:=inbClean
else
Item.NeedBuild:=inbNormal;
end;
end
else if Item.Target is TLazPackage then begin
Pkg:=TLazPackage(Item.Target);
Item.Note:='';
NeedBuild:=PackageGraph.CheckIfPackageNeedsCompilation(
Pkg,SkipDesignTimePackages,true,NeedBuildAll,Item.Note);
if NeedBuild=mrYes then begin
PackageGraph.SetFlagDependenciesNeedBuild(Pkg);
if NeedBuildAll then
Item.NeedBuild:=inbClean
else
Item.NeedBuild:=inbNormal;
end;
end;
if not All then break;
end;
inc(i);
end;
Result:=i=FTargets.Count; // true = all checked
// update memo
s:='';
for i:=0 to FTargets.Count-1 do begin
Item:=TInfoNeedBuildItem(FTargets[i]);
s+='Target: '+Item.Caption+LineEnding;
case Item.NeedBuild of
inbNone: s+='checking ...';
inbNo: s+='No build needed.';
inbNormal: s+='Build needed.';
inbClean: s+='Clean build needed.';
end;
s+=LineEnding;
if Item.Filename<>'' then
s+='File: '+Item.Filename+LineEnding;
if Item.Note<>'' then
s+='Note: '+Item.Note+LineEnding;
s+=LineEnding;
end;
MainMemo.Lines.Text:=s;
end;
function TIDEInfoNeedBuildDlg.GetTargets(Target: string): TFPList;
function GetList(Main: TObject; FirstDependency: TPkgDependency;
ReqFlags: TPkgIntfRequiredFlags): TFPList;
begin
Result:=nil;
if Main=nil then exit;
PackageGraph.GetAllRequiredPackages(nil,FirstDependency,Result,ReqFlags);
if Result<>nil then begin
// PackageGraph.GetAllRequiredPackages starts with the inner nodes
// => reverse order
ReverseList(Result);
end
else
Result:=TFPList.Create;
Result.Add(Main);
end;
var
Pkg: TLazPackage;
ReqFlags: TPkgIntfRequiredFlags;
begin
ReqFlags:=[];
if Target=IDEAsTarget then begin
Result:=GetList(LazarusIDE,PackageGraph.FirstInstallDependency,ReqFlags);
end else if Target=ProjectAsTarget(Project1) then begin
if not (pfUseDesignTimePackages in Project1.Flags) then
Include(ReqFlags,pirSkipDesignTimeOnly);
Result:=GetList(Project1,Project1.FirstRequiredDependency,ReqFlags);
end else begin
Pkg:=PackageGraph.FindPackageWithName(Target,nil);
Result:=GetList(Pkg,Pkg.FirstRequiredDependency,ReqFlags);
end;
end;
function TIDEInfoNeedBuildDlg.HaveSameTargets(BuildItems, Targets: TFPList
): boolean;
// check if BuildItems and Targets have the same targets
var
Targets1: TFPList;
Targets2: TFPList;
i: Integer;
Target: TObject;
begin
Result:=false;
if (BuildItems=nil)<>(Targets=nil) then exit;
Targets1:=TFPList.Create;
Targets2:=TFPList.Create;
try
// create a list of targets from BuildItems and a second list from Targets
for i:=0 to BuildItems.Count-1 do
Targets1.Add(TInfoNeedBuildItem(BuildItems[i]).Target);
for i:=0 to Targets.Count-1 do begin
Target:=TObject(Targets[i]);
if (Target=LazarusIDE) or (Target is TProject) or (Target is TLazPackage) then
Targets2.Add(Target);
end;
if Targets1.Count<>Targets2.Count then exit;
// sort both lists
Targets1.Sort(@ComparePointers);
Targets2.Sort(@ComparePointers);
// compare each item
for i:=0 to Targets1.Count-1 do
if Targets1[i]<>Targets2[i] then exit;
finally
Targets1.Free;
Targets2.Free;
end;
Result:=true;
end;
procedure TIDEInfoNeedBuildDlg.ClearTargets;
begin
FTargetToItem.FreeAndClear;
FTargets.Clear;
end;
procedure TIDEInfoNeedBuildDlg.SetMainTarget(AValue: TObject);
begin
if FMainTarget=AValue then Exit;
FMainTarget:=AValue;
//debugln(['TIDEInfoNeedBuildDlg.SetMainTarget ',DbgSName(MainTarget)]);
if (MainTarget=LazarusIDE) then
MainBuildBoss.SetBuildTargetIDE
else
MainBuildBoss.SetBuildTargetProject1(true);
end;
end.