mirror of https://gitlab.com/basile.b/dexed.git
965 lines
25 KiB
Plaintext
965 lines
25 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;
|
|
|
|
type
|
|
|
|
TCEDubProject = class(TComponent, ICECommonProject)
|
|
private
|
|
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;
|
|
//
|
|
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 aFilename: string);
|
|
procedure saveToFile(const aFilename: string);
|
|
//
|
|
function getFormat: TCEProjectFormat;
|
|
function getProject: TObject;
|
|
function modified: boolean;
|
|
function binaryKind: TProjectBinaryKind;
|
|
function getCommandLine: string;
|
|
function outputFilename: string;
|
|
//
|
|
function isSource(const aFilename: 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;
|
|
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;
|
|
|
|
function getDubCompiler: TCECompiler;
|
|
procedure setDubCompiler(value: TCECompiler);
|
|
|
|
implementation
|
|
|
|
var
|
|
|
|
DubCompiler: TCECompiler = dmd;
|
|
DubCompilerFilename: string = 'dmd';
|
|
|
|
const
|
|
|
|
DubBuiltTypeName: array[TDubBuildType] of string = ('plain', 'debug', 'release',
|
|
'unittest', 'docs', 'ddox', 'profile', 'cov', 'unittest-cov'
|
|
);
|
|
|
|
DubDefaultConfigName = '(default config)';
|
|
|
|
|
|
{$REGION Standard Comp/Obj -----------------------------------------------------}
|
|
constructor TCEDubProject.create(aOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
fSaveAsUtf8 := true;
|
|
fJSON := TJSONObject.Create();
|
|
fProjectSubject := TCEProjectSubject.Create;
|
|
fBuildTypes := TStringList.Create;
|
|
fConfigs := TStringList.Create;
|
|
fSrcs := TStringList.Create;
|
|
fImportPaths := TStringList.Create;
|
|
//
|
|
subjProjNew(fProjectSubject, self);
|
|
subjProjChanged(fProjectSubject, self);
|
|
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 ---------------------------------------}
|
|
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.loadFromFile(const aFilename: string);
|
|
var
|
|
loader: TMemoryStream;
|
|
parser : TJSONParser;
|
|
bom: dword = 0;
|
|
begin
|
|
loader := TMemoryStream.Create;
|
|
try
|
|
fBasePath := aFilename.extractFilePath;
|
|
fFilename := aFilename;
|
|
loader.LoadFromFile(fFilename);
|
|
fSaveAsUtf8 := false;
|
|
// skip BOM, this crashes 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 in etc = rev 33310.
|
|
try
|
|
try
|
|
fJSON := parser.Parse as TJSONObject;
|
|
except
|
|
if assigned(fJSON) then
|
|
FreeAndNil(fJSON);
|
|
fFilename := '';
|
|
end;
|
|
finally
|
|
parser.Free;
|
|
end;
|
|
finally
|
|
loader.Free;
|
|
if not assigned(fJSON) then
|
|
fJson := TJSONObject.Create(['name','invalid json']);
|
|
updateFields;
|
|
subjProjChanged(fProjectSubject, self);
|
|
fModified := false;
|
|
end;
|
|
end;
|
|
|
|
procedure TCEDubProject.saveToFile(const aFilename: string);
|
|
var
|
|
saver: TMemoryStream;
|
|
str: string;
|
|
begin
|
|
saver := TMemoryStream.Create;
|
|
try
|
|
fFilename := aFilename;
|
|
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);
|
|
result := str.Text;
|
|
finally
|
|
str.Free;
|
|
end;
|
|
end;
|
|
|
|
function TCEDubProject.outputFilename: string;
|
|
begin
|
|
exit(fOutputFileName);
|
|
end;
|
|
{$ENDREGION --------------------------------------------------------------------}
|
|
|
|
{$REGION ICECommonProject: sources ---------------------------------------------}
|
|
function TCEDubProject.isSource(const aFilename: string): boolean;
|
|
var
|
|
fname: string;
|
|
begin
|
|
fname := aFilename;
|
|
if fname.fileExists then
|
|
fname := ExtractRelativepath(fBasePath, fname);
|
|
result := fSrcs.IndexOf(fname) <> -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;
|
|
msgs: ICEMessagesDisplay;
|
|
begin
|
|
lst := TStringList.Create;
|
|
msgs := getMessageDisplay;
|
|
try
|
|
fDubProc.getFullLines(lst);
|
|
for str in lst do
|
|
msgs.message(str, self as ICECommonProject, amcProj, amkAuto);
|
|
finally
|
|
lst.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCEDubProject.dubProcTerminated(proc: TObject);
|
|
var
|
|
msgs: ICEMessagesDisplay;
|
|
prjname: string;
|
|
begin
|
|
dubProcOutput(proc);
|
|
msgs := getMessageDisplay;
|
|
prjname := shortenPath(filename);
|
|
fCompiled := fDubProc.ExitStatus = 0;
|
|
if fCompiled then
|
|
msgs.message(prjname + ' has been successfully compiled',
|
|
self as ICECommonProject, amcProj, amkInf)
|
|
else
|
|
msgs.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;
|
|
msgs: ICEMessagesDisplay;
|
|
begin
|
|
msgs := getMessageDisplay;
|
|
if fDubProc.isNotNil and fDubProc.Active then
|
|
begin
|
|
msgs.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;
|
|
msgs.clearByData(Self as ICECommonProject);
|
|
prjname := shortenPath(fFilename);
|
|
fDubProc:= TCEProcess.Create(nil);
|
|
olddir := GetCurrentDir;
|
|
try
|
|
if not run then
|
|
begin
|
|
subjProjCompiling(fProjectSubject, self as ICECommonProject);
|
|
msgs.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);
|
|
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;
|
|
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;
|
|
deleteDups(fSrcs);
|
|
// 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;
|
|
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}
|
|
|
|
{$ENDREGION --------------------------------------------------------------------}
|
|
|
|
{$REGION Miscellaneous DUB free functions --------------------------------------}
|
|
function isValidDubProject(const filename: string): boolean;
|
|
var
|
|
maybe: TCEDubProject;
|
|
begin
|
|
if (filename.extractFileExt.upperCase <> '.JSON') 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
|
|
dmd: DubCompilerFilename := exeFullName('dmd' + exeExt);
|
|
gdc: DubCompilerFilename := exeFullName('gdc' + exeExt);
|
|
ldc: DubCompilerFilename := exeFullName('ldc2' + exeExt);
|
|
end;
|
|
if (not DubCompilerFilename.fileExists) or DubCompilerFilename.isEmpty then
|
|
begin
|
|
value := dmd;
|
|
DubCompilerFilename:= 'dmd' + exeExt;
|
|
end;
|
|
DubCompiler := value;
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
initialization
|
|
setDubCompiler(dmd);
|
|
end.
|
|
|