mirror of https://gitlab.com/basile.b/dexed.git
1242 lines
32 KiB
Plaintext
1242 lines
32 KiB
Plaintext
unit ce_dubproject;
|
|
|
|
{$I ce_defines.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, xfpjson, xjsonparser, xjsonscanner, process, strutils,
|
|
LazFileUtils, RegExpr,
|
|
ce_common, ce_interfaces, ce_observer, ce_dialogs, ce_processes,
|
|
ce_writableComponent;
|
|
|
|
type
|
|
|
|
TDubLinkMode = (dlmSeparate, dlmAllAtOnce, dlmSingleFile);
|
|
|
|
TDubDependencyCheck = (dcStandard, dcOffline, dcNo);
|
|
|
|
(**
|
|
* Stores the build options, always applied when a project is build
|
|
*)
|
|
TCEDubBuildOptionsBase = class(TWritableLfmTextComponent)
|
|
strict private
|
|
fParallel: boolean;
|
|
fForceRebuild: boolean;
|
|
fLinkMode: TDubLinkMode;
|
|
fCombined: boolean;
|
|
fDepCheck: TDubDependencyCheck;
|
|
fOther: string;
|
|
fCompiler: TCECompiler;
|
|
procedure setLinkMode(value: TDubLinkMode);
|
|
procedure setCompiler(value: TCECompiler);
|
|
function getCompiler: TCECompiler;
|
|
published
|
|
property compiler: TCECOmpiler read getCompiler write setCompiler;
|
|
property parallel: boolean read fParallel write fParallel;
|
|
property forceRebuild: boolean read fForceRebuild write fForceRebuild;
|
|
property linkMode: TDubLinkMode read fLinkMode write setLinkMode;
|
|
property combined: boolean read fCombined write fCombined;
|
|
property other: string read fOther write fOther;
|
|
property dependenciesCheck: TDubDependencyCheck read fDepCheck write fDepCheck;
|
|
public
|
|
procedure assign(source: TPersistent); override;
|
|
procedure getOpts(options: TStrings);
|
|
end;
|
|
|
|
(**
|
|
* Make the build options editable
|
|
*)
|
|
TCEDubBuildOptions = class(TCEDubBuildOptionsBase, ICEEditableOptions)
|
|
strict private
|
|
fBackup: TCEDubBuildOptionsBase;
|
|
function optionedWantCategory(): string;
|
|
function optionedWantEditorKind: TOptionEditorKind;
|
|
function optionedWantContainer: TPersistent;
|
|
procedure optionedEvent(event: TOptionEditorEvent);
|
|
function optionedOptionsModified: boolean;
|
|
public
|
|
constructor create(aOwner: TComponent); override;
|
|
destructor destroy; override;
|
|
end;
|
|
|
|
TCEDubProject = class(TComponent, ICECommonProject)
|
|
private
|
|
fIsSdl: boolean;
|
|
fInGroup: boolean;
|
|
fDubProc: TCEProcess;
|
|
fPreCompilePath: string;
|
|
fPackageName: string;
|
|
fFilename: string;
|
|
fModified: boolean;
|
|
fJSON: TJSONObject;
|
|
fSrcs: TStringList;
|
|
fProjectSubject: TCEProjectSubject;
|
|
fConfigsCount: integer;
|
|
fImportPaths: TStringList;
|
|
fBuildTypes: TStringList;
|
|
fConfigs: TStringList;
|
|
fBuiltTypeIx: integer;
|
|
fConfigIx: integer;
|
|
fBinKind: TProjectBinaryKind;
|
|
fBasePath: string;
|
|
fModificationCount: integer;
|
|
fOutputFileName: string;
|
|
fSaveAsUtf8: boolean;
|
|
fCompiled: boolean;
|
|
fMsgs: ICEMessagesDisplay;
|
|
//
|
|
procedure doModified;
|
|
procedure updateFields;
|
|
procedure updatePackageNameFromJson;
|
|
procedure udpateConfigsFromJson;
|
|
procedure updateSourcesFromJson;
|
|
procedure updateTargetKindFromJson;
|
|
procedure updateImportPathsFromJson;
|
|
procedure updateOutputNameFromJson;
|
|
function findTargetKindInd(value: TJSONObject): boolean;
|
|
procedure dubProcOutput(proc: TObject);
|
|
procedure dubProcTerminated(proc: TObject);
|
|
function getCurrentCustomConfig: TJSONObject;
|
|
procedure compileOrRun(run: boolean; const runArgs: string = '');
|
|
public
|
|
constructor create(aOwner: TComponent); override;
|
|
destructor destroy; override;
|
|
//
|
|
procedure beginModification;
|
|
procedure endModification;
|
|
//
|
|
function filename: string;
|
|
function basePath: string;
|
|
procedure loadFromFile(const fname: string);
|
|
procedure saveToFile(const fname: string);
|
|
//
|
|
procedure activate;
|
|
function inGroup: boolean;
|
|
procedure inGroup(value: boolean);
|
|
function getFormat: TCEProjectFormat;
|
|
function getProject: TObject;
|
|
function modified: boolean;
|
|
function binaryKind: TProjectBinaryKind;
|
|
function getCommandLine: string;
|
|
function outputFilename: string;
|
|
procedure reload;
|
|
//
|
|
function isSource(const fname: string): boolean;
|
|
function sourcesCount: integer;
|
|
function sourceRelative(index: integer): string;
|
|
function sourceAbsolute(index: integer): string;
|
|
function importsPathCount: integer;
|
|
function importPath(index: integer): string;
|
|
//
|
|
function configurationCount: integer;
|
|
function getActiveConfigurationIndex: integer;
|
|
procedure setActiveConfigurationIndex(index: integer);
|
|
function configurationName(index: integer): string;
|
|
//
|
|
procedure compile;
|
|
function compiled: boolean;
|
|
procedure run(const runArgs: string = '');
|
|
function targetUpToDate: boolean;
|
|
//
|
|
property json: TJSONObject read fJSON;
|
|
property packageName: string read fPackageName;
|
|
property isSDL: boolean read fIsSdl;
|
|
end;
|
|
|
|
// these 9 built types always exist
|
|
TDubBuildType = (plain, debug, release, unittest, docs, ddox, profile, cov, unittestcov);
|
|
|
|
// returns true if filename is a valid dub project. Only json format is supported.
|
|
function isValidDubProject(const filename: string): boolean;
|
|
|
|
// converts a sdl description to json, returns the json
|
|
function sdl2json(const filename: string): TJSONObject;
|
|
|
|
function getDubCompiler: TCECompiler;
|
|
procedure setDubCompiler(value: TCECompiler);
|
|
|
|
var
|
|
DubCompiler: TCECompiler = TCECompiler.dmd;
|
|
DubCompilerFilename: string = 'dmd';
|
|
|
|
const
|
|
DubSdlWarning = 'this feature is deactivated in DUB projects with the SDL format';
|
|
|
|
implementation
|
|
|
|
var
|
|
dubBuildOptions: TCEDubBuildOptions;
|
|
|
|
const
|
|
|
|
optFname = 'dubbuild.txt';
|
|
|
|
DubBuiltTypeName: array[TDubBuildType] of string = ('plain', 'debug', 'release',
|
|
'unittest', 'docs', 'ddox', 'profile', 'cov', 'unittest-cov'
|
|
);
|
|
|
|
DubDefaultConfigName = '(default config)';
|
|
|
|
{$REGION Options ---------------------------------------------------------------}
|
|
procedure TCEDubBuildOptionsBase.setLinkMode(value: TDubLinkMode);
|
|
begin
|
|
if fLinkMode = value then
|
|
exit;
|
|
if not (value in [low(TDubLinkMode)..high(TDubLinkMode)]) then
|
|
value := low(TDubLinkMode);
|
|
fLinkMode:=value;
|
|
end;
|
|
|
|
procedure TCEDubBuildOptionsBase.setCompiler(value: TCECompiler);
|
|
begin
|
|
fCompiler := value;
|
|
setDubCompiler(fCompiler);
|
|
end;
|
|
|
|
function TCEDubBuildOptionsBase.getCompiler: TCECompiler;
|
|
begin
|
|
result := fCompiler;
|
|
end;
|
|
|
|
procedure TCEDubBuildOptionsBase.assign(source: TPersistent);
|
|
var
|
|
opts: TCEDubBuildOptionsBase;
|
|
begin
|
|
if source is TCEDubBuildOptionsBase then
|
|
begin
|
|
opts := TCEDubBuildOptionsBase(source);
|
|
parallel:=opts.parallel;
|
|
forceRebuild:=opts.forceRebuild;
|
|
combined:=opts.combined;
|
|
linkMode:=opts.linkMode;
|
|
other:=opts.other;
|
|
dependenciesCheck:=opts.dependenciesCheck;
|
|
compiler:=opts.compiler;
|
|
end
|
|
else inherited;
|
|
end;
|
|
|
|
procedure TCEDubBuildOptionsBase.getOpts(options: TStrings);
|
|
begin
|
|
if parallel then
|
|
options.Add('--parallel');
|
|
if forceRebuild then
|
|
options.Add('--force');
|
|
if combined then
|
|
options.Add('--combined');
|
|
case linkMode of
|
|
dlmAllAtOnce: options.Add('--build-mode=allAtOnce');
|
|
dlmSingleFile: options.Add('--build-mode=singleFile');
|
|
end;
|
|
case dependenciesCheck of
|
|
dcNo: options.Add('--skip-registry=all');
|
|
dcOffline: options.Add('--skip-registry=standard');
|
|
end;
|
|
if other.isNotEmpty then
|
|
CommandToList(other, options);
|
|
end;
|
|
|
|
constructor TCEDubBuildOptions.create(aOwner: TComponent);
|
|
var
|
|
fname: string;
|
|
begin
|
|
inherited;
|
|
fBackup := TCEDubBuildOptionsBase.Create(nil);
|
|
EntitiesConnector.addObserver(self);
|
|
fname := getCoeditDocPath + optFname;
|
|
if fname.fileExists then
|
|
loadFromFile(fname);
|
|
end;
|
|
|
|
destructor TCEDubBuildOptions.destroy;
|
|
begin
|
|
saveToFile(getCoeditDocPath + optFname);
|
|
EntitiesConnector.removeObserver(self);
|
|
fBackup.free;
|
|
inherited;
|
|
end;
|
|
|
|
function TCEDubBuildOptions.optionedWantCategory(): string;
|
|
begin
|
|
exit('DUB build');
|
|
end;
|
|
|
|
function TCEDubBuildOptions.optionedWantEditorKind: TOptionEditorKind;
|
|
begin
|
|
exit(oekGeneric);
|
|
end;
|
|
|
|
function TCEDubBuildOptions.optionedWantContainer: TPersistent;
|
|
begin
|
|
exit(self);
|
|
fBackup.assign(self);
|
|
end;
|
|
|
|
procedure TCEDubBuildOptions.optionedEvent(event: TOptionEditorEvent);
|
|
begin
|
|
case event of
|
|
oeeAccept: fBackup.assign(self);
|
|
oeeCancel: self.assign(fBackup);
|
|
oeeSelectCat:fBackup.assign(self);
|
|
end;
|
|
end;
|
|
|
|
function TCEDubBuildOptions.optionedOptionsModified: boolean;
|
|
begin
|
|
exit(false);
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
{$REGION Standard Comp/Obj -----------------------------------------------------}
|
|
constructor TCEDubProject.create(aOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
fSaveAsUtf8 := true;
|
|
fJSON := TJSONObject.Create();
|
|
fProjectSubject := TCEProjectSubject.Create;
|
|
fMsgs:= getMessageDisplay;
|
|
fBuildTypes := TStringList.Create;
|
|
fConfigs := TStringList.Create;
|
|
fSrcs := TStringList.Create;
|
|
fSrcs.Sorted:=true;
|
|
fSrcs.Duplicates:=dupIgnore;
|
|
fImportPaths := TStringList.Create;
|
|
fImportPaths.Sorted:=true;
|
|
fImportPaths.Duplicates:=dupIgnore;
|
|
//
|
|
json.Add('name', '');
|
|
endModification;
|
|
subjProjNew(fProjectSubject, self);
|
|
doModified;
|
|
fModified:=false;
|
|
end;
|
|
|
|
destructor TCEDubProject.destroy;
|
|
begin
|
|
killProcess(fDubProc);
|
|
subjProjClosing(fProjectSubject, self);
|
|
fProjectSubject.free;
|
|
//
|
|
fJSON.Free;
|
|
fBuildTypes.Free;
|
|
fConfigs.Free;
|
|
fSrcs.Free;
|
|
fImportPaths.Free;
|
|
inherited;
|
|
end;
|
|
{$ENDREGION --------------------------------------------------------------------}
|
|
|
|
{$REGION ICECommonProject: project props ---------------------------------------}
|
|
procedure TCEDubProject.activate;
|
|
begin
|
|
subjProjFocused(fProjectSubject, self as ICECommonProject);
|
|
end;
|
|
|
|
function TCEDubProject.inGroup: boolean;
|
|
begin
|
|
exit(fInGroup);
|
|
end;
|
|
|
|
procedure TCEDubProject.inGroup(value: boolean);
|
|
begin
|
|
fInGroup:=value;
|
|
end;
|
|
|
|
|
|
function TCEDubProject.getFormat: TCEProjectFormat;
|
|
begin
|
|
exit(pfDUB);
|
|
end;
|
|
|
|
function TCEDubProject.getProject: TObject;
|
|
begin
|
|
exit(self);
|
|
end;
|
|
|
|
function TCEDubProject.modified: boolean;
|
|
begin
|
|
exit(fModified);
|
|
end;
|
|
|
|
function TCEDubProject.filename: string;
|
|
begin
|
|
exit(fFilename);
|
|
end;
|
|
|
|
function TCEDubProject.basePath: string;
|
|
begin
|
|
exit(fBasePath);
|
|
end;
|
|
|
|
procedure TCEDubProject.reload;
|
|
begin
|
|
if fFilename.fileExists then
|
|
loadFromFile(fFilename);
|
|
end;
|
|
|
|
procedure TCEDubProject.loadFromFile(const fname: string);
|
|
var
|
|
loader: TMemoryStream;
|
|
parser : TJSONParser;
|
|
ext: string;
|
|
bom: dword = 0;
|
|
begin
|
|
ext := fname.extractFileExt.upperCase;
|
|
fBasePath := fname.extractFilePath;
|
|
fFilename := fname;
|
|
fSaveAsUtf8 := false;
|
|
fIsSdl := false;
|
|
if ext = '.JSON' then
|
|
begin
|
|
loader := TMemoryStream.Create;
|
|
try
|
|
loader.LoadFromFile(fFilename);
|
|
// skip BOMs, they crash the parser
|
|
loader.Read(bom, 4);
|
|
if (bom and $BFBBEF) = $BFBBEF then
|
|
begin
|
|
loader.Position:= 3;
|
|
fSaveAsUtf8 := true;
|
|
end
|
|
else if (bom = $FFFE0000) or (bom = $FEFF) then
|
|
begin
|
|
// UCS-4 LE/BE not handled by DUB
|
|
loader.clear;
|
|
loader.WriteByte(byte('{'));
|
|
loader.WriteByte(byte('}'));
|
|
loader.Position:= 0;
|
|
fFilename := '';
|
|
end
|
|
else if ((bom and $FEFF) = $FEFF) or ((bom and $FFFE) = $FFFE) then
|
|
begin
|
|
// UCS-2 LE/BE not handled by DUB
|
|
loader.clear;
|
|
loader.WriteByte(byte('{'));
|
|
loader.WriteByte(byte('}'));
|
|
loader.Position:= 0;
|
|
fFilename := '';
|
|
end
|
|
else
|
|
loader.Position:= 0;
|
|
//
|
|
FreeAndNil(fJSON);
|
|
parser := TJSONParser.Create(loader, [joIgnoreTrailingComma, joUTF8]);
|
|
//TODO-cfcl-json: remove etc/fcl-json the day they'll merge and rlz the version with 'Options'
|
|
//TODO-cfcl-json: track possible changes and fixes at http://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/packages/fcl-json/
|
|
//latest in etc = rev 34196.
|
|
try
|
|
try
|
|
fJSON := parser.Parse as TJSONObject;
|
|
except
|
|
if assigned(fJSON) then
|
|
FreeAndNil(fJSON);
|
|
fFilename := '';
|
|
end;
|
|
finally
|
|
parser.Free;
|
|
end;
|
|
finally
|
|
loader.Free;
|
|
end;
|
|
end
|
|
else if ext = '.SDL' then
|
|
begin
|
|
FreeAndNil(fJSON);
|
|
fJSON := sdl2json(fFilename);
|
|
if fJSON.isNil then
|
|
fFilename := ''
|
|
else
|
|
fIsSdl := true;
|
|
end;
|
|
|
|
if not assigned(fJSON) then
|
|
fJson := TJSONObject.Create(['name','invalid json']);
|
|
|
|
updateFields;
|
|
subjProjChanged(fProjectSubject, self);
|
|
fModified := false;
|
|
end;
|
|
|
|
procedure TCEDubProject.saveToFile(const fname: string);
|
|
var
|
|
saver: TMemoryStream;
|
|
str: string;
|
|
begin
|
|
if fname <> fFilename then
|
|
inGroup(false);
|
|
saver := TMemoryStream.Create;
|
|
try
|
|
fFilename := fname;
|
|
str := fJSON.FormatJSON;
|
|
if fSaveAsUtf8 then
|
|
begin
|
|
saver.WriteDWord($00BFBBEF);
|
|
saver.Position:=saver.Position-1;
|
|
end;
|
|
saver.Write(str[1], str.length);
|
|
saver.SaveToFile(fFilename);
|
|
finally
|
|
saver.Free;
|
|
fModified := false;
|
|
end;
|
|
end;
|
|
|
|
function TCEDubProject.binaryKind: TProjectBinaryKind;
|
|
begin
|
|
exit(fBinKind);
|
|
end;
|
|
|
|
function TCEDubProject.getCommandLine: string;
|
|
var
|
|
str: TStringList;
|
|
begin
|
|
str := TStringList.Create;
|
|
try
|
|
str.Add('dub' + exeExt);
|
|
str.Add('build');
|
|
str.Add('--build=' + fBuildTypes[fBuiltTypeIx]);
|
|
if (fConfigs.Count <> 1) and (fConfigs[0] <> DubDefaultConfigName) then
|
|
str.Add('--config=' + fConfigs[fConfigIx]);
|
|
str.Add('--compiler=' + DubCompilerFilename);
|
|
dubBuildOptions.getOpts(str);
|
|
result := str.Text;
|
|
finally
|
|
str.Free;
|
|
end;
|
|
end;
|
|
|
|
function TCEDubProject.outputFilename: string;
|
|
begin
|
|
exit(fOutputFileName);
|
|
end;
|
|
{$ENDREGION --------------------------------------------------------------------}
|
|
|
|
{$REGION ICECommonProject: sources ---------------------------------------------}
|
|
function TCEDubProject.isSource(const fname: string): boolean;
|
|
var
|
|
str: string;
|
|
begin
|
|
str := fname;
|
|
if str.fileExists then
|
|
str := ExtractRelativepath(fBasePath, str);
|
|
result := fSrcs.IndexOf(str) <> -1;
|
|
end;
|
|
|
|
function TCEDubProject.sourcesCount: integer;
|
|
begin
|
|
exit(fSrcs.Count);
|
|
end;
|
|
|
|
function TCEDubProject.sourceRelative(index: integer): string;
|
|
begin
|
|
exit(fSrcs[index]);
|
|
end;
|
|
|
|
function TCEDubProject.sourceAbsolute(index: integer): string;
|
|
var
|
|
fname: string;
|
|
begin
|
|
fname := fSrcs[index];
|
|
if fname.fileExists then
|
|
result := fname
|
|
else
|
|
result := expandFilenameEx(fBasePath, fname);
|
|
end;
|
|
|
|
function TCEDubProject.importsPathCount: integer;
|
|
begin
|
|
result := fImportPaths.Count;
|
|
end;
|
|
|
|
function TCEDubProject.importPath(index: integer): string;
|
|
begin
|
|
result := expandFilenameEx(fBasePath, fImportPaths[index]);
|
|
end;
|
|
{$ENDREGION --------------------------------------------------------------------}
|
|
|
|
{$REGION ICECommonProject: configs ---------------------------------------------}
|
|
function TCEDubProject.configurationCount: integer;
|
|
begin
|
|
exit(fConfigsCount);
|
|
end;
|
|
|
|
function TCEDubProject.getActiveConfigurationIndex: integer;
|
|
begin
|
|
exit(fBuiltTypeIx * fConfigs.Count + fConfigIx);
|
|
end;
|
|
|
|
procedure TCEDubProject.setActiveConfigurationIndex(index: integer);
|
|
begin
|
|
fBuiltTypeIx := index div fConfigs.Count;
|
|
fConfigIx := index mod fConfigs.Count;
|
|
doModified;
|
|
// DUB does not store an active config
|
|
fModified:=false;
|
|
end;
|
|
|
|
function TCEDubProject.configurationName(index: integer): string;
|
|
begin
|
|
result := fBuildTypes[index div fConfigs.Count] + ' - ' +
|
|
fConfigs[index mod fConfigs.Count];
|
|
end;
|
|
{$ENDREGION --------------------------------------------------------------------}
|
|
|
|
{$REGION ICECommonProject: actions ---------------------------------------------}
|
|
procedure TCEDubProject.dubProcOutput(proc: TObject);
|
|
var
|
|
lst: TStringList;
|
|
str: string;
|
|
begin
|
|
lst := TStringList.Create;
|
|
try
|
|
fDubProc.getFullLines(lst);
|
|
for str in lst do
|
|
fMsgs.message(str, self as ICECommonProject, amcProj, amkAuto);
|
|
finally
|
|
lst.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCEDubProject.dubProcTerminated(proc: TObject);
|
|
var
|
|
prjname: string;
|
|
begin
|
|
dubProcOutput(proc);
|
|
prjname := shortenPath(filename);
|
|
fCompiled := fDubProc.ExitStatus = 0;
|
|
if fCompiled then
|
|
fMsgs.message(prjname + ' has been successfully compiled',
|
|
self as ICECommonProject, amcProj, amkInf)
|
|
else
|
|
fMsgs.message(prjname + ' has not been compiled',
|
|
self as ICECommonProject, amcProj, amkWarn);
|
|
subjProjCompiled(fProjectSubject, self as ICECommonProject, fCompiled);
|
|
SetCurrentDirUTF8(fPreCompilePath);
|
|
end;
|
|
|
|
procedure TCEDubProject.compileOrRun(run: boolean; const runArgs: string = '');
|
|
var
|
|
olddir: string;
|
|
prjname: string;
|
|
begin
|
|
if fDubProc.isNotNil and fDubProc.Active then
|
|
begin
|
|
fMsgs.message('the project is already being compiled',
|
|
self as ICECommonProject, amcProj, amkWarn);
|
|
exit;
|
|
end;
|
|
killProcess(fDubProc);
|
|
fCompiled := false;
|
|
if not fFilename.fileExists then
|
|
begin
|
|
dlgOkInfo('The DUB project must be saved before being compiled or run !');
|
|
exit;
|
|
end;
|
|
fMsgs.clearByData(Self as ICECommonProject);
|
|
prjname := shortenPath(fFilename);
|
|
fDubProc:= TCEProcess.Create(nil);
|
|
olddir := GetCurrentDir;
|
|
try
|
|
if not run then
|
|
begin
|
|
subjProjCompiling(fProjectSubject, self as ICECommonProject);
|
|
fMsgs.message('compiling ' + prjname, self as ICECommonProject, amcProj, amkInf);
|
|
if modified then saveToFile(fFilename);
|
|
end;
|
|
chDir(fFilename.extractFilePath);
|
|
fDubProc.Executable := 'dub' + exeExt;
|
|
fDubProc.Options := fDubProc.Options + [poStderrToOutPut, poUsePipes];
|
|
fDubProc.CurrentDirectory := fFilename.extractFilePath;
|
|
fDubProc.ShowWindow := swoHIDE;
|
|
fDubProc.OnReadData:= @dubProcOutput;
|
|
if not run then
|
|
begin
|
|
fDubProc.Parameters.Add('build');
|
|
fDubProc.OnTerminate:= @dubProcTerminated;
|
|
end
|
|
else
|
|
begin
|
|
fDubProc.Parameters.Add('run');
|
|
fDubProc.OnTerminate:= @dubProcOutput;
|
|
end;
|
|
fDubProc.Parameters.Add('--build=' + fBuildTypes[fBuiltTypeIx]);
|
|
if (fConfigs.Count <> 1) and (fConfigs[0] <> DubDefaultConfigName) then
|
|
fDubProc.Parameters.Add('--config=' + fConfigs[fConfigIx]);
|
|
fDubProc.Parameters.Add('--compiler=' + DubCompilerFilename);
|
|
dubBuildOptions.getOpts(fDubProc.Parameters);
|
|
if run and runArgs.isNotEmpty then
|
|
fDubProc.Parameters.Add('--' + runArgs);
|
|
fDubProc.Execute;
|
|
finally
|
|
SetCurrentDirUTF8(olddir);
|
|
end;
|
|
end;
|
|
|
|
procedure TCEDubProject.compile;
|
|
begin
|
|
fPreCompilePath := GetCurrentDirUTF8;
|
|
compileOrRun(false);
|
|
end;
|
|
|
|
function TCEDubProject.compiled: boolean;
|
|
begin
|
|
exit(fCompiled);
|
|
end;
|
|
|
|
procedure TCEDubProject.run(const runArgs: string = '');
|
|
begin
|
|
compileOrRun(true);
|
|
end;
|
|
|
|
function TCEDubProject.targetUpToDate: boolean;
|
|
begin
|
|
// rebuilding is done automatically when the command is 'run'
|
|
result := true;
|
|
end;
|
|
{$ENDREGION --------------------------------------------------------------------}
|
|
|
|
{$REGION JSON to internal fields -----------------------------------------------}
|
|
function TCEDubProject.getCurrentCustomConfig: TJSONObject;
|
|
var
|
|
item: TJSONData;
|
|
confs: TJSONArray;
|
|
begin
|
|
result := nil;
|
|
if fConfigIx = 0 then exit;
|
|
//
|
|
item := fJSON.Find('configurations');
|
|
if item.isNil then exit;
|
|
//
|
|
confs := TJSONArray(item);
|
|
if fConfigIx > confs.Count -1 then exit;
|
|
//
|
|
result := confs.Objects[fConfigIx];
|
|
end;
|
|
|
|
procedure TCEDubProject.updatePackageNameFromJson;
|
|
var
|
|
value: TJSONData;
|
|
begin
|
|
if not assigned(fJSON) then
|
|
exit;
|
|
value := fJSON.Find('name');
|
|
if value.isNil then fPackageName := ''
|
|
else fPackageName := value.AsString;
|
|
end;
|
|
|
|
procedure TCEDubProject.udpateConfigsFromJson;
|
|
var
|
|
i: integer;
|
|
dat: TJSONData;
|
|
arr: TJSONArray = nil;
|
|
item: TJSONObject = nil;
|
|
obj: TJSONObject = nil;
|
|
itemname: string;
|
|
begin
|
|
fBuildTypes.Clear;
|
|
fConfigs.Clear;
|
|
if fJSON.isNil then
|
|
exit;
|
|
// the CE interface for dub doesn't make the difference between build type
|
|
//and config, instead each possible combination type + build is generated.
|
|
if fJSON.Find('configurations') <> nil then
|
|
begin
|
|
arr := fJSON.Arrays['configurations'];
|
|
for i:= 0 to arr.Count-1 do
|
|
begin
|
|
item := TJSONObject(arr.Items[i]);
|
|
if item.Find('name').isNil then
|
|
continue;
|
|
fConfigs.Add(item.Strings['name']);
|
|
end;
|
|
end else
|
|
begin
|
|
fConfigs.Add(DubDefaultConfigName);
|
|
// default = what dub set as 'application' or 'library'
|
|
// in this case Coedit will pass only the type to DUB: 'DUB --build=release'
|
|
end;
|
|
|
|
fBuildTypes.AddStrings(DubBuiltTypeName);
|
|
dat := fJSON.Find('buildTypes');
|
|
if dat.isNotNil and (dat.JSONType = jtObject) then
|
|
begin
|
|
obj := fJSON.Objects['buildTypes'];
|
|
for i := 0 to obj.Count-1 do
|
|
begin
|
|
itemname := obj.Names[i];
|
|
// defaults build types can be overridden
|
|
if fBuildTypes.IndexOf(itemname) <> -1 then
|
|
continue;
|
|
fBuildTypes.Add(itemname);
|
|
end;
|
|
end;
|
|
deleteDups(fConfigs);
|
|
deleteDups(fBuildTypes);
|
|
fConfigsCount := fConfigs.Count * fBuildTypes.Count;
|
|
end;
|
|
|
|
procedure TCEDubProject.updateSourcesFromJson;
|
|
var
|
|
lst: TStringList;
|
|
item: TJSONData;
|
|
conf: TJSONObject;
|
|
arr: TJSONArray;
|
|
i, j: integer;
|
|
procedure getExclusion(from: TJSONObject);
|
|
var
|
|
i: integer;
|
|
begin
|
|
item := from.Find('excludedSourceFiles');
|
|
if item.isNotNil and (item.JSONType = jtArray) then
|
|
begin
|
|
arr := TJSONArray(item);
|
|
for i := 0 to arr.Count-1 do
|
|
lst.Add(patchPlateformPath(arr.Strings[i]));
|
|
end;
|
|
end;
|
|
procedure tryAddRelOrAbsFile(const fname: string);
|
|
begin
|
|
if not isDlangCompilable(fname.extractFileExt) then
|
|
exit;
|
|
if fname.fileExists and FilenameIsAbsolute(fname) then
|
|
begin
|
|
fSrcs.Add(patchPlateformPath(ExtractRelativepath(fBasePath, fname)))
|
|
end
|
|
else if patchPlateformPath(expandFilenameEx(fBasePath, fname)).fileExists then
|
|
fSrcs.Add(fname);
|
|
end;
|
|
procedure tryAddFromFolder(const pth: string);
|
|
var
|
|
abs: string;
|
|
begin
|
|
if pth.dirExists then
|
|
begin
|
|
lst.Clear;
|
|
listFiles(lst, pth, true);
|
|
for abs in lst do
|
|
if isDlangCompilable(abs.extractFileExt) then
|
|
fSrcs.Add(ExtractRelativepath(fBasePath, abs));
|
|
end;
|
|
end;
|
|
var
|
|
pth: string;
|
|
glb: TRegExpr;
|
|
begin
|
|
fSrcs.Clear;
|
|
if not assigned(fJSON) then
|
|
exit;
|
|
lst := TStringList.Create;
|
|
try
|
|
// auto folders & files
|
|
item := fJSON.Find('mainSourceFile');
|
|
if item.isNotNil then
|
|
fSrcs.Add(patchPlateformPath(ExtractRelativepath(fBasePath, item.AsString)));
|
|
tryAddFromFolder(fBasePath + 'src');
|
|
tryAddFromFolder(fBasePath + 'source');
|
|
// custom folders
|
|
item := fJSON.Find('sourcePaths');
|
|
if item.isNotNil then
|
|
begin
|
|
arr := TJSONArray(item);
|
|
for i := 0 to arr.Count-1 do
|
|
begin
|
|
pth := TrimRightSet(arr.Strings[i], ['/','\']);
|
|
if pth.dirExists and FilenameIsAbsolute(pth) then
|
|
tryAddFromFolder(pth)
|
|
else
|
|
tryAddFromFolder(expandFilenameEx(fBasePath, pth));
|
|
end;
|
|
end;
|
|
// custom files
|
|
item := fJSON.Find('sourceFiles');
|
|
if item.isNotNil then
|
|
begin
|
|
arr := TJSONArray(item);
|
|
for i := 0 to arr.Count-1 do
|
|
tryAddRelOrAbsFile(arr.Strings[i]);
|
|
end;
|
|
conf := getCurrentCustomConfig;
|
|
if conf.isNotNil then
|
|
begin
|
|
item := conf.Find('mainSourceFile');
|
|
if item.isNotNil then
|
|
fSrcs.Add(patchPlateformPath(ExtractRelativepath(fBasePath, item.AsString)));
|
|
// custom folders in current config
|
|
item := conf.Find('sourcePaths');
|
|
if item.isNotNil then
|
|
begin
|
|
arr := TJSONArray(item);
|
|
for i := 0 to arr.Count-1 do
|
|
begin
|
|
pth := TrimRightSet(arr.Strings[i], ['/','\']);
|
|
if pth.dirExists and FilenameIsAbsolute(pth) then
|
|
tryAddFromFolder(pth)
|
|
else
|
|
tryAddFromFolder(expandFilenameEx(fBasePath, pth));
|
|
end;
|
|
end;
|
|
// custom files in current config
|
|
item := conf.Find('sourceFiles');
|
|
if item.isNotNil then
|
|
begin
|
|
arr := TJSONArray(item);
|
|
for i := 0 to arr.Count-1 do
|
|
tryAddRelOrAbsFile(arr.Strings[i]);
|
|
end;
|
|
end;
|
|
// exclusions
|
|
lst.Clear;
|
|
getExclusion(fJSON);
|
|
conf := getCurrentCustomConfig;
|
|
if conf.isNotNil then
|
|
getExclusion(conf);
|
|
if lst.Count > 0 then
|
|
begin
|
|
glb := TRegExpr.Create;
|
|
try
|
|
for j := 0 to lst.Count-1 do
|
|
begin
|
|
try
|
|
glb.Expression := globToReg(lst[j]);
|
|
glb.Compile;
|
|
for i := fSrcs.Count-1 downto 0 do
|
|
if glb.Exec(fSrcs[i]) then
|
|
fSrcs.Delete(i);
|
|
except
|
|
continue;
|
|
end;
|
|
end;
|
|
finally
|
|
glb.Free;
|
|
end;
|
|
end;
|
|
finally
|
|
lst.Free;
|
|
end;
|
|
deleteDups(fSrcs);
|
|
end;
|
|
|
|
function TCEDubProject.findTargetKindInd(value: TJSONObject): boolean;
|
|
var
|
|
tt: TJSONData;
|
|
begin
|
|
result := true;
|
|
if value.Find('mainSourceFile').isNotNil then
|
|
begin
|
|
fBinKind := executable;
|
|
exit;
|
|
end;
|
|
tt := value.Find('targetType');
|
|
if tt.isNotNil then
|
|
begin
|
|
case tt.AsString of
|
|
'executable': fBinKind := executable;
|
|
'staticLibrary', 'library' : fBinKind := staticlib;
|
|
'dynamicLibrary' : fBinKind := sharedlib;
|
|
'autodetect': result := false;
|
|
else fBinKind := executable;
|
|
end;
|
|
end else result := false;
|
|
end;
|
|
|
|
procedure TCEDubProject.updateTargetKindFromJson;
|
|
var
|
|
found: boolean = false;
|
|
conf: TJSONObject;
|
|
src: string;
|
|
begin
|
|
fBinKind := executable;
|
|
if fJSON.isNil then exit;
|
|
// note: in Coedit this is only used to known if output can be launched
|
|
found := findTargetKindInd(fJSON);
|
|
conf := getCurrentCustomConfig;
|
|
if conf.isNotNil then
|
|
found := found or findTargetKindInd(conf);
|
|
if not found then
|
|
begin
|
|
for src in fSrcs do
|
|
begin
|
|
if (src = 'source' + DirectorySeparator + 'app.d')
|
|
or (src = 'src' + DirectorySeparator + 'app.d')
|
|
or (src = 'source' + DirectorySeparator + 'main.d')
|
|
or (src = 'src' + DirectorySeparator + 'main.d')
|
|
or (src = 'source' + DirectorySeparator + fPackageName + DirectorySeparator + 'app.d')
|
|
or (src = 'src' + DirectorySeparator + fPackageName + DirectorySeparator + 'app.d')
|
|
or (src = 'source' + DirectorySeparator + fPackageName + DirectorySeparator + 'main.d')
|
|
or (src = 'src' + DirectorySeparator + fPackageName + DirectorySeparator + 'main.d')
|
|
then fBinKind:= executable
|
|
else fBinKind:= staticlib;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCEDubProject.updateImportPathsFromJson;
|
|
procedure addFrom(obj: TJSONObject);
|
|
var
|
|
arr: TJSONArray;
|
|
item: TJSONData;
|
|
pth: string;
|
|
i: integer;
|
|
begin
|
|
item := obj.Find('importPaths');
|
|
if assigned(item) then
|
|
begin
|
|
arr := TJSONArray(item);
|
|
for i := 0 to arr.Count-1 do
|
|
begin
|
|
pth := TrimRightSet(arr.Strings[i], ['/','\']);
|
|
if pth.dirExists and FilenameIsAbsolute(pth) then
|
|
fImportPaths.Add(pth)
|
|
else
|
|
fImportPaths.Add(expandFilenameEx(fBasePath, pth));
|
|
end;
|
|
end;
|
|
end;
|
|
// note: dependencies are added as import to allow DCD completion
|
|
// see TCEDcdWrapper.projChanged()
|
|
procedure addDepsFrom(obj: TJSONObject);
|
|
var
|
|
folds: TStringList;
|
|
deps: TJSONObject;
|
|
item: TJSONData;
|
|
pth: string;
|
|
str: string;
|
|
i,j,k: integer;
|
|
begin
|
|
item := obj.Find('dependencies');
|
|
if assigned(item) then
|
|
begin
|
|
{$IFDEF WINDOWS}
|
|
pth := GetEnvironmentVariable('APPDATA') + '\dub\packages\';
|
|
{$ELSE}
|
|
pth := GetEnvironmentVariable('HOME') + '/.dub/packages/';
|
|
{$ENDIF}
|
|
deps := TJSONObject(item);
|
|
folds := TStringList.Create;
|
|
listFolders(folds, pth);
|
|
try
|
|
// remove semver from folder names
|
|
for i := 0 to folds.Count-1 do
|
|
begin
|
|
str := folds[i];
|
|
k := -1;
|
|
for j := 1 to length(str) do
|
|
if str[j] = '-' then
|
|
k := j;
|
|
if k <> -1 then
|
|
folds[i] := str[1..k-1] + '=' + str[k .. length(str)];
|
|
end;
|
|
// add as import if names match
|
|
for i := 0 to deps.Count-1 do
|
|
begin
|
|
str := pth + deps.Names[i];
|
|
if folds.IndexOfName(str) <> -1 then
|
|
begin
|
|
if (str + folds.Values[str] + DirectorySeparator + 'source').dirExists then
|
|
fImportPaths.Add(str + DirectorySeparator + 'source')
|
|
else if (str + folds.Values[str] + DirectorySeparator + 'src').dirExists then
|
|
fImportPaths.Add(str + DirectorySeparator + 'src');
|
|
end;
|
|
end;
|
|
finally
|
|
folds.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
var
|
|
conf: TJSONObject;
|
|
begin
|
|
if fJSON.isNil then exit;
|
|
//
|
|
addFrom(fJSON);
|
|
addDepsFrom(fJSON);
|
|
conf := getCurrentCustomConfig;
|
|
if conf.isNotNil then
|
|
begin
|
|
addFrom(conf);
|
|
addDepsFrom(conf);
|
|
end;
|
|
end;
|
|
|
|
procedure TCEDubProject.updateOutputNameFromJson;
|
|
var
|
|
conf: TJSONObject;
|
|
item: TJSONData;
|
|
namePart, pathPart: string;
|
|
procedure setFrom(obj: TJSONObject);
|
|
var
|
|
n,p: TJSONData;
|
|
begin
|
|
p := obj.Find('targetPath');
|
|
n := obj.Find('targetName');
|
|
if p.isNotNil then pathPart := p.AsString;
|
|
if n.isNotNil then namePart := n.AsString;
|
|
end;
|
|
begin
|
|
fOutputFileName := '';
|
|
if fJSON.isNil then exit;
|
|
item := fJSON.Find('name');
|
|
if item.isNil then exit;
|
|
|
|
namePart := item.AsString;
|
|
pathPart := fBasePath;
|
|
setFrom(fJSON);
|
|
conf := getCurrentCustomConfig;
|
|
if conf.isNotNil then
|
|
setFrom(conf);
|
|
pathPart := TrimRightSet(pathPart, ['/','\']);
|
|
{$IFNDEF WINDOWS}
|
|
if fBinKind in [staticlib, sharedlib] then
|
|
namePart := 'lib' + namePart;
|
|
{$ENDIF}
|
|
fOutputFileName:= pathPart + DirectorySeparator + namePart;
|
|
patchPlateformPath(fOutputFileName);
|
|
fOutputFileName := expandFilenameEx(fBasePath, fOutputFileName);
|
|
case fBinKind of
|
|
executable: fOutputFileName += exeExt;
|
|
staticlib: fOutputFileName += libExt;
|
|
obj: fOutputFileName += objExt;
|
|
sharedlib: fOutputFileName += dynExt;
|
|
end;
|
|
end;
|
|
|
|
procedure TCEDubProject.updateFields;
|
|
begin
|
|
updatePackageNameFromJson;
|
|
udpateConfigsFromJson;
|
|
updateSourcesFromJson;
|
|
updateTargetKindFromJson;
|
|
updateImportPathsFromJson;
|
|
updateOutputNameFromJson;
|
|
end;
|
|
|
|
procedure TCEDubProject.beginModification;
|
|
begin
|
|
fModificationCount += 1;
|
|
end;
|
|
|
|
procedure TCEDubProject.endModification;
|
|
begin
|
|
fModificationCount -=1;
|
|
if fModificationCount <= 0 then
|
|
doModified;
|
|
end;
|
|
|
|
procedure TCEDubProject.doModified;
|
|
begin
|
|
fModificationCount := 0;
|
|
fModified:=true;
|
|
updateFields;
|
|
subjProjChanged(fProjectSubject, self as ICECommonProject);
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
{$REGION Miscellaneous DUB free functions --------------------------------------}
|
|
function sdl2json(const filename: string): TJSONObject;
|
|
var
|
|
dub: TProcess;
|
|
str: TStringList;
|
|
jsn: TJSONData;
|
|
prs: TJSONParser;
|
|
old: string;
|
|
begin
|
|
result := nil;
|
|
dub := TProcess.Create(nil);
|
|
str := TStringList.Create;
|
|
old := GetCurrentDirUTF8;
|
|
try
|
|
SetCurrentDirUTF8(filename.extractFilePath);
|
|
dub.Executable := 'dub' + exeExt;
|
|
dub.Options := [poUsePipes{$IFDEF WINDOWS}, poNewConsole{$ENDIF}];
|
|
dub.ShowWindow := swoHIDE;
|
|
dub.CurrentDirectory:= filename.extractFilePath;
|
|
dub.Parameters.Add('convert');
|
|
dub.Parameters.Add('-s');
|
|
dub.Parameters.Add('-f');
|
|
dub.Parameters.Add('json');
|
|
dub.Execute;
|
|
processOutputToStrings(dub, str);
|
|
while dub.Running do;
|
|
prs := TJSONParser.Create(str.Text, [joIgnoreTrailingComma, joUTF8]);
|
|
try
|
|
try
|
|
jsn := prs.Parse;
|
|
try
|
|
if jsn.isNotNil then
|
|
result := TJSONObject(jsn.Clone)
|
|
else
|
|
result := nil;
|
|
finally
|
|
jsn.free;
|
|
end;
|
|
finally
|
|
prs.Free
|
|
end;
|
|
except
|
|
result := nil;
|
|
end;
|
|
finally
|
|
SetCurrentDirUTF8(old);
|
|
dub.free;
|
|
str.Free;
|
|
end;
|
|
end;
|
|
|
|
function isValidDubProject(const filename: string): boolean;
|
|
var
|
|
maybe: TCEDubProject;
|
|
ext: string;
|
|
begin
|
|
ext := filename.extractFileExt.upperCase;
|
|
if (ext <> '.JSON') and (ext <> '.SDL') then
|
|
exit(false);
|
|
result := true;
|
|
// avoid the project to notify the observers, current project is not replaced
|
|
EntitiesConnector.beginUpdate;
|
|
maybe := TCEDubProject.create(nil);
|
|
try
|
|
try
|
|
maybe.loadFromFile(filename);
|
|
if maybe.json.isNil or maybe.filename.isEmpty then
|
|
result := false
|
|
else if maybe.json.Find('name').isNil then
|
|
result := false;
|
|
except
|
|
result := false;
|
|
end;
|
|
finally
|
|
maybe.Free;
|
|
EntitiesConnector.endUpdate;
|
|
end;
|
|
end;
|
|
|
|
function getDubCompiler: TCECompiler;
|
|
begin
|
|
exit(DubCompiler);
|
|
end;
|
|
|
|
procedure setDubCompiler(value: TCECompiler);
|
|
begin
|
|
case value of
|
|
TCECompiler.dmd: DubCompilerFilename := exeFullName('dmd' + exeExt);
|
|
TCECompiler.gdc: DubCompilerFilename := exeFullName('gdc' + exeExt);
|
|
TCECompiler.ldc: DubCompilerFilename := exeFullName('ldc2' + exeExt);
|
|
end;
|
|
if (not DubCompilerFilename.fileExists) or DubCompilerFilename.isEmpty then
|
|
begin
|
|
value := TCECompiler.dmd;
|
|
DubCompilerFilename:= 'dmd' + exeExt;
|
|
end;
|
|
DubCompiler := value;
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
initialization
|
|
setDubCompiler(TCECompiler.dmd);
|
|
dubBuildOptions:= TCEDubBuildOptions.create(nil);
|
|
finalization
|
|
dubBuildOptions.free;
|
|
end.
|
|
|