|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2009 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the Python3 debugger interface for the debug server. |
|
8 """ |
|
9 |
|
10 import sys |
|
11 import os |
|
12 import logging |
|
13 import shlex |
|
14 import contextlib |
|
15 |
|
16 from PyQt6.QtCore import QObject, QProcess, QProcessEnvironment, QTimer |
|
17 |
|
18 from EricWidgets.EricApplication import ericApp |
|
19 from EricWidgets import EricMessageBox |
|
20 |
|
21 from . import DebugClientCapabilities |
|
22 |
|
23 import Globals |
|
24 import Preferences |
|
25 import Utilities |
|
26 |
|
27 from eric7config import getConfig |
|
28 |
|
29 |
|
30 ClientDefaultCapabilities = DebugClientCapabilities.HasAll |
|
31 |
|
32 |
|
33 class DebuggerInterfacePython(QObject): |
|
34 """ |
|
35 Class implementing the debugger interface for the debug server for |
|
36 Python 3. |
|
37 """ |
|
38 |
|
39 def __init__(self, debugServer, passive): |
|
40 """ |
|
41 Constructor |
|
42 |
|
43 @param debugServer reference to the debug server |
|
44 @type DebugServer |
|
45 @param passive flag indicating passive connection mode |
|
46 @type bool |
|
47 """ |
|
48 super().__init__() |
|
49 |
|
50 self.__isNetworked = True |
|
51 self.__autoContinue = False |
|
52 self.__autoContinued = [] |
|
53 self.__isStepCommand = False |
|
54 |
|
55 self.debugServer = debugServer |
|
56 self.passive = passive |
|
57 self.process = None |
|
58 self.__startedVenv = "" |
|
59 |
|
60 self.queue = [] |
|
61 self.__master = None |
|
62 self.__connections = {} |
|
63 self.__pendingConnections = [] |
|
64 self.__inShutdown = False |
|
65 |
|
66 # set default values for capabilities of clients |
|
67 self.clientCapabilities = ClientDefaultCapabilities |
|
68 |
|
69 # set translation function |
|
70 self.translate = self.__identityTranslation |
|
71 |
|
72 if passive: |
|
73 # set translation function |
|
74 if Preferences.getDebugger("PathTranslation"): |
|
75 self.translateRemote = Preferences.getDebugger("PathTranslationRemote") |
|
76 self.translateRemoteWindows = "\\" in self.translateRemote |
|
77 self.translateLocal = Preferences.getDebugger("PathTranslationLocal") |
|
78 self.translateLocalWindows = "\\" in self.translateLocal |
|
79 self.translate = self.__remoteTranslation |
|
80 else: |
|
81 self.translate = self.__identityTranslation |
|
82 |
|
83 # attribute to remember the name of the executed script |
|
84 self.__scriptName = "" |
|
85 |
|
86 def __identityTranslation(self, fn, remote2local=True): |
|
87 """ |
|
88 Private method to perform the identity path translation. |
|
89 |
|
90 @param fn filename to be translated |
|
91 @type str |
|
92 @param remote2local flag indicating the direction of translation |
|
93 (False = local to remote, True = remote to local [default]) |
|
94 @type bool |
|
95 @return translated filename |
|
96 @rtype str |
|
97 """ |
|
98 return fn |
|
99 |
|
100 def __remoteTranslation(self, fn, remote2local=True): |
|
101 """ |
|
102 Private method to perform the path translation. |
|
103 |
|
104 @param fn filename to be translated |
|
105 @type str |
|
106 @param remote2local flag indicating the direction of translation |
|
107 (False = local to remote, True = remote to local [default]) |
|
108 @type bool |
|
109 @return translated filename |
|
110 @rtype str |
|
111 """ |
|
112 if remote2local: |
|
113 path = fn.replace(self.translateRemote, self.translateLocal) |
|
114 if self.translateLocalWindows: |
|
115 path = path.replace("/", "\\") |
|
116 else: |
|
117 path = fn.replace(self.translateLocal, self.translateRemote) |
|
118 if not self.translateRemoteWindows: |
|
119 path = path.replace("\\", "/") |
|
120 |
|
121 return path |
|
122 |
|
123 def __startProcess(self, program, arguments, environment=None, workingDir=None): |
|
124 """ |
|
125 Private method to start the debugger client process. |
|
126 |
|
127 @param program name of the executable to start |
|
128 @type str |
|
129 @param arguments arguments to be passed to the program |
|
130 @type list of str |
|
131 @param environment dictionary of environment settings to pass |
|
132 @type dict of str |
|
133 @param workingDir directory to start the debugger client in |
|
134 @type str |
|
135 @return the process object |
|
136 @rtype QProcess or None |
|
137 """ |
|
138 proc = QProcess(self) |
|
139 if environment is not None: |
|
140 env = QProcessEnvironment() |
|
141 for key, value in list(environment.items()): |
|
142 env.insert(key, value) |
|
143 proc.setProcessEnvironment(env) |
|
144 args = arguments[:] |
|
145 if workingDir: |
|
146 proc.setWorkingDirectory(workingDir) |
|
147 proc.start(program, args) |
|
148 if not proc.waitForStarted(10000): |
|
149 proc = None |
|
150 |
|
151 return proc |
|
152 |
|
153 def startRemote( |
|
154 self, |
|
155 port, |
|
156 runInConsole, |
|
157 venvName, |
|
158 originalPathString, |
|
159 workingDir=None, |
|
160 configOverride=None, |
|
161 ): |
|
162 """ |
|
163 Public method to start a remote Python interpreter. |
|
164 |
|
165 @param port port number the debug server is listening on |
|
166 @type int |
|
167 @param runInConsole flag indicating to start the debugger in a |
|
168 console window |
|
169 @type bool |
|
170 @param venvName name of the virtual environment to be used |
|
171 @type str |
|
172 @param originalPathString original PATH environment variable |
|
173 @type str |
|
174 @param workingDir directory to start the debugger client in |
|
175 @type str |
|
176 @param configOverride dictionary containing the global config override |
|
177 data |
|
178 @type dict |
|
179 @return client process object, a flag to indicate a network connection |
|
180 and the name of the interpreter in case of a local execution |
|
181 @rtype tuple of (QProcess, bool, str) |
|
182 """ |
|
183 global origPathEnv |
|
184 |
|
185 if not venvName: |
|
186 venvName = Preferences.getDebugger("Python3VirtualEnv") |
|
187 venvManager = ericApp().getObject("VirtualEnvManager") |
|
188 interpreter = venvManager.getVirtualenvInterpreter(venvName) |
|
189 execPath = venvManager.getVirtualenvExecPath(venvName) |
|
190 if interpreter == "": |
|
191 # use the interpreter used to run eric for identical variants |
|
192 interpreter = Globals.getPythonExecutable() |
|
193 if interpreter == "": |
|
194 EricMessageBox.critical( |
|
195 None, |
|
196 self.tr("Start Debugger"), |
|
197 self.tr("""<p>No suitable Python3 environment configured.</p>"""), |
|
198 ) |
|
199 return None, False, "" |
|
200 |
|
201 self.__inShutdown = False |
|
202 |
|
203 debugClientType = Preferences.getDebugger("DebugClientType3") |
|
204 if debugClientType == "standard": |
|
205 debugClient = os.path.join( |
|
206 getConfig("ericDir"), "DebugClients", "Python", "DebugClient.py" |
|
207 ) |
|
208 else: |
|
209 debugClient = Preferences.getDebugger("DebugClient3") |
|
210 if debugClient == "": |
|
211 debugClient = os.path.join( |
|
212 sys.path[0], "DebugClients", "Python", "DebugClient.py" |
|
213 ) |
|
214 |
|
215 redirect = ( |
|
216 str(configOverride["redirect"]) |
|
217 if configOverride and configOverride["enable"] |
|
218 else str(Preferences.getDebugger("Python3Redirect")) |
|
219 ) |
|
220 noencoding = ( |
|
221 Preferences.getDebugger("Python3NoEncoding") and "--no-encoding" or "" |
|
222 ) |
|
223 multiprocessEnabled = ( |
|
224 "--multiprocess" if Preferences.getDebugger("MultiProcessEnabled") else "" |
|
225 ) |
|
226 |
|
227 if Preferences.getDebugger("RemoteDbgEnabled"): |
|
228 ipaddr = self.debugServer.getHostAddress(False) |
|
229 rexec = Preferences.getDebugger("RemoteExecution") |
|
230 rhost = Preferences.getDebugger("RemoteHost") |
|
231 if rhost == "": |
|
232 rhost = "localhost" |
|
233 if rexec: |
|
234 args = Utilities.parseOptionString(rexec) + [ |
|
235 rhost, |
|
236 interpreter, |
|
237 debugClient, |
|
238 ] |
|
239 if noencoding: |
|
240 args.append(noencoding) |
|
241 if multiprocessEnabled: |
|
242 args.append(multiprocessEnabled) |
|
243 args.extend([str(port), redirect, ipaddr]) |
|
244 if Utilities.isWindowsPlatform(): |
|
245 if not os.path.splitext(args[0])[1]: |
|
246 for ext in [".exe", ".com", ".cmd", ".bat"]: |
|
247 prog = Utilities.getExecutablePath(args[0] + ext) |
|
248 if prog: |
|
249 args[0] = prog |
|
250 break |
|
251 else: |
|
252 args[0] = Utilities.getExecutablePath(args[0]) |
|
253 process = self.__startProcess(args[0], args[1:], workingDir=workingDir) |
|
254 if process is None: |
|
255 EricMessageBox.critical( |
|
256 None, |
|
257 self.tr("Start Debugger"), |
|
258 self.tr( |
|
259 """<p>The debugger backend could not be""" |
|
260 """ started.</p>""" |
|
261 ), |
|
262 ) |
|
263 |
|
264 # set translation function |
|
265 if Preferences.getDebugger("PathTranslation"): |
|
266 self.translateRemote = Preferences.getDebugger( |
|
267 "PathTranslationRemote" |
|
268 ) |
|
269 self.translateRemoteWindows = "\\" in self.translateRemote |
|
270 self.translateLocal = Preferences.getDebugger( |
|
271 "PathTranslationLocal" |
|
272 ) |
|
273 self.translate = self.__remoteTranslation |
|
274 self.translateLocalWindows = "\\" in self.translateLocal |
|
275 else: |
|
276 self.translate = self.__identityTranslation |
|
277 return process, self.__isNetworked, "" |
|
278 |
|
279 # set translation function |
|
280 self.translate = self.__identityTranslation |
|
281 |
|
282 # setup the environment for the debugger |
|
283 if Preferences.getDebugger("DebugEnvironmentReplace"): |
|
284 clientEnv = {} |
|
285 else: |
|
286 clientEnv = os.environ.copy() |
|
287 if originalPathString: |
|
288 clientEnv["PATH"] = originalPathString |
|
289 envlist = shlex.split(Preferences.getDebugger("DebugEnvironment")) |
|
290 for el in envlist: |
|
291 with contextlib.suppress(ValueError): |
|
292 key, value = el.split("=", 1) |
|
293 clientEnv[str(key)] = str(value) |
|
294 if execPath: |
|
295 if "PATH" in clientEnv: |
|
296 clientEnv["PATH"] = os.pathsep.join([execPath, clientEnv["PATH"]]) |
|
297 else: |
|
298 clientEnv["PATH"] = execPath |
|
299 |
|
300 ipaddr = self.debugServer.getHostAddress(True) |
|
301 if runInConsole or Preferences.getDebugger("ConsoleDbgEnabled"): |
|
302 ccmd = Preferences.getDebugger("ConsoleDbgCommand") |
|
303 if ccmd: |
|
304 args = Utilities.parseOptionString(ccmd) + [ |
|
305 interpreter, |
|
306 os.path.abspath(debugClient), |
|
307 ] |
|
308 if noencoding: |
|
309 args.append(noencoding) |
|
310 if multiprocessEnabled: |
|
311 args.append(multiprocessEnabled) |
|
312 args.extend([str(port), "0", ipaddr]) |
|
313 args[0] = Utilities.getExecutablePath(args[0]) |
|
314 process = self.__startProcess( |
|
315 args[0], args[1:], clientEnv, workingDir=workingDir |
|
316 ) |
|
317 if process is None: |
|
318 EricMessageBox.critical( |
|
319 None, |
|
320 self.tr("Start Debugger"), |
|
321 self.tr( |
|
322 """<p>The debugger backend could not be""" |
|
323 """ started.</p>""" |
|
324 ), |
|
325 ) |
|
326 return process, self.__isNetworked, interpreter |
|
327 |
|
328 args = [debugClient] |
|
329 if noencoding: |
|
330 args.append(noencoding) |
|
331 if multiprocessEnabled: |
|
332 args.append(multiprocessEnabled) |
|
333 args.extend([str(port), redirect, ipaddr]) |
|
334 process = self.__startProcess( |
|
335 interpreter, args, clientEnv, workingDir=workingDir |
|
336 ) |
|
337 if process is None: |
|
338 self.__startedVenv = "" |
|
339 EricMessageBox.critical( |
|
340 None, |
|
341 self.tr("Start Debugger"), |
|
342 self.tr("""<p>The debugger backend could not be started.</p>"""), |
|
343 ) |
|
344 else: |
|
345 self.__startedVenv = venvName |
|
346 |
|
347 return process, self.__isNetworked, interpreter |
|
348 |
|
349 def startRemoteForProject( |
|
350 self, |
|
351 port, |
|
352 runInConsole, |
|
353 venvName, |
|
354 originalPathString, |
|
355 workingDir=None, |
|
356 configOverride=None, |
|
357 ): |
|
358 """ |
|
359 Public method to start a remote Python interpreter for a project. |
|
360 |
|
361 @param port port number the debug server is listening on |
|
362 @type int |
|
363 @param runInConsole flag indicating to start the debugger in a |
|
364 console window |
|
365 @type bool |
|
366 @param venvName name of the virtual environment to be used |
|
367 @type str |
|
368 @param originalPathString original PATH environment variable |
|
369 @type str |
|
370 @param workingDir directory to start the debugger client in |
|
371 @type str |
|
372 @param configOverride dictionary containing the global config override |
|
373 data |
|
374 @type dict |
|
375 @return client process object, a flag to indicate a network connection |
|
376 and the name of the interpreter in case of a local execution |
|
377 @rtype tuple of (QProcess, bool, str) |
|
378 """ |
|
379 global origPathEnv |
|
380 |
|
381 project = ericApp().getObject("Project") |
|
382 if not project.isDebugPropertiesLoaded(): |
|
383 return None, self.__isNetworked, "" |
|
384 |
|
385 # start debugger with project specific settings |
|
386 debugClient = project.getDebugProperty("DEBUGCLIENT") |
|
387 |
|
388 redirect = ( |
|
389 str(configOverride["redirect"]) |
|
390 if configOverride and configOverride["enable"] |
|
391 else str(project.getDebugProperty("REDIRECT")) |
|
392 ) |
|
393 noencoding = "--no-encoding" if project.getDebugProperty("NOENCODING") else "" |
|
394 multiprocessEnabled = ( |
|
395 "--multiprocess" if Preferences.getDebugger("MultiProcessEnabled") else "" |
|
396 ) |
|
397 |
|
398 if venvName: |
|
399 venvManager = ericApp().getObject("VirtualEnvManager") |
|
400 interpreter = venvManager.getVirtualenvInterpreter(venvName) |
|
401 execPath = venvManager.getVirtualenvExecPath(venvName) |
|
402 else: |
|
403 venvName = project.getProjectVenv() |
|
404 execPath = project.getProjectExecPath() |
|
405 interpreter = project.getProjectInterpreter() |
|
406 if interpreter == "": |
|
407 EricMessageBox.critical( |
|
408 None, |
|
409 self.tr("Start Debugger"), |
|
410 self.tr("""<p>No suitable Python3 environment configured.</p>"""), |
|
411 ) |
|
412 return None, self.__isNetworked, "" |
|
413 |
|
414 self.__inShutdown = False |
|
415 |
|
416 if project.getDebugProperty("REMOTEDEBUGGER"): |
|
417 ipaddr = self.debugServer.getHostAddress(False) |
|
418 rexec = project.getDebugProperty("REMOTECOMMAND") |
|
419 rhost = project.getDebugProperty("REMOTEHOST") |
|
420 if rhost == "": |
|
421 rhost = "localhost" |
|
422 if rexec: |
|
423 args = Utilities.parseOptionString(rexec) + [ |
|
424 rhost, |
|
425 interpreter, |
|
426 debugClient, |
|
427 ] |
|
428 if noencoding: |
|
429 args.append(noencoding) |
|
430 if multiprocessEnabled: |
|
431 args.append(multiprocessEnabled) |
|
432 args.extend([str(port), redirect, ipaddr]) |
|
433 if Utilities.isWindowsPlatform(): |
|
434 if not os.path.splitext(args[0])[1]: |
|
435 for ext in [".exe", ".com", ".cmd", ".bat"]: |
|
436 prog = Utilities.getExecutablePath(args[0] + ext) |
|
437 if prog: |
|
438 args[0] = prog |
|
439 break |
|
440 else: |
|
441 args[0] = Utilities.getExecutablePath(args[0]) |
|
442 process = self.__startProcess(args[0], args[1:], workingDir=workingDir) |
|
443 if process is None: |
|
444 EricMessageBox.critical( |
|
445 None, |
|
446 self.tr("Start Debugger"), |
|
447 self.tr( |
|
448 """<p>The debugger backend could not be""" |
|
449 """ started.</p>""" |
|
450 ), |
|
451 ) |
|
452 # set translation function |
|
453 if project.getDebugProperty("PATHTRANSLATION"): |
|
454 self.translateRemote = project.getDebugProperty("REMOTEPATH") |
|
455 self.translateRemoteWindows = "\\" in self.translateRemote |
|
456 self.translateLocal = project.getDebugProperty("LOCALPATH") |
|
457 self.translateLocalWindows = "\\" in self.translateLocal |
|
458 self.translate = self.__remoteTranslation |
|
459 else: |
|
460 self.translate = self.__identityTranslation |
|
461 return process, self.__isNetworked, "" |
|
462 else: |
|
463 # remote shell command is missing |
|
464 return None, self.__isNetworked, "" |
|
465 |
|
466 # set translation function |
|
467 self.translate = self.__identityTranslation |
|
468 |
|
469 # setup the environment for the debugger |
|
470 if project.getDebugProperty("ENVIRONMENTOVERRIDE"): |
|
471 clientEnv = {} |
|
472 else: |
|
473 clientEnv = os.environ.copy() |
|
474 if originalPathString: |
|
475 clientEnv["PATH"] = originalPathString |
|
476 envlist = shlex.split(project.getDebugProperty("ENVIRONMENTSTRING")) |
|
477 for el in envlist: |
|
478 with contextlib.suppress(ValueError): |
|
479 key, value = el.split("=", 1) |
|
480 clientEnv[str(key)] = str(value) |
|
481 if execPath: |
|
482 if "PATH" in clientEnv: |
|
483 clientEnv["PATH"] = os.pathsep.join([execPath, clientEnv["PATH"]]) |
|
484 else: |
|
485 clientEnv["PATH"] = execPath |
|
486 |
|
487 ipaddr = self.debugServer.getHostAddress(True) |
|
488 if runInConsole or project.getDebugProperty("CONSOLEDEBUGGER"): |
|
489 ccmd = project.getDebugProperty( |
|
490 "CONSOLECOMMAND" |
|
491 ) or Preferences.getDebugger("ConsoleDbgCommand") |
|
492 if ccmd: |
|
493 args = Utilities.parseOptionString(ccmd) + [ |
|
494 interpreter, |
|
495 os.path.abspath(debugClient), |
|
496 ] |
|
497 if noencoding: |
|
498 args.append(noencoding) |
|
499 if multiprocessEnabled: |
|
500 args.append(multiprocessEnabled) |
|
501 args.extend([str(port), "0", ipaddr]) |
|
502 args[0] = Utilities.getExecutablePath(args[0]) |
|
503 process = self.__startProcess( |
|
504 args[0], args[1:], clientEnv, workingDir=workingDir |
|
505 ) |
|
506 if process is None: |
|
507 EricMessageBox.critical( |
|
508 None, |
|
509 self.tr("Start Debugger"), |
|
510 self.tr( |
|
511 """<p>The debugger backend could not be""" |
|
512 """ started.</p>""" |
|
513 ), |
|
514 ) |
|
515 return process, self.__isNetworked, interpreter |
|
516 |
|
517 args = [debugClient] |
|
518 if noencoding: |
|
519 args.append(noencoding) |
|
520 if multiprocessEnabled: |
|
521 args.append(multiprocessEnabled) |
|
522 args.extend([str(port), redirect, ipaddr]) |
|
523 process = self.__startProcess( |
|
524 interpreter, args, clientEnv, workingDir=workingDir |
|
525 ) |
|
526 if process is None: |
|
527 self.__startedVenv = "" |
|
528 EricMessageBox.critical( |
|
529 None, |
|
530 self.tr("Start Debugger"), |
|
531 self.tr("""<p>The debugger backend could not be started.</p>"""), |
|
532 ) |
|
533 else: |
|
534 self.__startedVenv = venvName |
|
535 |
|
536 return process, self.__isNetworked, interpreter |
|
537 |
|
538 def getClientCapabilities(self): |
|
539 """ |
|
540 Public method to retrieve the debug clients capabilities. |
|
541 |
|
542 @return debug client capabilities |
|
543 @rtype int |
|
544 """ |
|
545 return self.clientCapabilities |
|
546 |
|
547 def newConnection(self, sock): |
|
548 """ |
|
549 Public slot to handle a new connection. |
|
550 |
|
551 @param sock reference to the socket object |
|
552 @type QTcpSocket |
|
553 @return flag indicating success |
|
554 @rtype bool |
|
555 """ |
|
556 self.__pendingConnections.append(sock) |
|
557 |
|
558 sock.readyRead.connect(lambda: self.__parseClientLine(sock)) |
|
559 sock.disconnected.connect(lambda: self.__socketDisconnected(sock)) |
|
560 |
|
561 return True |
|
562 |
|
563 def __assignDebuggerId(self, sock, debuggerId): |
|
564 """ |
|
565 Private method to set the debugger id for a recent debugger connection |
|
566 attempt. |
|
567 |
|
568 @param sock reference to the socket object |
|
569 @type QTcpSocket |
|
570 @param debuggerId id of the connected debug client |
|
571 @type str |
|
572 """ |
|
573 if sock in self.__pendingConnections: |
|
574 self.__connections[debuggerId] = sock |
|
575 self.__pendingConnections.remove(sock) |
|
576 |
|
577 if self.__master is None: |
|
578 self.__master = debuggerId |
|
579 # Get the remote clients capabilities |
|
580 self.remoteCapabilities(debuggerId) |
|
581 |
|
582 self.debugServer.signalClientDebuggerId(debuggerId) |
|
583 |
|
584 if debuggerId == self.__master: |
|
585 self.__flush() |
|
586 self.debugServer.masterClientConnected() |
|
587 |
|
588 self.debugServer.initializeClient(debuggerId) |
|
589 |
|
590 # perform auto-continue except for master |
|
591 if ( |
|
592 debuggerId != self.__master |
|
593 and self.__autoContinue |
|
594 and not self.__isStepCommand |
|
595 ): |
|
596 self.__autoContinued.append(debuggerId) |
|
597 QTimer.singleShot(0, lambda: self.remoteContinue(debuggerId)) |
|
598 |
|
599 def __socketDisconnected(self, sock): |
|
600 """ |
|
601 Private slot handling a socket disconnecting. |
|
602 |
|
603 @param sock reference to the disconnected socket |
|
604 @type QTcpSocket |
|
605 """ |
|
606 for debuggerId in self.__connections: |
|
607 if self.__connections[debuggerId] is sock: |
|
608 del self.__connections[debuggerId] |
|
609 if debuggerId == self.__master: |
|
610 self.__master = None |
|
611 if debuggerId in self.__autoContinued: |
|
612 self.__autoContinued.remove(debuggerId) |
|
613 if not self.__inShutdown: |
|
614 with contextlib.suppress(RuntimeError): |
|
615 # can be ignored during a shutdown |
|
616 self.debugServer.signalClientDisconnected(debuggerId) |
|
617 break |
|
618 else: |
|
619 if sock in self.__pendingConnections: |
|
620 self.__pendingConnections.remove(sock) |
|
621 |
|
622 if not self.__connections: |
|
623 # no active connections anymore |
|
624 with contextlib.suppress(RuntimeError): |
|
625 self.debugServer.signalLastClientExited() |
|
626 # debug server object might have been deleted already |
|
627 # ignore this |
|
628 self.__autoContinued.clear() |
|
629 self.debugServer.startClient() |
|
630 |
|
631 def getDebuggerIds(self): |
|
632 """ |
|
633 Public method to return the IDs of the connected debugger backends. |
|
634 |
|
635 @return list of connected debugger backend IDs |
|
636 @rtype list of str |
|
637 """ |
|
638 return sorted(self.__connections.keys()) |
|
639 |
|
640 def __flush(self): |
|
641 """ |
|
642 Private slot to flush the queue. |
|
643 """ |
|
644 if self.__master: |
|
645 # Send commands that were waiting for the connection. |
|
646 for cmd in self.queue: |
|
647 self.__writeJsonCommandToSocket(cmd, self.__connections[self.__master]) |
|
648 |
|
649 self.queue = [] |
|
650 |
|
651 def shutdown(self): |
|
652 """ |
|
653 Public method to cleanly shut down. |
|
654 |
|
655 It closes our sockets and shuts down the debug clients. |
|
656 (Needed on Win OS) |
|
657 """ |
|
658 if not self.__master: |
|
659 return |
|
660 |
|
661 self.__inShutdown = True |
|
662 |
|
663 while self.__connections: |
|
664 debuggerId, sock = self.__connections.popitem() |
|
665 self.__shutdownSocket(sock) |
|
666 |
|
667 while self.__pendingConnections: |
|
668 sock = self.__pendingConnections.pop() |
|
669 self.__shutdownSocket(sock) |
|
670 |
|
671 # reinitialize |
|
672 self.queue = [] |
|
673 |
|
674 self.__master = None |
|
675 |
|
676 def __shutdownSocket(self, sock): |
|
677 """ |
|
678 Private slot to shut down a socket. |
|
679 |
|
680 @param sock reference to the socket |
|
681 @type QTcpSocket |
|
682 """ |
|
683 # do not want any slots called during shutdown |
|
684 sock.readyRead.disconnect() |
|
685 sock.disconnected.disconnect() |
|
686 |
|
687 # close down socket, and shut down client as well. |
|
688 self.__sendJsonCommand("RequestShutdown", {}, sock=sock) |
|
689 sock.flush() |
|
690 sock.close() |
|
691 |
|
692 sock.setParent(None) |
|
693 sock.deleteLater() |
|
694 del sock |
|
695 |
|
696 def isConnected(self): |
|
697 """ |
|
698 Public method to test, if a debug client has connected. |
|
699 |
|
700 @return flag indicating the connection status |
|
701 @rtype bool |
|
702 """ |
|
703 return bool(self.__connections) |
|
704 |
|
705 def remoteEnvironment(self, env): |
|
706 """ |
|
707 Public method to set the environment for a program to debug, run, ... |
|
708 |
|
709 @param env environment settings |
|
710 @type dict |
|
711 """ |
|
712 self.__sendJsonCommand( |
|
713 "RequestEnvironment", {"environment": env}, self.__master |
|
714 ) |
|
715 |
|
716 def remoteLoad( |
|
717 self, |
|
718 fn, |
|
719 argv, |
|
720 wd, |
|
721 traceInterpreter=False, |
|
722 autoContinue=True, |
|
723 enableMultiprocess=False, |
|
724 ): |
|
725 """ |
|
726 Public method to load a new program to debug. |
|
727 |
|
728 @param fn the filename to debug |
|
729 @type str |
|
730 @param argv the commandline arguments to pass to the program |
|
731 @type str |
|
732 @param wd the working directory for the program |
|
733 @type str |
|
734 @param traceInterpreter flag indicating if the interpreter library |
|
735 should be traced as well |
|
736 @type bool |
|
737 @param autoContinue flag indicating, that the debugger should not |
|
738 stop at the first executable line |
|
739 @type bool |
|
740 @param enableMultiprocess flag indicating to perform multiprocess |
|
741 debugging |
|
742 @type bool |
|
743 """ |
|
744 self.__autoContinue = autoContinue |
|
745 self.__scriptName = os.path.abspath(fn) |
|
746 self.__isStepCommand = False |
|
747 |
|
748 wd = self.translate(wd, False) |
|
749 fn = self.translate(os.path.abspath(fn), False) |
|
750 self.__sendJsonCommand( |
|
751 "RequestLoad", |
|
752 { |
|
753 "workdir": wd, |
|
754 "filename": fn, |
|
755 "argv": Utilities.parseOptionString(argv), |
|
756 "traceInterpreter": traceInterpreter, |
|
757 "multiprocess": enableMultiprocess, |
|
758 }, |
|
759 self.__master, |
|
760 ) |
|
761 |
|
762 def remoteRun(self, fn, argv, wd): |
|
763 """ |
|
764 Public method to load a new program to run. |
|
765 |
|
766 @param fn the filename to run |
|
767 @type str |
|
768 @param argv the commandline arguments to pass to the program |
|
769 @type str |
|
770 @param wd the working directory for the program |
|
771 @type str |
|
772 """ |
|
773 self.__scriptName = os.path.abspath(fn) |
|
774 |
|
775 wd = self.translate(wd, False) |
|
776 fn = self.translate(os.path.abspath(fn), False) |
|
777 self.__sendJsonCommand( |
|
778 "RequestRun", |
|
779 { |
|
780 "workdir": wd, |
|
781 "filename": fn, |
|
782 "argv": Utilities.parseOptionString(argv), |
|
783 }, |
|
784 self.__master, |
|
785 ) |
|
786 |
|
787 def remoteCoverage(self, fn, argv, wd, erase=False): |
|
788 """ |
|
789 Public method to load a new program to collect coverage data. |
|
790 |
|
791 @param fn the filename to run |
|
792 @type str |
|
793 @param argv the commandline arguments to pass to the program |
|
794 @type str |
|
795 @param wd the working directory for the program |
|
796 @type str |
|
797 @param erase flag indicating that coverage info should be |
|
798 cleared first |
|
799 @type bool |
|
800 """ |
|
801 self.__scriptName = os.path.abspath(fn) |
|
802 |
|
803 wd = self.translate(wd, False) |
|
804 fn = self.translate(os.path.abspath(fn), False) |
|
805 self.__sendJsonCommand( |
|
806 "RequestCoverage", |
|
807 { |
|
808 "workdir": wd, |
|
809 "filename": fn, |
|
810 "argv": Utilities.parseOptionString(argv), |
|
811 "erase": erase, |
|
812 }, |
|
813 self.__master, |
|
814 ) |
|
815 |
|
816 def remoteProfile(self, fn, argv, wd, erase=False): |
|
817 """ |
|
818 Public method to load a new program to collect profiling data. |
|
819 |
|
820 @param fn the filename to run |
|
821 @type str |
|
822 @param argv the commandline arguments to pass to the program |
|
823 @type str |
|
824 @param wd the working directory for the program |
|
825 @type str |
|
826 @param erase flag indicating that timing info should be cleared |
|
827 first |
|
828 @type bool |
|
829 """ |
|
830 self.__scriptName = os.path.abspath(fn) |
|
831 |
|
832 wd = self.translate(wd, False) |
|
833 fn = self.translate(os.path.abspath(fn), False) |
|
834 self.__sendJsonCommand( |
|
835 "RequestProfile", |
|
836 { |
|
837 "workdir": wd, |
|
838 "filename": fn, |
|
839 "argv": Utilities.parseOptionString(argv), |
|
840 "erase": erase, |
|
841 }, |
|
842 self.__master, |
|
843 ) |
|
844 |
|
845 def remoteStatement(self, debuggerId, stmt): |
|
846 """ |
|
847 Public method to execute a Python statement. |
|
848 |
|
849 @param debuggerId ID of the debugger backend |
|
850 @type str |
|
851 @param stmt the Python statement to execute. |
|
852 @type str |
|
853 """ |
|
854 self.__sendJsonCommand( |
|
855 "ExecuteStatement", |
|
856 { |
|
857 "statement": stmt, |
|
858 }, |
|
859 debuggerId, |
|
860 ) |
|
861 |
|
862 def remoteStep(self, debuggerId): |
|
863 """ |
|
864 Public method to single step the debugged program. |
|
865 |
|
866 @param debuggerId ID of the debugger backend |
|
867 @type str |
|
868 """ |
|
869 self.__isStepCommand = True |
|
870 self.__sendJsonCommand("RequestStep", {}, debuggerId) |
|
871 |
|
872 def remoteStepOver(self, debuggerId): |
|
873 """ |
|
874 Public method to step over the debugged program. |
|
875 |
|
876 @param debuggerId ID of the debugger backend |
|
877 @type str |
|
878 """ |
|
879 self.__isStepCommand = True |
|
880 self.__sendJsonCommand("RequestStepOver", {}, debuggerId) |
|
881 |
|
882 def remoteStepOut(self, debuggerId): |
|
883 """ |
|
884 Public method to step out the debugged program. |
|
885 |
|
886 @param debuggerId ID of the debugger backend |
|
887 @type str |
|
888 """ |
|
889 self.__isStepCommand = True |
|
890 self.__sendJsonCommand("RequestStepOut", {}, debuggerId) |
|
891 |
|
892 def remoteStepQuit(self, debuggerId): |
|
893 """ |
|
894 Public method to stop the debugged program. |
|
895 |
|
896 @param debuggerId ID of the debugger backend |
|
897 @type str |
|
898 """ |
|
899 self.__isStepCommand = True |
|
900 self.__sendJsonCommand("RequestStepQuit", {}, debuggerId) |
|
901 |
|
902 def remoteContinue(self, debuggerId, special=False): |
|
903 """ |
|
904 Public method to continue the debugged program. |
|
905 |
|
906 @param debuggerId ID of the debugger backend |
|
907 @type str |
|
908 @param special flag indicating a special continue operation |
|
909 @type bool |
|
910 """ |
|
911 self.__isStepCommand = False |
|
912 self.__sendJsonCommand( |
|
913 "RequestContinue", |
|
914 { |
|
915 "special": special, |
|
916 }, |
|
917 debuggerId, |
|
918 ) |
|
919 |
|
920 def remoteContinueUntil(self, debuggerId, line): |
|
921 """ |
|
922 Public method to continue the debugged program to the given line |
|
923 or until returning from the current frame. |
|
924 |
|
925 @param debuggerId ID of the debugger backend |
|
926 @type str |
|
927 @param line the new line, where execution should be continued to |
|
928 @type int |
|
929 """ |
|
930 self.__isStepCommand = False |
|
931 self.__sendJsonCommand( |
|
932 "RequestContinueUntil", |
|
933 { |
|
934 "newLine": line, |
|
935 }, |
|
936 debuggerId, |
|
937 ) |
|
938 |
|
939 def remoteMoveIP(self, debuggerId, line): |
|
940 """ |
|
941 Public method to move the instruction pointer to a different line. |
|
942 |
|
943 @param debuggerId ID of the debugger backend |
|
944 @type str |
|
945 @param line the new line, where execution should be continued |
|
946 @type int |
|
947 """ |
|
948 self.__sendJsonCommand( |
|
949 "RequestMoveIP", |
|
950 { |
|
951 "newLine": line, |
|
952 }, |
|
953 debuggerId, |
|
954 ) |
|
955 |
|
956 def remoteBreakpoint( |
|
957 self, debuggerId, fn, line, setBreakpoint, cond=None, temp=False |
|
958 ): |
|
959 """ |
|
960 Public method to set or clear a breakpoint. |
|
961 |
|
962 @param debuggerId ID of the debugger backend |
|
963 @type str |
|
964 @param fn filename the breakpoint belongs to |
|
965 @type str |
|
966 @param line linenumber of the breakpoint |
|
967 @type int |
|
968 @param setBreakpoint flag indicating setting or resetting a breakpoint |
|
969 @type bool |
|
970 @param cond condition of the breakpoint |
|
971 @type str |
|
972 @param temp flag indicating a temporary breakpoint |
|
973 @type bool |
|
974 """ |
|
975 debuggerList = [debuggerId] if debuggerId else list(self.__connections.keys()) |
|
976 for debuggerId in debuggerList: |
|
977 self.__sendJsonCommand( |
|
978 "RequestBreakpoint", |
|
979 { |
|
980 "filename": self.translate(fn, False), |
|
981 "line": line, |
|
982 "temporary": temp, |
|
983 "setBreakpoint": setBreakpoint, |
|
984 "condition": cond, |
|
985 }, |
|
986 debuggerId, |
|
987 ) |
|
988 |
|
989 def remoteBreakpointEnable(self, debuggerId, fn, line, enable): |
|
990 """ |
|
991 Public method to enable or disable a breakpoint. |
|
992 |
|
993 @param debuggerId ID of the debugger backend |
|
994 @type str |
|
995 @param fn filename the breakpoint belongs to |
|
996 @type str |
|
997 @param line linenumber of the breakpoint |
|
998 @type int |
|
999 @param enable flag indicating enabling or disabling a breakpoint |
|
1000 @type bool |
|
1001 """ |
|
1002 debuggerList = [debuggerId] if debuggerId else list(self.__connections.keys()) |
|
1003 for debuggerId in debuggerList: |
|
1004 self.__sendJsonCommand( |
|
1005 "RequestBreakpointEnable", |
|
1006 { |
|
1007 "filename": self.translate(fn, False), |
|
1008 "line": line, |
|
1009 "enable": enable, |
|
1010 }, |
|
1011 debuggerId, |
|
1012 ) |
|
1013 |
|
1014 def remoteBreakpointIgnore(self, debuggerId, fn, line, count): |
|
1015 """ |
|
1016 Public method to ignore a breakpoint the next couple of occurrences. |
|
1017 |
|
1018 @param debuggerId ID of the debugger backend |
|
1019 @type str |
|
1020 @param fn filename the breakpoint belongs to |
|
1021 @type str |
|
1022 @param line linenumber of the breakpoint |
|
1023 @type int |
|
1024 @param count number of occurrences to ignore |
|
1025 @type int |
|
1026 """ |
|
1027 debuggerList = [debuggerId] if debuggerId else list(self.__connections.keys()) |
|
1028 for debuggerId in debuggerList: |
|
1029 self.__sendJsonCommand( |
|
1030 "RequestBreakpointIgnore", |
|
1031 { |
|
1032 "filename": self.translate(fn, False), |
|
1033 "line": line, |
|
1034 "count": count, |
|
1035 }, |
|
1036 debuggerId, |
|
1037 ) |
|
1038 |
|
1039 def remoteWatchpoint(self, debuggerId, cond, setWatch, temp=False): |
|
1040 """ |
|
1041 Public method to set or clear a watch expression. |
|
1042 |
|
1043 @param debuggerId ID of the debugger backend |
|
1044 @type str |
|
1045 @param cond expression of the watch expression |
|
1046 @type str |
|
1047 @param setWatch flag indicating setting or resetting a watch expression |
|
1048 @type bool |
|
1049 @param temp flag indicating a temporary watch expression |
|
1050 @type bool |
|
1051 """ |
|
1052 debuggerList = [debuggerId] if debuggerId else list(self.__connections.keys()) |
|
1053 for debuggerId in debuggerList: |
|
1054 # cond is combination of cond and special (s. watch expression |
|
1055 # viewer) |
|
1056 self.__sendJsonCommand( |
|
1057 "RequestWatch", |
|
1058 { |
|
1059 "temporary": temp, |
|
1060 "setWatch": setWatch, |
|
1061 "condition": cond, |
|
1062 }, |
|
1063 debuggerId, |
|
1064 ) |
|
1065 |
|
1066 def remoteWatchpointEnable(self, debuggerId, cond, enable): |
|
1067 """ |
|
1068 Public method to enable or disable a watch expression. |
|
1069 |
|
1070 @param debuggerId ID of the debugger backend |
|
1071 @type str |
|
1072 @param cond expression of the watch expression |
|
1073 @type str |
|
1074 @param enable flag indicating enabling or disabling a watch expression |
|
1075 @type bool |
|
1076 """ |
|
1077 debuggerList = [debuggerId] if debuggerId else list(self.__connections.keys()) |
|
1078 for debuggerId in debuggerList: |
|
1079 # cond is combination of cond and special (s. watch expression |
|
1080 # viewer) |
|
1081 self.__sendJsonCommand( |
|
1082 "RequestWatchEnable", |
|
1083 { |
|
1084 "condition": cond, |
|
1085 "enable": enable, |
|
1086 }, |
|
1087 debuggerId, |
|
1088 ) |
|
1089 |
|
1090 def remoteWatchpointIgnore(self, debuggerId, cond, count): |
|
1091 """ |
|
1092 Public method to ignore a watch expression the next couple of |
|
1093 occurrences. |
|
1094 |
|
1095 @param debuggerId ID of the debugger backend |
|
1096 @type str |
|
1097 @param cond expression of the watch expression |
|
1098 @type str |
|
1099 @param count number of occurrences to ignore |
|
1100 @type int |
|
1101 """ |
|
1102 debuggerList = [debuggerId] if debuggerId else list(self.__connections.keys()) |
|
1103 for debuggerId in debuggerList: |
|
1104 # cond is combination of cond and special (s. watch expression |
|
1105 # viewer) |
|
1106 self.__sendJsonCommand( |
|
1107 "RequestWatchIgnore", |
|
1108 { |
|
1109 "condition": cond, |
|
1110 "count": count, |
|
1111 }, |
|
1112 debuggerId, |
|
1113 ) |
|
1114 |
|
1115 def remoteRawInput(self, debuggerId, inputString): |
|
1116 """ |
|
1117 Public method to send the raw input to the debugged program. |
|
1118 |
|
1119 @param debuggerId ID of the debugger backend |
|
1120 @type str |
|
1121 @param inputString the raw input |
|
1122 @type str |
|
1123 """ |
|
1124 self.__sendJsonCommand( |
|
1125 "RawInput", |
|
1126 { |
|
1127 "input": inputString, |
|
1128 }, |
|
1129 debuggerId, |
|
1130 ) |
|
1131 |
|
1132 def remoteThreadList(self, debuggerId): |
|
1133 """ |
|
1134 Public method to request the list of threads from the client. |
|
1135 |
|
1136 @param debuggerId ID of the debugger backend |
|
1137 @type str |
|
1138 """ |
|
1139 self.__sendJsonCommand("RequestThreadList", {}, debuggerId) |
|
1140 |
|
1141 def remoteSetThread(self, debuggerId, tid): |
|
1142 """ |
|
1143 Public method to request to set the given thread as current thread. |
|
1144 |
|
1145 @param debuggerId ID of the debugger backend |
|
1146 @type str |
|
1147 @param tid id of the thread |
|
1148 @type int |
|
1149 """ |
|
1150 self.__sendJsonCommand( |
|
1151 "RequestThreadSet", |
|
1152 { |
|
1153 "threadID": tid, |
|
1154 }, |
|
1155 debuggerId, |
|
1156 ) |
|
1157 |
|
1158 def remoteClientStack(self, debuggerId): |
|
1159 """ |
|
1160 Public method to request the stack of the main thread. |
|
1161 |
|
1162 @param debuggerId ID of the debugger backend |
|
1163 @type str |
|
1164 """ |
|
1165 self.__sendJsonCommand("RequestStack", {}, debuggerId) |
|
1166 |
|
1167 def remoteClientVariables( |
|
1168 self, debuggerId, scope, filterList, framenr=0, maxSize=0 |
|
1169 ): |
|
1170 """ |
|
1171 Public method to request the variables of the debugged program. |
|
1172 |
|
1173 @param debuggerId ID of the debugger backend |
|
1174 @type str |
|
1175 @param scope the scope of the variables (0 = local, 1 = global) |
|
1176 @type int |
|
1177 @param filterList list of variable types to filter out |
|
1178 @type list of str |
|
1179 @param framenr framenumber of the variables to retrieve |
|
1180 @type int |
|
1181 @param maxSize maximum size the formatted value of a variable will |
|
1182 be shown. If it is bigger than that, a 'too big' indication will |
|
1183 be given (@@TOO_BIG_TO_SHOW@@). |
|
1184 @type int |
|
1185 """ |
|
1186 self.__sendJsonCommand( |
|
1187 "RequestVariables", |
|
1188 { |
|
1189 "frameNumber": framenr, |
|
1190 "scope": scope, |
|
1191 "filters": filterList, |
|
1192 "maxSize": maxSize, |
|
1193 }, |
|
1194 debuggerId, |
|
1195 ) |
|
1196 |
|
1197 def remoteClientVariable( |
|
1198 self, debuggerId, scope, filterList, var, framenr=0, maxSize=0 |
|
1199 ): |
|
1200 """ |
|
1201 Public method to request the variables of the debugged program. |
|
1202 |
|
1203 @param debuggerId ID of the debugger backend |
|
1204 @type str |
|
1205 @param scope the scope of the variables (0 = local, 1 = global) |
|
1206 @type int |
|
1207 @param filterList list of variable types to filter out |
|
1208 @type list of str |
|
1209 @param var list encoded name of variable to retrieve |
|
1210 @type list of str |
|
1211 @param framenr framenumber of the variables to retrieve |
|
1212 @type int |
|
1213 @param maxSize maximum size the formatted value of a variable will |
|
1214 be shown. If it is bigger than that, a 'too big' indication will |
|
1215 be given (@@TOO_BIG_TO_SHOW@@). |
|
1216 @type int |
|
1217 """ |
|
1218 self.__sendJsonCommand( |
|
1219 "RequestVariable", |
|
1220 { |
|
1221 "variable": var, |
|
1222 "frameNumber": framenr, |
|
1223 "scope": scope, |
|
1224 "filters": filterList, |
|
1225 "maxSize": maxSize, |
|
1226 }, |
|
1227 debuggerId, |
|
1228 ) |
|
1229 |
|
1230 def remoteClientDisassembly(self, debuggerId): |
|
1231 """ |
|
1232 Public method to ask the client for the latest traceback disassembly. |
|
1233 |
|
1234 @param debuggerId ID of the debugger backend |
|
1235 @type str |
|
1236 """ |
|
1237 self.__sendJsonCommand("RequestDisassembly", {}, debuggerId) |
|
1238 |
|
1239 def remoteClientSetFilter(self, debuggerId, scope, filterStr): |
|
1240 """ |
|
1241 Public method to set a variables filter list. |
|
1242 |
|
1243 @param debuggerId ID of the debugger backend |
|
1244 @type str |
|
1245 @param scope the scope of the variables (0 = local, 1 = global) |
|
1246 @type int |
|
1247 @param filterStr regexp string for variable names to filter out |
|
1248 @type str |
|
1249 """ |
|
1250 self.__sendJsonCommand( |
|
1251 "RequestSetFilter", |
|
1252 { |
|
1253 "scope": scope, |
|
1254 "filter": filterStr, |
|
1255 }, |
|
1256 debuggerId, |
|
1257 ) |
|
1258 |
|
1259 def setCallTraceEnabled(self, debuggerId, on): |
|
1260 """ |
|
1261 Public method to set the call trace state. |
|
1262 |
|
1263 @param debuggerId ID of the debugger backend |
|
1264 @type str |
|
1265 @param on flag indicating to enable the call trace function |
|
1266 @type bool |
|
1267 """ |
|
1268 self.__sendJsonCommand( |
|
1269 "RequestCallTrace", |
|
1270 { |
|
1271 "enable": on, |
|
1272 }, |
|
1273 debuggerId, |
|
1274 ) |
|
1275 |
|
1276 def remoteNoDebugList(self, debuggerId, noDebugList): |
|
1277 """ |
|
1278 Public method to set a list of programs not to be debugged. |
|
1279 |
|
1280 The programs given in the list will not be run under the control |
|
1281 of the multi process debugger. |
|
1282 |
|
1283 @param debuggerId ID of the debugger backend |
|
1284 @type str |
|
1285 @param noDebugList list of Python programs not to be debugged |
|
1286 @type list of str |
|
1287 """ |
|
1288 self.__sendJsonCommand( |
|
1289 "RequestSetNoDebugList", |
|
1290 { |
|
1291 "noDebug": noDebugList, |
|
1292 }, |
|
1293 debuggerId, |
|
1294 ) |
|
1295 |
|
1296 def remoteBanner(self): |
|
1297 """ |
|
1298 Public slot to get the banner info of the remote client. |
|
1299 """ |
|
1300 self.__sendJsonCommand("RequestBanner", {}) |
|
1301 |
|
1302 def remoteCapabilities(self, debuggerId): |
|
1303 """ |
|
1304 Public slot to get the debug clients capabilities. |
|
1305 |
|
1306 @param debuggerId ID of the debugger backend |
|
1307 @type str |
|
1308 """ |
|
1309 self.__sendJsonCommand("RequestCapabilities", {}, debuggerId) |
|
1310 |
|
1311 def remoteCompletion(self, debuggerId, text): |
|
1312 """ |
|
1313 Public slot to get the a list of possible commandline completions |
|
1314 from the remote client. |
|
1315 |
|
1316 @param debuggerId ID of the debugger backend |
|
1317 @type str |
|
1318 @param text the text to be completed |
|
1319 @type str |
|
1320 """ |
|
1321 self.__sendJsonCommand( |
|
1322 "RequestCompletion", |
|
1323 { |
|
1324 "text": text, |
|
1325 }, |
|
1326 debuggerId, |
|
1327 ) |
|
1328 |
|
1329 def __parseClientLine(self, sock): |
|
1330 """ |
|
1331 Private method to handle data from the client. |
|
1332 |
|
1333 @param sock reference to the socket to read data from |
|
1334 @type QTcpSocket |
|
1335 """ |
|
1336 while sock and sock.canReadLine(): |
|
1337 qs = sock.readLine() |
|
1338 line = bytes(qs).decode(encoding=Preferences.getSystem("StringEncoding")) |
|
1339 |
|
1340 logging.debug("<Debug-Server> %s", line) |
|
1341 ##print("Server: ", line) ## debug # __IGNORE_WARNING_M891__ |
|
1342 |
|
1343 self.__handleJsonCommand(line, sock) |
|
1344 |
|
1345 def __handleJsonCommand(self, jsonStr, sock): |
|
1346 """ |
|
1347 Private method to handle a command or response serialized as a |
|
1348 JSON string. |
|
1349 |
|
1350 @param jsonStr string containing the command or response received |
|
1351 from the debug backend |
|
1352 @type str |
|
1353 @param sock reference to the socket the data was received from |
|
1354 @type QTcpSocket |
|
1355 """ |
|
1356 import json |
|
1357 |
|
1358 try: |
|
1359 commandDict = json.loads(jsonStr.strip()) |
|
1360 except (TypeError, ValueError) as err: |
|
1361 EricMessageBox.critical( |
|
1362 None, |
|
1363 self.tr("Debug Protocol Error"), |
|
1364 self.tr( |
|
1365 """<p>The response received from the debugger""" |
|
1366 """ backend could not be decoded. Please report""" |
|
1367 """ this issue with the received data to the""" |
|
1368 """ eric bugs email address.</p>""" |
|
1369 """<p>Error: {0}</p>""" |
|
1370 """<p>Data:<br/>{1}</p>""" |
|
1371 ).format(str(err), Utilities.html_encode(jsonStr.strip())), |
|
1372 EricMessageBox.Ok, |
|
1373 ) |
|
1374 return |
|
1375 |
|
1376 method = commandDict["method"] |
|
1377 params = commandDict["params"] |
|
1378 |
|
1379 if method == "DebuggerId": |
|
1380 self.__assignDebuggerId(sock, params["debuggerId"]) |
|
1381 |
|
1382 elif method == "ClientOutput": |
|
1383 self.debugServer.signalClientOutput(params["text"], params["debuggerId"]) |
|
1384 |
|
1385 elif method in ["ResponseLine", "ResponseStack"]: |
|
1386 # Check if obsolete thread was clicked |
|
1387 if params["stack"] == []: |
|
1388 # Request updated list |
|
1389 self.remoteThreadList(params["debuggerId"]) |
|
1390 return |
|
1391 for s in params["stack"]: |
|
1392 s[0] = self.translate(s[0], True) |
|
1393 cf = params["stack"][0] |
|
1394 if self.__autoContinue and params["debuggerId"] not in self.__autoContinued: |
|
1395 self.__autoContinued.append(params["debuggerId"]) |
|
1396 QTimer.singleShot(0, lambda: self.remoteContinue(params["debuggerId"])) |
|
1397 else: |
|
1398 self.debugServer.signalClientLine( |
|
1399 cf[0], |
|
1400 int(cf[1]), |
|
1401 params["debuggerId"], |
|
1402 method == "ResponseStack", |
|
1403 threadName=params["threadName"], |
|
1404 ) |
|
1405 self.debugServer.signalClientStack( |
|
1406 params["stack"], |
|
1407 params["debuggerId"], |
|
1408 threadName=params["threadName"], |
|
1409 ) |
|
1410 |
|
1411 elif method == "CallTrace": |
|
1412 isCall = params["event"].lower() == "c" |
|
1413 fromInfo = params["from"] |
|
1414 toInfo = params["to"] |
|
1415 self.debugServer.signalClientCallTrace( |
|
1416 isCall, |
|
1417 fromInfo["filename"], |
|
1418 str(fromInfo["linenumber"]), |
|
1419 fromInfo["codename"], |
|
1420 toInfo["filename"], |
|
1421 str(toInfo["linenumber"]), |
|
1422 toInfo["codename"], |
|
1423 params["debuggerId"], |
|
1424 ) |
|
1425 |
|
1426 elif method == "ResponseVariables": |
|
1427 self.debugServer.signalClientVariables( |
|
1428 params["scope"], params["variables"], params["debuggerId"] |
|
1429 ) |
|
1430 |
|
1431 elif method == "ResponseVariable": |
|
1432 self.debugServer.signalClientVariable( |
|
1433 params["scope"], |
|
1434 [params["variable"]] + params["variables"], |
|
1435 params["debuggerId"], |
|
1436 ) |
|
1437 |
|
1438 elif method == "ResponseThreadList": |
|
1439 self.debugServer.signalClientThreadList( |
|
1440 params["currentID"], params["threadList"], params["debuggerId"] |
|
1441 ) |
|
1442 |
|
1443 elif method == "ResponseThreadSet": |
|
1444 self.debugServer.signalClientThreadSet(params["debuggerId"]) |
|
1445 |
|
1446 elif method == "ResponseCapabilities": |
|
1447 self.clientCapabilities = params["capabilities"] |
|
1448 if params["debuggerId"] == self.__master: |
|
1449 # signal only for the master connection |
|
1450 self.debugServer.signalClientCapabilities( |
|
1451 params["capabilities"], |
|
1452 params["clientType"], |
|
1453 self.__startedVenv, |
|
1454 ) |
|
1455 |
|
1456 elif method == "ResponseBanner": |
|
1457 if params["debuggerId"] == self.__master: |
|
1458 # signal only for the master connection |
|
1459 self.debugServer.signalClientBanner( |
|
1460 params["version"], |
|
1461 params["platform"], |
|
1462 self.__startedVenv, |
|
1463 ) |
|
1464 |
|
1465 elif method == "ResponseOK": |
|
1466 self.debugServer.signalClientStatement(False, params["debuggerId"]) |
|
1467 |
|
1468 elif method == "ResponseContinue": |
|
1469 self.debugServer.signalClientStatement(True, params["debuggerId"]) |
|
1470 |
|
1471 elif method == "RequestRaw": |
|
1472 self.debugServer.signalClientRawInput( |
|
1473 params["prompt"], params["echo"], params["debuggerId"] |
|
1474 ) |
|
1475 |
|
1476 elif method == "ResponseBPConditionError": |
|
1477 fn = self.translate(params["filename"], True) |
|
1478 self.debugServer.signalClientBreakConditionError( |
|
1479 fn, params["line"], params["debuggerId"] |
|
1480 ) |
|
1481 |
|
1482 elif method == "ResponseClearBreakpoint": |
|
1483 fn = self.translate(params["filename"], True) |
|
1484 self.debugServer.signalClientClearBreak( |
|
1485 fn, params["line"], params["debuggerId"] |
|
1486 ) |
|
1487 |
|
1488 elif method == "ResponseWatchConditionError": |
|
1489 self.debugServer.signalClientWatchConditionError( |
|
1490 params["condition"], params["debuggerId"] |
|
1491 ) |
|
1492 |
|
1493 elif method == "ResponseClearWatch": |
|
1494 self.debugServer.signalClientClearWatch( |
|
1495 params["condition"], params["debuggerId"] |
|
1496 ) |
|
1497 |
|
1498 elif method == "ResponseDisassembly": |
|
1499 self.debugServer.signalClientDisassembly( |
|
1500 params["disassembly"], params["debuggerId"] |
|
1501 ) |
|
1502 |
|
1503 elif method == "ResponseException": |
|
1504 exctype = params["type"] |
|
1505 excmessage = params["message"] |
|
1506 stack = params["stack"] |
|
1507 if stack: |
|
1508 for stackEntry in stack: |
|
1509 stackEntry[0] = self.translate(stackEntry[0], True) |
|
1510 if stack[0] and stack[0][0] == "<string>": |
|
1511 for stackEntry in stack: |
|
1512 if stackEntry[0] == "<string>": |
|
1513 stackEntry[0] = self.__scriptName |
|
1514 else: |
|
1515 break |
|
1516 |
|
1517 self.debugServer.signalClientException( |
|
1518 exctype, excmessage, stack, params["debuggerId"], params["threadName"] |
|
1519 ) |
|
1520 |
|
1521 elif method == "ResponseSyntax": |
|
1522 self.debugServer.signalClientSyntaxError( |
|
1523 params["message"], |
|
1524 self.translate(params["filename"], True), |
|
1525 params["linenumber"], |
|
1526 params["characternumber"], |
|
1527 params["debuggerId"], |
|
1528 params["threadName"], |
|
1529 ) |
|
1530 |
|
1531 elif method == "ResponseSignal": |
|
1532 self.debugServer.signalClientSignal( |
|
1533 params["message"], |
|
1534 self.translate(params["filename"], True), |
|
1535 params["linenumber"], |
|
1536 params["function"], |
|
1537 params["arguments"], |
|
1538 params["debuggerId"], |
|
1539 ) |
|
1540 |
|
1541 elif method == "ResponseExit": |
|
1542 self.__scriptName = "" |
|
1543 self.debugServer.signalClientExit( |
|
1544 params["program"], |
|
1545 params["status"], |
|
1546 params["message"], |
|
1547 params["debuggerId"], |
|
1548 ) |
|
1549 if params["debuggerId"] == self.__master: |
|
1550 self.debugServer.signalMainClientExit() |
|
1551 |
|
1552 elif method == "PassiveStartup": |
|
1553 self.debugServer.passiveStartUp( |
|
1554 self.translate(params["filename"], True), |
|
1555 params["exceptions"], |
|
1556 params["debuggerId"], |
|
1557 ) |
|
1558 |
|
1559 elif method == "ResponseCompletion": |
|
1560 self.debugServer.signalClientCompletionList( |
|
1561 params["completions"], params["text"], params["debuggerId"] |
|
1562 ) |
|
1563 |
|
1564 def __sendJsonCommand(self, command, params, debuggerId="", sock=None): |
|
1565 """ |
|
1566 Private method to send a single command to the client. |
|
1567 |
|
1568 @param command command name to be sent |
|
1569 @type str |
|
1570 @param params dictionary of named parameters for the command |
|
1571 @type dict |
|
1572 @param debuggerId id of the debug client to send the command to |
|
1573 @type str |
|
1574 @param sock reference to the socket object to be used (only used if |
|
1575 debuggerId is not given) |
|
1576 @type QTcpSocket |
|
1577 """ |
|
1578 import json |
|
1579 |
|
1580 commandDict = { |
|
1581 "jsonrpc": "2.0", |
|
1582 "method": command, |
|
1583 "params": params, |
|
1584 } |
|
1585 cmd = json.dumps(commandDict) + "\n" |
|
1586 |
|
1587 if debuggerId and debuggerId in self.__connections: |
|
1588 sock = self.__connections[debuggerId] |
|
1589 elif sock is None and self.__master is not None: |
|
1590 sock = self.__connections[self.__master] |
|
1591 if sock is not None: |
|
1592 self.__writeJsonCommandToSocket(cmd, sock) |
|
1593 else: |
|
1594 self.queue.append(cmd) |
|
1595 |
|
1596 def __writeJsonCommandToSocket(self, cmd, sock): |
|
1597 """ |
|
1598 Private method to write a JSON command to the socket. |
|
1599 |
|
1600 @param cmd JSON command to be sent |
|
1601 @type str |
|
1602 @param sock reference to the socket to write to |
|
1603 @type QTcpSocket |
|
1604 """ |
|
1605 data = cmd.encode("utf8", "backslashreplace") |
|
1606 length = "{0:09d}".format(len(data)) |
|
1607 sock.write(length.encode() + data) |
|
1608 sock.flush() |
|
1609 |
|
1610 |
|
1611 def createDebuggerInterfacePython3(debugServer, passive): |
|
1612 """ |
|
1613 Module function to create a debugger interface instance. |
|
1614 |
|
1615 |
|
1616 @param debugServer reference to the debug server |
|
1617 @type DebugServer |
|
1618 @param passive flag indicating passive connection mode |
|
1619 @type bool |
|
1620 @return instantiated debugger interface |
|
1621 @rtype DebuggerInterfacePython |
|
1622 """ |
|
1623 return DebuggerInterfacePython(debugServer, passive) |
|
1624 |
|
1625 |
|
1626 def getRegistryData(): |
|
1627 """ |
|
1628 Module function to get characterizing data for the supported debugger |
|
1629 interfaces. |
|
1630 |
|
1631 @return list of tuples containing the client type, the client capabilities, |
|
1632 the client file type associations and a reference to the creation |
|
1633 function |
|
1634 @rtype list of tuple of (str, int, list of str, function) |
|
1635 """ |
|
1636 py3Exts = [] |
|
1637 for ext in Preferences.getDebugger("Python3Extensions").split(): |
|
1638 if ext.startswith("."): |
|
1639 py3Exts.append(ext) |
|
1640 else: |
|
1641 py3Exts.append(".{0}".format(ext)) |
|
1642 |
|
1643 registryData = [] |
|
1644 if py3Exts: |
|
1645 registryData.append( |
|
1646 ( |
|
1647 "Python3", |
|
1648 ClientDefaultCapabilities, |
|
1649 py3Exts, |
|
1650 createDebuggerInterfacePython3, |
|
1651 ) |
|
1652 ) |
|
1653 |
|
1654 return registryData |