Implemented multi process debugging support for the 'subprocess' module. multi_processing

Mon, 17 Feb 2020 19:23:27 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Mon, 17 Feb 2020 19:23:27 +0100
branch
multi_processing
changeset 7424
9bb7d8b0f966
parent 7422
9a008ab4811b
child 7428
27c55a3d0b89

Implemented multi process debugging support for the 'subprocess' module.

eric6.e4p file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/DebugClientBase.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/ModuleLoader.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/QProcessExtension.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/SubprocessExtension.py file | annotate | diff | comparison | revisions
--- 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

eric ide

mercurial