Sun, 16 Feb 2020 19:36:46 +0100
Started implementing the patching of the os module.
--- a/eric6.e4p Sun Feb 16 16:14:25 2020 +0100 +++ b/eric6.e4p Sun Feb 16 19:36:46 2020 +0100 @@ -50,6 +50,7 @@ <Source>eric6/DebugClients/Python/DebugVariables.py</Source> <Source>eric6/DebugClients/Python/FlexCompleter.py</Source> <Source>eric6/DebugClients/Python/ModuleLoader.py</Source> + <Source>eric6/DebugClients/Python/MultiProcessDebugExtension.py</Source> <Source>eric6/DebugClients/Python/PyProfile.py</Source> <Source>eric6/DebugClients/Python/QProcessExtension.py</Source> <Source>eric6/DebugClients/Python/ThreadExtension.py</Source>
--- a/eric6/DebugClients/Python/DebugClientBase.py Sun Feb 16 16:14:25 2020 +0100 +++ b/eric6/DebugClients/Python/DebugClientBase.py Sun Feb 16 19:36:46 2020 +0100 @@ -33,6 +33,7 @@ from FlexCompleter import Completer from DebugUtilities import prepareJsonCommand from BreakpointWatch import Breakpoint, Watch +from MultiProcessDebugExtension import patchNewProcessFunctions if sys.version_info[0] == 2: from inspect import getargvalues, formatargvalues @@ -2285,6 +2286,7 @@ ) if not self.noencoding: self.__coding = self.defaultCoding + patchNewProcessFunctions(multiprocess, self) res = self.startProgInDebugger( args, wd, host, port, exceptions=exceptions, tracePython=tracePython, redirect=redirect, @@ -2296,6 +2298,10 @@ self.noencoding = True del sys.argv[1] + if sys.argv[1] == '--multiprocess': + self.multiprocessSupport = True + del sys.argv[1] + if sys.argv[1] == '': del sys.argv[1] @@ -2338,6 +2344,7 @@ if not self.noencoding: self.__coding = self.defaultCoding self.connectDebugger(port, remoteAddress, redirect) + patchNewProcessFunctions(self.multiprocessSupport, self) self.__interact() else: print("No network port given. Aborting...")
--- a/eric6/DebugClients/Python/DebugUtilities.py Sun Feb 16 16:14:25 2020 +0100 +++ b/eric6/DebugClients/Python/DebugUtilities.py Sun Feb 16 19:36:46 2020 +0100 @@ -242,7 +242,7 @@ @return modified argument list @rtype list of str """ - args = arguments[:] # create a copy of the arguments list + args = list(arguments[:]) # create a copy of the arguments list # support for shebang line program = os.path.basename(args[0]).lower()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/DebugClients/Python/MultiProcessDebugExtension.py Sun Feb 16 19:36:46 2020 +0100 @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2002 - 2020 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a function to patch the process creation functions to +support multiprocess debugging. +""" + +import sys + +from DebugUtilities import isWindowsPlatform, patchArguments, isPythonProgram + +_debugClient = None + + +def patchModule(module, functionName, createFunction): + """ + Function to replace a function of a module with a modified one. + + @param module reference to the module + @type types.ModuleType + @param functionName name of the function to be replaced + @type str + @param createFunction function creating the replacement + @type types.FunctionType + """ + if hasattr(module, functionName): + originalName = 'original_' + functionName + if not hasattr(module, originalName): + setattr(module, originalName, getattr(module, functionName)) + setattr(module, functionName, createFunction(originalName)) + + +def createExecl(originalName): + """ + Function to patch the 'execl' process creation functions. + + <ul> + <li>os.execl(path, arg0, arg1, ...)</li> + <li>os.execle(path, arg0, arg1, ..., env)</li> + <li>os.execlp(file, arg0, arg1, ...)</li> + <li>os.execlpe(file, arg0, arg1, ..., env)</li> + </ul> + """ + def newExecl(path, *args): + """ + Function replacing the 'execl' functions of the os module. + """ + print(args) + import os + if ( + _debugClient.debugging and + _debugClient.multiprocessSupport + ): + args = patchArguments(_debugClient, args) + if isPythonProgram(args[0]): + path = args[0] + print(args) + return getattr(os, originalName)(path, *args) + return newExecl + +def patchNewProcessFunctions(multiprocessEnabled, debugClient): + """ + Function to patch the process creation functions to support multiprocess + debugging. + + @param multiprocessEnabled flag indicating multiprocess support + @type bool + @param debugClient reference to the debug client object + @type DebugClient + """ + global _debugClient + + if not multiprocessEnabled: + # return without patching + return + + import os + + # patch 'os.exec...()' functions + patchModule(os, "execl", createExecl) + patchModule(os, "execle", createExecl) + patchModule(os, "execlp", createExecl) + patchModule(os, "execlpe", createExecl) + + # TODO: implement patching of the various functions of the os module + + _debugClient = debugClient
--- a/eric6/Debugger/DebuggerInterfacePython.py Sun Feb 16 16:14:25 2020 +0100 +++ b/eric6/Debugger/DebuggerInterfacePython.py Sun Feb 16 19:36:46 2020 +0100 @@ -226,6 +226,10 @@ redirect = str(Preferences.getDebugger("Python3Redirect")) noencoding = (Preferences.getDebugger("Python3NoEncoding") and '--no-encoding' or '') + multiprocessEnabled = ( + '--multiprocess' if Preferences.getDebugger("MultiProcessEnabled") + else '' + ) if Preferences.getDebugger("RemoteDbgEnabled"): ipaddr = self.debugServer.getHostAddress(False) @@ -235,8 +239,12 @@ rhost = "localhost" if rexec: args = Utilities.parseOptionString(rexec) + [ - rhost, interpreter, debugClient, noencoding, str(port), - redirect, ipaddr] + rhost, interpreter, debugClient] + if noencoding: + args.append(noencoding) + if multiprocessEnabled: + args.append(multiprocessEnabled) + args.extend([str(port), redirect, ipaddr]) if Utilities.isWindowsPlatform(): if not os.path.splitext(args[0])[1]: for ext in [".exe", ".com", ".cmd", ".bat"]: @@ -301,8 +309,12 @@ ccmd = Preferences.getDebugger("ConsoleDbgCommand") if ccmd: args = Utilities.parseOptionString(ccmd) + [ - interpreter, os.path.abspath(debugClient), noencoding, - str(port), '0', ipaddr] + interpreter, os.path.abspath(debugClient)] + if noencoding: + args.append(noencoding) + if multiprocessEnabled: + args.append(multiprocessEnabled) + args.extend([str(port), '0', ipaddr]) args[0] = Utilities.getExecutablePath(args[0]) process = self.__startProcess(args[0], args[1:], clientEnv, workingDir=workingDir) @@ -315,11 +327,14 @@ """ started.</p>""")) return process, self.__isNetworked, interpreter - process = self.__startProcess( - interpreter, - [debugClient, noencoding, str(port), redirect, ipaddr], - clientEnv, - workingDir=workingDir) + args = [debugClient] + if noencoding: + args.append(noencoding) + if multiprocessEnabled: + args.append(multiprocessEnabled) + args.extend([str(port), redirect, ipaddr]) + process = self.__startProcess(interpreter, args, clientEnv, + workingDir=workingDir) if process is None: self.__startedVenv = "" E5MessageBox.critical( @@ -370,7 +385,12 @@ redirect = str(project.getDebugProperty("REDIRECT")) noencoding = ( - project.getDebugProperty("NOENCODING") and '--no-encoding' or '') + '--no-encoding' if project.getDebugProperty("NOENCODING") else '' + ) + multiprocessEnabled = ( + '--multiprocess' if Preferences.getDebugger("MultiProcessEnabled") + else '' + ) venvManager = e5App().getObject("VirtualEnvManager") interpreter = venvManager.getVirtualenvInterpreter(venvName) @@ -396,8 +416,12 @@ rhost = "localhost" if rexec: args = Utilities.parseOptionString(rexec) + [ - rhost, interpreter, debugClient, noencoding, str(port), - redirect, ipaddr] + rhost, interpreter, debugClient] + if noencoding: + args.append(noencoding) + if multiprocessEnabled: + args.append(multiprocessEnabled) + args.extend([str(port), redirect, ipaddr]) if Utilities.isWindowsPlatform(): if not os.path.splitext(args[0])[1]: for ext in [".exe", ".com", ".cmd", ".bat"]: @@ -464,8 +488,12 @@ Preferences.getDebugger("ConsoleDbgCommand")) if ccmd: args = Utilities.parseOptionString(ccmd) + [ - interpreter, os.path.abspath(debugClient), noencoding, - str(port), '0', ipaddr] + interpreter, os.path.abspath(debugClient)] + if noencoding: + args.append(noencoding) + if multiprocessEnabled: + args.append(multiprocessEnabled) + args.extend([str(port), '0', ipaddr]) args[0] = Utilities.getExecutablePath(args[0]) process = self.__startProcess(args[0], args[1:], clientEnv, workingDir=workingDir) @@ -478,11 +506,14 @@ """ started.</p>""")) return process, self.__isNetworked, interpreter - process = self.__startProcess( - interpreter, - [debugClient, noencoding, str(port), redirect, ipaddr], - clientEnv, - workingDir=workingDir) + args = [debugClient] + if noencoding: + args.append(noencoding) + if multiprocessEnabled: + args.append(multiprocessEnabled) + args.extend([str(port), redirect, ipaddr]) + process = self.__startProcess(interpreter, args, clientEnv, + workingDir=workingDir) if process is None: self.__startedVenv = "" E5MessageBox.critical(
--- a/eric6/Debugger/StartDialog.py Sun Feb 16 16:14:25 2020 +0100 +++ b/eric6/Debugger/StartDialog.py Sun Feb 16 19:36:46 2020 +0100 @@ -134,12 +134,16 @@ self.ui.venvComboBox.setCurrentIndex(venvIndex) if dialogType == 0: # start debug dialog + enableMultiprocessGlobal = Preferences.getDebugger( + "MultiProcessEnabled") self.ui.tracePythonCheckBox.setChecked(tracePython) self.ui.tracePythonCheckBox.show() self.ui.autoContinueCheckBox.setChecked(autoContinue) self.ui.forkModeCheckBox.setChecked(autoFork) self.ui.forkChildCheckBox.setChecked(forkChild) - self.ui.multiprocessGroup.setChecked(enableMultiprocess) + self.ui.multiprocessGroup.setEnabled(enableMultiprocessGlobal) + self.ui.multiprocessGroup.setChecked( + enableMultiprocess & enableMultiprocessGlobal) self.ui.multiprocessNoDebugCombo.clear() if multiprocessNoDebugHistory: self.ui.multiprocessNoDebugCombo.addItems(
--- a/eric6/Preferences/ConfigurationPages/DebuggerGeneralPage.py Sun Feb 16 16:14:25 2020 +0100 +++ b/eric6/Preferences/ConfigurationPages/DebuggerGeneralPage.py Sun Feb 16 19:36:46 2020 +0100 @@ -137,6 +137,8 @@ Preferences.getDebugger("PathTranslationRemote")) self.dbgTranslationLocalEdit.setText( Preferences.getDebugger("PathTranslationLocal")) + self.multiprocessCheckBox.setChecked( + Preferences.getDebugger("MultiProcessEnabled")) self.debugThreeStateBreakPoint.setChecked( Preferences.getDebugger("ThreeStateBreakPoints")) self.recentFilesSpinBox.setValue( @@ -230,6 +232,9 @@ "PathTranslationLocal", self.dbgTranslationLocalEdit.text()) Preferences.setDebugger( + "MultiProcessEnabled", + self.multiprocessCheckBox.isChecked()) + Preferences.setDebugger( "ThreeStateBreakPoints", self.debugThreeStateBreakPoint.isChecked()) Preferences.setDebugger(
--- a/eric6/Preferences/ConfigurationPages/DebuggerGeneralPage.ui Sun Feb 16 16:14:25 2020 +0100 +++ b/eric6/Preferences/ConfigurationPages/DebuggerGeneralPage.ui Sun Feb 16 19:36:46 2020 +0100 @@ -479,6 +479,25 @@ </widget> </item> <item> + <widget class="QGroupBox" name="groupBox_6"> + <property name="title"> + <string>Multi Process Debugging</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_8"> + <item> + <widget class="QCheckBox" name="multiprocessCheckBox"> + <property name="toolTip"> + <string>Select to enable multiprocess debugging support globally</string> + </property> + <property name="text"> + <string>Enable Multi Process Debugging Support</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> <widget class="QGroupBox" name="groupBox_8"> <property name="title"> <string>Breakpoints</string> @@ -804,6 +823,7 @@ <tabstop>debugAutoSaveScriptsCheckBox</tabstop> <tabstop>automaticResetCheckBox</tabstop> <tabstop>dontShowClientExitCheckBox</tabstop> + <tabstop>multiprocessCheckBox</tabstop> <tabstop>debugThreeStateBreakPoint</tabstop> <tabstop>recentFilesSpinBox</tabstop> <tabstop>exceptionBreakCheckBox</tabstop>
--- a/eric6/Preferences/__init__.py Sun Feb 16 16:14:25 2020 +0100 +++ b/eric6/Preferences/__init__.py Sun Feb 16 19:36:46 2020 +0100 @@ -111,7 +111,9 @@ # space separated list of Python2 extensions "PythonExtensions": ".py2 .pyw2 .ptl", # space separated list of Python3 extensions - "Python3Extensions": ".py .pyw .py3 .pyw3" + "Python3Extensions": ".py .pyw .py3 .pyw3", + # Global Multiprocess Debugging Support + "MultiProcessEnabled": True, } # defaults for the UI settings @@ -1723,6 +1725,7 @@ "Autosave", "ThreeStateBreakPoints", "SuppressClientExit", "BreakAlways", "AutoViewSourceCode", "ShowExceptionInShell", + "MultiProcessEnabled", ]: return toBool(prefClass.settings.value( "Debugger/" + key, prefClass.debuggerDefaults[key]))