Mon, 10 Feb 2020 18:49:49 +0100
Continued with the multiprocess debugger. Started with QProcess support.
--- a/eric6.e4p Sun Feb 09 19:27:49 2020 +0100 +++ b/eric6.e4p Mon Feb 10 18:49:49 2020 +0100 @@ -51,6 +51,7 @@ <Source>eric6/DebugClients/Python/FlexCompleter.py</Source> <Source>eric6/DebugClients/Python/ModuleLoader.py</Source> <Source>eric6/DebugClients/Python/PyProfile.py</Source> + <Source>eric6/DebugClients/Python/QProcessExtension.py</Source> <Source>eric6/DebugClients/Python/ThreadExtension.py</Source> <Source>eric6/DebugClients/Python/__init__.py</Source> <Source>eric6/DebugClients/Python/coverage/__init__.py</Source> @@ -2044,6 +2045,9 @@ <Other>eric6/APIs/MicroPython/circuitpython.api</Other> <Other>eric6/APIs/MicroPython/microbit.api</Other> <Other>eric6/APIs/MicroPython/micropython.api</Other> + <Other>eric6/APIs/Python/zope-2.10.7.api</Other> + <Other>eric6/APIs/Python/zope-2.11.2.api</Other> + <Other>eric6/APIs/Python/zope-3.3.1.api</Other> <Other>eric6/APIs/Python3/PyQt4.bas</Other> <Other>eric6/APIs/Python3/PyQt5.bas</Other> <Other>eric6/APIs/Python3/PyQtChart.bas</Other> @@ -2051,9 +2055,6 @@ <Other>eric6/APIs/Python3/QScintilla2.bas</Other> <Other>eric6/APIs/Python3/eric6.api</Other> <Other>eric6/APIs/Python3/eric6.bas</Other> - <Other>eric6/APIs/Python/zope-2.10.7.api</Other> - <Other>eric6/APIs/Python/zope-2.11.2.api</Other> - <Other>eric6/APIs/Python/zope-3.3.1.api</Other> <Other>eric6/APIs/QSS/qss.api</Other> <Other>eric6/APIs/Ruby/Ruby-1.8.7.api</Other> <Other>eric6/APIs/Ruby/Ruby-1.8.7.bas</Other>
--- a/eric6/DebugClients/Python/DebugClientBase.py Sun Feb 09 19:27:49 2020 +0100 +++ b/eric6/DebugClients/Python/DebugClientBase.py Mon Feb 10 18:49:49 2020 +0100 @@ -2075,7 +2075,7 @@ def startProgInDebugger(self, progargs, wd='', host=None, port=None, exceptions=True, tracePython=False, - redirect=True): + redirect=True, passive=True): """ Public method used to start the remote debugger. @@ -2090,6 +2090,8 @@ (boolean) @param redirect flag indicating redirection of stdin, stdout and stderr (boolean) + @param passive flag indicating a passive debugging session + @type bool """ if host is None: host = os.getenv('ERICHOST', 'localhost') @@ -2112,8 +2114,9 @@ self.__setCoding(self.running) self.debugging = True - self.passive = True - self.sendPassiveStartup(self.running, exceptions) + self.passive = passive + if passive: + self.sendPassiveStartup(self.running, exceptions) self.__interact() self.attachThread(mainThread=True) @@ -2125,16 +2128,14 @@ self.__interceptSignals() # This will eventually enter a local event loop. - # Note the use of backquotes to cause a repr of self.running. The - # need for this is on Windows os where backslash is the path separator. - # They will get inadvertantly stripped away during the eval causing - # IOErrors if self.running is passed as a normal str. self.debugMod.__dict__['__file__'] = self.running sys.modules['__main__'] = self.debugMod - res = self.mainThread.run( - 'exec(open(' + repr(self.running) + ').read())', - self.debugMod.__dict__) - self.progTerminated(res) + code = self.__compileFileSource(self.running) + if code: + res = self.mainThread.run(code, self.debugMod.__dict__, debug=True) + self.progTerminated(res) + else: + self.progTerminated(42) # should not happen def run_call(self, scriptname, func, *args): """ @@ -2189,6 +2190,7 @@ tracePython = False exceptions = True redirect = True + passive = True while args[0]: if args[0] == '-h': host = args[1] @@ -2222,6 +2224,9 @@ self.fork_auto = True self.fork_child = False del args[0] + elif args[0] == '--no-passive': + passive = False + del args[0] elif args[0] == '--': del args[0] break @@ -2230,6 +2235,9 @@ if not args: print("No program given. Aborting!") # __IGNORE_WARNING_M801__ + elif "-m" in args: + print("Running module as a script is not supported. Aborting!") + # __IGNORE_WARNING_M801__ else: # Store options if a process is spawn # TODO: check which ones are really needed @@ -2242,7 +2250,8 @@ self.startProgInDebugger(args, wd, host, port, exceptions=exceptions, tracePython=tracePython, - redirect=redirect) + redirect=redirect, + passive=passive) else: if sys.argv[1] == '--no-encoding': self.noencoding = True
--- a/eric6/DebugClients/Python/ModuleLoader.py Sun Feb 09 19:27:49 2020 +0100 +++ b/eric6/DebugClients/Python/ModuleLoader.py Mon Feb 10 18:49:49 2020 +0100 @@ -10,6 +10,8 @@ import sys import importlib +from QProcessExtension import patchQProcess + class ModuleLoader(object): """ @@ -38,17 +40,17 @@ del sys.modules[moduleName] self.__modulesToPatch = ( - 'thread', '_thread', 'threading', - 'greenlet', - 'PyQt4.QtCore', 'PyQt5.QtCore', - 'PySide.QtCore', 'PySide2.QtCore', + 'thread', '_thread', 'threading', + 'greenlet', + 'PyQt4.QtCore', 'PyQt5.QtCore', + 'PySide.QtCore', 'PySide2.QtCore', ) sys.meta_path.insert(0, self) def __loadModule(self, fullname): """ - Public method to load a module. + Private method to load a module. @param fullname name of the module to be loaded @type str @@ -82,7 +84,7 @@ if self.__dbgClient.patchGreenlet(module): module.eric6_patched = True - ## Add hook for *.QThread + ## Add hook for *.QThread and *.QProcess elif ( fullname in ('PyQt4.QtCore', 'PyQt5.QtCore', 'PySide.QtCore', 'PySide2.QtCore') and @@ -90,6 +92,7 @@ ): module.eric6_patched = True self.__dbgClient.patchQThread(module) + patchQProcess(module, self.__dbgClient.startOptions) self.__enableImportHooks = True return module @@ -135,7 +138,7 @@ def exec_module(self, module): """ - Public method to execute the created module + Public method to execute the created module. @param module module to be executed @type module
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/DebugClients/Python/QProcessExtension.py Mon Feb 10 18:49:49 2020 +0100 @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a function to patch QProcess to support debugging of the +process. +""" + +import os + +_startOptions = [] + + +def patchQProcess(module, startOptions): + """ + Function to patch the QtCore module's QProcess. + + @param module reference to the imported module to be patched + @type module + @param startOptions reference to the saved start options + @type tuple + """ # __IGNORE_WARNING_D234__ + global _startOptions + + class QProcessWrapper(module.QProcess): + """ + Wrapper class for *.QProcess. + """ + def __init__(self, parent=None): + """ + Constructor + """ + super(QProcessWrapper, self).__init__(parent) + + def __modifyArgs(self, arguments): + """ + Private method to modify the arguments given to the start method. + + @param arguments list of program arguments + @type list of str + @return modified argument list + @rtype list of str + """ + (wd, host, port, exceptions, tracePython, redirect, + noencoding) = _startOptions[:7] + modifiedArguments = [ + os.path.join(os.path.dirname(__file__), "DebugClient.py"), + "-h", host, + "-p", str(port), + "--no-passive" + ] + + if wd: + modifiedArguments.extend(["-w", wd]) + if not exceptions: + modifiedArguments.append("-e") + if tracePython: + modifiedArguments.append("-t") + if not redirect: + modifiedArguments.append("-n") + if noencoding: + modifiedArguments.append("--no-encoding") + modifiedArguments.append("--") + # end the arguments for DebugClient + modifiedArguments.extend(arguments) + + return modifiedArguments + + def start(self, *args, **kwargs): + """ + Public method to start the process. + + This method patches the arguments such, that a debug client is + started for the Python script. A Python script is assumed, if the + program to be started contains the string 'python'. + + @param args arguments of the start call + @type list + @param kwargs keyword arguments of the start call + @type dict + """ + if ( + (len(args) >= 2 and isinstance(args[1], list)) or + (len(args) == 1 and not isinstance(args[0], str)) or + len(args) == 0 + ): + # TODO: implement this + if len(args) >= 2: + program = args[0] + arguments = args[1] + if len(args) > 2: + mode = args[2] + else: + mode = module.QIODevice.ReadWrite + else: + program = self.program() + arguments = self.arguments() + if len(args) == 1: + mode = args[0] + else: + mode = module.QIODevice.ReadWrite + if "python" in program.lower(): + # assume a Python script is to be started + newArgs = self.__modifyArgs(arguments) + super(QProcessWrapper, self).start(program, newArgs, mode) + else: + super(QProcessWrapper, self).start(*args, **kwargs) + else: + super(QProcessWrapper, self).start(*args, **kwargs) + + _startOptions = startOptions[:] + module.QProcess = QProcessWrapper
--- a/eric6/Debugger/DebugServer.py Sun Feb 09 19:27:49 2020 +0100 +++ b/eric6/Debugger/DebugServer.py Mon Feb 10 18:49:49 2020 +0100 @@ -965,7 +965,6 @@ @type bool """ self.__autoClearShell = autoClearShell - self.__autoContinue = autoContinue if clientType not in self.getSupportedLanguages(): # a not supported client language was requested @@ -1476,6 +1475,7 @@ @param text the text to be completed (string) """ + # TODO: send this to the currently selected debugger self.debuggerInterface.remoteCompletion(text) def remoteUTDiscover(self, clientType, forProject, venvName, syspath,
--- a/eric6/Debugger/DebugViewer.py Sun Feb 09 19:27:49 2020 +0100 +++ b/eric6/Debugger/DebugViewer.py Mon Feb 10 18:49:49 2020 +0100 @@ -65,7 +65,7 @@ self.setWindowIcon(UI.PixmapCache.getIcon("eric")) self.__mainLayout = QVBoxLayout() - self.__mainLayout.setContentsMargins(0, 0, 0, 0) + self.__mainLayout.setContentsMargins(0, 3, 0, 0) self.setLayout(self.__mainLayout) # add the viewer showing the connected debug backends @@ -432,7 +432,8 @@ @param debuggerId ID of the debugger backend @type str """ - self.__setDebuggerIconAndState(debuggerId, "mediaPlaybackPause", "broken") + self.__setDebuggerIconAndState(debuggerId, "mediaPlaybackPause", + "broken") def __clientSyntaxError(self, message, filename, lineNo, characterNo, debuggerId): @@ -450,7 +451,8 @@ @param debuggerId ID of the debugger backend @type str """ - self.__setDebuggerIconAndState(debuggerId, "syntaxError22", "exception") + self.__setDebuggerIconAndState(debuggerId, "syntaxError22", + "exception") def __clientException(self, exceptionType, exceptionMessage, stackTrace, debuggerId):
--- a/eric6/Debugger/DebuggerInterfacePython.py Sun Feb 09 19:27:49 2020 +0100 +++ b/eric6/Debugger/DebuggerInterfacePython.py Mon Feb 10 18:49:49 2020 +0100 @@ -51,6 +51,7 @@ self.__isNetworked = True self.__autoContinue = False + self.__autoContinued = [] self.debugServer = debugServer self.passive = passive @@ -1335,8 +1336,11 @@ for s in params["stack"]: s[0] = self.translate(s[0], True) cf = params["stack"][0] - if self.__autoContinue: - self.__autoContinue = False + if ( + self.__autoContinue and + params["debuggerId"] not in self.__autoContinued + ): + self.__autoContinued.append(params["debuggerId"]) QTimer.singleShot( 0, lambda: self.remoteContinue(params["debuggerId"])) else: