Mon, 17 Feb 2020 19:23:27 +0100
Implemented multi process debugging support for the 'subprocess' module.
--- a/eric6.e4p Sun Feb 16 19:36:46 2020 +0100 +++ b/eric6.e4p Mon Feb 17 19:23:27 2020 +0100 @@ -53,6 +53,7 @@ <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/SubprocessExtension.py</Source> <Source>eric6/DebugClients/Python/ThreadExtension.py</Source> <Source>eric6/DebugClients/Python/__init__.py</Source> <Source>eric6/DebugClients/Python/coverage/__init__.py</Source>
--- a/eric6/DebugClients/Python/DebugClientBase.py Sun Feb 16 19:36:46 2020 +0100 +++ b/eric6/DebugClients/Python/DebugClientBase.py Mon Feb 17 19:23:27 2020 +0100 @@ -33,7 +33,7 @@ from FlexCompleter import Completer from DebugUtilities import prepareJsonCommand from BreakpointWatch import Breakpoint, Watch -from MultiProcessDebugExtension import patchNewProcessFunctions +##from MultiProcessDebugExtension import patchNewProcessFunctions if sys.version_info[0] == 2: from inspect import getargvalues, formatargvalues @@ -105,12 +105,12 @@ else: try: DebugClientOrigInput = __builtins__.__dict__['input'] - __builtins__.__dict__['input'] = DebugClientRawInput + __builtins__.__dict__['input'] = DebugClientInput except (AttributeError, KeyError): try: import __main__ DebugClientOrigInput = __main__.__builtins__.__dict__['input'] - __main__.__builtins__.__dict__['input'] = DebugClientRawInput + __main__.__builtins__.__dict__['input'] = DebugClientInput except (AttributeError, KeyError): DebugClientOrigInput = lambda x: '' # __IGNORE_WARNING__ @@ -2286,7 +2286,7 @@ ) if not self.noencoding: self.__coding = self.defaultCoding - patchNewProcessFunctions(multiprocess, self) +## patchNewProcessFunctions(multiprocess, self) res = self.startProgInDebugger( args, wd, host, port, exceptions=exceptions, tracePython=tracePython, redirect=redirect, @@ -2344,7 +2344,7 @@ if not self.noencoding: self.__coding = self.defaultCoding self.connectDebugger(port, remoteAddress, redirect) - patchNewProcessFunctions(self.multiprocessSupport, self) +## patchNewProcessFunctions(self.multiprocessSupport, self) self.__interact() else: print("No network port given. Aborting...")
--- a/eric6/DebugClients/Python/ModuleLoader.py Sun Feb 16 19:36:46 2020 +0100 +++ b/eric6/DebugClients/Python/ModuleLoader.py Mon Feb 17 19:23:27 2020 +0100 @@ -11,6 +11,7 @@ import importlib from QProcessExtension import patchQProcess +from SubprocessExtension import patchSubprocess class ModuleLoader(object): @@ -36,6 +37,7 @@ self.__modulesToPatch = ( 'thread', '_thread', 'threading', 'greenlet', + 'subprocess', 'PyQt4.QtCore', 'PyQt5.QtCore', 'PySide.QtCore', 'PySide2.QtCore', ) @@ -78,6 +80,14 @@ if self.__dbgClient.patchGreenlet(module): module.eric6_patched = True + ## Add hook for subprocess.Popen() + elif ( + fullname == 'subprocess' and + not hasattr(module, 'eric6_patched') + ): + module.eric6_patched = True + patchSubprocess(module, self.__dbgClient) + ## Add hook for *.QThread and *.QProcess elif ( fullname in ('PyQt4.QtCore', 'PyQt5.QtCore',
--- a/eric6/DebugClients/Python/QProcessExtension.py Sun Feb 16 19:36:46 2020 +0100 +++ b/eric6/DebugClients/Python/QProcessExtension.py Mon Feb 17 19:23:27 2020 +0100 @@ -10,7 +10,7 @@ import os -from DebugUtilities import isPythonProgram, patchArguments +from DebugUtilities import isPythonProgram, startsWithShebang, patchArguments _debugClient = None @@ -79,19 +79,19 @@ else: mode = module.QIODevice.ReadWrite ok = isPythonProgram(program) - if ( - ok and ( - not os.path.basename(arguments[0]) - in _debugClient.noDebugList - ) - ): - newArgs = patchArguments( - _debugClient, - [program] + arguments, - ) - super(QProcessWrapper, self).start( - newArgs[0], newArgs[1:], mode) - return + if ok: + if startsWithShebang(program): + scriptName = os.path.basename(program) + else: + scriptName = os.path.basename(arguments[0]) + if scriptName not in _debugClient.noDebugList: + newArgs = patchArguments( + _debugClient, + [program] + arguments, + ) + super(QProcessWrapper, self).start( + newArgs[0], newArgs[1:], mode) + return super(QProcessWrapper, self).start(*args, **kwargs) @@ -180,12 +180,17 @@ wd = "" ok = isPythonProgram(program) if ok: - newArgs = patchArguments( - _debugClient, - [program] + arguments, - ) - return QProcessWrapper._origQProcessStartDetached( - newArgs[0], newArgs[1:], wd) + if startsWithShebang(program): + scriptName = os.path.basename(program) + else: + scriptName = os.path.basename(arguments[0]) + if scriptName not in _debugClient.noDebugList: + newArgs = patchArguments( + _debugClient, + [program] + arguments, + ) + return QProcessWrapper._origQProcessStartDetached( + newArgs[0], newArgs[1:], wd) return QProcessWrapper._origQProcessStartDetached( *args, **kwargs)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/DebugClients/Python/SubprocessExtension.py Mon Feb 17 19:23:27 2020 +0100 @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a function to patch subprocess.Popen to support debugging +of the process. +""" + +import os +import shlex + +from DebugUtilities import isPythonProgram, startsWithShebang, patchArguments + +_debugClient = None + + +def patchSubprocess(module, debugClient): + """ + Function to patch the subprocess module. + + @param module reference to the imported module to be patched + @type module + @param debugClient reference to the debug client object + @type DebugClient + """ # __IGNORE_WARNING_D234__ + global _debugClient + + # TODO: implement a process tracer + # i.e. report which processes are started + class PopenWrapper(module.Popen): + """ + Wrapper class for subprocess.Popen. + """ + def __init__(self, arguments, *args, **kwargs): + """ + Constructor + + @param arguments command line arguments for the new process + @type list of str or str + @param args constructor arguments of Popen + @type list + @param kwargs constructor keword only arguments of Popen + @type dict + """ + if ( + _debugClient.debugging and + _debugClient.multiprocessSupport and + isinstance(arguments, (str, list)) + ): + if isinstance(arguments, str): + # convert to arguments list + arguments = shlex.split(arguments) + else: + # create a copy of the arguments + arguments = arguments[:] + ok = isPythonProgram(arguments[0]) + if ok: + if startsWithShebang(arguments[0]): + scriptName = os.path.basename(arguments[0]) + else: + scriptName = os.path.basename(arguments[0]) + if scriptName not in _debugClient.noDebugList: + arguments = patchArguments( + _debugClient, arguments, noRedirect=True + ) + + super(PopenWrapper, self).__init__(arguments, *args, **kwargs) + + _debugClient = debugClient + module.Popen = PopenWrapper