dexed/src/ce_dubproject.pas

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.