From 02d7d13379a18ecbb56062b4bf748a41ec1be774 Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Wed, 9 Dec 2015 12:18:32 +0300 Subject: [PATCH] new Run implementation - no DUB --- dlangide-monod-linux.dproj | 2 + dlangide-monod-osx.dproj | 2 + dlangide_msvc.visualdproj | 9 ++ src/ddebug/common/execution.d | 31 +++++++ src/ddebug/common/nodebug.d | 146 +++++++++++++++++++++++++++++++ src/dlangide/ui/frame.d | 69 ++++++++++++++- src/dlangide/workspace/project.d | 19 ++++ workspaces/tetris/src/gui.d | 4 +- 8 files changed, 278 insertions(+), 4 deletions(-) create mode 100644 src/ddebug/common/execution.d create mode 100644 src/ddebug/common/nodebug.d diff --git a/dlangide-monod-linux.dproj b/dlangide-monod-linux.dproj index 72f829f..70f2bc5 100644 --- a/dlangide-monod-linux.dproj +++ b/dlangide-monod-linux.dproj @@ -218,6 +218,8 @@ + + diff --git a/dlangide-monod-osx.dproj b/dlangide-monod-osx.dproj index b5b35e0..9ea4c6d 100644 --- a/dlangide-monod-osx.dproj +++ b/dlangide-monod-osx.dproj @@ -128,6 +128,8 @@ + + diff --git a/dlangide_msvc.visualdproj b/dlangide_msvc.visualdproj index aaa86a9..6dd4474 100644 --- a/dlangide_msvc.visualdproj +++ b/dlangide_msvc.visualdproj @@ -419,6 +419,15 @@ + + + + + + + + + diff --git a/src/ddebug/common/execution.d b/src/ddebug/common/execution.d new file mode 100644 index 0000000..6dc35d5 --- /dev/null +++ b/src/ddebug/common/execution.d @@ -0,0 +1,31 @@ +/** + Support for running stopping of project executable. + + */ +module ddebug.common.execution; + +enum ExecutionStatus { + NotStarted, + Running, + Finished, // finished normally + Killed, // killed + Error // error while trying to start program +} + +interface ProgramExecutionStatusListener { + /// called when program execution is stopped + void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode); +} + +interface ProgramExecution { + /// returns true if it's debugger + @property bool isDebugger(); + /// executable file + @property string executableFile(); + /// returns execution status + @property ExecutionStatus status(); + /// start execution + bool run(); + /// stop execution + bool stop(); +} diff --git a/src/ddebug/common/nodebug.d b/src/ddebug/common/nodebug.d new file mode 100644 index 0000000..5dd5dad --- /dev/null +++ b/src/ddebug/common/nodebug.d @@ -0,0 +1,146 @@ +module ddebug.common.nodebug; + +import ddebug.common.execution; + +import core.thread; +import std.process; +import dlangui.core.logger; + +class ProgramExecutionNoDebug : Thread, ProgramExecution { + + // parameters + protected string _executableFile; + protected string[] _args; + protected string _workDir; + protected string _externalConsole; + protected ProgramExecutionStatusListener _listener; + + + // status + protected Pid _pid; + protected ExecutionStatus _status = ExecutionStatus.NotStarted; + protected int _exitCode = 0; + + + + /// initialize but do not run + this(string executable, string[] args, string workDir, string externalConsole, ProgramExecutionStatusListener listener) { + super(&threadFunc); + _executableFile = executable; + _args = args; + _workDir = workDir; + _externalConsole = externalConsole; + _listener = listener; + assert(_listener !is null); + } + + ~this() { + stop(); + } + + private bool isProcessActive() { + if (_pid is null) + return false; + auto res = tryWait(_pid); + if (res.terminated) { + Log.d("Process ", _executableFile, " is stopped"); + _exitCode = wait(_pid); + _pid = Pid.init; + return false; + } else { + return true; + } + } + + private void killProcess() { + if (_pid is null) + return; + try { + Log.d("Trying to kill process", _executableFile); + kill(_pid, 9); + Log.d("Waiting for process termination"); + _exitCode = wait(_pid); + _pid = Pid.init; + Log.d("Killed"); + } catch (Exception e) { + Log.d("Exception while killing process " ~ _executableFile, e); + _pid = Pid.init; + } + } + + private void threadFunc() { + import std.stdio; + string[] params; + params ~= _executableFile; + params ~= _args; + File newstdin; + File newstdout; + File newstderr; + try { + _pid = spawnProcess(params, newstdin, newstdout, newstderr, null, Config.none, _workDir); + } catch (Exception e) { + Log.e("ProgramExecutionNoDebug: Failed to spawn process: ", e); + killProcess(); + _status = ExecutionStatus.Error; + } + + if (_status != ExecutionStatus.Error) { + // thread loop: poll process status + while (!_stopRequested) { + Thread.sleep(dur!"msecs"(50)); + if (!isProcessActive()) { + _status = ExecutionStatus.Finished; + break; + } + } + if (_stopRequested) { + killProcess(); + _status = ExecutionStatus.Killed; + } + } + + // finished + _listener.onProgramExecutionStatus(this, _status, _exitCode); + } + + // implement ProgramExecution interface + + /// returns true if it's debugger + @property bool isDebugger() { return false; } + + /// executable file + @property string executableFile() { return _executableFile; } + + /// returns execution status + @property ExecutionStatus status() { return _status; } + + /// start execution + bool run() { + if (_runRequested) + return false; // already running + _runRequested = true; + _threadStarted = true; + _status = ExecutionStatus.Running; + start(); + return true; + } + + /// stop execution (call from GUI thread) + bool stop() { + if (!_runRequested) + return false; + if (_stopRequested) + return true; + _stopRequested = true; + if (_threadStarted && !_threadJoined) { + _threadJoined = true; + join(); + } + return true; + } + + protected bool _threadStarted; + protected bool _threadJoined; + protected bool _stopRequested; + protected bool _runRequested; +} diff --git a/src/dlangide/ui/frame.d b/src/dlangide/ui/frame.d index d1070f9..bf6005a 100644 --- a/src/dlangide/ui/frame.d +++ b/src/dlangide/ui/frame.d @@ -31,6 +31,9 @@ import dlangide.workspace.project; import dlangide.builders.builder; import dlangide.tools.editorTool; +import ddebug.common.execution; +import ddebug.common.nodebug; + import std.conv; import std.utf; import std.algorithm; @@ -61,7 +64,7 @@ class BackgroundOperationWatcherTest : BackgroundOperationWatcher { } /// DIDE app frame -class IDEFrame : AppFrame { +class IDEFrame : AppFrame, ProgramExecutionStatusListener { private ToolBarComboBox projectConfigurationCombo; @@ -72,6 +75,7 @@ class IDEFrame : AppFrame { TabWidget _tabs; DCDServer _dcdServer; IDESettings _settings; + ProgramExecution _execution; dstring frameWindowCaptionSuffix = "DLangIDE"d; @@ -84,6 +88,65 @@ class IDEFrame : AppFrame { applySettings(_settings); } + /// stop current program execution + void stopExecution() { + if (_execution) { + _execution.stop(); + destroy(_execution); + _execution = null; + } + } + + /// called when program execution is stopped + protected void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode) { + executeInUiThread(delegate() { + Log.d("onProgramExecutionStatus process: ", process.executableFile, " status: ", status, " exitCode: ", exitCode); + _execution = null; + // TODO: update state + switch(status) { + case ExecutionStatus.Error: + _logPanel.logLine("Cannot run program " ~ process.executableFile); + break; + case ExecutionStatus.Finished: + _logPanel.logLine("Program " ~ process.executableFile ~ " finished with exit code " ~ to!string(exitCode)); + break; + case ExecutionStatus.Killed: + _logPanel.logLine("Program " ~ process.executableFile ~ " is killed"); + break; + default: + _logPanel.logLine("Program " ~ process.executableFile ~ " is finished"); + break; + } + }); + } + + protected void runProject() { + import std.file; + stopExecution(); + if (!currentWorkspace) + return; + Project project = currentWorkspace.startupProject; + if (!project) { + window.showMessageBox(UIString("Cannot run project"d), UIString("Startup project is not specified"d)); + return; + } + // build project + // TODO + string executableFileName = project.executableFileName; + if (!executableFileName || !exists(executableFileName) || !isFile(executableFileName)) { + window.showMessageBox(UIString("Cannot run project"d), UIString("Cannot find executable"d)); + return; + } + string[] args; + string externalConsoleExecutable = null; // TODO + string workingDirectory = null; // TODO + // TODO: provide thread safe listener + _logPanel.logLine("Starting " ~ executableFileName); + _execution = new ProgramExecutionNoDebug(executableFileName, args, workingDirectory, externalConsoleExecutable, this); + _execution.run(); + // TODO: update status + } + override protected void init() { _appName = "dlangide"; //_editorTool = new DEditorTool(this); @@ -586,7 +649,8 @@ class IDEFrame : AppFrame { case IDEActions.DebugStart: case IDEActions.DebugStartNoDebug: case IDEActions.DebugContinue: - buildProject(BuildOperation.Run); + runProject(); + //buildProject(BuildOperation.Run); return true; case IDEActions.UpdateProjectDependencies: buildProject(BuildOperation.Upgrade); @@ -980,6 +1044,7 @@ class IDEFrame : AppFrame { /// called when main window is closing void onWindowClose() { Log.i("onWindowClose()"); + stopExecution(); if (_dcdServer) { if (_dcdServer.isRunning) _dcdServer.stop(); diff --git a/src/dlangide/workspace/project.d b/src/dlangide/workspace/project.d index c507d06..b57b6c2 100644 --- a/src/dlangide/workspace/project.d +++ b/src/dlangide/workspace/project.d @@ -438,6 +438,25 @@ class Project : WorkspaceItem { return buildNormalizedPath(_filename.dirName, toUTF8(name) ~ WORKSPACE_EXTENSION); } + @property bool isExecutable() { + // TODO: use targetType + return true; + } + + /// return executable file name, or null if it's library project or executable is not found + @property string executableFileName() { + if (!isExecutable) + return null; + string exename = toUTF8(name); + // TODO: use targetName + version (Windows) { + exename = exename ~ ".exe"; + } + // TODO: use targetPath + string exePath = buildNormalizedPath(_filename.dirName, "bin", exename); + return exePath; + } + ProjectFolder findItems(string[] srcPaths) { ProjectFolder folder = new ProjectFolder(_filename); folder.project = this; diff --git a/workspaces/tetris/src/gui.d b/workspaces/tetris/src/gui.d index 9a5ba39..8f11ae1 100644 --- a/workspaces/tetris/src/gui.d +++ b/workspaces/tetris/src/gui.d @@ -33,7 +33,7 @@ Widget createAboutWidget() res.addChild(new TextWidget(null, "(C) Vadim Lopatin, 2014"d)); res.addChild(new TextWidget(null, "http://github.com/buggins/dlangui"d)); Button closeButton = new Button("close", "Close"d); - closeButton.onClickListener = delegate(Widget src) { + closeButton.click = delegate(Widget src) { Log.i("Closing window"); res.window.close(); return true; @@ -521,7 +521,7 @@ class StatusWidget : VerticalLayout { ImageWidget image = new ImageWidget(null, "tetris_logo_big"); image.layoutWidth(FILL_PARENT).alignment(Align.Center).clickable(true); - image.onClickListener = delegate(Widget src) { + image.click = delegate(Widget src) { _cup.handleAction(ACTION_PAUSE); // about dialog when clicking on image Window wnd = Platform.instance.createWindow("About...", window, WindowFlag.Modal);