mirror of https://gitlab.com/basile.b/dexed.git
593 lines
15 KiB
Plaintext
593 lines
15 KiB
Plaintext
unit u_makeproject;
|
|
|
|
{$I u_defines.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, fpjson, jsonparser, jsonscanner, process, strutils,
|
|
LazFileUtils, RegExpr, fgl, math,
|
|
u_common, u_interfaces, u_observer, u_dialogs, u_processes,
|
|
u_writableComponent, u_compilers, u_semver, u_stringrange;
|
|
|
|
type
|
|
|
|
TMakeProjectOptionsBase = class(TWritableLfmTextComponent)
|
|
strict private
|
|
fNumThreads: integer;
|
|
fSourceDirectories: TStringList;
|
|
fExcludedSourceExtensions: TStringList;
|
|
fQuiet: boolean;
|
|
fKeepGoing: boolean;
|
|
procedure setSourceDirectories(value: TStringList);
|
|
procedure setExcludedSourceExtensions(value: TStringList);
|
|
published
|
|
property keepGoing: boolean read fKeepGoing write fKeepGoing default false;
|
|
property quiet: boolean read fQuiet write fQuiet default false;
|
|
property numThreads: integer read fNumThreads write fNumThreads default 1;
|
|
property sourceDirectories: TStringList read fSourceDirectories write setSourceDirectories;
|
|
property excludedSourceExtensions: TStringList read fExcludedSourceExtensions write setExcludedSourceExtensions;
|
|
public
|
|
constructor create(aOwner: TComponent); override;
|
|
destructor destroy(); override;
|
|
procedure assign(other: TPersistent); override;
|
|
end;
|
|
|
|
TMakeProjectOptions = class(TMakeProjectOptionsBase, IEditableOptions)
|
|
strict private
|
|
fBackup: TMakeProjectOptionsBase;
|
|
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;
|
|
|
|
TMakeProject = class(TComponent, ICommonProject)
|
|
private
|
|
|
|
fProjectSubject: TProjectSubject;
|
|
fRules: TStringList;
|
|
fMsgs: IMessagesDisplay;
|
|
fMakeProc: TDexedProcess;
|
|
fAsProjectItf: ICommonProject;
|
|
fBasePath: string;
|
|
fFilename: string;
|
|
fCompiled: boolean;
|
|
fInGroup: boolean;
|
|
fRuleIndex: integer;
|
|
fSrcs: TStringList;
|
|
|
|
procedure makeProcOutput(proc: TObject);
|
|
procedure makeProcTerminated(proc: TObject);
|
|
|
|
public
|
|
|
|
constructor create(aOwner: TComponent); override;
|
|
destructor destroy; override;
|
|
|
|
// indicates if the project is owned by a group.
|
|
function inGroup: boolean;
|
|
// flag the project as grouped
|
|
procedure inGroup(value: boolean);
|
|
// in a context of a group, activates the project
|
|
procedure activate;
|
|
// indicates the project format
|
|
function getFormat: TProjectFormat;
|
|
// returns an object that can be cast using the result of getFormat()
|
|
function getProject: TObject;
|
|
// returns the project filename
|
|
function filename: string;
|
|
// loads project from filename
|
|
procedure loadFromFile(const fname: string);
|
|
// saves project to filename
|
|
procedure saveToFile(const fname: string);
|
|
// reloads
|
|
procedure reload;
|
|
// indicates if the project is modified
|
|
function modified: boolean;
|
|
// returns the base dir used to solve relative paths
|
|
function basePath: string;
|
|
// returns the name of the file that's produced
|
|
function outputFilename: string;
|
|
// returns the kind of binary produced
|
|
function binaryKind: TProjectBinaryKind;
|
|
// returns the command line used to compile the project
|
|
function getCommandLine: string;
|
|
// stops compilation
|
|
procedure stopCompilation;
|
|
|
|
// configs -----------------------------------------------------------------
|
|
|
|
// returns the count of configuration (or config count * type count for pfDUB)
|
|
function configurationCount: integer;
|
|
// sets the active configuration
|
|
procedure setActiveConfigurationIndex(index: integer);
|
|
// returns the name of the nth configuration
|
|
function configurationName(index: integer): string;
|
|
// return the index of the active config
|
|
function getActiveConfigurationIndex: integer;
|
|
|
|
// project sources ---------------------------------------------------------
|
|
|
|
// returns the count of source files for the current config
|
|
function sourcesCount: integer;
|
|
// returns the source absolute filename.
|
|
function sourceAbsolute(index: integer): string;
|
|
// returns the source relative filename.
|
|
function sourceRelative(index: integer): string;
|
|
// returns true if aFilename is a project source.
|
|
function isSource(const aFilename: string): boolean;
|
|
// returns the count of import paths for the current config
|
|
function importsPathCount: integer;
|
|
// returns the import absolute path
|
|
function importPath(index: integer): string;
|
|
|
|
// sub routines for the actions --------------------------------------------
|
|
|
|
// tries to compile.
|
|
procedure compile;
|
|
//
|
|
procedure checkSemantics;
|
|
// indicates wether last complation was successful.
|
|
function compiled: boolean;
|
|
// tries to execute the project output.
|
|
procedure run(const runArgs: string = '');
|
|
// test the project (only for pfDUB)
|
|
procedure test;
|
|
// returns true if the target has not to be recompiled
|
|
function targetUpToDate: boolean;
|
|
end;
|
|
|
|
implementation
|
|
|
|
var
|
|
makeProjectOptions: TMakeProjectOptions;
|
|
const
|
|
optFname = 'makeProjectsOptions.txt';
|
|
|
|
constructor TMakeProjectOptionsBase.create(aOwner: TComponent);
|
|
begin
|
|
inherited create(aOwner);
|
|
fNumThreads := 1;
|
|
fSourceDirectories := TStringList.Create;
|
|
fSourceDirectories.AddStrings(['src', 'import', 'include']);
|
|
fExcludedSourceExtensions := TStringList.Create;
|
|
fExcludedSourceExtensions.AddStrings(['.txt', '.md', '.gcov']);
|
|
end;
|
|
|
|
destructor TMakeProjectOptionsBase.destroy();
|
|
begin
|
|
fSourceDirectories.Free;
|
|
fExcludedSourceExtensions.Free;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TMakeProjectOptionsBase.setSourceDirectories(value: TStringList);
|
|
begin
|
|
fSourceDirectories.Assign(value);
|
|
end;
|
|
|
|
procedure TMakeProjectOptionsBase.setExcludedSourceExtensions(value: TStringList);
|
|
begin
|
|
fExcludedSourceExtensions.Assign(value);
|
|
end;
|
|
|
|
procedure TMakeProjectOptionsBase.assign(other: TPersistent);
|
|
var
|
|
src: TMakeProjectOptionsBase;
|
|
begin
|
|
if other is TMakeProjectOptionsBase then
|
|
begin
|
|
src := TMakeProjectOptionsBase(other);
|
|
fSourceDirectories.Assign(src.sourceDirectories);
|
|
fExcludedSourceExtensions.Assign(src.excludedSourceExtensions);
|
|
fQuiet:= src.fQuiet;
|
|
fKeepGoing:= src.fKeepGoing;
|
|
fNumThreads:= src.fNumThreads;
|
|
end
|
|
else inherited;
|
|
end;
|
|
|
|
constructor TMakeProjectOptions.create(aOwner: TComponent);
|
|
var
|
|
fname: string;
|
|
begin
|
|
inherited;
|
|
fBackup := TMakeProjectOptionsBase.Create(nil);
|
|
EntitiesConnector.addObserver(self);
|
|
fname := getDocPath + optFname;
|
|
if fname.fileExists then
|
|
loadFromFile(fname);
|
|
end;
|
|
|
|
destructor TMakeProjectOptions.destroy;
|
|
begin
|
|
saveToFile(getDocPath + optFname);
|
|
EntitiesConnector.removeObserver(self);
|
|
fBackup.free;
|
|
inherited;
|
|
end;
|
|
|
|
function TMakeProjectOptions.optionedWantCategory(): string;
|
|
begin
|
|
exit('Makefile projects');
|
|
end;
|
|
|
|
function TMakeProjectOptions.optionedWantEditorKind: TOptionEditorKind;
|
|
begin
|
|
exit(oekGeneric);
|
|
end;
|
|
|
|
function TMakeProjectOptions.optionedWantContainer: TPersistent;
|
|
begin
|
|
fBackup.assign(self);
|
|
exit(self);
|
|
end;
|
|
|
|
procedure TMakeProjectOptions.optionedEvent(event: TOptionEditorEvent);
|
|
begin
|
|
case event of
|
|
oeeAccept: fBackup.assign(self);
|
|
oeeCancel: self.assign(fBackup);
|
|
oeeSelectCat:fBackup.assign(self);
|
|
end;
|
|
end;
|
|
|
|
function TMakeProjectOptions.optionedOptionsModified: boolean;
|
|
begin
|
|
exit(false);
|
|
end;
|
|
|
|
constructor TMakeProject.create(aOwner: TComponent);
|
|
begin
|
|
inherited create(aOwner);
|
|
fRules := TStringList.Create;
|
|
fSrcs := TStringList.Create;
|
|
fProjectSubject := TProjectSubject.Create;
|
|
fAsProjectItf := self as ICommonProject;
|
|
fMsgs := getMessageDisplay;
|
|
|
|
subjProjNew(fProjectSubject, self);
|
|
end;
|
|
|
|
destructor TMakeProject.destroy;
|
|
begin
|
|
killProcess(fMakeProc);
|
|
subjProjClosing(fProjectSubject, self);
|
|
fProjectSubject.free;
|
|
fRules.Free;
|
|
fSrcs.Free;
|
|
inherited;
|
|
end;
|
|
|
|
function TMakeProject.inGroup: boolean;
|
|
begin
|
|
result := fInGroup;
|
|
end;
|
|
|
|
procedure TMakeProject.inGroup(value: boolean);
|
|
begin
|
|
fInGroup := value;
|
|
end;
|
|
|
|
procedure TMakeProject.activate;
|
|
begin
|
|
subjProjFocused(fProjectSubject, fAsProjectItf);
|
|
end;
|
|
|
|
function TMakeProject.getFormat: TProjectFormat;
|
|
begin
|
|
result := pfMAKE;
|
|
end;
|
|
|
|
function TMakeProject.getProject: TObject;
|
|
begin
|
|
result := self;
|
|
end;
|
|
|
|
function TMakeProject.filename: string;
|
|
begin
|
|
result := fFilename;
|
|
end;
|
|
|
|
procedure TMakeProject.loadFromFile(const fname: string);
|
|
var
|
|
lines: TStringList;
|
|
srcs: TStringList;
|
|
line0: string;
|
|
line1: string;
|
|
hidden: string = DirectorySeparator + '.';
|
|
f: string;
|
|
p: string;
|
|
e: string;
|
|
s: string;
|
|
colPos: integer;
|
|
i: integer;
|
|
badExt: boolean;
|
|
begin
|
|
fRules.Clear;
|
|
fRules.add('default');
|
|
fRuleIndex := 0;
|
|
|
|
fFilename := fname;
|
|
if not FilenameIsAbsolute(fFilename) then
|
|
fFilename := ExpandFileName(fFilename);
|
|
fBasePath := fFilename.extractFilePath;
|
|
|
|
lines := TStringList.Create;
|
|
try
|
|
lines.LoadFromFile(fFilename);
|
|
if lines.Count > 1 then
|
|
for i := 0 to lines.Count-2 do
|
|
begin
|
|
line0 := lines[i];
|
|
line1 := lines[i+1];
|
|
colPos:= pos(':', line0);
|
|
if line0.isBlank or line1.isBlank or (colPos < 2) then
|
|
continue;
|
|
if (line0.length > 1) and not (line0[1] in [' ', #9]) and
|
|
(line1.length > 1) and (line1[1] in [' ', #9]) and not (line1[2] in [' ', #9]) then
|
|
fRules.Add(line0[1..colPos-1]);
|
|
end;
|
|
finally
|
|
lines.Free;
|
|
end;
|
|
|
|
fSrcs.Clear;
|
|
fSrcs.Sorted:=false;
|
|
srcs := TStringList.Create;
|
|
try
|
|
for p in makeProjectOptions.sourceDirectories do
|
|
begin
|
|
listFiles(srcs, fBasePath + p, true);
|
|
for f in srcs do
|
|
begin
|
|
if pos(hidden, f) = 0 then
|
|
begin
|
|
badExt := false;
|
|
s := f.extractFileExt();
|
|
for e in makeProjectOptions.excludedSourceExtensions do
|
|
if SameText(e, s) then
|
|
begin
|
|
badExt := true;
|
|
break;
|
|
end;
|
|
if not badExt then
|
|
fSrcs.Add(f[fBasePath.length + 1 .. f.length]);
|
|
end;
|
|
end;
|
|
srcs.Clear;
|
|
end;
|
|
finally
|
|
srcs.Free;
|
|
end;
|
|
fSrcs.Sorted:=true;
|
|
|
|
subjProjChanged(fProjectSubject, self);
|
|
|
|
end;
|
|
|
|
procedure TMakeProject.saveToFile(const fname: string);
|
|
begin
|
|
end;
|
|
|
|
procedure TMakeProject.reload;
|
|
begin
|
|
if fFilename.fileExists then
|
|
loadFromFile(fFilename);
|
|
end;
|
|
|
|
function TMakeProject.modified: boolean;
|
|
begin
|
|
result := false;
|
|
end;
|
|
|
|
function TMakeProject.basePath: string;
|
|
begin
|
|
result := fBasePath;
|
|
end;
|
|
|
|
function TMakeProject.outputFilename: string;
|
|
begin
|
|
result := '';
|
|
end;
|
|
|
|
function TMakeProject.binaryKind: TProjectBinaryKind;
|
|
begin
|
|
result := TProjectBinaryKind.unknown;
|
|
end;
|
|
|
|
function TMakeProject.getCommandLine: string;
|
|
begin
|
|
result := 'make ' + '-f' + fFilename;
|
|
if not fRuleIndex.equals(0) then
|
|
result += ' ' + fRules[fRuleIndex];
|
|
if makeProjectOptions.keepGoing then
|
|
result += ' -k';
|
|
if makeProjectOptions.quiet then
|
|
result += ' -q';
|
|
if makeProjectOptions.numThreads > 1 then
|
|
result += ' -j' + makeProjectOptions.numThreads.ToString();
|
|
end;
|
|
|
|
procedure TMakeProject.stopCompilation;
|
|
begin
|
|
if fMakeProc.isAssigned and fMakeProc.Running then
|
|
fMakeProc.Terminate(1);
|
|
end;
|
|
|
|
function TMakeProject.configurationCount: integer;
|
|
begin
|
|
result := fRules.Count;
|
|
end;
|
|
|
|
procedure TMakeProject.setActiveConfigurationIndex(index: integer);
|
|
begin
|
|
if fRuleIndex = index then
|
|
exit;
|
|
fRuleIndex := min(fRules.Count - 1, index);
|
|
fRuleIndex := max(0, fRuleIndex);
|
|
subjProjChanged(fProjectSubject, fAsProjectItf);
|
|
end;
|
|
|
|
function TMakeProject.configurationName(index: integer): string;
|
|
begin
|
|
result := fRules[index];
|
|
end;
|
|
|
|
function TMakeProject.getActiveConfigurationIndex: integer;
|
|
begin
|
|
result := fRuleIndex;
|
|
end;
|
|
|
|
function TMakeProject.sourcesCount: integer;
|
|
begin
|
|
result := fSrcs.count;
|
|
end;
|
|
|
|
function TMakeProject.sourceAbsolute(index: integer): string;
|
|
var
|
|
fname: string;
|
|
begin
|
|
fname := fSrcs[index];
|
|
if FilenameIsAbsolute(fname) then
|
|
result := fname
|
|
else
|
|
result := expandFilenameEx(fBasePath, fname);
|
|
end;
|
|
|
|
function TMakeProject.sourceRelative(index: integer): string;
|
|
begin
|
|
result := fSrcs[index];
|
|
end;
|
|
|
|
function TMakeProject.isSource(const aFilename: string): boolean;
|
|
var
|
|
str: string;
|
|
begin
|
|
str := fFilename.extractFileDir;
|
|
result := aFilename.StartsWith(str);
|
|
end;
|
|
|
|
function TMakeProject.importsPathCount: integer;
|
|
begin
|
|
result := 0;
|
|
end;
|
|
|
|
function TMakeProject.importPath(index: integer): string;
|
|
begin
|
|
result := '';
|
|
end;
|
|
|
|
procedure TMakeProject.makeProcOutput(proc: TObject);
|
|
var
|
|
lst: TStringList;
|
|
str: string;
|
|
begin
|
|
lst := TStringList.Create;
|
|
try
|
|
fMakeProc.getFullLines(lst);
|
|
fMsgs.beginMessageCall();
|
|
for str in lst do
|
|
fMsgs.message(str, fAsProjectItf, amcProj, amkAuto);
|
|
fMsgs.endMessageCall();
|
|
finally
|
|
lst.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TMakeProject.makeProcTerminated(proc: TObject);
|
|
var
|
|
n: string;
|
|
begin
|
|
makeProcOutput(proc);
|
|
n := shortenPath(filename);
|
|
fCompiled := fMakeProc.ExitStatus = 0;
|
|
if fCompiled then
|
|
begin
|
|
fMsgs.message(n + ' has been successfully compiled', fAsProjectItf, amcProj, amkInf);
|
|
end
|
|
else
|
|
begin
|
|
fMsgs.message(n + ' has not been successfully compiled', fAsProjectItf, amcProj, amkWarn);
|
|
fMsgs.message(format('error: make has returned the status %s',
|
|
[prettyReturnStatus(fMakeProc)]), fAsProjectItf, amcProj, amkErr);
|
|
if fMakeProc.autoKilled then
|
|
fMsgs.message(format('the process was autokilled because the size of its output exceeded %d',
|
|
[fMakeProc.autoKillProcThreshold]), nil, amcProj, amkWarn);
|
|
end;
|
|
subjProjCompiled(fProjectSubject, fAsProjectItf, fCompiled);
|
|
end;
|
|
|
|
procedure TMakeProject.compile;
|
|
var
|
|
p: string;
|
|
begin
|
|
if fMakeProc.isAssigned and fMakeProc.Active then
|
|
begin
|
|
fMsgs.message('the project is already being compiled by make', fAsProjectItf, amcProj, amkWarn);
|
|
exit;
|
|
end;
|
|
killProcess(fMakeProc);
|
|
fCompiled := false;
|
|
|
|
fMsgs.clearByData(fAsProjectItf);
|
|
subjProjCompiling(fProjectSubject, fAsProjectItf);
|
|
|
|
p := shortenPath(fFilename);
|
|
fMakeProc := TDexedProcess.create(nil);
|
|
fMakeProc.executable := 'make';
|
|
|
|
fMakeProc.Parameters.AddStrings([ '-f', fFilename]);
|
|
if not fRuleIndex.equals(0) then
|
|
fMakeProc.Parameters.Add(fRules[fRuleIndex]);
|
|
if makeProjectOptions.keepGoing then
|
|
fMakeProc.Parameters.Add('-k');
|
|
if makeProjectOptions.quiet then
|
|
fMakeProc.Parameters.Add('-q');
|
|
if makeProjectOptions.numThreads > 1 then
|
|
fMakeProc.Parameters.Add('-j' + makeProjectOptions.numThreads.ToString());
|
|
|
|
fMakeProc.Options := fMakeProc.Options + [poStderrToOutPut, poUsePipes];
|
|
fMakeProc.ShowWindow := swoHIDE;
|
|
fMakeProc.CurrentDirectory := fFilename.extractFilePath;
|
|
fMakeProc.XTermProgram:=consoleProgram;
|
|
fMakeProc.OnTerminate:= @makeProcTerminated;
|
|
fMakeProc.OnReadData:= @makeProcOutput;
|
|
fMsgs.message('compiling ' + p, fAsProjectItf, amcProj, amkInf);
|
|
fMakeProc.execute;
|
|
end;
|
|
|
|
procedure TMakeProject.checkSemantics;
|
|
begin
|
|
end;
|
|
|
|
function TMakeProject.compiled: boolean;
|
|
begin
|
|
result := fCompiled;
|
|
end;
|
|
|
|
procedure TMakeProject.run(const runArgs: string = '');
|
|
begin
|
|
end;
|
|
|
|
procedure TMakeProject.test;
|
|
begin
|
|
end;
|
|
|
|
function TMakeProject.targetUpToDate: boolean;
|
|
begin
|
|
result := false;
|
|
end;
|
|
|
|
initialization
|
|
makeProjectOptions:= TMakeProjectOptions.create(nil);
|
|
finalization
|
|
makeProjectOptions.free;
|
|
end.
|
|
|