eric6/Debugger/DebuggerInterfacePython.py

changeset 6942
2602857055c5
parent 6904
3f35037a08d4
child 7229
53054eb5b15a
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the Python3 debugger interface for the debug server.
8 """
9
10 from __future__ import unicode_literals
11
12 import sys
13 import os
14 import logging
15
16 from PyQt5.QtCore import QObject, QTextCodec, QProcess, QProcessEnvironment, \
17 QTimer
18 from PyQt5.QtWidgets import QInputDialog
19
20 from E5Gui.E5Application import e5App
21 from E5Gui import E5MessageBox
22
23 from . import DebugClientCapabilities
24
25 import Preferences
26 import Utilities
27
28 from eric6config import getConfig
29
30
31 ClientDefaultCapabilities = DebugClientCapabilities.HasAll
32
33
34 class DebuggerInterfacePython(QObject):
35 """
36 Class implementing the debugger interface for the debug server for Python 2
37 and Python 3.
38 """
39 def __init__(self, debugServer, passive, pythonVariant):
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 @param pythonVariant Python variant to instantiate for
48 @type str (one of Python2 or Python3)
49 """
50 super(DebuggerInterfacePython, self).__init__()
51
52 self.__isNetworked = True
53 self.__autoContinue = False
54
55 self.debugServer = debugServer
56 self.passive = passive
57 self.process = None
58 self.__variant = pythonVariant
59 self.__startedVenv = ""
60
61 self.qsock = None
62 self.queue = []
63
64 # set default values for capabilities of clients
65 self.clientCapabilities = ClientDefaultCapabilities
66
67 # set translation function
68 self.translate = self.__identityTranslation
69
70 self.codec = QTextCodec.codecForName(
71 str(Preferences.getSystem("StringEncoding")))
72
73 if passive:
74 # set translation function
75 if Preferences.getDebugger("PathTranslation"):
76 self.translateRemote = \
77 Preferences.getDebugger("PathTranslationRemote")
78 self.translateRemoteWindows = "\\" in self.translateRemote
79 self.translateLocal = \
80 Preferences.getDebugger("PathTranslationLocal")
81 self.translateLocalWindows = "\\" in self.translateLocal
82 self.translate = self.__remoteTranslation
83 else:
84 self.translate = self.__identityTranslation
85
86 # attribute to remember the name of the executed script
87 self.__scriptName = ""
88
89 def __identityTranslation(self, fn, remote2local=True):
90 """
91 Private method to perform the identity path translation.
92
93 @param fn filename to be translated (string)
94 @param remote2local flag indicating the direction of translation
95 (False = local to remote, True = remote to local [default])
96 @return translated filename (string)
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 (string)
105 @param remote2local flag indicating the direction of translation
106 (False = local to remote, True = remote to local [default])
107 @return translated filename (string)
108 """
109 if remote2local:
110 path = fn.replace(self.translateRemote, self.translateLocal)
111 if self.translateLocalWindows:
112 path = path.replace("/", "\\")
113 else:
114 path = fn.replace(self.translateLocal, self.translateRemote)
115 if not self.translateRemoteWindows:
116 path = path.replace("\\", "/")
117
118 return path
119
120 def __startProcess(self, program, arguments, environment=None,
121 workingDir=None):
122 """
123 Private method to start the debugger client process.
124
125 @param program name of the executable to start
126 @type str
127 @param arguments arguments to be passed to the program
128 @type list of str
129 @param environment dictionary of environment settings to pass
130 @type dict of str
131 @param workingDir directory to start the debugger client in
132 @type str
133 @return the process object
134 @rtype QProcess or None
135 """
136 proc = QProcess()
137 if environment is not None:
138 env = QProcessEnvironment()
139 for key, value in list(environment.items()):
140 env.insert(key, value)
141 proc.setProcessEnvironment(env)
142 args = arguments[:]
143 if workingDir:
144 proc.setWorkingDirectory(workingDir)
145 proc.start(program, args)
146 if not proc.waitForStarted(10000):
147 proc = None
148
149 return proc
150
151 def startRemote(self, port, runInConsole, venvName, originalPathString,
152 workingDir=None):
153 """
154 Public method to start a remote Python interpreter.
155
156 @param port port number the debug server is listening on
157 @type int
158 @param runInConsole flag indicating to start the debugger in a
159 console window
160 @type bool
161 @param venvName name of the virtual environment to be used
162 @type str
163 @param originalPathString original PATH environment variable
164 @type str
165 @param workingDir directory to start the debugger client in
166 @type str
167 @return client process object, a flag to indicate a network connection
168 and the name of the interpreter in case of a local execution
169 @rtype tuple of (QProcess, bool, str)
170 """
171 global origPathEnv
172
173 if not venvName:
174 if self.__variant == "Python2":
175 venvName = Preferences.getDebugger("Python2VirtualEnv")
176 else:
177 venvName = Preferences.getDebugger("Python3VirtualEnv")
178 venvManager = e5App().getObject("VirtualEnvManager")
179 interpreter = venvManager.getVirtualenvInterpreter(venvName)
180 execPath = venvManager.getVirtualenvExecPath(venvName)
181 if interpreter == "" and \
182 int(self.__variant[-1]) == sys.version_info[0]:
183 # use the interpreter used to run eric for identical variants
184 interpreter = sys.executable.replace("w.exe", ".exe")
185 if interpreter == "":
186 E5MessageBox.critical(
187 None,
188 self.tr("Start Debugger"),
189 self.tr(
190 """<p>No suitable {0} environment configured.</p>""")
191 .format(self.__variant))
192 return None, False, ""
193
194 if self.__variant == "Python2":
195 debugClientType = Preferences.getDebugger("DebugClientType")
196 else:
197 debugClientType = Preferences.getDebugger("DebugClientType3")
198 if debugClientType == "standard":
199 debugClient = os.path.join(getConfig('ericDir'),
200 "DebugClients", "Python",
201 "DebugClient.py")
202 else:
203 if self.__variant == "Python2":
204 debugClient = Preferences.getDebugger("DebugClient")
205 else:
206 debugClient = Preferences.getDebugger("DebugClient3")
207 if debugClient == "":
208 debugClient = os.path.join(sys.path[0],
209 "DebugClients", "Python",
210 "DebugClient.py")
211
212 if self.__variant == "Python2":
213 redirect = str(Preferences.getDebugger("PythonRedirect"))
214 noencoding = Preferences.getDebugger("PythonNoEncoding") and \
215 '--no-encoding' or ''
216 else:
217 redirect = str(Preferences.getDebugger("Python3Redirect"))
218 noencoding = Preferences.getDebugger("Python3NoEncoding") and \
219 '--no-encoding' or ''
220
221 if Preferences.getDebugger("RemoteDbgEnabled"):
222 ipaddr = self.debugServer.getHostAddress(False)
223 rexec = Preferences.getDebugger("RemoteExecution")
224 rhost = Preferences.getDebugger("RemoteHost")
225 if rhost == "":
226 rhost = "localhost"
227 if rexec:
228 args = Utilities.parseOptionString(rexec) + \
229 [rhost, interpreter, debugClient,
230 noencoding, str(port), redirect, ipaddr]
231 if Utilities.isWindowsPlatform():
232 if not os.path.splitext(args[0])[1]:
233 for ext in [".exe", ".com", ".cmd", ".bat"]:
234 prog = Utilities.getExecutablePath(args[0] + ext)
235 if prog:
236 args[0] = prog
237 break
238 else:
239 args[0] = Utilities.getExecutablePath(args[0])
240 process = self.__startProcess(args[0], args[1:],
241 workingDir=workingDir)
242 if process is None:
243 E5MessageBox.critical(
244 None,
245 self.tr("Start Debugger"),
246 self.tr(
247 """<p>The debugger backend could not be"""
248 """ started.</p>"""))
249
250 # set translation function
251 if Preferences.getDebugger("PathTranslation"):
252 self.translateRemote = \
253 Preferences.getDebugger("PathTranslationRemote")
254 self.translateRemoteWindows = "\\" in self.translateRemote
255 self.translateLocal = \
256 Preferences.getDebugger("PathTranslationLocal")
257 self.translate = self.__remoteTranslation
258 self.translateLocalWindows = "\\" in self.translateLocal
259 else:
260 self.translate = self.__identityTranslation
261 return process, self.__isNetworked, ""
262
263 # set translation function
264 self.translate = self.__identityTranslation
265
266 # setup the environment for the debugger
267 if Preferences.getDebugger("DebugEnvironmentReplace"):
268 clientEnv = {}
269 else:
270 clientEnv = os.environ.copy()
271 if originalPathString:
272 clientEnv["PATH"] = originalPathString
273 envlist = Utilities.parseEnvironmentString(
274 Preferences.getDebugger("DebugEnvironment"))
275 for el in envlist:
276 try:
277 key, value = el.split('=', 1)
278 if value.startswith('"') or value.startswith("'"):
279 value = value[1:-1]
280 clientEnv[str(key)] = str(value)
281 except ValueError:
282 pass
283 if execPath:
284 if "PATH" in clientEnv:
285 clientEnv["PATH"] = os.pathsep.join(
286 [execPath, clientEnv["PATH"]])
287 else:
288 clientEnv["PATH"] = execPath
289
290 ipaddr = self.debugServer.getHostAddress(True)
291 if runInConsole or Preferences.getDebugger("ConsoleDbgEnabled"):
292 ccmd = Preferences.getDebugger("ConsoleDbgCommand")
293 if ccmd:
294 args = Utilities.parseOptionString(ccmd) + \
295 [interpreter, os.path.abspath(debugClient),
296 noencoding, str(port), '0', ipaddr]
297 args[0] = Utilities.getExecutablePath(args[0])
298 process = self.__startProcess(args[0], args[1:], clientEnv,
299 workingDir=workingDir)
300 if process is None:
301 E5MessageBox.critical(
302 None,
303 self.tr("Start Debugger"),
304 self.tr(
305 """<p>The debugger backend could not be"""
306 """ started.</p>"""))
307 return process, self.__isNetworked, interpreter
308
309 process = self.__startProcess(
310 interpreter,
311 [debugClient, noencoding, str(port), redirect, ipaddr],
312 clientEnv,
313 workingDir=workingDir)
314 if process is None:
315 self.__startedVenv = ""
316 E5MessageBox.critical(
317 None,
318 self.tr("Start Debugger"),
319 self.tr(
320 """<p>The debugger backend could not be started.</p>"""))
321 else:
322 self.__startedVenv = venvName
323
324 return process, self.__isNetworked, interpreter
325
326 def startRemoteForProject(self, port, runInConsole, venvName,
327 originalPathString, workingDir=None):
328 """
329 Public method to start a remote Python interpreter for a project.
330
331 @param port port number the debug server is listening on
332 @type int
333 @param runInConsole flag indicating to start the debugger in a
334 console window
335 @type bool
336 @param venvName name of the virtual environment to be used
337 @type str
338 @param originalPathString original PATH environment variable
339 @type str
340 @param workingDir directory to start the debugger client in
341 @type str
342 @return client process object, a flag to indicate a network connection
343 and the name of the interpreter in case of a local execution
344 @rtype tuple of (QProcess, bool, str)
345 """
346 global origPathEnv
347
348 project = e5App().getObject("Project")
349 if not project.isDebugPropertiesLoaded():
350 return None, self.__isNetworked, ""
351
352 # start debugger with project specific settings
353 debugClient = project.getDebugProperty("DEBUGCLIENT")
354 if not venvName:
355 venvName = project.getDebugProperty("VIRTUALENV")
356 if not venvName:
357 if project.getProjectLanguage() == "Python2":
358 venvName = Preferences.getDebugger("Python2VirtualEnv")
359 elif project.getProjectLanguage() == "Python3":
360 venvName = Preferences.getDebugger("Python3VirtualEnv")
361
362 redirect = str(project.getDebugProperty("REDIRECT"))
363 noencoding = \
364 project.getDebugProperty("NOENCODING") and '--no-encoding' or ''
365
366 venvManager = e5App().getObject("VirtualEnvManager")
367 interpreter = venvManager.getVirtualenvInterpreter(venvName)
368 execPath = venvManager.getVirtualenvExecPath(venvName)
369 if interpreter == "" and \
370 project.getProjectLanguage().startswith("Python") and \
371 sys.version_info[0] == int(project.getProjectLanguage()[-1]):
372 interpreter = sys.executable.replace("w.exe", ".exe")
373 if interpreter == "":
374 E5MessageBox.critical(
375 None,
376 self.tr("Start Debugger"),
377 self.tr(
378 """<p>No suitable {0} environment configured.</p>""")
379 .format(self.__variant))
380 return None, self.__isNetworked, ""
381
382 if project.getDebugProperty("REMOTEDEBUGGER"):
383 ipaddr = self.debugServer.getHostAddress(False)
384 rexec = project.getDebugProperty("REMOTECOMMAND")
385 rhost = project.getDebugProperty("REMOTEHOST")
386 if rhost == "":
387 rhost = "localhost"
388 if rexec:
389 args = Utilities.parseOptionString(rexec) + \
390 [rhost, interpreter, debugClient, noencoding,
391 str(port), redirect, ipaddr]
392 if Utilities.isWindowsPlatform():
393 if not os.path.splitext(args[0])[1]:
394 for ext in [".exe", ".com", ".cmd", ".bat"]:
395 prog = Utilities.getExecutablePath(args[0] + ext)
396 if prog:
397 args[0] = prog
398 break
399 else:
400 args[0] = Utilities.getExecutablePath(args[0])
401 process = self.__startProcess(args[0], args[1:],
402 workingDir=workingDir)
403 if process is None:
404 E5MessageBox.critical(
405 None,
406 self.tr("Start Debugger"),
407 self.tr(
408 """<p>The debugger backend could not be"""
409 """ started.</p>"""))
410 # set translation function
411 if project.getDebugProperty("PATHTRANSLATION"):
412 self.translateRemote = \
413 project.getDebugProperty("REMOTEPATH")
414 self.translateRemoteWindows = "\\" in self.translateRemote
415 self.translateLocal = \
416 project.getDebugProperty("LOCALPATH")
417 self.translateLocalWindows = "\\" in self.translateLocal
418 self.translate = self.__remoteTranslation
419 else:
420 self.translate = self.__identityTranslation
421 return process, self.__isNetworked, ""
422 else:
423 # remote shell command is missing
424 return None, self.__isNetworked, ""
425
426 # set translation function
427 self.translate = self.__identityTranslation
428
429 # setup the environment for the debugger
430 if project.getDebugProperty("ENVIRONMENTOVERRIDE"):
431 clientEnv = {}
432 else:
433 clientEnv = os.environ.copy()
434 if originalPathString:
435 clientEnv["PATH"] = originalPathString
436 envlist = Utilities.parseEnvironmentString(
437 project.getDebugProperty("ENVIRONMENTSTRING"))
438 for el in envlist:
439 try:
440 key, value = el.split('=', 1)
441 if value.startswith('"') or value.startswith("'"):
442 value = value[1:-1]
443 clientEnv[str(key)] = str(value)
444 except ValueError:
445 pass
446 if execPath:
447 if "PATH" in clientEnv:
448 clientEnv["PATH"] = os.pathsep.join(
449 [execPath, clientEnv["PATH"]])
450 else:
451 clientEnv["PATH"] = execPath
452
453 ipaddr = self.debugServer.getHostAddress(True)
454 if runInConsole or project.getDebugProperty("CONSOLEDEBUGGER"):
455 ccmd = project.getDebugProperty("CONSOLECOMMAND") or \
456 Preferences.getDebugger("ConsoleDbgCommand")
457 if ccmd:
458 args = Utilities.parseOptionString(ccmd) + \
459 [interpreter, os.path.abspath(debugClient),
460 noencoding, str(port), '0', ipaddr]
461 args[0] = Utilities.getExecutablePath(args[0])
462 process = self.__startProcess(args[0], args[1:], clientEnv,
463 workingDir=workingDir)
464 if process is None:
465 E5MessageBox.critical(
466 None,
467 self.tr("Start Debugger"),
468 self.tr(
469 """<p>The debugger backend could not be"""
470 """ started.</p>"""))
471 return process, self.__isNetworked, interpreter
472
473 process = self.__startProcess(
474 interpreter,
475 [debugClient, noencoding, str(port), redirect, ipaddr],
476 clientEnv,
477 workingDir=workingDir)
478 if process is None:
479 self.__startedVenv = ""
480 E5MessageBox.critical(
481 None,
482 self.tr("Start Debugger"),
483 self.tr(
484 """<p>The debugger backend could not be started.</p>"""))
485 else:
486 self.__startedVenv = venvName
487
488 return process, self.__isNetworked, interpreter
489
490 def getClientCapabilities(self):
491 """
492 Public method to retrieve the debug clients capabilities.
493
494 @return debug client capabilities (integer)
495 """
496 return self.clientCapabilities
497
498 def newConnection(self, sock):
499 """
500 Public slot to handle a new connection.
501
502 @param sock reference to the socket object (QTcpSocket)
503 @return flag indicating success (boolean)
504 """
505 # If we already have a connection, refuse this one. It will be closed
506 # automatically.
507 if self.qsock is not None:
508 return False
509
510 sock.disconnected.connect(self.debugServer.startClient)
511 sock.readyRead.connect(self.__parseClientLine)
512
513 self.qsock = sock
514
515 # Get the remote clients capabilities
516 self.remoteCapabilities()
517 return True
518
519 def flush(self):
520 """
521 Public slot to flush the queue.
522 """
523 # Send commands that were waiting for the connection.
524 for cmd in self.queue:
525 self.__writeJsonCommandToSocket(cmd)
526
527 self.queue = []
528
529 def shutdown(self):
530 """
531 Public method to cleanly shut down.
532
533 It closes our socket and shuts down
534 the debug client. (Needed on Win OS)
535 """
536 if self.qsock is None:
537 return
538
539 # do not want any slots called during shutdown
540 self.qsock.disconnected.disconnect(self.debugServer.startClient)
541 self.qsock.readyRead.disconnect(self.__parseClientLine)
542
543 # close down socket, and shut down client as well.
544 self.__sendJsonCommand("RequestShutdown", {})
545 self.qsock.flush()
546 self.qsock.close()
547
548 # reinitialize
549 self.qsock = None
550 self.queue = []
551
552 def isConnected(self):
553 """
554 Public method to test, if a debug client has connected.
555
556 @return flag indicating the connection status (boolean)
557 """
558 return self.qsock is not None
559
560 def remoteEnvironment(self, env):
561 """
562 Public method to set the environment for a program to debug, run, ...
563
564 @param env environment settings (dictionary)
565 """
566 self.__sendJsonCommand("RequestEnvironment", {"environment": env})
567
568 def remoteLoad(self, fn, argv, wd, traceInterpreter=False,
569 autoContinue=True, autoFork=False, forkChild=False):
570 """
571 Public method to load a new program to debug.
572
573 @param fn the filename to debug (string)
574 @param argv the commandline arguments to pass to the program (string)
575 @param wd the working directory for the program (string)
576 @keyparam traceInterpreter flag indicating if the interpreter library
577 should be traced as well (boolean)
578 @keyparam autoContinue flag indicating, that the debugger should not
579 stop at the first executable line (boolean)
580 @keyparam autoFork flag indicating the automatic fork mode (boolean)
581 @keyparam forkChild flag indicating to debug the child after forking
582 (boolean)
583 """
584 self.__autoContinue = autoContinue
585 self.__scriptName = os.path.abspath(fn)
586
587 wd = self.translate(wd, False)
588 fn = self.translate(os.path.abspath(fn), False)
589 self.__sendJsonCommand("RequestLoad", {
590 "workdir": wd,
591 "filename": fn,
592 "argv": Utilities.parseOptionString(argv),
593 "traceInterpreter": traceInterpreter,
594 "autofork": autoFork,
595 "forkChild": forkChild,
596 })
597
598 def remoteRun(self, fn, argv, wd, autoFork=False, forkChild=False):
599 """
600 Public method to load a new program to run.
601
602 @param fn the filename to run (string)
603 @param argv the commandline arguments to pass to the program (string)
604 @param wd the working directory for the program (string)
605 @keyparam autoFork flag indicating the automatic fork mode (boolean)
606 @keyparam forkChild flag indicating to debug the child after forking
607 (boolean)
608 """
609 self.__scriptName = os.path.abspath(fn)
610
611 wd = self.translate(wd, False)
612 fn = self.translate(os.path.abspath(fn), False)
613 self.__sendJsonCommand("RequestRun", {
614 "workdir": wd,
615 "filename": fn,
616 "argv": Utilities.parseOptionString(argv),
617 "autofork": autoFork,
618 "forkChild": forkChild,
619 })
620
621 def remoteCoverage(self, fn, argv, wd, erase=False):
622 """
623 Public method to load a new program to collect coverage data.
624
625 @param fn the filename to run (string)
626 @param argv the commandline arguments to pass to the program (string)
627 @param wd the working directory for the program (string)
628 @keyparam erase flag indicating that coverage info should be
629 cleared first (boolean)
630 """
631 self.__scriptName = os.path.abspath(fn)
632
633 wd = self.translate(wd, False)
634 fn = self.translate(os.path.abspath(fn), False)
635 self.__sendJsonCommand("RequestCoverage", {
636 "workdir": wd,
637 "filename": fn,
638 "argv": Utilities.parseOptionString(argv),
639 "erase": erase,
640 })
641
642 def remoteProfile(self, fn, argv, wd, erase=False):
643 """
644 Public method to load a new program to collect profiling data.
645
646 @param fn the filename to run (string)
647 @param argv the commandline arguments to pass to the program (string)
648 @param wd the working directory for the program (string)
649 @keyparam erase flag indicating that timing info should be cleared
650 first (boolean)
651 """
652 self.__scriptName = os.path.abspath(fn)
653
654 wd = self.translate(wd, False)
655 fn = self.translate(os.path.abspath(fn), False)
656 self.__sendJsonCommand("RequestProfile", {
657 "workdir": wd,
658 "filename": fn,
659 "argv": Utilities.parseOptionString(argv),
660 "erase": erase,
661 })
662
663 def remoteStatement(self, stmt):
664 """
665 Public method to execute a Python statement.
666
667 @param stmt the Python statement to execute (string). It
668 should not have a trailing newline.
669 """
670 self.__sendJsonCommand("ExecuteStatement", {
671 "statement": stmt,
672 })
673
674 def remoteStep(self):
675 """
676 Public method to single step the debugged program.
677 """
678 self.__sendJsonCommand("RequestStep", {})
679
680 def remoteStepOver(self):
681 """
682 Public method to step over the debugged program.
683 """
684 self.__sendJsonCommand("RequestStepOver", {})
685
686 def remoteStepOut(self):
687 """
688 Public method to step out the debugged program.
689 """
690 self.__sendJsonCommand("RequestStepOut", {})
691
692 def remoteStepQuit(self):
693 """
694 Public method to stop the debugged program.
695 """
696 self.__sendJsonCommand("RequestStepQuit", {})
697
698 def remoteContinue(self, special=False):
699 """
700 Public method to continue the debugged program.
701
702 @param special flag indicating a special continue operation
703 """
704 self.__sendJsonCommand("RequestContinue", {
705 "special": special,
706 })
707
708 def remoteMoveIP(self, line):
709 """
710 Public method to move the instruction pointer to a different line.
711
712 @param line the new line, where execution should be continued
713 """
714 self.__sendJsonCommand("RequestMoveIP", {
715 "newLine": line,
716 })
717
718 def remoteBreakpoint(self, fn, line, setBreakpoint, cond=None, temp=False):
719 """
720 Public method to set or clear a breakpoint.
721
722 @param fn filename the breakpoint belongs to (string)
723 @param line linenumber of the breakpoint (int)
724 @param setBreakpoint flag indicating setting or resetting a
725 breakpoint (boolean)
726 @param cond condition of the breakpoint (string)
727 @param temp flag indicating a temporary breakpoint (boolean)
728 """
729 self.__sendJsonCommand("RequestBreakpoint", {
730 "filename": self.translate(fn, False),
731 "line": line,
732 "temporary": temp,
733 "setBreakpoint": setBreakpoint,
734 "condition": cond,
735 })
736
737 def remoteBreakpointEnable(self, fn, line, enable):
738 """
739 Public method to enable or disable a breakpoint.
740
741 @param fn filename the breakpoint belongs to (string)
742 @param line linenumber of the breakpoint (int)
743 @param enable flag indicating enabling or disabling a breakpoint
744 (boolean)
745 """
746 self.__sendJsonCommand("RequestBreakpointEnable", {
747 "filename": self.translate(fn, False),
748 "line": line,
749 "enable": enable,
750 })
751
752 def remoteBreakpointIgnore(self, fn, line, count):
753 """
754 Public method to ignore a breakpoint the next couple of occurrences.
755
756 @param fn filename the breakpoint belongs to (string)
757 @param line linenumber of the breakpoint (int)
758 @param count number of occurrences to ignore (int)
759 """
760 self.__sendJsonCommand("RequestBreakpointIgnore", {
761 "filename": self.translate(fn, False),
762 "line": line,
763 "count": count,
764 })
765
766 def remoteWatchpoint(self, cond, setWatch, temp=False):
767 """
768 Public method to set or clear a watch expression.
769
770 @param cond expression of the watch expression (string)
771 @param setWatch flag indicating setting or resetting a watch expression
772 (boolean)
773 @param temp flag indicating a temporary watch expression (boolean)
774 """
775 # cond is combination of cond and special (s. watch expression viewer)
776 self.__sendJsonCommand("RequestWatch", {
777 "temporary": temp,
778 "setWatch": setWatch,
779 "condition": cond,
780 })
781
782 def remoteWatchpointEnable(self, cond, enable):
783 """
784 Public method to enable or disable a watch expression.
785
786 @param cond expression of the watch expression (string)
787 @param enable flag indicating enabling or disabling a watch expression
788 (boolean)
789 """
790 # cond is combination of cond and special (s. watch expression viewer)
791 self.__sendJsonCommand("RequestWatchEnable", {
792 "condition": cond,
793 "enable": enable,
794 })
795
796 def remoteWatchpointIgnore(self, cond, count):
797 """
798 Public method to ignore a watch expression the next couple of
799 occurrences.
800
801 @param cond expression of the watch expression (string)
802 @param count number of occurrences to ignore (int)
803 """
804 # cond is combination of cond and special (s. watch expression viewer)
805 self.__sendJsonCommand("RequestWatchIgnore", {
806 "condition": cond,
807 "count": count,
808 })
809
810 def remoteRawInput(self, s):
811 """
812 Public method to send the raw input to the debugged program.
813
814 @param s the raw input (string)
815 """
816 self.__sendJsonCommand("RawInput", {
817 "input": s,
818 })
819
820 def remoteThreadList(self):
821 """
822 Public method to request the list of threads from the client.
823 """
824 self.__sendJsonCommand("RequestThreadList", {})
825
826 def remoteSetThread(self, tid):
827 """
828 Public method to request to set the given thread as current thread.
829
830 @param tid id of the thread (integer)
831 """
832 self.__sendJsonCommand("RequestThreadSet", {
833 "threadID": tid,
834 })
835
836 def remoteClientVariables(self, scope, filterList, framenr=0, maxSize=0):
837 """
838 Public method to request the variables of the debugged program.
839
840 @param scope the scope of the variables (0 = local, 1 = global)
841 @type int
842 @param filterList list of variable types to filter out
843 @type list of int
844 @param framenr framenumber of the variables to retrieve
845 @type int
846 @param maxSize maximum size the formatted value of a variable will
847 be shown. If it is bigger than that, a 'too big' indication will
848 be given (@@TOO_BIG_TO_SHOW@@).
849 @type int
850 """
851 self.__sendJsonCommand("RequestVariables", {
852 "frameNumber": framenr,
853 "scope": scope,
854 "filters": filterList,
855 "maxSize": maxSize,
856 })
857
858 def remoteClientVariable(self, scope, filterList, var, framenr=0,
859 maxSize=0):
860 """
861 Public method to request the variables of the debugged program.
862
863 @param scope the scope of the variables (0 = local, 1 = global)
864 @type int
865 @param filterList list of variable types to filter out
866 @type list of int
867 @param var list encoded name of variable to retrieve
868 @type list of str
869 @param framenr framenumber of the variables to retrieve
870 @type int
871 @param maxSize maximum size the formatted value of a variable will
872 be shown. If it is bigger than that, a 'too big' indication will
873 be given (@@TOO_BIG_TO_SHOW@@).
874 @type int
875 """
876 self.__sendJsonCommand("RequestVariable", {
877 "variable": var,
878 "frameNumber": framenr,
879 "scope": scope,
880 "filters": filterList,
881 "maxSize": maxSize,
882 })
883
884 def remoteClientSetFilter(self, scope, filterStr):
885 """
886 Public method to set a variables filter list.
887
888 @param scope the scope of the variables (0 = local, 1 = global)
889 @param filterStr regexp string for variable names to filter out
890 (string)
891 """
892 self.__sendJsonCommand("RequestSetFilter", {
893 "scope": scope,
894 "filter": filterStr,
895 })
896
897 def setCallTraceEnabled(self, on):
898 """
899 Public method to set the call trace state.
900
901 @param on flag indicating to enable the call trace function (boolean)
902 """
903 self.__sendJsonCommand("RequestCallTrace", {
904 "enable": on,
905 })
906
907 def remoteBanner(self):
908 """
909 Public slot to get the banner info of the remote client.
910 """
911 self.__sendJsonCommand("RequestBanner", {})
912
913 def remoteCapabilities(self):
914 """
915 Public slot to get the debug clients capabilities.
916 """
917 self.__sendJsonCommand("RequestCapabilities", {})
918
919 def remoteCompletion(self, text):
920 """
921 Public slot to get the a list of possible commandline completions
922 from the remote client.
923
924 @param text the text to be completed (string)
925 """
926 self.__sendJsonCommand("RequestCompletion", {
927 "text": text,
928 })
929
930 def remoteUTDiscover(self, syspath, workdir, discoveryStart):
931 """
932 Public method to perform a test case discovery.
933
934 @param syspath list of directories to be added to sys.path on the
935 remote side
936 @type list of str
937 @param workdir path name of the working directory
938 @type str
939 @param discoveryStart directory to start auto-discovery at
940 @type str
941 """
942 self.__sendJsonCommand("RequestUTDiscover", {
943 "syspath": [] if syspath is None else syspath,
944 "workdir": workdir,
945 "discoverystart": discoveryStart,
946 })
947
948 def remoteUTPrepare(self, fn, tn, tfn, failed, cov, covname, coverase,
949 syspath, workdir, discover, discoveryStart, testCases,
950 debug):
951 """
952 Public method to prepare a new unittest run.
953
954 @param fn name of file to load
955 @type str
956 @param tn name of test to load
957 @type str
958 @param tfn test function name to load tests from
959 @type str
960 @param failed list of failed test, if only failed test should be run
961 @type list of str
962 @param cov flag indicating collection of coverage data is requested
963 @type bool
964 @param covname name of file to be used to assemble the coverage caches
965 filename
966 @type str
967 @param coverase flag indicating erasure of coverage data is requested
968 @type bool
969 @param syspath list of directories to be added to sys.path on the
970 remote side
971 @type list of str
972 @param workdir path name of the working directory
973 @type str
974 @param discover flag indicating to discover the tests automatically
975 @type bool
976 @param discoveryStart directory to start auto-discovery at
977 @type str
978 @param testCases list of test cases to be loaded
979 @type list of str
980 @param debug flag indicating to run unittest with debugging
981 @type bool
982 """
983 if fn:
984 self.__scriptName = os.path.abspath(fn)
985
986 fn = self.translate(os.path.abspath(fn), False)
987 else:
988 self.__scriptName = "unittest discover"
989
990 self.__sendJsonCommand("RequestUTPrepare", {
991 "filename": fn,
992 "testname": tn,
993 "testfunctionname": tfn,
994 "failed": failed,
995 "coverage": cov,
996 "coveragefile": covname,
997 "coverageerase": coverase,
998 "syspath": [] if syspath is None else syspath,
999 "workdir": workdir,
1000 "discover": discover,
1001 "discoverystart": discoveryStart,
1002 "testcases": [] if testCases is None else testCases,
1003 "debug": debug,
1004 })
1005
1006 def remoteUTRun(self, debug, failfast):
1007 """
1008 Public method to start a unittest run.
1009
1010 @param debug flag indicating to run unittest with debugging
1011 @type bool
1012 @param failfast flag indicating to stop at the first error
1013 @type bool
1014 """
1015 if debug:
1016 self.__autoContinue = True
1017 self.__sendJsonCommand("RequestUTRun", {
1018 "debug": debug,
1019 "failfast": failfast,
1020 })
1021
1022 def remoteUTStop(self):
1023 """
1024 Public method to stop a unittest run.
1025 """
1026 self.__sendJsonCommand("RequestUTStop", {})
1027
1028 def __askForkTo(self):
1029 """
1030 Private method to ask the user which branch of a fork to follow.
1031 """
1032 selections = [self.tr("Parent Process"),
1033 self.tr("Child process")]
1034 res, ok = QInputDialog.getItem(
1035 None,
1036 self.tr("Client forking"),
1037 self.tr("Select the fork branch to follow."),
1038 selections,
1039 0, False)
1040 if not ok or res == selections[0]:
1041 self.__sendJsonCommand("ResponseForkTo", {
1042 "target": "parent",
1043 })
1044 else:
1045 self.__sendJsonCommand("ResponseForkTo", {
1046 "target": "child",
1047 })
1048
1049 def __parseClientLine(self):
1050 """
1051 Private method to handle data from the client.
1052 """
1053 while self.qsock and self.qsock.canReadLine():
1054 qs = self.qsock.readLine()
1055 if self.codec is not None:
1056 line = self.codec.toUnicode(qs)
1057 else:
1058 line = bytes(qs).decode()
1059
1060 logging.debug("<Debug-Server> %s", line)
1061 ## print("Server: ", line) ##debug
1062
1063 self.__handleJsonCommand(line)
1064 continue
1065
1066 def __handleJsonCommand(self, jsonStr):
1067 """
1068 Private method to handle a command or response serialized as a
1069 JSON string.
1070
1071 @param jsonStr string containing the command or response received
1072 from the debug backend
1073 @type str
1074 """
1075 import json
1076
1077 try:
1078 commandDict = json.loads(jsonStr.strip())
1079 except (TypeError, ValueError) as err:
1080 E5MessageBox.critical(
1081 None,
1082 self.tr("Debug Protocol Error"),
1083 self.tr("""<p>The response received from the debugger"""
1084 """ backend could not be decoded. Please report"""
1085 """ this issue with the received data to the"""
1086 """ eric bugs email address.</p>"""
1087 """<p>Error: {0}</p>"""
1088 """<p>Data:<br/>{1}</p>""").format(
1089 str(err), Utilities.html_encode(jsonStr.strip())),
1090 E5MessageBox.StandardButtons(
1091 E5MessageBox.Ok))
1092 return
1093
1094 method = commandDict["method"]
1095 params = commandDict["params"]
1096
1097 if method == "ClientOutput":
1098 self.debugServer.signalClientOutput(params["text"])
1099
1100 elif method in ["ResponseLine", "ResponseStack"]:
1101 # Check if obsolet thread was clicked
1102 if params["stack"] == []:
1103 # Request updated list
1104 self.remoteThreadList()
1105 return
1106 for s in params["stack"]:
1107 s[0] = self.translate(s[0], True)
1108 cf = params["stack"][0]
1109 if self.__autoContinue:
1110 self.__autoContinue = False
1111 QTimer.singleShot(0, self.remoteContinue)
1112 else:
1113 self.debugServer.signalClientLine(
1114 cf[0], int(cf[1]),
1115 method == "ResponseStack")
1116 self.debugServer.signalClientStack(params["stack"])
1117
1118 elif method == "CallTrace":
1119 isCall = params["event"].lower() == "c"
1120 fromInfo = params["from"]
1121 toInfo = params["to"]
1122 self.debugServer.signalClientCallTrace(
1123 isCall,
1124 fromInfo["filename"], str(fromInfo["linenumber"]),
1125 fromInfo["codename"],
1126 toInfo["filename"], str(toInfo["linenumber"]),
1127 toInfo["codename"])
1128
1129 elif method == "ResponseVariables":
1130 self.debugServer.signalClientVariables(
1131 params["scope"], params["variables"])
1132
1133 elif method == "ResponseVariable":
1134 self.debugServer.signalClientVariable(
1135 params["scope"], [params["variable"]] + params["variables"])
1136
1137 elif method == "ResponseThreadList":
1138 self.debugServer.signalClientThreadList(
1139 params["currentID"], params["threadList"])
1140
1141 elif method == "ResponseThreadSet":
1142 self.debugServer.signalClientThreadSet()
1143
1144 elif method == "ResponseCapabilities":
1145 self.clientCapabilities = params["capabilities"]
1146 self.debugServer.signalClientCapabilities(
1147 params["capabilities"],
1148 params["clientType"],
1149 self.__startedVenv,
1150 )
1151
1152 elif method == "ResponseBanner":
1153 self.debugServer.signalClientBanner(
1154 params["version"],
1155 params["platform"],
1156 params["dbgclient"],
1157 self.__startedVenv,
1158 )
1159
1160 elif method == "ResponseOK":
1161 self.debugServer.signalClientStatement(False)
1162
1163 elif method == "ResponseContinue":
1164 self.debugServer.signalClientStatement(True)
1165
1166 elif method == "RequestRaw":
1167 self.debugServer.signalClientRawInput(
1168 params["prompt"], params["echo"])
1169
1170 elif method == "ResponseBPConditionError":
1171 fn = self.translate(params["filename"], True)
1172 self.debugServer.signalClientBreakConditionError(
1173 fn, params["line"])
1174
1175 elif method == "ResponseClearBreakpoint":
1176 fn = self.translate(params["filename"], True)
1177 self.debugServer.signalClientClearBreak(fn, params["line"])
1178
1179 elif method == "ResponseWatchConditionError":
1180 self.debugServer.signalClientWatchConditionError(
1181 params["condition"])
1182
1183 elif method == "ResponseClearWatch":
1184 self.debugServer.signalClientClearWatch(params["condition"])
1185
1186 elif method == "ResponseException":
1187 if params:
1188 exctype = params["type"]
1189 excmessage = params["message"]
1190 stack = params["stack"]
1191 if stack:
1192 for stackEntry in stack:
1193 stackEntry[0] = self.translate(stackEntry[0], True)
1194 if stack[0] and stack[0][0] == "<string>":
1195 for stackEntry in stack:
1196 if stackEntry[0] == "<string>":
1197 stackEntry[0] = self.__scriptName
1198 else:
1199 break
1200 else:
1201 exctype = ''
1202 excmessage = ''
1203 stack = []
1204
1205 self.debugServer.signalClientException(
1206 exctype, excmessage, stack)
1207
1208 elif method == "ResponseSyntax":
1209 self.debugServer.signalClientSyntaxError(
1210 params["message"], self.translate(params["filename"], True),
1211 params["linenumber"], params["characternumber"])
1212
1213 elif method == "ResponseSignal":
1214 self.debugServer.signalClientSignal(
1215 params["message"], self.translate(params["filename"], True),
1216 params["linenumber"], params["function"], params["arguments"])
1217
1218 elif method == "ResponseExit":
1219 self.__scriptName = ""
1220 self.debugServer.signalClientExit(
1221 params["status"], params["message"])
1222
1223 elif method == "PassiveStartup":
1224 self.debugServer.passiveStartUp(
1225 self.translate(params["filename"], True), params["exceptions"])
1226
1227 elif method == "ResponseCompletion":
1228 self.debugServer.signalClientCompletionList(
1229 params["completions"], params["text"])
1230
1231 elif method == "ResponseUTDiscover":
1232 self.debugServer.clientUtDiscovered(
1233 params["testCasesList"], params["exception"],
1234 params["message"])
1235
1236 elif method == "ResponseUTPrepared":
1237 self.debugServer.clientUtPrepared(
1238 params["count"], params["exception"], params["message"])
1239
1240 elif method == "ResponseUTFinished":
1241 self.debugServer.clientUtFinished(params["status"])
1242
1243 elif method == "ResponseUTStartTest":
1244 self.debugServer.clientUtStartTest(
1245 params["testname"], params["description"])
1246
1247 elif method == "ResponseUTStopTest":
1248 self.debugServer.clientUtStopTest()
1249
1250 elif method == "ResponseUTTestFailed":
1251 self.debugServer.clientUtTestFailed(
1252 params["testname"], params["traceback"], params["id"])
1253
1254 elif method == "ResponseUTTestErrored":
1255 self.debugServer.clientUtTestErrored(
1256 params["testname"], params["traceback"], params["id"])
1257
1258 elif method == "ResponseUTTestSkipped":
1259 self.debugServer.clientUtTestSkipped(
1260 params["testname"], params["reason"], params["id"])
1261
1262 elif method == "ResponseUTTestFailedExpected":
1263 self.debugServer.clientUtTestFailedExpected(
1264 params["testname"], params["traceback"], params["id"])
1265
1266 elif method == "ResponseUTTestSucceededUnexpected":
1267 self.debugServer.clientUtTestSucceededUnexpected(
1268 params["testname"], params["id"])
1269
1270 elif method == "RequestForkTo":
1271 self.__askForkTo()
1272
1273 def __sendJsonCommand(self, command, params):
1274 """
1275 Private method to send a single command to the client.
1276
1277 @param command command name to be sent
1278 @type str
1279 @param params dictionary of named parameters for the command
1280 @type dict
1281 """
1282 import json
1283
1284 commandDict = {
1285 "jsonrpc": "2.0",
1286 "method": command,
1287 "params": params,
1288 }
1289 cmd = json.dumps(commandDict) + '\n'
1290 if self.qsock is not None:
1291 self.__writeJsonCommandToSocket(cmd)
1292 else:
1293 self.queue.append(cmd)
1294
1295 def __writeJsonCommandToSocket(self, cmd):
1296 """
1297 Private method to write a JSON command to the socket.
1298
1299 @param cmd JSON command to be sent
1300 @type str
1301 """
1302 data = cmd.encode('utf8', 'backslashreplace')
1303 length = "{0:09d}".format(len(data))
1304 self.qsock.write(length.encode() + data)
1305 self.qsock.flush()
1306
1307
1308 def createDebuggerInterfacePython2(debugServer, passive):
1309 """
1310 Module function to create a debugger interface instance.
1311
1312
1313 @param debugServer reference to the debug server
1314 @type DebugServer
1315 @param passive flag indicating passive connection mode
1316 @type bool
1317 @return instantiated debugger interface
1318 @rtype DebuggerInterfacePython
1319 """
1320 return DebuggerInterfacePython(debugServer, passive, "Python2")
1321
1322
1323 def createDebuggerInterfacePython3(debugServer, passive):
1324 """
1325 Module function to create a debugger interface instance.
1326
1327
1328 @param debugServer reference to the debug server
1329 @type DebugServer
1330 @param passive flag indicating passive connection mode
1331 @type bool
1332 @return instantiated debugger interface
1333 @rtype DebuggerInterfacePython
1334 """
1335 return DebuggerInterfacePython(debugServer, passive, "Python3")
1336
1337
1338 def getRegistryData():
1339 """
1340 Module function to get characterizing data for the supported debugger
1341 interfaces.
1342
1343 @return list of tuples containing the client type, the client capabilities,
1344 the client file type associations and a reference to the creation
1345 function
1346 @rtype list of tuple of (str, int, list of str, function)
1347 """
1348 py2Exts = []
1349 for ext in Preferences.getDebugger("PythonExtensions").split():
1350 if ext.startswith("."):
1351 py2Exts.append(ext)
1352 else:
1353 py2Exts.append(".{0}".format(ext))
1354
1355 py3Exts = []
1356 for ext in Preferences.getDebugger("Python3Extensions").split():
1357 if ext.startswith("."):
1358 py3Exts.append(ext)
1359 else:
1360 py3Exts.append(".{0}".format(ext))
1361
1362 registryData = []
1363 if py2Exts and (Preferences.getDebugger("Python2VirtualEnv") or
1364 sys.version_info[0] == 2):
1365 registryData.append(
1366 ("Python2", ClientDefaultCapabilities, py2Exts,
1367 createDebuggerInterfacePython2)
1368 )
1369
1370 if py3Exts and (Preferences.getDebugger("Python3VirtualEnv") or
1371 sys.version_info[0] == 3):
1372 registryData.append(
1373 ("Python3", ClientDefaultCapabilities, py3Exts,
1374 createDebuggerInterfacePython3)
1375 )
1376
1377 return registryData

eric ide

mercurial