Wed, 12 Feb 2020 20:04:31 +0100
Continued with the multiprocess debugger. Implemented QProcess.startDetached() wrapper.
--- a/eric6/DebugClients/Python/QProcessExtension.py Tue Feb 11 18:58:38 2020 +0100 +++ b/eric6/DebugClients/Python/QProcessExtension.py Wed Feb 12 20:04:31 2020 +0100 @@ -13,6 +13,25 @@ _debugClient = None +# TODO: extend this with first line logic +def _isPythonProgram(program, arguments): + """ + Protected function to check, if program is a Python interpreter and + arguments don't include '-m'. + + @param program program to be executed + @type str + @param arguments list of command line arguments + @type list of str + @return flag indicating a python program and a tuple containing the + interpreter to be used and the arguments + @rtype tuple of (bool, tuple of (str, list of str)) + """ + prog = program.lower() + ok = "python" in prog and arguments[0] != '-m' + return ok, (program, arguments[:]) + + def patchQProcess(module, debugClient): """ Function to patch the QtCore module's QProcess. @@ -28,13 +47,16 @@ """ Wrapper class for *.QProcess. """ + _origQProcessStartDetached = module.QProcess.startDetached + def __init__(self, parent=None): """ Constructor """ super(QProcessWrapper, self).__init__(parent) - def __modifyArgs(self, arguments, multiprocessSupport): + @classmethod + def modifyArgs(cls, arguments, multiprocessSupport): """ Private method to modify the arguments given to the start method. @@ -71,6 +93,10 @@ return modifiedArguments + ################################################################### + ## Handling of 'start(...)' below + ################################################################### + def start(self, *args, **kwargs): """ Public method to start the process. @@ -91,7 +117,6 @@ (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] @@ -106,16 +131,115 @@ mode = args[0] else: mode = module.QIODevice.ReadWrite - if "python" in program.lower() and arguments[0] != "-m": - # assume a Python script is to be started - # '-m' option to python is not (yet) supported - newArgs = self.__modifyArgs( + ok, (program, arguments) = _isPythonProgram(program, arguments) + if ok: + newArgs = self.modifyArgs( arguments, _debugClient.multiprocessSupport) super(QProcessWrapper, self).start(program, newArgs, mode) else: super(QProcessWrapper, self).start(*args, **kwargs) else: super(QProcessWrapper, self).start(*args, **kwargs) + + ################################################################### + ## Handling of 'startDetached(...)' below + ################################################################### + + def startDetached(self, *args, **kwargs): + """ + Public method to start the detached 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 + @return flag indicating a successful start + @rtype bool + """ + if isinstance(self, str): + return QProcessWrapper.startDetachedStatic( + self, *args) + else: + return self.__startDetached(*args, **kwargs) + + def __startDetached(self, *args, **kwargs): + """ + Private method to start the detached 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 + @return flag indicating a successful start + @rtype bool + """ + if ( + _debugClient.debugging and + _debugClient.multiprocessSupport and + len(args) == 0 + ): + program = self.program() + arguments = self.arguments() + wd = self.workingDirectory() + + ok, (program, arguments) = _isPythonProgram(program, arguments) + if ok: + return QProcessWrapper.startDetachedStatic( + program, arguments, wd) + else: + return super(QProcessWrapper, self).startDetached( + *args, **kwargs) + else: + return super(QProcessWrapper, self).startDetached( + *args, **kwargs) + + @staticmethod + def startDetachedStatic(*args, **kwargs): + """ + Static method to start the detached 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 + @return flag indicating a successful start + @rtype bool + """ + if ( + _debugClient.debugging and + _debugClient.multiprocessSupport and + (len(args) >= 2 and isinstance(args[1], list)) + ): + program = args[0] + arguments = args[1] + if len(args) >= 3: + wd = args[2] + else: + wd = "" + ok, (program, arguments) = _isPythonProgram(program, arguments) + if ok: + newArgs = QProcessWrapper.modifyArgs( + arguments, _debugClient.multiprocessSupport) + return QProcessWrapper._origQProcessStartDetached( + program, newArgs, wd) + else: + return QProcessWrapper._origQProcessStartDetached( + *args, **kwargs) + else: + return QProcessWrapper._origQProcessStartDetached( + *args, **kwargs) _debugClient = debugClient module.QProcess = QProcessWrapper
--- a/eric6/Debugger/DebugUI.py Tue Feb 11 18:58:38 2020 +0100 +++ b/eric6/Debugger/DebugUI.py Wed Feb 12 20:04:31 2020 +0100 @@ -2028,7 +2028,6 @@ cap = self.tr("Debug Project") else: cap = self.tr("Debug Script") - # TODO: add "Multiprocess" checkbox and pass to debugger dlg = StartDialog( cap, self.lastUsedVenvName, self.argvHistory, self.wdHistory, self.envHistory, self.exceptions, self.ui, 0,
--- a/eric6/Debugger/DebugViewer.py Tue Feb 11 18:58:38 2020 +0100 +++ b/eric6/Debugger/DebugViewer.py Wed Feb 12 20:04:31 2020 +0100 @@ -23,7 +23,7 @@ import os -from PyQt5.QtCore import pyqtSignal, Qt +from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt from PyQt5.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QSizePolicy, QPushButton, QComboBox, QLabel, QTreeWidget, QTreeWidgetItem, QHeaderView, QFrame @@ -282,6 +282,8 @@ self.__clientSyntaxError) self.debugServer.clientException.connect( self.__clientException) + self.debugServer.clientExit.connect( + self.__clientExit) self.debugServer.clientException.connect( self.exceptionLogger.addException) @@ -432,8 +434,35 @@ @param debuggerId ID of the debugger backend @type str """ - self.__setDebuggerIconAndState(debuggerId, "mediaPlaybackPause", - "broken") + self.__setDebuggerIconAndState(debuggerId, "break", "broken") + if debuggerId != self.getSelectedDebuggerId(): + self.__debuggersCombo.setCurrentText(debuggerId) + + @pyqtSlot(int, str, bool, str) + def __clientExit(self, status, message, quiet, debuggerId): + """ + Private method to handle the debugged program terminating. + + @param status exit code of the debugged program + @type int + @param message exit message of the debugged program + @type str + @param quiet flag indicating to suppress exit info display + @type bool + @param debuggerId ID of the debugger backend + @type str + """ + self.__setDebuggerIconAndState(debuggerId, "exit", "exited") + if debuggerId == self.getSelectedDebuggerId(): + # the current client has exited + self.globalsViewer.handleResetUI() + self.localsViewer.handleResetUI() + self.setGlobalsFilter() + self.setLocalsFilter() + self.sourceButton.setEnabled(False) + self.currentStack = None + self.stackComboBox.clear() + self.__threadList.clear() def __clientSyntaxError(self, message, filename, lineNo, characterNo, debuggerId): @@ -580,7 +609,7 @@ debugStatus = 1 elif thread['broken']: state = self.tr("waiting at breakpoint") - icon = "mediaPlaybackPause" + icon = "break" if debugStatus < 1: debugStatus = 0 else: @@ -608,16 +637,22 @@ elif thread['broken']: if debugStatus < 1: debugStatus = 0 + if not threadList: + # empty threadlist means 'exited' + debugStatus = 2 if debugStatus == -1: icon = "mediaPlaybackStart" state = "running" elif debugStatus == 0: - icon = "mediaPlaybackPause" + icon = "break" state = "broken" - else: + elif debugStatus == 1: icon = "exceptions" state = "exception" + else: + icon = "exit" + state = "exited" self.__setDebuggerIconAndState("", icon, state) def __threadSelected(self, current, previous): @@ -694,7 +729,7 @@ """ Public method to get the currently selected debugger's state. - @return selected debugger's state (running, broken, exception) + @return selected debugger's state (broken, exception, exited, running) @rtype str """ return self.__debuggersCombo.currentData() @@ -708,7 +743,7 @@ @type str @param iconName name of the icon to be used @type str - @param state state of the debugger (running, broken, exception) + @param state state of the debugger (broken, exception, exited, running) @type str """ if debuggerId:
--- a/eric6/Debugger/DebuggerInterfacePython.py Tue Feb 11 18:58:38 2020 +0100 +++ b/eric6/Debugger/DebuggerInterfacePython.py Wed Feb 12 20:04:31 2020 +0100 @@ -549,18 +549,9 @@ self.debugServer.initializeClient(debuggerId) # perform auto-continue except for master - if ( - self.__autoContinue and - debuggerId != self.__master and - debuggerId not in self.__autoContinued - ): - self.__autoContinued.append(debuggerId) + if debuggerId != self.__master: QTimer.singleShot( 0, lambda: self.remoteContinue(debuggerId)) - # TODO: maybe the debugger ID should not be registered here but - # only in user_line. Seems the debugger stops in a frame - # outside of the debugged script and this continue moves it to - # the first line of the script. def __socketDisconnected(self, sock): """