1 module ddebug.common.nodebug;
2 
3 import ddebug.common.execution;
4 
5 import core.thread;
6 import std.process;
7 import dlangui.core.logger;
8 
9 class ProgramExecutionNoDebug : Thread, ProgramExecution {
10 
11     // parameters
12     /// provides _executableFile, _executableArgs, _executableWorkingDir, _executableEnvVars parameters and setter function setExecutableParams
13     mixin ExecutableParams;
14     /// provides _terminalExecutable, _terminalTty, setTerminalExecutable, and setTerminalTty
15     mixin TerminalParams;
16 
17     protected ProgramExecutionStatusListener _listener;
18     void setProgramExecutionStatusListener(ProgramExecutionStatusListener listener) {
19         _listener = listener;
20     }
21 
22     // status
23     protected Pid _pid;
24     protected ExecutionStatus _status = ExecutionStatus.NotStarted;
25     protected int _exitCode = 0;
26 
27     /// initialize but do not run
28     this() {
29         super(&threadFunc);
30     }
31 
32     ~this() {
33         stop();
34     }
35 
36     private bool isProcessActive() {
37         if (_pid is null)
38             return false;
39         auto res = tryWait(_pid);
40         if (res.terminated) {
41             Log.d("Process ", _executableFile, " is stopped");
42             _exitCode = wait(_pid);
43             _pid = Pid.init;
44             return false;
45         } else {
46             return true;
47         }
48     }
49 
50     private void killProcess() {
51         if (_pid is null)
52             return;
53         try {
54             Log.d("Trying to kill process", _executableFile);
55             kill(_pid, 9);
56             Log.d("Waiting for process termination");
57             _exitCode = wait(_pid);
58             _pid = Pid.init;
59             Log.d("Killed");
60         } catch (Exception e) {
61             Log.d("Exception while killing process " ~ _executableFile, e);
62             _pid = Pid.init;
63         }
64     }
65 
66     private void threadFunc() {
67         import std.stdio;
68         import std.array: empty;
69 
70         // prepare parameter list
71         string[] params;
72         params ~= _executableFile;
73         params ~= _executableArgs;
74 
75         // external console support
76         if (!_terminalExecutable.empty) {
77             string cmdline = escapeShellCommand(params);
78             string shellScript = `
79 rm $0
80 ` ~ cmdline ~ `
81 exit_code=$?
82 echo "
83 -----------------------
84 (program returned exit code: $exit_code)"
85 echo "Press return to continue..."
86 dummy_var=""
87 read dummy_var
88 exit $exit_code
89 `;
90             static import std.file;
91             static import std.path;
92             std.file.write(std.path.buildPath(_executableWorkingDir, "dlangide_run_script.sh"), shellScript);
93             string setExecFlagCommand = escapeShellCommand("chmod", "+x", "dlangide_run_script.sh");
94             spawnShell(setExecFlagCommand, stdin, stdout, stderr, null, Config.none, _executableWorkingDir);
95             params = [_terminalExecutable, "-e", "./dlangide_run_script.sh"];
96         }
97 
98         File newstdin;
99         File newstdout;
100         File newstderr;
101         version (Windows) {
102         } else {
103             newstdin = stdin;
104             newstdout = stdout;
105             newstderr = stderr;
106         }
107         try {
108             _pid = spawnProcess(params, newstdin, newstdout, newstderr, null, Config.none, _executableWorkingDir);
109         } catch (Exception e) {
110             Log.e("ProgramExecutionNoDebug: Failed to spawn process: ", e);
111             killProcess();
112             _status = ExecutionStatus.Error;
113         }
114 
115         if (_status != ExecutionStatus.Error) {
116             // thread loop: poll process status
117             while (!_stopRequested) {
118                 Thread.sleep(dur!"msecs"(50));
119                 if (!isProcessActive()) {
120                     _status = ExecutionStatus.Finished;
121                     break;
122                 }
123             }
124             if (_stopRequested) {
125                 killProcess();
126                 _status = ExecutionStatus.Killed;
127             }
128         }
129 
130         // finished
131         Log.d("ProgramExecutionNoDebug: finished, execution status: ", _status);
132         _listener.onProgramExecutionStatus(this, _status, _exitCode);
133     }
134 
135     // implement ProgramExecution interface
136 
137     /// returns true if it's debugger
138     @property bool isDebugger() { return false; }
139 
140     /// returns true if it's mago debugger
141     @property bool isMagoDebugger() { return false; }
142 
143     /// executable file
144     @property string executableFile() { return _executableFile; }
145 
146     /// returns execution status
147     @property ExecutionStatus status() { return _status; }
148 
149     /// start execution
150     void run() {
151         if (_runRequested)
152             return; // already running
153         assert(_listener !is null);
154         _runRequested = true;
155         _threadStarted = true;
156         _status = ExecutionStatus.Running;
157         start();
158     }
159 
160     /// stop execution (call from GUI thread)
161     void stop() {
162         if (!_runRequested)
163             return;
164         if (_stopRequested)
165             return;
166         _stopRequested = true;
167         if (_threadStarted && !_threadJoined) {
168             _threadJoined = true;
169             join();
170         }
171     }
172 
173     protected bool _threadStarted;
174     protected bool _threadJoined;
175     protected bool _stopRequested;
176     protected bool _runRequested;
177 }