|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2007 - 2009 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the Python debugger interface for the debug server. |
|
8 """ |
|
9 |
|
10 import sys |
|
11 import os |
|
12 import socket |
|
13 import subprocess |
|
14 |
|
15 from PyQt4.QtCore import * |
|
16 from PyQt4.QtGui import QInputDialog, QMessageBox |
|
17 |
|
18 from E4Gui.E4Application import e4App |
|
19 |
|
20 from DebugProtocol import * |
|
21 import DebugClientCapabilities |
|
22 |
|
23 import Preferences |
|
24 import Utilities |
|
25 |
|
26 from eric4config import getConfig |
|
27 |
|
28 |
|
29 ClientDefaultCapabilities = DebugClientCapabilities.HasAll |
|
30 |
|
31 def getRegistryData(): |
|
32 """ |
|
33 Module function to get characterising data for the debugger interface. |
|
34 |
|
35 @return list of the following data. Client type (string), client |
|
36 capabilities (integer), client type association (list of strings) |
|
37 """ |
|
38 exts = [] |
|
39 for ext in Preferences.getDebugger("PythonExtensions").split(): |
|
40 if ext.startswith("."): |
|
41 exts.append(ext) |
|
42 else: |
|
43 exts.append(".%s" % ext) |
|
44 |
|
45 if exts: |
|
46 return ["Python", ClientDefaultCapabilities, exts] |
|
47 else: |
|
48 return ["", 0, []] |
|
49 |
|
50 class DebuggerInterfacePython(QObject): |
|
51 """ |
|
52 Class implementing the Python debugger interface for the debug server. |
|
53 """ |
|
54 def __init__(self, debugServer, passive): |
|
55 """ |
|
56 Constructor |
|
57 |
|
58 @param debugServer reference to the debug server (DebugServer) |
|
59 @param passive flag indicating passive connection mode (boolean) |
|
60 """ |
|
61 QObject.__init__(self) |
|
62 |
|
63 self.__isNetworked = True |
|
64 self.__autoContinue = not passive |
|
65 |
|
66 self.debugServer = debugServer |
|
67 self.passive = passive |
|
68 self.process = None |
|
69 |
|
70 self.qsock = None |
|
71 self.queue = [] |
|
72 # set default values for capabilities of clients |
|
73 self.clientCapabilities = ClientDefaultCapabilities |
|
74 |
|
75 self.codec = QTextCodec.codecForName(Preferences.getSystem("StringEncoding")) |
|
76 |
|
77 if passive: |
|
78 # set translation function |
|
79 if Preferences.getDebugger("PathTranslation"): |
|
80 self.translateRemote = \ |
|
81 Preferences.getDebugger("PathTranslationRemote") |
|
82 self.translateLocal = \ |
|
83 Preferences.getDebugger("PathTranslationLocal") |
|
84 self.translate = self.__remoteTranslation |
|
85 else: |
|
86 self.translate = self.__identityTranslation |
|
87 |
|
88 def __identityTranslation(self, fn, remote2local = True): |
|
89 """ |
|
90 Private method to perform the identity path translation. |
|
91 |
|
92 @param fn filename to be translated (string) |
|
93 @param remote2local flag indicating the direction of translation |
|
94 (False = local to remote, True = remote to local [default]) |
|
95 @return translated filename (string) |
|
96 """ |
|
97 return fn |
|
98 |
|
99 def __remoteTranslation(self, fn, remote2local = True): |
|
100 """ |
|
101 Private method to perform the path translation. |
|
102 |
|
103 @param fn filename to be translated (string) |
|
104 @param remote2local flag indicating the direction of translation |
|
105 (False = local to remote, True = remote to local [default]) |
|
106 @return translated filename (string) |
|
107 """ |
|
108 if remote2local: |
|
109 return fn.replace(self.translateRemote, self.translateLocal) |
|
110 else: |
|
111 return fn.replace(self.translateLocal, self.translateRemote) |
|
112 |
|
113 def __startProcess(self, program, arguments, environment = None): |
|
114 """ |
|
115 Private method to start the debugger client process. |
|
116 |
|
117 @param program name of the executable to start (string) |
|
118 @param arguments arguments to be passed to the program (list of string) |
|
119 @param environment dictionary of environment settings to pass (dict of string) |
|
120 @return the process object (QProcess) or None |
|
121 """ |
|
122 proc = QProcess() |
|
123 if environment is not None: |
|
124 env = [] |
|
125 for key, value in environment.items(): |
|
126 env.append("%s=%s" % (key, value)) |
|
127 proc.setEnvironment(env) |
|
128 args = [] |
|
129 for arg in arguments: |
|
130 args.append(arg) |
|
131 proc.start(program, args) |
|
132 if not proc.waitForStarted(10000): |
|
133 proc = None |
|
134 |
|
135 return proc |
|
136 |
|
137 def startRemote(self, port, runInConsole): |
|
138 """ |
|
139 Public method to start a remote Python interpreter. |
|
140 |
|
141 @param port portnumber the debug server is listening on (integer) |
|
142 @param runInConsole flag indicating to start the debugger in a |
|
143 console window (boolean) |
|
144 @return client process object (QProcess) and a flag to indicate |
|
145 a network connection (boolean) |
|
146 """ |
|
147 if Preferences.getDebugger("CustomPythonInterpreter"): |
|
148 interpreter = Preferences.getDebugger("PythonInterpreter") |
|
149 if interpreter == "": |
|
150 interpreter = sys.executable |
|
151 else: |
|
152 interpreter = sys.executable |
|
153 |
|
154 debugClientType = Preferences.getDebugger("DebugClientType") |
|
155 if debugClientType == "standard": |
|
156 debugClient = os.path.join(getConfig('ericDir'), |
|
157 "DebugClients", "Python", "DebugClient.py") |
|
158 elif debugClientType == "threaded": |
|
159 debugClient = os.path.join(getConfig('ericDir'), |
|
160 "DebugClients", "Python", "DebugClientThreads.py") |
|
161 else: |
|
162 debugClient = Preferences.getDebugger("DebugClient") |
|
163 if debugClient == "": |
|
164 debugClient = os.path.join(sys.path[0], |
|
165 "DebugClients", "Python", "DebugClient.py") |
|
166 |
|
167 redirect = str(Preferences.getDebugger("PythonRedirect")) |
|
168 noencoding = \ |
|
169 Preferences.getDebugger("PythonNoEncoding") and '--no-encoding' or '' |
|
170 |
|
171 if Preferences.getDebugger("RemoteDbgEnabled"): |
|
172 ipaddr = self.debugServer.getHostAddress(False) |
|
173 rexec = Preferences.getDebugger("RemoteExecution") |
|
174 rhost = Preferences.getDebugger("RemoteHost") |
|
175 if rhost == "": |
|
176 rhost = "localhost" |
|
177 if rexec: |
|
178 args = Utilities.parseOptionString(rexec) + \ |
|
179 [rhost, interpreter, os.path.abspath(debugClient), |
|
180 noencoding, str(port), redirect, ipaddr] |
|
181 args[0] = Utilities.getExecutablePath(args[0]) |
|
182 process = self.__startProcess(args[0], args[1:]) |
|
183 if process is None: |
|
184 QMessageBox.critical(None, |
|
185 self.trUtf8("Start Debugger"), |
|
186 self.trUtf8(\ |
|
187 """<p>The debugger backend could not be started.</p>""")) |
|
188 |
|
189 # set translation function |
|
190 if Preferences.getDebugger("PathTranslation"): |
|
191 self.translateRemote = \ |
|
192 Preferences.getDebugger("PathTranslationRemote") |
|
193 self.translateLocal = \ |
|
194 Preferences.getDebugger("PathTranslationLocal") |
|
195 self.translate = self.__remoteTranslation |
|
196 else: |
|
197 self.translate = self.__identityTranslation |
|
198 return process, self.__isNetworked |
|
199 |
|
200 # set translation function |
|
201 self.translate = self.__identityTranslation |
|
202 |
|
203 # setup the environment for the debugger |
|
204 if Preferences.getDebugger("DebugEnvironmentReplace"): |
|
205 clientEnv = {} |
|
206 else: |
|
207 clientEnv = os.environ.copy() |
|
208 envlist = Utilities.parseEnvironmentString(\ |
|
209 Preferences.getDebugger("DebugEnvironment")) |
|
210 for el in envlist: |
|
211 try: |
|
212 key, value = el.split('=', 1) |
|
213 if value.startswith('"') or value.startswith("'"): |
|
214 value = value[1:-1] |
|
215 clientEnv[str(key)] = str(value) |
|
216 except UnpackError: |
|
217 pass |
|
218 |
|
219 ipaddr = self.debugServer.getHostAddress(True) |
|
220 if runInConsole or Preferences.getDebugger("ConsoleDbgEnabled"): |
|
221 ccmd = Preferences.getDebugger("ConsoleDbgCommand") |
|
222 if ccmd: |
|
223 args = Utilities.parseOptionString(ccmd) + \ |
|
224 [interpreter, os.path.abspath(debugClient), |
|
225 noencoding, str(port), '0', ipaddr] |
|
226 args[0] = Utilities.getExecutablePath(args[0]) |
|
227 process = self.__startProcess(args[0], args[1:], clientEnv) |
|
228 if process is None: |
|
229 QMessageBox.critical(None, |
|
230 self.trUtf8("Start Debugger"), |
|
231 self.trUtf8(\ |
|
232 """<p>The debugger backend could not be started.</p>""")) |
|
233 return process, self.__isNetworked |
|
234 |
|
235 process = self.__startProcess(interpreter, |
|
236 [debugClient, noencoding, str(port), redirect, ipaddr], |
|
237 clientEnv) |
|
238 if process is None: |
|
239 QMessageBox.critical(None, |
|
240 self.trUtf8("Start Debugger"), |
|
241 self.trUtf8("""<p>The debugger backend could not be started.</p>""")) |
|
242 return process, self.__isNetworked |
|
243 |
|
244 def startRemoteForProject(self, port, runInConsole): |
|
245 """ |
|
246 Public method to start a remote Python interpreter for a project. |
|
247 |
|
248 @param port portnumber the debug server is listening on (integer) |
|
249 @param runInConsole flag indicating to start the debugger in a |
|
250 console window (boolean) |
|
251 @return client process object (QProcess) and a flag to indicate |
|
252 a network connection (boolean) |
|
253 """ |
|
254 project = e4App().getObject("Project") |
|
255 if not project.isDebugPropertiesLoaded(): |
|
256 return None, self.__isNetworked |
|
257 |
|
258 # start debugger with project specific settings |
|
259 interpreter = project.getDebugProperty("INTERPRETER") |
|
260 debugClient = project.getDebugProperty("DEBUGCLIENT") |
|
261 |
|
262 redirect = str(project.getDebugProperty("REDIRECT")) |
|
263 noencoding = \ |
|
264 project.getDebugProperty("NOENCODING") and '--no-encoding' or '' |
|
265 |
|
266 if project.getDebugProperty("REMOTEDEBUGGER"): |
|
267 ipaddr = self.debugServer.getHostAddress(False) |
|
268 rexec = project.getDebugProperty("REMOTECOMMAND") |
|
269 rhost = project.getDebugProperty("REMOTEHOST") |
|
270 if rhost == "": |
|
271 rhost = "localhost" |
|
272 if rexec: |
|
273 args = Utilities.parseOptionString(rexec) + \ |
|
274 [rhost, interpreter, os.path.abspath(debugClient), |
|
275 noencoding, str(port), redirect, ipaddr] |
|
276 args[0] = Utilities.getExecutablePath(args[0]) |
|
277 process = self.__startProcess(args[0], args[1:]) |
|
278 if process is None: |
|
279 QMessageBox.critical(None, |
|
280 self.trUtf8("Start Debugger"), |
|
281 self.trUtf8(\ |
|
282 """<p>The debugger backend could not be started.</p>""")) |
|
283 # set translation function |
|
284 if project.getDebugProperty("PATHTRANSLATION"): |
|
285 self.translateRemote = project.getDebugProperty("REMOTEPATH") |
|
286 self.translateLocal = project.getDebugProperty("LOCALPATH") |
|
287 self.translate = self.__remoteTranslation |
|
288 else: |
|
289 self.translate = self.__identityTranslation |
|
290 return process, self.__isNetworked |
|
291 |
|
292 # set translation function |
|
293 self.translate = self.__identityTranslation |
|
294 |
|
295 # setup the environment for the debugger |
|
296 if project.getDebugProperty("ENVIRONMENTOVERRIDE"): |
|
297 clientEnv = {} |
|
298 else: |
|
299 clientEnv = os.environ.copy() |
|
300 envlist = Utilities.parseEnvironmentString( |
|
301 project.getDebugProperty("ENVIRONMENTSTRING")) |
|
302 for el in envlist: |
|
303 try: |
|
304 key, value = el.split('=', 1) |
|
305 if value.startswith('"') or value.startswith("'"): |
|
306 value = value[1:-1] |
|
307 clientEnv[str(key)] = str(value) |
|
308 except UnpackError: |
|
309 pass |
|
310 |
|
311 ipaddr = self.debugServer.getHostAddress(True) |
|
312 if runInConsole or project.getDebugProperty("CONSOLEDEBUGGER"): |
|
313 ccmd = project.getDebugProperty("CONSOLECOMMAND") or \ |
|
314 Preferences.getDebugger("ConsoleDbgCommand") |
|
315 if ccmd: |
|
316 args = Utilities.parseOptionString(ccmd) + \ |
|
317 [interpreter, os.path.abspath(debugClient), |
|
318 noencoding, str(port), '0', ipaddr] |
|
319 args[0] = Utilities.getExecutablePath(args[0]) |
|
320 process = self.__startProcess(args[0], args[1:], clientEnv) |
|
321 if process is None: |
|
322 QMessageBox.critical(None, |
|
323 self.trUtf8("Start Debugger"), |
|
324 self.trUtf8(\ |
|
325 """<p>The debugger backend could not be started.</p>""")) |
|
326 return process, self.__isNetworked |
|
327 |
|
328 process = self.__startProcess(interpreter, |
|
329 [debugClient, noencoding, str(port), redirect, ipaddr], |
|
330 clientEnv) |
|
331 if process is None: |
|
332 QMessageBox.critical(None, |
|
333 self.trUtf8("Start Debugger"), |
|
334 self.trUtf8("""<p>The debugger backend could not be started.</p>""")) |
|
335 return process, self.__isNetworked |
|
336 |
|
337 def getClientCapabilities(self): |
|
338 """ |
|
339 Public method to retrieve the debug clients capabilities. |
|
340 |
|
341 @return debug client capabilities (integer) |
|
342 """ |
|
343 return self.clientCapabilities |
|
344 |
|
345 def newConnection(self, sock): |
|
346 """ |
|
347 Public slot to handle a new connection. |
|
348 |
|
349 @param sockreference to the socket object (QTcpSocket) |
|
350 @return flag indicating success (boolean) |
|
351 """ |
|
352 # If we already have a connection, refuse this one. It will be closed |
|
353 # automatically. |
|
354 if self.qsock is not None: |
|
355 return False |
|
356 |
|
357 self.connect(sock, SIGNAL('disconnected()'), self.debugServer.startClient) |
|
358 self.connect(sock, SIGNAL('readyRead()'), self.__parseClientLine) |
|
359 |
|
360 self.qsock = sock |
|
361 |
|
362 # Get the remote clients capabilities |
|
363 self.remoteCapabilities() |
|
364 return True |
|
365 |
|
366 def flush(self): |
|
367 """ |
|
368 Public slot to flush the queue. |
|
369 """ |
|
370 # Send commands that were waiting for the connection. |
|
371 for cmd in self.queue: |
|
372 self.qsock.write(cmd.encode('utf8')) |
|
373 |
|
374 self.queue = [] |
|
375 |
|
376 def shutdown(self): |
|
377 """ |
|
378 Public method to cleanly shut down. |
|
379 |
|
380 It closes our socket and shuts down |
|
381 the debug client. (Needed on Win OS) |
|
382 """ |
|
383 if self.qsock is None: |
|
384 return |
|
385 |
|
386 # do not want any slots called during shutdown |
|
387 self.disconnect(self.qsock, SIGNAL('disconnected()'), |
|
388 self.debugServer.startClient) |
|
389 self.disconnect(self.qsock, SIGNAL('readyRead()'), self.__parseClientLine) |
|
390 |
|
391 # close down socket, and shut down client as well. |
|
392 self.__sendCommand('%s\n' % RequestShutdown) |
|
393 self.qsock.flush() |
|
394 |
|
395 self.qsock.close() |
|
396 |
|
397 # reinitialize |
|
398 self.qsock = None |
|
399 self.queue = [] |
|
400 |
|
401 def isConnected(self): |
|
402 """ |
|
403 Public method to test, if a debug client has connected. |
|
404 |
|
405 @return flag indicating the connection status (boolean) |
|
406 """ |
|
407 return self.qsock is not None |
|
408 |
|
409 def remoteEnvironment(self, env): |
|
410 """ |
|
411 Public method to set the environment for a program to debug, run, ... |
|
412 |
|
413 @param env environment settings (dictionary) |
|
414 """ |
|
415 self.__sendCommand('%s%s\n' % (RequestEnv, unicode(env))) |
|
416 |
|
417 def remoteLoad(self, fn, argv, wd, traceInterpreter = False, autoContinue = True, |
|
418 autoFork = False, forkChild = False): |
|
419 """ |
|
420 Public method to load a new program to debug. |
|
421 |
|
422 @param fn the filename to debug (string) |
|
423 @param argv the commandline arguments to pass to the program (string) |
|
424 @param wd the working directory for the program (string) |
|
425 @keyparam traceInterpreter flag indicating if the interpreter library should be |
|
426 traced as well (boolean) |
|
427 @keyparam autoContinue flag indicating, that the debugger should not stop |
|
428 at the first executable line (boolean) |
|
429 @keyparam autoFork flag indicating the automatic fork mode (boolean) |
|
430 @keyparam forkChild flag indicating to debug the child after forking (boolean) |
|
431 """ |
|
432 self.__autoContinue = autoContinue |
|
433 |
|
434 wd = self.translate(wd, False) |
|
435 fn = self.translate(os.path.abspath(fn), False) |
|
436 self.__sendCommand('%s%s\n' % (RequestForkMode, repr((autoFork, forkChild)))) |
|
437 self.__sendCommand('%s%s|%s|%s|%d\n' % \ |
|
438 (RequestLoad, wd, fn, unicode(Utilities.parseOptionString(argv)), |
|
439 traceInterpreter)) |
|
440 |
|
441 def remoteRun(self, fn, argv, wd): |
|
442 """ |
|
443 Public method to load a new program to run. |
|
444 |
|
445 @param fn the filename to run (string) |
|
446 @param argv the commandline arguments to pass to the program (string) |
|
447 @param wd the working directory for the program (string) |
|
448 """ |
|
449 wd = self.translate(wd, False) |
|
450 fn = self.translate(os.path.abspath(fn), False) |
|
451 self.__sendCommand('%s%s|%s|%s\n' % \ |
|
452 (RequestRun, wd, fn, unicode(Utilities.parseOptionString(argv)))) |
|
453 |
|
454 def remoteCoverage(self, fn, argv, wd, erase = False): |
|
455 """ |
|
456 Public method to load a new program to collect coverage data. |
|
457 |
|
458 @param fn the filename to run (string) |
|
459 @param argv the commandline arguments to pass to the program (string) |
|
460 @param wd the working directory for the program (string) |
|
461 @keyparam erase flag indicating that coverage info should be |
|
462 cleared first (boolean) |
|
463 """ |
|
464 wd = self.translate(wd, False) |
|
465 fn = self.translate(os.path.abspath(fn), False) |
|
466 self.__sendCommand('%s%s@@%s@@%s@@%d\n' % \ |
|
467 (RequestCoverage, wd, fn, unicode(Utilities.parseOptionString(argv)), |
|
468 erase)) |
|
469 |
|
470 def remoteProfile(self, fn, argv, wd, erase = False): |
|
471 """ |
|
472 Public method to load a new program to collect profiling data. |
|
473 |
|
474 @param fn the filename to run (string) |
|
475 @param argv the commandline arguments to pass to the program (string) |
|
476 @param wd the working directory for the program (string) |
|
477 @keyparam erase flag indicating that timing info should be cleared first (boolean) |
|
478 """ |
|
479 wd = self.translate(wd, False) |
|
480 fn = self.translate(os.path.abspath(fn), False) |
|
481 self.__sendCommand('%s%s|%s|%s|%d\n' % \ |
|
482 (RequestProfile, wd, fn, unicode(Utilities.parseOptionString(argv)), erase)) |
|
483 |
|
484 def remoteStatement(self, stmt): |
|
485 """ |
|
486 Public method to execute a Python statement. |
|
487 |
|
488 @param stmt the Python statement to execute (string). It |
|
489 should not have a trailing newline. |
|
490 """ |
|
491 self.__sendCommand('%s\n' % stmt) |
|
492 self.__sendCommand(RequestOK + '\n') |
|
493 |
|
494 def remoteStep(self): |
|
495 """ |
|
496 Public method to single step the debugged program. |
|
497 """ |
|
498 self.__sendCommand(RequestStep + '\n') |
|
499 |
|
500 def remoteStepOver(self): |
|
501 """ |
|
502 Public method to step over the debugged program. |
|
503 """ |
|
504 self.__sendCommand(RequestStepOver + '\n') |
|
505 |
|
506 def remoteStepOut(self): |
|
507 """ |
|
508 Public method to step out the debugged program. |
|
509 """ |
|
510 self.__sendCommand(RequestStepOut + '\n') |
|
511 |
|
512 def remoteStepQuit(self): |
|
513 """ |
|
514 Public method to stop the debugged program. |
|
515 """ |
|
516 self.__sendCommand(RequestStepQuit + '\n') |
|
517 |
|
518 def remoteContinue(self, special = False): |
|
519 """ |
|
520 Public method to continue the debugged program. |
|
521 |
|
522 @param special flag indicating a special continue operation (boolean) |
|
523 """ |
|
524 self.__sendCommand('%s%d\n' % (RequestContinue, special)) |
|
525 |
|
526 def remoteBreakpoint(self, fn, line, set, cond = None, temp = False): |
|
527 """ |
|
528 Public method to set or clear a breakpoint. |
|
529 |
|
530 @param fn filename the breakpoint belongs to (string) |
|
531 @param line linenumber of the breakpoint (int) |
|
532 @param set flag indicating setting or resetting a breakpoint (boolean) |
|
533 @param cond condition of the breakpoint (string) |
|
534 @param temp flag indicating a temporary breakpoint (boolean) |
|
535 """ |
|
536 fn = self.translate(fn, False) |
|
537 self.__sendCommand('%s%s@@%d@@%d@@%d@@%s\n' % \ |
|
538 (RequestBreak, fn, line, temp, set, cond)) |
|
539 |
|
540 def remoteBreakpointEnable(self, fn, line, enable): |
|
541 """ |
|
542 Public method to enable or disable a breakpoint. |
|
543 |
|
544 @param fn filename the breakpoint belongs to (string) |
|
545 @param line linenumber of the breakpoint (int) |
|
546 @param enable flag indicating enabling or disabling a breakpoint (boolean) |
|
547 """ |
|
548 fn = self.translate(fn, False) |
|
549 self.__sendCommand('%s%s,%d,%d\n' % (RequestBreakEnable, fn, line, enable)) |
|
550 |
|
551 def remoteBreakpointIgnore(self, fn, line, count): |
|
552 """ |
|
553 Public method to ignore a breakpoint the next couple of occurrences. |
|
554 |
|
555 @param fn filename the breakpoint belongs to (string) |
|
556 @param line linenumber of the breakpoint (int) |
|
557 @param count number of occurrences to ignore (int) |
|
558 """ |
|
559 fn = self.translate(fn, False) |
|
560 self.__sendCommand('%s%s,%d,%d\n' % (RequestBreakIgnore, fn, line, count)) |
|
561 |
|
562 def remoteWatchpoint(self, cond, set, temp = False): |
|
563 """ |
|
564 Public method to set or clear a watch expression. |
|
565 |
|
566 @param cond expression of the watch expression (string) |
|
567 @param set flag indicating setting or resetting a watch expression (boolean) |
|
568 @param temp flag indicating a temporary watch expression (boolean) |
|
569 """ |
|
570 # cond is combination of cond and special (s. watch expression viewer) |
|
571 self.__sendCommand('%s%s@@%d@@%d\n' % (RequestWatch, cond, temp, set)) |
|
572 |
|
573 def remoteWatchpointEnable(self, cond, enable): |
|
574 """ |
|
575 Public method to enable or disable a watch expression. |
|
576 |
|
577 @param cond expression of the watch expression (string) |
|
578 @param enable flag indicating enabling or disabling a watch expression (boolean) |
|
579 """ |
|
580 # cond is combination of cond and special (s. watch expression viewer) |
|
581 self.__sendCommand('%s%s,%d\n' % (RequestWatchEnable, cond, enable)) |
|
582 |
|
583 def remoteWatchpointIgnore(self, cond, count): |
|
584 """ |
|
585 Public method to ignore a watch expression the next couple of occurrences. |
|
586 |
|
587 @param cond expression of the watch expression (string) |
|
588 @param count number of occurrences to ignore (int) |
|
589 """ |
|
590 # cond is combination of cond and special (s. watch expression viewer) |
|
591 self.__sendCommand('%s%s,%d\n' % (RequestWatchIgnore, cond, count)) |
|
592 |
|
593 def remoteRawInput(self,s): |
|
594 """ |
|
595 Public method to send the raw input to the debugged program. |
|
596 |
|
597 @param s the raw input (string) |
|
598 """ |
|
599 self.__sendCommand(s + '\n') |
|
600 |
|
601 def remoteThreadList(self): |
|
602 """ |
|
603 Public method to request the list of threads from the client. |
|
604 """ |
|
605 self.__sendCommand('%s\n' % RequestThreadList) |
|
606 |
|
607 def remoteSetThread(self, tid): |
|
608 """ |
|
609 Public method to request to set the given thread as current thread. |
|
610 |
|
611 @param tid id of the thread (integer) |
|
612 """ |
|
613 self.__sendCommand('%s%d\n' % (RequestThreadSet, tid)) |
|
614 |
|
615 def remoteClientVariables(self, scope, filter, framenr = 0): |
|
616 """ |
|
617 Public method to request the variables of the debugged program. |
|
618 |
|
619 @param scope the scope of the variables (0 = local, 1 = global) |
|
620 @param filter list of variable types to filter out (list of int) |
|
621 @param framenr framenumber of the variables to retrieve (int) |
|
622 """ |
|
623 self.__sendCommand('%s%d, %d, %s\n' % \ |
|
624 (RequestVariables, framenr, scope, unicode(filter))) |
|
625 |
|
626 def remoteClientVariable(self, scope, filter, var, framenr = 0): |
|
627 """ |
|
628 Public method to request the variables of the debugged program. |
|
629 |
|
630 @param scope the scope of the variables (0 = local, 1 = global) |
|
631 @param filter list of variable types to filter out (list of int) |
|
632 @param var list encoded name of variable to retrieve (string) |
|
633 @param framenr framenumber of the variables to retrieve (int) |
|
634 """ |
|
635 self.__sendCommand('%s%s, %d, %d, %s\n' % \ |
|
636 (RequestVariable, unicode(var), framenr, scope, str(filter))) |
|
637 |
|
638 def remoteClientSetFilter(self, scope, filter): |
|
639 """ |
|
640 Public method to set a variables filter list. |
|
641 |
|
642 @param scope the scope of the variables (0 = local, 1 = global) |
|
643 @param filter regexp string for variable names to filter out (string) |
|
644 """ |
|
645 self.__sendCommand('%s%d, "%s"\n' % (RequestSetFilter, scope, filter)) |
|
646 |
|
647 def remoteEval(self, arg): |
|
648 """ |
|
649 Public method to evaluate arg in the current context of the debugged program. |
|
650 |
|
651 @param arg the arguments to evaluate (string) |
|
652 """ |
|
653 self.__sendCommand('%s%s\n' % (RequestEval, arg)) |
|
654 |
|
655 def remoteExec(self, stmt): |
|
656 """ |
|
657 Public method to execute stmt in the current context of the debugged program. |
|
658 |
|
659 @param stmt statement to execute (string) |
|
660 """ |
|
661 self.__sendCommand('%s%s\n' % (RequestExec, stmt)) |
|
662 |
|
663 def remoteBanner(self): |
|
664 """ |
|
665 Public slot to get the banner info of the remote client. |
|
666 """ |
|
667 self.__sendCommand(RequestBanner + '\n') |
|
668 |
|
669 def remoteCapabilities(self): |
|
670 """ |
|
671 Public slot to get the debug clients capabilities. |
|
672 """ |
|
673 self.__sendCommand(RequestCapabilities + '\n') |
|
674 |
|
675 def remoteCompletion(self, text): |
|
676 """ |
|
677 Public slot to get the a list of possible commandline completions |
|
678 from the remote client. |
|
679 |
|
680 @param text the text to be completed (string) |
|
681 """ |
|
682 self.__sendCommand("%s%s\n" % (RequestCompletion, text)) |
|
683 |
|
684 def remoteUTPrepare(self, fn, tn, tfn, cov, covname, coverase): |
|
685 """ |
|
686 Public method to prepare a new unittest run. |
|
687 |
|
688 @param fn the filename to load (string) |
|
689 @param tn the testname to load (string) |
|
690 @param tfn the test function name to load tests from (string) |
|
691 @param cov flag indicating collection of coverage data is requested |
|
692 @param covname filename to be used to assemble the coverage caches |
|
693 filename |
|
694 @param coverase flag indicating erasure of coverage data is requested |
|
695 """ |
|
696 fn = self.translate(os.path.abspath(fn), False) |
|
697 self.__sendCommand('%s%s|%s|%s|%d|%s|%d\n' % \ |
|
698 (RequestUTPrepare, fn, tn, tfn, cov, covname, coverase)) |
|
699 |
|
700 def remoteUTRun(self): |
|
701 """ |
|
702 Public method to start a unittest run. |
|
703 """ |
|
704 self.__sendCommand('%s\n' % RequestUTRun) |
|
705 |
|
706 def remoteUTStop(self): |
|
707 """ |
|
708 Public method to stop a unittest run. |
|
709 """ |
|
710 self.__sendCommand('%s\n' % RequestUTStop) |
|
711 |
|
712 def __askForkTo(self): |
|
713 """ |
|
714 Private method to ask the user which branch of a fork to follow. |
|
715 """ |
|
716 selections = [self.trUtf8("Parent Process"), self.trUtf8("Child process")] |
|
717 res, ok = QInputDialog.getItem(\ |
|
718 None, |
|
719 self.trUtf8("Client forking"), |
|
720 self.trUtf8("Select the fork branch to follow."), |
|
721 selections, |
|
722 0, False) |
|
723 if not ok or res == selections[0]: |
|
724 self.__sendCommand(ResponseForkTo + 'parent\n') |
|
725 else: |
|
726 self.__sendCommand(ResponseForkTo + 'child\n') |
|
727 |
|
728 def __parseClientLine(self): |
|
729 """ |
|
730 Private method to handle data from the client. |
|
731 """ |
|
732 while self.qsock and self.qsock.canReadLine(): |
|
733 qs = self.qsock.readLine() |
|
734 if self.codec is not None: |
|
735 us = self.codec.fromUnicode(unicode(qs)) |
|
736 else: |
|
737 us = qs |
|
738 line = str(us) |
|
739 if line.endswith(EOT): |
|
740 line = line[:-len(EOT)] |
|
741 if not line: |
|
742 continue |
|
743 |
|
744 ## print "Server: ", line ##debug |
|
745 |
|
746 eoc = line.find('<') + 1 |
|
747 |
|
748 # Deal with case where user has written directly to stdout |
|
749 # or stderr, but not line terminated and we stepped over the |
|
750 # write call, in that case the >line< will not be the first |
|
751 # string read from the socket... |
|
752 boc = line.find('>') |
|
753 if boc > 0 and eoc > boc: |
|
754 self.debugServer.clientOutput(line[:boc]) |
|
755 line = line[boc:] |
|
756 eoc = line.find('<') + 1 |
|
757 boc = line.find('>') |
|
758 |
|
759 if boc >= 0 and eoc > boc: |
|
760 resp = line[boc:eoc] |
|
761 |
|
762 if resp == ResponseLine or resp == ResponseStack: |
|
763 stack = eval(line[eoc:-1]) |
|
764 for s in stack: |
|
765 s[0] = self.translate(s[0], True) |
|
766 cf = stack[0] |
|
767 if self.__autoContinue: |
|
768 self.__autoContinue = False |
|
769 QTimer.singleShot(0, self.remoteContinue) |
|
770 else: |
|
771 self.debugServer.clientLine(cf[0], int(cf[1]), |
|
772 resp == ResponseStack) |
|
773 self.debugServer.clientStack(stack) |
|
774 continue |
|
775 |
|
776 if resp == ResponseThreadList: |
|
777 currentId, threadList = eval(line[eoc:-1]) |
|
778 self.debugServer.clientThreadList(currentId, threadList) |
|
779 continue |
|
780 |
|
781 if resp == ResponseThreadSet: |
|
782 self.debugServer.clientThreadSet() |
|
783 continue |
|
784 |
|
785 if resp == ResponseVariables: |
|
786 vlist = eval(line[eoc:-1]) |
|
787 scope = vlist[0] |
|
788 try: |
|
789 variables = vlist[1:] |
|
790 except IndexError: |
|
791 variables = [] |
|
792 self.debugServer.clientVariables(scope, variables) |
|
793 continue |
|
794 |
|
795 if resp == ResponseVariable: |
|
796 vlist = eval(line[eoc:-1]) |
|
797 scope = vlist[0] |
|
798 try: |
|
799 variables = vlist[1:] |
|
800 except IndexError: |
|
801 variables = [] |
|
802 self.debugServer.clientVariable(scope, variables) |
|
803 continue |
|
804 |
|
805 if resp == ResponseOK: |
|
806 self.debugServer.clientStatement(False) |
|
807 continue |
|
808 |
|
809 if resp == ResponseContinue: |
|
810 self.debugServer.clientStatement(True) |
|
811 continue |
|
812 |
|
813 if resp == ResponseException: |
|
814 exc = line[eoc:-1] |
|
815 exc = self.translate(exc, True) |
|
816 try: |
|
817 exclist = eval(exc) |
|
818 exctype = exclist[0] |
|
819 excmessage = exclist[1] |
|
820 stack = exclist[2:] |
|
821 except (IndexError, ValueError, SyntaxError): |
|
822 exctype = None |
|
823 excmessage = '' |
|
824 stack = [] |
|
825 self.debugServer.clientException(exctype, excmessage, stack) |
|
826 continue |
|
827 |
|
828 if resp == ResponseSyntax: |
|
829 exc = line[eoc:-1] |
|
830 exc = self.translate(exc, True) |
|
831 try: |
|
832 message, (fn, ln, cn) = eval(exc) |
|
833 if fn is None: |
|
834 fn = '' |
|
835 except (IndexError, ValueError): |
|
836 message = None |
|
837 fn = '' |
|
838 ln = 0 |
|
839 cn = 0 |
|
840 if cn is None: |
|
841 cn = 0 |
|
842 self.debugServer.clientSyntaxError(message, fn, ln, cn) |
|
843 continue |
|
844 |
|
845 if resp == ResponseExit: |
|
846 self.debugServer.clientExit(line[eoc:-1]) |
|
847 continue |
|
848 |
|
849 if resp == ResponseClearBreak: |
|
850 fn, lineno = line[eoc:-1].split(',') |
|
851 lineno = int(lineno) |
|
852 fn = self.translate(fn, True) |
|
853 self.debugServer.clientClearBreak(fn, lineno) |
|
854 continue |
|
855 |
|
856 if resp == ResponseBPConditionError: |
|
857 fn, lineno = line[eoc:-1].split(',') |
|
858 lineno = int(lineno) |
|
859 fn = self.translate(fn, True) |
|
860 self.debugServer.clientBreakConditionError(fn, lineno) |
|
861 continue |
|
862 |
|
863 if resp == ResponseClearWatch: |
|
864 cond = line[eoc:-1] |
|
865 self.debugServer.clientClearWatch(cond) |
|
866 continue |
|
867 |
|
868 if resp == ResponseWPConditionError: |
|
869 cond = line[eoc:-1] |
|
870 self.debugServer.clientWatchConditionError(cond) |
|
871 continue |
|
872 |
|
873 if resp == ResponseRaw: |
|
874 prompt, echo = eval(line[eoc:-1]) |
|
875 self.debugServer.clientRawInput(prompt, echo) |
|
876 continue |
|
877 |
|
878 if resp == ResponseBanner: |
|
879 version, platform, dbgclient = eval(line[eoc:-1]) |
|
880 self.debugServer.clientBanner(version, platform, dbgclient) |
|
881 continue |
|
882 |
|
883 if resp == ResponseCapabilities: |
|
884 cap, clType = eval(line[eoc:-1]) |
|
885 self.clientCapabilities = cap |
|
886 self.debugServer.clientCapabilities(cap, clType) |
|
887 continue |
|
888 |
|
889 if resp == ResponseCompletion: |
|
890 clstring, text = line[eoc:-1].split('||') |
|
891 cl = eval(clstring) |
|
892 self.debugServer.clientCompletionList(cl, text) |
|
893 continue |
|
894 |
|
895 if resp == PassiveStartup: |
|
896 fn, exc = line[eoc:-1].split('|') |
|
897 exc = bool(exc) |
|
898 fn = self.translate(fn, True) |
|
899 self.debugServer.passiveStartUp(fn, exc) |
|
900 continue |
|
901 |
|
902 if resp == ResponseUTPrepared: |
|
903 res, exc_type, exc_value = eval(line[eoc:-1]) |
|
904 self.debugServer.clientUtPrepared(res, exc_type, exc_value) |
|
905 continue |
|
906 |
|
907 if resp == ResponseUTStartTest: |
|
908 testname, doc = eval(line[eoc:-1]) |
|
909 self.debugServer.clientUtStartTest(testname, doc) |
|
910 continue |
|
911 |
|
912 if resp == ResponseUTStopTest: |
|
913 self.debugServer.clientUtStopTest() |
|
914 continue |
|
915 |
|
916 if resp == ResponseUTTestFailed: |
|
917 testname, traceback = eval(line[eoc:-1]) |
|
918 self.debugServer.clientUtTestFailed(testname, traceback) |
|
919 continue |
|
920 |
|
921 if resp == ResponseUTTestErrored: |
|
922 testname, traceback = eval(line[eoc:-1]) |
|
923 self.debugServer.clientUtTestErrored(testname, traceback) |
|
924 continue |
|
925 |
|
926 if resp == ResponseUTFinished: |
|
927 self.debugServer.clientUtFinished() |
|
928 continue |
|
929 |
|
930 if resp == RequestForkTo: |
|
931 self.__askForkTo() |
|
932 continue |
|
933 |
|
934 self.debugServer.clientOutput(line) |
|
935 |
|
936 def __sendCommand(self, cmd): |
|
937 """ |
|
938 Private method to send a single line command to the client. |
|
939 |
|
940 @param cmd command to send to the debug client (string) |
|
941 """ |
|
942 if self.qsock is not None: |
|
943 self.qsock.write(cmd.encode('utf8')) |
|
944 else: |
|
945 self.queue.append(cmd) |