dexed/src/ce_ceproject.pas

1171 lines
31 KiB
Plaintext

unit ce_ceproject;
{$I ce_defines.inc}
interface
uses
{$IFDEF DEBUG}
LclProc,
{$ENDIF}
{$IFNDEF CEBUILD}
ce_dialogs,
{$ENDIF}
Classes, SysUtils, process, strUtils, RegExpr,
ce_common, ce_writableComponent, ce_dmdwrap, ce_observer, ce_interfaces,
ce_processes, LazFileUtils, ce_dastworx;
type
(*******************************************************************************
* Represents a D project.
*
* It includes all the options defined in ce_dmdwrap, organized in
* a collection to allow multiples configurations.
*
* Basically it' s designed to provide the options for the dmd process.
*)
TCENativeProject = class(TWritableLfmTextComponent, ICECommonProject)
private
fInGroup: boolean;
fCompilProc: TCEProcess;
fOnChange: TNotifyEvent;
fModified: boolean;
fPreCompilePath: string;
fRootFolder: string;
fBasePath: string;
fRunnerOldCwd: string;
fVersion: string;
fLibAliases: TStringList;
fAutoDeps: boolean;
fConfigs: TCollection;
fSrcs: TStringList;
fConfIx: Integer;
fUpdateCount: NativeInt;
fProjectSubject: TCEProjectSubject;
fRunner: TCEProcess;
fOutputFilename: string;
fCanBeRun: boolean;
fBaseConfig: TCompilerConfiguration;
fCompiled: boolean;
fSymStringExpander: ICESymStringExpander;
fMsgs: ICEMessagesDisplay;
fAsProjectItf: ICECommonProject;
procedure updateOutFilename;
procedure doChanged(modified: boolean = true);
procedure getBaseConfig;
procedure setLibAliases(value: TStringList);
procedure subMemberChanged(sender : TObject);
procedure setOptsColl(value: TCollection);
procedure setRoot(const value: string);
procedure setSrcs(value: TStringList);
procedure setConfIx(value: Integer);
function getConfig(value: integer): TCompilerConfiguration;
function getCurrConf: TCompilerConfiguration;
function runPrePostProcess(processInfo: TCompileProcOptions): Boolean;
// passes pre/post/executed project/ outputs as bubles.
procedure runProcOutput(sender: TObject);
// passes compilation message as "to be guessed"
procedure compProcOutput(proc: TObject);
procedure compProcTerminated(proc: TObject);
function getObjectsDirectory: string; inline;
procedure getUpToDateObjects(str: TStrings);
protected
procedure beforeLoad; override;
procedure afterSave; override;
procedure afterLoad; override;
procedure customSaveToFile(const fname: string); override;
procedure customLoadFromFile(const fname: string); override;
procedure readerPropNoFound(Reader: TReader; Instance: TPersistent;
var PropName: string; IsPath: Boolean; var Handled, Skip: Boolean); override;
published
property RootFolder: string read fRootFolder write setRoot;
property OptionsCollection: TCollection read fConfigs write setOptsColl;
property Sources: TStringList read fSrcs write setSrcs; // 'read' should return a copy to avoid abs/rel errors
property ConfigurationIndex: Integer read fConfIx write setConfIx;
property LibraryAliases: TStringList read fLibAliases write setLibAliases;
property AutoSolveDependencies: boolean read fAutoDeps write fAutoDeps default false;
property version: string read fVersion write fVersion;
public
constructor create(aOwner: TComponent); override;
destructor destroy; override;
procedure beginUpdate;
procedure endUpdate(modified: boolean = true);
procedure reset;
procedure addDefaults;
procedure addSource(const fname: string);
function addConfiguration: TCompilerConfiguration;
procedure getOpts(opts: TStrings);
//
procedure activate;
procedure inGroup(value: boolean);
function inGroup: boolean;
function getFormat: TCEProjectFormat;
function getProject: TObject;
function filename: string;
function basePath: string;
function outputFilename: string;
function binaryKind: TProjectBinaryKind;
function getCommandLine: string;
function modified: boolean;
procedure reload;
procedure stopCompilation;
//
function configurationCount: integer;
procedure setActiveConfigurationIndex(index: integer);
function configurationName(index: integer): string;
function getActiveConfigurationIndex: integer;
//
function sourcesCount: integer;
function sourceRelative(index: integer): string;
function sourceAbsolute(index: integer): string;
function isSource(const fname: string): boolean;
function importsPathCount: integer;
function importPath(index: integer): string;
//
procedure run(const runArgs: string = '');
function compiled: Boolean;
procedure compile;
procedure test;
function targetUpToDate: boolean;
procedure checkMissingFiles;
//
property configuration[ix: integer]: TCompilerConfiguration read getConfig;
property currentConfiguration: TCompilerConfiguration read getCurrConf;
property onChange: TNotifyEvent read fOnChange write fOnChange;
property canBeRun: Boolean read fCanBeRun;
end;
// native project have no ext constraint, this function tells if filename is project
function isValidNativeProject(const filename: string): boolean;
function getCEProjectCompiler: DCompiler;
procedure setCEProjectCompiler(value: DCompiler);
implementation
uses
controls, dialogs, ce_libman, ce_dcd;
var
CEProjectCompilerFilename: string = 'dmd';
CEProjectCompiler: DCompiler;
constructor TCENativeProject.create(aOwner: TComponent);
begin
inherited create(aOwner);
fAsProjectItf := self as ICECommonProject;
fSymStringExpander := getSymStringExpander;
fMsgs:= getMessageDisplay;
//
fRunnerOldCwd := GetCurrentDirUTF8;
fProjectSubject := TCEProjectSubject.create;
//
fLibAliases := TStringList.Create;
fSrcs := TStringList.Create;
fSrcs.OnChange := @subMemberChanged;
fConfigs := TCollection.create(TCompilerConfiguration);
//
reset;
addDefaults;
subjProjNew(fProjectSubject, self);
subjProjChanged(fProjectSubject, self);
//
{$IFNDEF WINDOWS}
fBasePath := '/';
{$ENDIF}
//
fModified := false;
end;
destructor TCENativeProject.destroy;
begin
killProcess(fCompilProc);
subjProjClosing(fProjectSubject, self);
fProjectSubject.Free;
//
fOnChange := nil;
fLibAliases.Free;
fSrcs.free;
fConfigs.free;
killProcess(fRunner);
inherited;
end;
function TCENativeProject.inGroup: boolean;
begin
exit(fInGroup);
end;
procedure TCENativeProject.inGroup(value: boolean);
begin
fInGroup:=value;
end;
procedure TCENativeProject.activate;
begin
subjProjFocused(fProjectSubject, fAsProjectItf);
end;
function TCENativeProject.getFormat: TCEProjectFormat;
begin
exit(pfDEXED);
end;
function TCENativeProject.getProject: TObject;
begin
exit(Self);
end;
function TCENativeProject.addConfiguration: TCompilerConfiguration;
begin
result := TCompilerConfiguration(fConfigs.Add);
result.onChanged := @subMemberChanged;
end;
procedure TCENativeProject.setOptsColl(value: TCollection);
var
i: nativeInt;
begin
fConfigs.Assign(value);
for i:= 0 to fConfigs.Count-1 do
Configuration[i].onChanged := @subMemberChanged;
end;
procedure TCENativeProject.addSource(const fname: string);
var
relSrc, absSrc: string;
expand: boolean;
begin
if not isDlangCompilable(fname.extractFileExt) then
exit;
expand := fBasePath.dirExists;
for relSrc in fSrcs do
begin
if not expand then absSrc := relSrc
else absSrc := expandFilenameEx(fBasePath, relsrc);
if SameFileName(fname, absSrc) then exit;
end;
relSrc := ExtractRelativePath(fBasePath, fname);
fSrcs.Add(relSrc);
end;
procedure TCENativeProject.setRoot(const value: string);
begin
if fRootFolder = value then exit;
beginUpdate;
fRootFolder := value;
endUpdate;
end;
procedure TCENativeProject.reload;
begin
if fFilename.fileExists then
loadFromFile(fFilename);
end;
procedure TCENativeProject.customLoadFromFile(const fname: string);
var
f: string;
begin
f := fname;
if not FilenameIsAbsolute(f) then
f := ExpandFileName(f);
if fname.extractFileExt <> '.dprj' then
begin
dlgOkInfo('project file extension automatically updated to "dprj"');
f := ChangeFileExt(fname, '.dprj');
RenameFile(fname, f);
end;
fbasePath := f.extractFilePath;
inherited customLoadFromFile(f);
end;
procedure TCENativeProject.customSaveToFile(const fname: string);
var
oldAbs, newRel, oldBase: string;
f: string;
i: NativeInt;
begin
beginUpdate;
f := fname;
if f <> fFilename then
inGroup(false);
oldBase := fBasePath;
fBasePath := f.extractFilePath;
//
for i:= 0 to fSrcs.Count-1 do
begin
oldAbs := expandFilenameEx(oldBase,fSrcs[i]);
newRel := ExtractRelativepath(fBasePath, oldAbs);
fSrcs[i] := newRel;
end;
endUpdate;
f := ChangeFileExt(f, '.dprj');
inherited customSaveToFile(f);
end;
procedure TCENativeProject.setLibAliases(value: TStringList);
begin
beginUpdate;
fLibAliases.Assign(value);
endUpdate;
end;
procedure TCENativeProject.setSrcs(value: TStringList);
begin
beginUpdate;
fSrcs.Assign(value);
patchPlateformPaths(fSrcs);
endUpdate;
end;
procedure TCENativeProject.setConfIx(value: Integer);
begin
beginUpdate;
if value < 0 then
value := 0;
if value > fConfigs.Count-1 then
value := fConfigs.Count-1;
fConfIx := value;
endUpdate(false);
end;
procedure TCENativeProject.getBaseConfig;
var
i: integer;
begin
fBaseConfig := nil;
for i:= 0 to fConfigs.Count-1 do
if configuration[i].isBaseConfiguration then
fBaseConfig := configuration[i];
// silently disables any other config. set as base without calling doChange
Inc(fUpdateCount);
for i := 0 to fConfigs.Count-1 do
if configuration[i].isBaseConfiguration then
if configuration[i] <> fBaseConfig then
configuration[i].isBaseConfiguration := false;
Dec(fUpdateCount);
end;
procedure TCENativeProject.subMemberChanged(sender : TObject);
begin
beginUpdate;
fModified := true;
endUpdate;
end;
procedure TCENativeProject.beginUpdate;
begin
Inc(fUpdateCount);
end;
procedure TCENativeProject.endUpdate(modified: boolean = true);
begin
Dec(fUpdateCount);
if fUpdateCount > 0 then
exit;
fUpdateCount := 0;
doChanged(modified);
end;
procedure TCENativeProject.doChanged(modified: boolean = true);
begin
fModified := modified;
updateOutFilename;
getBaseConfig;
subjProjChanged(fProjectSubject, self);
if assigned(fOnChange) then
fOnChange(Self);
end;
function TCENativeProject.getConfig(value: integer): TCompilerConfiguration;
begin
result := TCompilerConfiguration(fConfigs.Items[value]);
result.onChanged := @subMemberChanged;
end;
function TCENativeProject.getCurrConf: TCompilerConfiguration;
begin
result := TCompilerConfiguration(fConfigs.Items[fConfIx]);
end;
procedure TCENativeProject.addDefaults;
begin
with TCompilerConfiguration(fConfigs.Add) do
begin
Name := 'debug';
debugingOptions.debugConditions := true;
debugingOptions.generateInfos := true;
outputOptions.boundsCheck := onAlways;
end;
with TCompilerConfiguration(fConfigs.Add) do
begin
Name := 'unittest';
outputOptions.unittest := true;
outputOptions.boundsCheck := onAlways;
end;
with TCompilerConfiguration(fConfigs.Add) do
begin
Name := 'release';
outputOptions.release := true;
outputOptions.inlining := true;
outputOptions.boundsCheck := offAlways;
outputOptions.optimizations := true;
end;
end;
procedure TCENativeProject.reset;
var
defConf: TCompilerConfiguration;
begin
beginUpdate;
fConfIx := 0;
fConfigs.Clear;
defConf := addConfiguration;
defConf.name := 'default';
fSrcs.Clear;
fFilename := '';
endUpdate;
fModified := false;
end;
procedure TCENativeProject.getOpts(opts: TStrings);
var
i: Integer;
exc: TStringList;
als: TStringList;
cfg: TCompilerConfiguration;
str: string;
rel: string;
lst: TStringList;
begin
if fConfIx = -1 then exit;
exc := TStringList.Create;
try
cfg := currentConfiguration;
// prepares the exclusions
for i := 0 to cfg.pathsOptions.exclusions.Count-1 do
begin
str := fSymStringExpander.expand(cfg.pathsOptions.exclusions[i]);
exc.Add(str)
end;
// sources
for rel in fSrcs do if rel <> '' then
opts.Add(expandFilenameEx(fBasePath, rel)); // note: process.inc ln 249. double quotes are added if there's a space.
// exclusions
if exc.Count > 0 then with TRegExpr.Create do
try
for str in exc do
begin
try
Expression:= globToReg(str);
Compile;
for i := opts.Count-1 downto 0 do
if Exec(opts[i]) then
opts.Delete(i);
except
continue;
end;
end;
finally
free;
end;
// libraries: an asterisk in opts selects all the entries
als := fLibAliases;
if (fLibAliases.Count > 0) and (fLibAliases[0] = '*') then
als := nil;
{$IFDEF WINDOWS}
// only link lib file if executable/shared lib
// OS switch: read more @ http://forum.dlang.org/post/ooekdkwrefposmchekrp@forum.dlang.org
if (cfg.outputOptions.binaryKind in [executable, sharedlib]) or
cfg.outputOptions.alwaysLinkStaticLibs then
{$ENDIF}
LibMan.getLibFiles(als, opts);
// but always adds -I<path>
LibMan.getLibSourcePath(als, opts);
if fAutoDeps then
begin
lst := TStringList.Create;
try
str := '';
for i := 0 to fSrcs.Count-1 do
str += sourceAbsolute(i) + PathSeparator;
cfg.pathsOptions.getExtraSources(lst);
for i := 0 to lst.Count-1 do
str += lst[i] + PathSeparator;
lst.Clear;
getModulesImports(str, lst);
Libman.getLibFilesForImports(lst, opts);
finally
lst.Free;
end;
end;
// config
if cfg.isOverriddenConfiguration then
begin
cfg.getOpts(opts, fBaseConfig);
cfg.otherOptions.getCompilerSpecificOpts(opts, fBaseConfig.otherOptions,
CEProjectCompiler);
end
else
begin
cfg.getOpts(opts);
cfg.otherOptions.getCompilerSpecificOpts(opts, nil, CEProjectCompiler);
end;
finally
exc.Free;
end;
end;
function TCENativeProject.isSource(const fname: string): boolean;
var
i: Integer;
begin
for i := 0 to fSrcs.Count-1 do
if sourceAbsolute(i) = fname then
exit(true);
exit(false);
end;
procedure TCENativeProject.afterSave;
begin
fModified := false;
updateOutFilename;
end;
procedure TCENativeProject.beforeLoad;
begin
beginUpdate;
Inherited;
end;
procedure TCENativeProject.checkMissingFiles;
var
hasPatched: Boolean = false;
// either all the source files have moved or only the project file
procedure checkMissingAllSources;
var
allMissing: boolean;
dirHint: string;
newdir: string;
ini: string;
src: string;
i: Integer;
begin
if fSrcs.Count = 0 then exit;
allMissing := true;
for i:= 0 to fSrcs.Count-1 do
if sourceAbsolute(i).fileExists then
allMissing := false;
if not allMissing then exit;
if dlgYesNo( 'The project source(s) are all missing. ' + LineEnding +
'This can be encountered if the project file has been moved from its original location.' + LineEnding + LineEnding +
'Do you wish to select the new root folder ?') <> mrYes then exit;
// TODO-cimprovement: use commonFolder() when it'll be compat. with the rel. paths.
// hint for the common dir
dirHint := fSrcs[i];
while (dirHint[1] = '.') or (dirHint[1] = DirectorySeparator) do
dirHint := dirHint[2..dirHint.length];
ini := fFilename.extractFilePath;
if not selectDirectory( format('select the folder (that contains "%s")',[dirHint]), ini, newdir) then
exit;
for i := 0 to fSrcs.Count-1 do
begin
src := fSrcs[i];
while (src[1] = '.') or (src[1] = DirectorySeparator) do
src := src[2..src.length];
if fileExists(expandFilenameEx(fBasePath, newdir + DirectorySeparator + src)) then
fSrcs[i] := ExtractRelativepath(fBasePath, newdir + DirectorySeparator + src);
hasPatched := true;
end;
end;
// single sources files are missing
procedure checkMissingSingleSource;
var
oldsrc: string;
opendlg: TOpenDialog;
i: Integer;
begin
for i:= fSrcs.Count-1 downto 0 do
begin
oldsrc := sourceAbsolute(i);
if oldsrc.fileExists then continue;
if dlgYesNo(format('a particular project source file ("%s") is missing. '
+ LineEnding + 'This happends if a source file has been moved, renamed ' +
'or deleted.' + LineEnding + LineEnding +
'Do you wish to select its new location?', [fSrcs[i]])) <> mrYes then exit;
//
opendlg := TOpenDialog.Create(nil);
try
opendlg.InitialDir := fFilename.extractFilePath;
opendlg.FileName := fSrcs[i];
if opendlg.execute then
begin
if oldsrc.extractFileName <> opendlg.filename.extractFileName then
if dlgYesNo('the filenames are different, replace the old file ?') <> mrYes then
continue;
fSrcs[i] := ExtractRelativepath(fBasePath, opendlg.Filename);
hasPatched := true;
end else
begin
if dlgYesNo('You have choosen not to update the file, ' +
'do you wish to remove it from the project ?') <> mrYes then
continue;
fSrcs.Delete(i);
hasPatched := true;
end;
finally
opendlg.Free;
end;
end;
end;
begin
beginUpdate;
checkMissingAllSources;
checkMissingSingleSource;
endUpdate;
if hasPatched then
begin
fModified:= true;
dlgOkInfo('some source file paths has been patched, some others invalid ' +
'paths or file may still exist (-of, -od, extraSources, etc)' +
'but cannot be automatically handled. Note that the modifications have not been saved.');
end
else fModified:= false;
end;
procedure TCENativeProject.afterLoad;
begin
//if not fHasLoaded then
//begin
// dlgOkError('"' + shortenPath(fFilename) + '"' + 'does not seem to be a valid CE project');
// fFilename:= '';
//end;
patchPlateformPaths(fSrcs);
updateOutFilename;
endUpdate;
fModified := false;
end;
procedure TCENativeProject.readerPropNoFound(Reader: TReader; Instance: TPersistent;
var PropName: string; IsPath: Boolean; var Handled, Skip: Boolean);
begin
// errors are avoided by property deprecation, error here means "not a project".
Skip := true;
Handled := false;
end;
procedure TCENativeProject.updateOutFilename;
var
fe: boolean = false;
ext: string;
begin
fOutputFilename := currentConfiguration.pathsOptions.outputFilename;
fe := currentConfiguration.pathsOptions.forceExtension;
if currentConfiguration.isOverriddenConfiguration and fOutputFilename.isEmpty and
fBaseConfig.isNotNil then
begin
fOutputFilename := fBaseConfig.pathsOptions.outputFilename;
fe := fBaseConfig.pathsOptions.forceExtension;
end;
// field is specified
if fOutputFilename.isNotEmpty then
begin
if (fSymStringExpander <> nil) then
fOutputFilename := fSymStringExpander.expand(fOutputFilename);
fOutputFilename := expandFilenameEx(fBasePath, fOutputFilename);
{$IFDEF WINDOWS}
// field is specified without ext or with a dot in the name.
// DMD will add the ext. (e.g: "-ofresourced")
// https://issues.dlang.org/show_bug.cgi?id=13989
if fileexists(fOutputFilename + exeExt) then
if currentConfiguration.outputOptions.binaryKind = executable then
fOutputFilename := fOutputFilename + exeExt;
{$ENDIF}
end
// try to guess
else if Sources.Count > 0 then
begin
// ideally, main() should be searched for, when project binaryKind is executable
fOutputFilename := Sources[0].extractFileName;
fOutputFilename := fOutputFilename.stripFileExt;
if fileName.fileExists then
fOutputFilename := fileName.extractFilePath + fOutputFilename
else
fOutputFilename := GetTempDir(false) + fOutputFilename;
fe := true;
end;
//
if fe then
begin
ext := fOutputFilename.extractFileExt;
case currentConfiguration.outputOptions.binaryKind of
{$IFDEF WINDOWS}
executable: if ext <> exeExt then fOutputFilename += exeExt;
{$ENDIF}
staticlib: if ext <> libExt then fOutputFilename += libExt;
sharedlib: if ext <> dynExt then fOutputFilename += dynExt;
obj: if ext <> dynExt then fOutputFilename += objExt;
end;
end;
//
fCanBeRun := false;
if currentConfiguration.outputOptions.binaryKind = executable then
fCanBeRun := fOutputFilename.fileExists;
end;
function TCENativeProject.runPrePostProcess(processInfo: TCompileProcOptions): Boolean;
var
lst: TStringList;
com: boolean;
prc: TProcess;
nme: string;
i, j: integer;
begin
for i := 0 to processInfo.simpleCommands.Count-1 do
begin
nme := fSymStringExpander.expand(processInfo.simpleCommands[i]);
if nme.isBlank then
continue;
prc := TProcess.Create(nil);
lst := TStringList.Create;
try
CommandToList(nme, lst);
prc.Executable := lst[0];
prc.Options:= [poUsePipes, poStderrToOutPut];
lst.Delete(0);
prc.Parameters.Assign(lst);
prc.XTermProgram:=consoleProgram;
prc.Execute;
lst.Clear;
processOutputToStrings(prc, lst);
while prc.Running do
sleep(1);
com := prc.ExitStatus = 0;
for j := 0 to lst.Count -1 do
fMsgs.message(lst[j], fAsProjectItf, amcProj, amkAuto);
finally
prc.Free;
lst.Free;
end;
if not com then
exit(false);
end;
//
nme := fSymStringExpander.expand(processInfo.executable);
if (not exeInSysPath(nme)) and nme.isNotEmpty then
exit(false)
else if nme.isEmpty then
exit(true);
//
prc := TProcess.Create(nil);
try
processInfo.setProcess(prc);
prc.Executable := exeFullName(nme);
j := prc.Parameters.Count-1;
for i:= 0 to j do
prc.Parameters.AddText(fSymStringExpander.expand(prc.Parameters[i]));
for i:= 0 to j do
prc.Parameters.Delete(0);
if prc.CurrentDirectory.isNotEmpty then
prc.CurrentDirectory := fSymStringExpander.expand(prc.CurrentDirectory);
// else cwd is set to project dir in compile()
ensureNoPipeIfWait(prc);
prc.Execute;
while prc.Running do
if poUsePipes in prc.Options then
runProcOutput(prc);
finally
result := prc.ExitStatus = 0;
prc.Free;
end;
end;
function TCENativeProject.compiled: boolean;
begin
exit(fCompiled);
end;
procedure TCENativeProject.stopCompilation;
begin
if fCompilProc.isNotNil and fCompilProc.Running then
fCompilProc.Terminate(1);
end;
procedure TCENativeProject.compile;
var
config: TCompilerConfiguration;
prjpath: string;
prjname: string;
begin
if fCompilProc.isNotNil and fCompilProc.Active then
begin
fMsgs.message('the project is already being compiled',
fAsProjectItf, amcProj, amkWarn);
exit;
end;
killProcess(fCompilProc);
fCompiled := false;
config := currentConfiguration;
if config.isNil then
begin
fMsgs.message('unexpected project error: no active configuration',
fAsProjectItf, amcProj, amkErr);
exit;
end;
//
fMsgs.clearByData(fAsProjectItf);
subjProjCompiling(fProjectSubject, Self);
//
prjpath := fFileName.extractFilePath;
fPreCompilePath := GetCurrentDirUTF8;
SetCurrentDirUTF8(prjpath);
//
if not runPrePostProcess(config.preBuildProcess) then
fMsgs.message('warning: pre-compilation process or commands not properly executed',
fAsProjectItf, amcProj, amkWarn);
//
SetCurrentDirUTF8(prjpath);
//
if (Sources.Count = 0) and (config.pathsOptions.extraSources.Count = 0) then
begin
SetCurrentDirUTF8(fPreCompilePath);
exit;
end;
//
prjname := shortenPath(filename, 25);
fCompilProc := TCEProcess.Create(nil);
subjProjCompiling(fProjectSubject, fAsProjectItf);
fMsgs.message('compiling ' + prjname, fAsProjectItf, amcProj, amkInf);
fMsgs.message(usingCompilerInfo(CEProjectCompiler), fAsProjectItf, amcProj, amkInf);
// this doesn't work under linux, so the previous ChDir.
if prjpath.dirExists then
fCompilProc.CurrentDirectory := prjpath;
fCompilProc.Executable := CEProjectCompilerFilename;
fCompilProc.Options := fCompilProc.Options + [poStderrToOutPut, poUsePipes];
fCompilProc.ShowWindow := swoHIDE;
fCompilProc.OnReadData:= @compProcOutput;
fCompilProc.OnTerminate:= @compProcTerminated;
getOpts(fCompilProc.Parameters);
//getUpToDateObjects(fCompilProc.Parameters);
if CEProjectCompiler = gdc then
fCompilProc.Parameters.Add('-gdc=gdc');
fCompilProc.Execute;
end;
procedure TCENativeProject.run(const runArgs: string = '');
var
prm: string;
i: Integer;
cwd: string;
begin
killProcess(fRunner);
if fRunnerOldCwd.dirExists then
ChDir(fRunnerOldCwd);
//
fRunner := TCEProcess.Create(nil);
fRunner.XTermProgram:=consoleProgram;
currentConfiguration.runOptions.setProcess(fRunner);
if runArgs.isNotEmpty then
begin
i := 1;
repeat
prm := ExtractDelimited(i, runArgs, [' ']);
prm := fSymStringExpander.expand(prm);
if prm.isNotEmpty then
fRunner.Parameters.AddText(prm);
Inc(i);
until prm = '';
end;
//
if not outputFilename.fileExists then
begin
fMsgs.message('output executable missing: ' + shortenPath(outputFilename, 25),
fAsProjectItf, amcProj, amkErr);
exit;
end;
//
fRunner.Executable := outputFilename;
fRunnerOldCwd := GetCurrentDirUTF8;
if fRunner.CurrentDirectory.isEmpty then
begin
cwd := fRunner.Executable.extractFilePath;
SetCurrentDirUTF8(cwd);
fRunner.CurrentDirectory := cwd;
end;
if poUsePipes in fRunner.Options then
begin
fRunner.OnReadData := @runProcOutput;
fRunner.OnTerminate := @runProcOutput;
getprocInputHandler.addProcess(fRunner);
end;
fRunner.Execute;
end;
procedure TCENativeProject.runProcOutput(sender: TObject);
var
lst: TStringList;
str: string;
proc: TProcess;
begin
lst := TStringList.Create;
try
if (sender is TCEProcess) then
(sender as TCEProcess).getFullLines(lst)
else
processOutputToStrings(TProcess(sender), lst);
for str in lst do
fMsgs.message(str, fAsProjectItf, amcProj, amkBub);
finally
lst.Free;
end;
//
proc := TProcess(sender);
if not proc.Running then
begin
getprocInputHandler.removeProcess(TProcess(sender));
SetCurrentDirUTF8(fRunnerOldCwd);
if (proc.ExitStatus <> 0) then
fMsgs.message(format('error: the process (%s) has returned the status %s',
[proc.Executable, prettyReturnStatus(proc)]), fAsProjectItf, amcProj, amkErr);
end;
end;
procedure TCENativeProject.compProcOutput(proc: TObject);
var
lst: TStringList;
str: string;
begin
lst := TStringList.Create;
try
fCompilProc.getFullLines(lst);
for str in lst do
fMsgs.message(str, fAsProjectItf, amcProj, amkAuto);
finally
lst.Free;
end;
end;
procedure TCENativeProject.compProcTerminated(proc: TObject);
var
prjname: string;
begin
compProcOutput(proc);
prjname := shortenPath(filename);
fCompiled := fCompilProc.ExitStatus = 0;
updateOutFilename;
if fCompiled then
fMsgs.message(prjname + ' has been successfully compiled',
fAsProjectItf, amcProj, amkInf)
else
fMsgs.message(prjname + ' has not been compiled',
fAsProjectItf, amcProj, amkWarn);
//
if not runPrePostProcess(getCurrConf.postBuildProcess) then
fMsgs.message( 'warning: post-compilation process or commands not properly executed',
fAsProjectItf, amcProj, amkWarn);
subjProjCompiled(fProjectSubject, fAsProjectItf, fCompiled);
//
SetCurrentDirUTF8(fPreCompilePath);
end;
function TCENativeProject.targetUpToDate: boolean;
var
dt: double;
i: integer;
begin
result := false;
if not fOutputFilename.fileExists then exit;
dt := FileAge(fOutputFilename);
for i := 0 to fSrcs.Count-1 do
if fileAge(sourceAbsolute(i)) > dt then exit;
result := true;
end;
function TCENativeProject.getObjectsDirectory: string; inline;
var
cfg: TCompilerConfiguration;
begin
result := '';
cfg := currentConfiguration;
if (cfg.pathsOptions.objectDirectory <> '') and
DirectoryExistsUTF8(cfg.pathsOptions.objectDirectory) then
result := cfg.pathsOptions.objectDirectory;
end;
procedure TCENativeProject.getUpToDateObjects(str: TStrings);
var
odr: string;
src: string;
obj: string;
i: integer;
begin
odr := getObjectsDirectory;
if odr.isEmpty then
begin
for i := 0 to fSrcs.Count-1 do
begin
src := sourceAbsolute(i);
obj := src.stripFileExt + objExt;
if obj.fileExists and src.fileExists then
begin
if FileAgeUTF8(src) > FileAgeUTF8(obj) then
DeleteFile(obj)
else
str.Add(obj);
end;
end;
end
else
begin
end;
end;
function TCENativeProject.outputFilename: string;
begin
exit(fOutputFilename);
end;
function TCENativeProject.configurationCount: integer;
begin
exit(fConfigs.Count);
end;
procedure TCENativeProject.setActiveConfigurationIndex(index: integer);
begin
setConfIx(index);
end;
function TCENativeProject.getActiveConfigurationIndex: integer;
begin
exit(fConfIx);
end;
function TCENativeProject.configurationName(index: integer): string;
begin
if index > fConfigs.Count -1 then index := fConfigs.Count -1;
if index < 0 then index := 0;
result := getConfig(index).name;
end;
function TCENativeProject.filename: string;
begin
exit(fFilename);
end;
function TCENativeProject.modified: boolean;
begin
exit(fModified);
end;
function TCENativeProject.basePath: string;
begin
exit(fBasePath);
end;
function TCENativeProject.binaryKind: TProjectBinaryKind;
begin
exit(currentConfiguration.outputOptions.binaryKind);
end;
function TCENativeProject.getCommandLine: string;
var
str: TStringList;
begin
str := TStringList.Create;
try
str.Add(CEProjectCompilerFilename);
getOpts(str);
result := str.Text;
finally
str.Free;
end;
end;
function TCENativeProject.sourcesCount: integer;
begin
exit(fSrcs.Count);
end;
function TCENativeProject.sourceRelative(index: integer): string;
begin
exit(fSrcs[index]);
end;
function TCENativeProject.sourceAbsolute(index: integer): string;
var
fname: string;
begin
fname := fSrcs[index];
if FilenameIsAbsolute(fname) then
result := fname
else
result := expandFilenameEx(fBasePath, fname);
end;
function TCENativeProject.importsPathCount: integer;
begin
result := currentConfiguration.pathsOptions.importModulePaths.Count;
end;
function TCENativeProject.importPath(index: integer): string;
begin
result := currentConfiguration.pathsOptions.importModulePaths[index];
if fBasePath.dirExists then
result := expandFilenameEx(fBasePath, result);
end;
procedure TCENativeProject.test;
begin
end;
function isValidNativeProject(const filename: string): boolean;
var
maybe: TCENativeProject;
begin
result := false;
if isDlangCompilable(filename.extractFileExt) then
exit;
// avoid the project to notify the observers, current project is not replaced
EntitiesConnector.beginUpdate;
maybe := TCENativeProject.create(nil);
try
maybe.loadFromFile(filename);
result := maybe.hasLoaded;
finally
maybe.Free;
EntitiesConnector.endUpdate;
end;
end;
function getCEProjectCompiler: DCompiler;
begin
exit(CEProjectCompiler);
end;
procedure setCEProjectCompiler(value: DCompiler);
var
sel: ICECompilerSelector;
begin
sel := getCompilerSelector;
if value = gdc then
value := gdmd
else if value = ldc then
value := ldmd;
CEProjectCompiler := value;
if not sel.isCompilerValid(CEProjectCompiler) then
CEProjectCompiler := dmd;
CEProjectCompilerFilename:=sel.getCompilerPath(CEProjectCompiler);
end;
initialization
setCEProjectCompiler(dmd);
end.