David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 1 | """Extension to execute a script in a separate process |
| 2 | |
| 3 | David Scherer <dscherer@cmu.edu> |
| 4 | |
| 5 | The ExecBinding module, a replacement for ScriptBinding, executes |
| 6 | programs in a separate process. Unlike previous versions, this version |
| 7 | communicates with the user process via an RPC protocol (see the 'protocol' |
| 8 | module). The user program is loaded by the 'loader' and 'Remote' |
| 9 | modules. Its standard output and input are directed back to the |
| 10 | ExecBinding class through the RPC mechanism and implemented here. |
| 11 | |
| 12 | A "stop program" command is provided and bound to control-break. Closing |
| 13 | the output window also stops the running program. |
| 14 | """ |
| 15 | |
| 16 | import sys |
| 17 | import os |
| 18 | import imp |
| 19 | import OutputWindow |
| 20 | import protocol |
| 21 | import spawn |
| 22 | import traceback |
| 23 | import tempfile |
| 24 | |
| 25 | # Find Python and the loader. This should be done as early in execution |
| 26 | # as possible, because if the current directory or sys.path is changed |
| 27 | # it may no longer be possible to get correct paths for these things. |
| 28 | |
| 29 | pyth_exe = spawn.hardpath( sys.executable ) |
| 30 | load_py = spawn.hardpath( imp.find_module("loader")[1] ) |
| 31 | |
| 32 | # The following mechanism matches loaders up with ExecBindings that are |
| 33 | # trying to load something. |
| 34 | |
| 35 | waiting_for_loader = [] |
| 36 | |
| 37 | def loader_connect(client, addr): |
| 38 | if waiting_for_loader: |
| 39 | a = waiting_for_loader.pop(0) |
| 40 | try: |
| 41 | return a.connect(client, addr) |
| 42 | except: |
| 43 | return loader_connect(client,addr) |
| 44 | |
| 45 | protocol.publish('ExecBinding', loader_connect) |
| 46 | |
| 47 | class ExecBinding: |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 48 | menudefs = [ |
| 49 | ('run', [None, |
| 50 | ('Run program', '<<run-complete-script>>'), |
| 51 | ('Stop program', '<<stop-execution>>'), |
| 52 | ] |
| 53 | ), |
| 54 | ] |
| 55 | |
| 56 | delegate = 1 |
| 57 | |
| 58 | def __init__(self, editwin): |
| 59 | self.editwin = editwin |
| 60 | self.client = None |
| 61 | self.temp = [] |
| 62 | |
| 63 | if not hasattr(editwin, 'source_window'): |
| 64 | self.delegate = 0 |
| 65 | self.output = OutputWindow.OnDemandOutputWindow(editwin.flist) |
| 66 | self.output.close_hook = self.stopProgram |
| 67 | self.output.source_window = editwin |
| 68 | else: |
| 69 | if (self.editwin.source_window and |
| 70 | self.editwin.source_window.extensions.has_key('ExecBinding') and |
| 71 | not self.editwin.source_window.extensions['ExecBinding'].delegate): |
| 72 | delegate = self.editwin.source_window.extensions['ExecBinding'] |
| 73 | self.run_complete_script_event = delegate.run_complete_script_event |
| 74 | self.stop_execution_event = delegate.stop_execution_event |
| 75 | |
| 76 | def __del__(self): |
| 77 | self.stopProgram() |
| 78 | |
| 79 | def stop_execution_event(self, event): |
| 80 | if self.client: |
| 81 | self.stopProgram() |
| 82 | self.write('\nProgram stopped.\n','stderr') |
| 83 | |
| 84 | def run_complete_script_event(self, event): |
| 85 | filename = self.getfilename() |
| 86 | if not filename: return |
| 87 | filename = os.path.abspath(filename) |
| 88 | |
| 89 | self.stopProgram() |
| 90 | |
| 91 | self.commands = [ ('run', filename) ] |
| 92 | waiting_for_loader.append(self) |
| 93 | spawn.spawn( pyth_exe, load_py ) |
| 94 | |
| 95 | def connect(self, client, addr): |
| 96 | # Called by loader_connect() above. It is remotely possible that |
| 97 | # we get connected to two loaders if the user is running the |
| 98 | # program repeatedly in a short span of time. In this case, we |
| 99 | # simply return None, refusing to connect and letting the redundant |
| 100 | # loader die. |
| 101 | if self.client: return None |
| 102 | |
| 103 | self.client = client |
| 104 | client.set_close_hook( self.connect_lost ) |
| 105 | |
| 106 | title = self.editwin.short_title() |
| 107 | if title: |
| 108 | self.output.set_title(title + " Output") |
| 109 | else: |
| 110 | self.output.set_title("Output") |
| 111 | self.output.write('\n',"stderr") |
| 112 | self.output.scroll_clear() |
| 113 | |
| 114 | return self |
| 115 | |
| 116 | def connect_lost(self): |
| 117 | # Called by the client's close hook when the loader closes its |
| 118 | # socket. |
| 119 | |
| 120 | # We print a disconnect message only if the output window is already |
| 121 | # open. |
| 122 | if self.output.owin and self.output.owin.text: |
| 123 | self.output.owin.interrupt() |
| 124 | self.output.write("\nProgram disconnected.\n","stderr") |
| 125 | |
| 126 | for t in self.temp: |
| 127 | try: |
| 128 | os.remove(t) |
| 129 | except: |
| 130 | pass |
| 131 | self.temp = [] |
| 132 | self.client = None |
| 133 | |
| 134 | def get_command(self): |
| 135 | # Called by Remote to find out what it should be executing. |
| 136 | # Later this will be used to implement debugging, interactivity, etc. |
| 137 | if self.commands: |
| 138 | return self.commands.pop(0) |
| 139 | return ('finish',) |
| 140 | |
| 141 | def program_exception(self, type, value, tb, first, last): |
| 142 | if type == SystemExit: return 0 |
| 143 | |
| 144 | for i in range(len(tb)): |
| 145 | filename, lineno, name, line = tb[i] |
| 146 | if filename in self.temp: |
| 147 | filename = 'Untitled' |
| 148 | tb[i] = filename, lineno, name, line |
| 149 | |
| 150 | list = traceback.format_list(tb[first:last]) |
| 151 | exc = traceback.format_exception_only( type, value ) |
| 152 | |
| 153 | self.write('Traceback (innermost last)\n', 'stderr') |
| 154 | for i in (list+exc): |
| 155 | self.write(i, 'stderr') |
| 156 | |
| 157 | self.commands = [] |
| 158 | return 1 |
| 159 | |
| 160 | def write(self, text, tag): |
| 161 | self.output.write(text,tag) |
| 162 | |
| 163 | def readline(self): |
| 164 | return self.output.readline() |
| 165 | |
| 166 | def stopProgram(self): |
| 167 | if self.client: |
| 168 | self.client.close() |
| 169 | self.client = None |
| 170 | |
| 171 | def getfilename(self): |
| 172 | # Save all files which have been named, because they might be modules |
| 173 | for edit in self.editwin.flist.inversedict.keys(): |
| 174 | if edit.io and edit.io.filename and not edit.get_saved(): |
| 175 | edit.io.save(None) |
| 176 | |
| 177 | # Experimental: execute unnamed buffer |
| 178 | if not self.editwin.io.filename: |
| 179 | filename = os.path.normcase(os.path.abspath(tempfile.mktemp())) |
| 180 | self.temp.append(filename) |
| 181 | if self.editwin.io.writefile(filename): |
| 182 | return filename |
| 183 | |
| 184 | # If the file isn't save, we save it. If it doesn't have a filename, |
| 185 | # the user will be prompted. |
| 186 | if self.editwin.io and not self.editwin.get_saved(): |
| 187 | self.editwin.io.save(None) |
| 188 | |
| 189 | # If the file *still* isn't saved, we give up. |
| 190 | if not self.editwin.get_saved(): |
| 191 | return |
| 192 | |
| 193 | return self.editwin.io.filename |