eric6/DebugClients/Python/DebugClientBase.py

branch
maintenance
changeset 8043
0acf98cd089a
parent 7939
0fc1df79965d
parent 8030
b356d92d4e02
child 8142
43248bafe9b2
diff -r 866adc8c315b -r 0acf98cd089a eric6/DebugClients/Python/DebugClientBase.py
--- a/eric6/DebugClients/Python/DebugClientBase.py	Sun Jan 17 13:53:08 2021 +0100
+++ b/eric6/DebugClients/Python/DebugClientBase.py	Mon Feb 01 10:38:16 2021 +0100
@@ -20,17 +20,19 @@
 import signal
 import time
 import types
-import importlib
+import importlib.util
+import fnmatch
 
 
 import DebugClientCapabilities
 import DebugVariables
 from DebugBase import setRecursionLimit, printerr   # __IGNORE_WARNING__
 from AsyncFile import AsyncFile, AsyncPendingWrite
-from DebugConfig import ConfigQtNames, ConfigVarTypeStrings
+from DebugConfig import ConfigQtNames, SpecialAttributes
 from FlexCompleter import Completer
 from DebugUtilities import prepareJsonCommand
 from BreakpointWatch import Breakpoint, Watch
+from MultiProcessDebugExtension import patchNewProcessFunctions
 
 from DebugUtilities import getargvalues, formatargvalues
 
@@ -67,25 +69,6 @@
 ###############################################################################
 
 
-def DebugClientFork():
-    """
-    Replacement for the standard os.fork().
-    
-    @return result of the fork() call
-    """
-    if DebugClientInstance is None:
-        return DebugClientOrigFork()
-    
-    return DebugClientInstance.fork()
-
-# use our own fork().
-if 'fork' in dir(os):
-    DebugClientOrigFork = os.fork
-    os.fork = DebugClientFork
-
-###############################################################################
-
-
 def DebugClientClose(fd):
     """
     Replacement for the standard os.close(fd).
@@ -179,20 +162,17 @@
         self.running = None
         self.test = None
         self.debugging = False
-        
-        self.fork_auto = False
-        self.fork_child = False
+        self.multiprocessSupport = False
+        self.noDebugList = []
 
         self.readstream = None
         self.writestream = None
         self.errorstream = None
         self.pollingDisabled = False
         
-        self.callTraceEnabled = None
+        self.__debuggerId = ""
         
-        self.disassembly = None
-        
-        self.variant = 'You should not see this'
+        self.callTraceEnabled = None
         
         self.compile_command = codeop.CommandCompiler()
         
@@ -200,6 +180,8 @@
         self.defaultCoding = 'utf-8'
         self.__coding = self.defaultCoding
         self.noencoding = False
+        
+        self.startOptions = None
     
     def getCoding(self):
         """
@@ -263,6 +245,8 @@
             pass
 
         self.debugging = False
+        self.multiprocessSupport = False
+        self.noDebugList = []
         
         # make sure we close down our end of the socket
         # might be overkill as normally stdin, stdout and stderr
@@ -279,13 +263,29 @@
         """
         Private method to compile source code read from a file.
         
-        @param filename name of the source file (string)
-        @param mode kind of code to be generated (string, exec or eval)
+        @param filename name of the source file
+        @type str
+        @param mode kind of code to be generated (exec or eval)
+        @type str
         @return compiled code object (None in case of errors)
         """
         with codecs.open(filename, encoding=self.__coding) as fp:
             statement = fp.read()
         
+        return self.__compileCommand(statement, filename=filename, mode=mode)
+    
+    def __compileCommand(self, statement, filename="<string>", mode="exec"):
+        """
+        Private method to compile source code.
+        
+        @param statement source code string to be compiled
+        @type str
+        @param filename name of the source file
+        @type str
+        @param mode kind of code to be generated (exec or eval)
+        @type str
+        @return compiled code object (None in case of errors)
+        """
         try:
             code = compile(statement + '\n', filename, mode)
         except SyntaxError:
@@ -304,7 +304,7 @@
                 lineno = 0
                 charno = 0
             
-            self.sendSyntaxError(message, filename, lineno, charno)
+            self.sendSyntaxError(message, filename, lineno, charno, self.name)
             return None
         
         return code
@@ -316,7 +316,7 @@
         @param jsonStr string containing the command received from the IDE
         @type str
         """
-##        printerr(jsonStr)          ##debug
+##        printerr(jsonStr)          ## debug       # __IGNORE_WARNING_M891__
         
         try:
             commandDict = json.loads(jsonStr.strip())
@@ -337,16 +337,29 @@
                 params["variable"], params["frameNumber"],
                 params["scope"], params["filters"])
         
+        elif method == "RequestStack":
+            stack = self.mainThread.getStack()
+            self.sendResponseLine(stack, self.mainThread.name)
+        
         elif method == "RequestThreadList":
             self.dumpThreadList()
         
         elif method == "RequestThreadSet":
-            if params["threadID"] in self.threads:
-                self.setCurrentThread(params["threadID"])
+            if params["threadID"] == -1:
+                # -1 is indication for the main thread
+                threadId = -1
+                for thread in self.threads.values():
+                    if thread.name == "MainThread":
+                        threadId = thread.id
+            else:
+                threadId = params["threadID"]
+            if threadId in self.threads:
+                self.setCurrentThread(threadId)
                 self.sendJsonCommand("ResponseThreadSet", {})
                 stack = self.currentThread.getStack()
                 self.sendJsonCommand("ResponseStack", {
                     "stack": stack,
+                    "threadName": self.currentThread.name,
                 })
         
         elif method == "RequestDisassembly":
@@ -370,7 +383,6 @@
             self.sendJsonCommand("ResponseBanner", {
                 "version": "Python {0}".format(sys.version),
                 "platform": socket.gethostname(),
-                "dbgclient": self.variant,
             })
         
         elif method == "RequestSetFilter":
@@ -414,9 +426,7 @@
             
             self.running = sys.argv[0]
             self.debugging = True
-            
-            self.fork_auto = params["autofork"]
-            self.fork_child = params["forkChild"]
+            self.multiprocessSupport = params["multiprocess"]
             
             self.threads.clear()
             self.attachThread(mainThread=True)
@@ -439,7 +449,8 @@
             code = self.__compileFileSource(self.running)
             if code:
                 sys.setprofile(self.callTraceEnabled)
-                self.mainThread.run(code, self.debugMod.__dict__, debug=True)
+                self.mainThread.run(code, self.debugMod.__dict__, debug=True,
+                                    closeSession=False)
 
         elif method == "RequestRun":
             self.disassembly = None
@@ -456,9 +467,6 @@
             self.running = sys.argv[0]
             self.botframe = None
             
-            self.fork_auto = params["autofork"]
-            self.fork_child = params["forkChild"]
-            
             self.threads.clear()
             self.attachThread(mainThread=True)
             
@@ -474,7 +482,8 @@
             res = 0
             code = self.__compileFileSource(self.running)
             if code:
-                self.mainThread.run(code, self.debugMod.__dict__, debug=False)
+                self.mainThread.run(code, self.debugMod.__dict__, debug=False,
+                                    closeSession=False)
 
         elif method == "RequestCoverage":
             from coverage import Coverage
@@ -508,7 +517,8 @@
             if code:
                 self.running = sys.argv[0]
                 self.cover.start()
-                self.mainThread.run(code, self.debugMod.__dict__, debug=False)
+                self.mainThread.run(code, self.debugMod.__dict__, debug=False,
+                                    closeSession=False)
                 self.cover.stop()
                 self.cover.save()
         
@@ -558,7 +568,7 @@
                     self.__unhandled_exception(*excinfo)
                 
                 self.prof.save()
-                self.progTerminated(res)
+                self.progTerminated(res, closeSession=False)
         
         elif method == "ExecuteStatement":
             if self.buffer:
@@ -609,8 +619,8 @@
                                     _locals = (
                                         self.currentThread.getFrameLocals(
                                             self.framenr))
-                            ## reset sys.stdout to our redirector
-                            ## (unconditionally)
+                            #- reset sys.stdout to our redirector
+                            #- (unconditionally)
                             if "sys" in _globals:
                                 __stdout = _globals["sys"].stdout
                                 _globals["sys"].stdout = self.writestream
@@ -679,6 +689,11 @@
             self.currentThreadExec.go(params["special"])
             self.eventExit = True
         
+        elif method == "RequestContinueUntil":
+            newLine = params["newLine"]
+            self.currentThreadExec.set_until(lineno=newLine)
+            self.eventExit = True
+        
         elif method == "RawInput":
             # If we are handling raw mode input then break out of the current
             # event loop.
@@ -758,6 +773,9 @@
         elif method == "RequestShutdown":
             self.sessionClose()
         
+        elif method == "RequestSetNoDebugList":
+            self.noDebugList = params["noDebug"][:]
+        
         elif method == "RequestCompletion":
             self.__completionList(params["text"])
         
@@ -809,14 +827,10 @@
         elif method == "RequestUTPrepare":
             if params["syspath"]:
                 sys.path = params["syspath"] + sys.path
-            sys.path.insert(
-                0, os.path.dirname(os.path.abspath(params["filename"])))
             top_level_dir = None
             if params["workdir"]:
                 os.chdir(params["workdir"])
                 top_level_dir = params["workdir"]
-            else:
-                os.chdir(sys.path[0])
             
             # set the system exception handling function to ensure, that
             # we report on all unhandled exceptions
@@ -830,6 +844,8 @@
                     discoveryStart = params["discoverystart"]
                     if not discoveryStart:
                         discoveryStart = params["workdir"]
+                    sys.path.insert(
+                        0, os.path.abspath(discoveryStart))
                     if params["testcases"]:
                         self.test = testLoader.loadTestsFromNames(
                             params["testcases"])
@@ -837,6 +853,10 @@
                         self.test = testLoader.discover(
                             discoveryStart, top_level_dir=top_level_dir)
                 else:
+                    sys.path.insert(
+                        0,
+                        os.path.dirname(os.path.abspath(params["filename"]))
+                    )
                     if params["filename"]:
                         spec = importlib.util.spec_from_file_location(
                             params["testname"], params["filename"])
@@ -897,9 +917,13 @@
                 self.threads.clear()
                 self.attachThread(mainThread=True)
                 sys.setprofile(None)
+                self.running = sys.argv[0]
                 self.mainThread.run(
                     "result = self.test.run(self.testResult)\n",
-                    localsDict=locals_)
+                    self.debugMod.__dict__,
+                    localsDict=locals_,
+                    debug=True,
+                    closeSession=False)
                 result = locals_["result"]
             else:
                 result = self.test.run(self.testResult)
@@ -912,11 +936,6 @@
         
         elif method == "RequestUTStop":
             self.testResult.stop()
-        
-        elif method == "ResponseForkTo":
-            # this results from a separate event loop
-            self.fork_child = (params["target"] == 'child')
-            self.eventExit = True
     
     def __assembleTestCasesList(self, suite, start):
         """
@@ -969,6 +988,10 @@
             response
         @type dict
         """
+        # send debugger ID with all responses
+        if "debuggerId" not in params:
+            params["debuggerId"] = self.__debuggerId
+        
         cmd = prepareJsonCommand(method, params)
         
         self.writestream.write_p(cmd)
@@ -999,15 +1022,18 @@
             "condition": condition,
         })
     
-    def sendResponseLine(self, stack):
+    def sendResponseLine(self, stack, threadName):
         """
         Public method to send the current call stack.
         
         @param stack call stack
         @type list
+        @param threadName name of the thread sending the event
+        @type str
         """
         self.sendJsonCommand("ResponseLine", {
             "stack": stack,
+            "threadName": threadName,
         })
     
     def sendCallTrace(self, event, fromInfo, toInfo):
@@ -1029,7 +1055,8 @@
             "to": toInfo,
         })
     
-    def sendException(self, exceptionType, exceptionMessage, stack):
+    def sendException(self, exceptionType, exceptionMessage, stack,
+                      threadName):
         """
         Public method to send information for an exception.
         
@@ -1039,14 +1066,17 @@
         @type str
         @param stack stack trace information
         @type list
+        @param threadName name of the thread sending the event
+        @type str
         """
         self.sendJsonCommand("ResponseException", {
             "type": exceptionType,
             "message": exceptionMessage,
             "stack": stack,
+            "threadName": threadName,
         })
     
-    def sendSyntaxError(self, message, filename, lineno, charno):
+    def sendSyntaxError(self, message, filename, lineno, charno, threadName):
         """
         Public method to send information for a syntax error.
         
@@ -1058,12 +1088,15 @@
         @type int
         @param charno character number info
         @type int
+        @param threadName name of the thread sending the event
+        @type str
         """
         self.sendJsonCommand("ResponseSyntax", {
             "message": message,
             "filename": filename,
             "linenumber": lineno,
             "characternumber": charno,
+            "threadName": threadName,
         })
     
     def sendPassiveStartup(self, filename, exceptions):
@@ -1080,6 +1113,17 @@
             "exceptions": exceptions,
         })
     
+    def sendDebuggerId(self, debuggerId):
+        """
+        Public method to send the debug client id.
+        
+        @param debuggerId id of this debug client instance (made up of
+            hostname and process ID)
+        @type str
+        """
+        # debugger ID is added automatically by sendJsonCommand
+        self.sendJsonCommand("DebuggerId", {})
+    
     def __clientCapabilities(self):
         """
         Private method to determine the clients capabilities.
@@ -1101,7 +1145,7 @@
         """
         Public method called when there is data ready to be read.
         
-        @param stream file like object that has data to be written
+        @param stream file like object that has data to be read
         @return flag indicating an error condition
         @rtype bool
         """
@@ -1177,6 +1221,9 @@
                 else:
                     # give up for too many errors
                     break
+            except ValueError:
+                # the client socket might already be closed, i.e. its fd is -1
+                break
             
             # reset the select error counter
             selectErrors = 0
@@ -1224,7 +1271,8 @@
         if self.errorstream in wrdy:
             self.writeReady(self.errorstream)
     
-    def connectDebugger(self, port, remoteAddress=None, redirect=True):
+    def connectDebugger(self, port, remoteAddress=None, redirect=True,
+                        name=""):
         """
         Public method to establish a session with the debugger.
         
@@ -1232,19 +1280,28 @@
         stdout and stderr and saves these file objects in case the application
         being debugged redirects them itself.
         
-        @param port the port number to connect to (int)
+        @param port the port number to connect to
+        @type int
         @param remoteAddress the network address of the debug server host
-            (string)
+        @type str
         @param redirect flag indicating redirection of stdin, stdout and
-            stderr (boolean)
+            stderr
+        @type bool
+        @param name name to be attached to the debugger ID
+        @type str
         """
         if remoteAddress is None:
             remoteAddress = "127.0.0.1"
         elif "@@i" in remoteAddress:
             remoteAddress = remoteAddress.split("@@i")[0]
         sock = socket.create_connection((remoteAddress, port))
-
-        self.readstream = AsyncFile(sock, sys.stdin.mode, sys.stdin.name)
+        
+        stdinName = sys.stdin.name
+        # Special case if in a multiprocessing.Process
+        if isinstance(stdinName, int):
+            stdinName = '<stdin>'
+        
+        self.readstream = AsyncFile(sock, sys.stdin.mode, stdinName)
         self.writestream = AsyncFile(sock, sys.stdout.mode, sys.stdout.name)
         self.errorstream = AsyncFile(sock, sys.stderr.mode, sys.stderr.name)
         
@@ -1256,6 +1313,14 @@
         
         # attach to the main thread here
         self.attachThread(mainThread=True)
+        
+        if not name:
+            name = "main"
+        self.__debuggerId = "{0}/{1}/{2}".format(
+            socket.gethostname(), os.getpid(), name
+        )
+        
+        self.sendDebuggerId(self.__debuggerId)
 
     def __unhandled_exception(self, exctype, excval, exctb):
         """
@@ -1375,7 +1440,7 @@
         """
         return self.running
 
-    def progTerminated(self, status, message=""):
+    def progTerminated(self, status, message="", closeSession=True):
         """
         Public method to tell the debugger that the program has terminated.
         
@@ -1383,23 +1448,32 @@
         @type int
         @param message status message
         @type str
+        @param closeSession flag indicating to close the debugger session
+        @type bool
         """
         if status is None:
             status = 0
         elif not isinstance(status, int):
             message = str(status)
             status = 1
-
+        if message is None:
+            message = ""
+        
         if self.running:
             self.set_quit()
+            program = self.running
             self.running = None
             self.sendJsonCommand("ResponseExit", {
                 "status": status,
                 "message": message,
+                "program": program,
             })
         
         # reset coding
         self.__coding = self.defaultCoding
+        
+        if closeSession:
+            self.sessionClose(False)
 
     def __dumpVariables(self, frmnr, scope, filterList):
         """
@@ -1409,8 +1483,8 @@
         @type int
         @param scope 1 to report global variables, 0 for local variables
         @type int
-        @param filterList the indices of variable types to be filtered
-        @type list of int
+        @param filterList list of variable types to be filtered
+        @type list of str
         """
         if self.currentThread is None:
             return
@@ -1458,7 +1532,7 @@
         @type int
         @param scope 1 to report global variables, 0 for local variables
         @type int
-        @param filterList the indices of variable types to be filtered
+        @param filterList list of variable types to be filtered
         @type list of int
         """
         if self.currentThread is None:
@@ -1714,10 +1788,10 @@
             Variables are only added to the list, if their name do not match
             any of the filter expressions.
         @type int
-        @param filterList the indices of variable types to be filtered.
+        @param filterList list of variable types to be filtered.
             Variables are only added to the list, if their type is not
             contained in the filter list.
-        @type list of int
+        @type list of str
         @return A tuple consisting of a list of formatted variables. Each
             variable entry is a tuple of three elements, the variable name,
             its type and value.
@@ -1748,15 +1822,25 @@
                 continue
             
             # filter hidden attributes (filter #0)
-            if 0 in filterList and str(key)[:2] == '__':
+            if '__' in filterList and str(key)[:2] == '__':
                 continue
             
             # special handling for '__builtins__' (it's way too big)
             if key == '__builtins__':
                 rvalue = '<module builtins (built-in)>'
                 valtype = 'module'
-                if ConfigVarTypeStrings.index(valtype) in filterList:
+                if valtype in filterList:
                     continue
+            elif (
+                key in SpecialAttributes and
+                "special_attributes" in filterList
+            ):
+                continue
+            elif (
+                key == "__hash__" and
+                "builtin_function_or_method" in filterList
+            ):
+                continue
             else:
                 isQt = False
                 # valtypestr, e.g. class 'PyQt5.QtCore.QPoint'
@@ -1767,37 +1851,37 @@
                 # Strip 'instance' to be equal with Python 3
                 if valtype == "instancemethod":
                     valtype = "method"
-                elif valtype == "type" or valtype == "classobj":
+                elif valtype in ("type", "classobj"):
                     valtype = "class"
+                elif valtype == "method-wrapper":
+                    valtype = "builtin_function_or_method"
                 
                 # valtypename, e.g. QPoint
                 valtypename = type(value).__name__
-                try:
-                    if ConfigVarTypeStrings.index(valtype) in filterList:
-                        continue
-                except ValueError:
-                    if valtype in ("sip.enumtype", "sip.wrappertype"):
-                        if ConfigVarTypeStrings.index('class') in filterList:
-                            continue
-                    elif (valtype == "sip.methoddescriptor" or
-                            valtype == "method_descriptor"):
-                        if ConfigVarTypeStrings.index('method') in filterList:
-                            continue
-                    elif valtype in ("numpy.ndarray", "array.array"):
-                        if ConfigVarTypeStrings.index('list') in filterList:
-                            continue
-                    elif valtypename == "MultiValueDict":
-                        if ConfigVarTypeStrings.index('dict') in filterList:
-                            continue
-                    elif ConfigVarTypeStrings.index('instance') in filterList:
-                        continue
-                    
-                    isQt = valtype.startswith(ConfigQtNames)
-                    if (not valtypestr.startswith('type ') and
-                        valtypename not in ("ndarray", "MultiValueDict",
-                                            "array", "defaultdict") and
-                            not isQt):
-                        valtype = valtypestr
+                if valtype in filterList:
+                    continue
+                elif (
+                    valtype in ("sip.enumtype", "sip.wrappertype") and
+                    'class' in filterList
+                ):
+                    continue
+                elif (
+                    valtype in (
+                        "sip.methoddescriptor", "method_descriptor") and
+                    'method' in filterList
+                ):
+                    continue
+                elif (
+                    valtype in ("numpy.ndarray", "array.array") and
+                    'list' in filterList
+                ):
+                    continue
+                elif valtypename == "MultiValueDict" and 'dict' in filterList:
+                    continue
+                elif 'instance' in filterList:
+                    continue
+                
+                isQt = valtype.startswith(ConfigQtNames)
                 
                 try:
                     if valtype in self.arrayTypes:
@@ -1909,32 +1993,45 @@
                 comp = completer(text, state)
             except Exception:
                 comp = None
-
+    
     def startDebugger(self, filename=None, host=None, port=None,
                       enableTrace=True, exceptions=True, tracePython=False,
-                      redirect=True):
+                      redirect=True, passive=True, multiprocessSupport=False):
         """
         Public method used to start the remote debugger.
         
-        @param filename the program to be debugged (string)
-        @param host hostname of the debug server (string)
-        @param port portnumber of the debug server (int)
-        @param enableTrace flag to enable the tracing function (boolean)
+        @param filename the program to be debugged
+        @type str
+        @param host hostname of the debug server
+        @type str
+        @param port portnumber of the debug server
+        @type int
+        @param enableTrace flag to enable the tracing function
+        @type bool
         @param exceptions flag to enable exception reporting of the IDE
-            (boolean)
+        @type bool
         @param tracePython flag to enable tracing into the Python library
-            (boolean)
+        @type bool
         @param redirect flag indicating redirection of stdin, stdout and
-            stderr (boolean)
+            stderr
+        @type bool
+        @param passive flag indicating a passive debugging session
+        @type bool
+        @param multiprocessSupport flag indicating to enable multiprocess
+            debugging support
+        @type bool
         """
-        global debugClient
         if host is None:
             host = os.getenv('ERICHOST', 'localhost')
         if port is None:
             port = os.getenv('ERICPORT', 42424)
         
         remoteAddress = self.__resolveHost(host)
-        self.connectDebugger(port, remoteAddress, redirect)
+        if filename is not None:
+            name = os.path.basename(filename)
+        else:
+            name = ""
+        self.connectDebugger(port, remoteAddress, redirect, name=name)
         if filename is not None:
             self.running = os.path.abspath(filename)
         else:
@@ -1944,8 +2041,7 @@
                 self.running = None
         if self.running:
             self.__setCoding(self.running)
-        self.passive = True
-        self.sendPassiveStartup(self.running, exceptions)
+        self.passive = passive
         self.__interact()
         
         # setup the debugger variables
@@ -1967,7 +2063,9 @@
     
     def startProgInDebugger(self, progargs, wd='', host=None,
                             port=None, exceptions=True, tracePython=False,
-                            redirect=True):
+                            redirect=True, passive=True,
+                            multiprocessSupport=False, codeStr="",
+                            scriptModule=""):
         """
         Public method used to start the remote debugger.
         
@@ -1982,6 +2080,17 @@
             (boolean)
         @param redirect flag indicating redirection of stdin, stdout and
             stderr (boolean)
+        @param passive flag indicating a passive debugging session
+        @type bool
+        @param multiprocessSupport flag indicating to enable multiprocess
+            debugging support
+        @type bool
+        @param codeStr string containing Python code to execute
+        @type str
+        @param scriptModule name of a module to be executed as a script
+        @type str
+        @return exit code of the debugged program
+        @rtype int
         """
         if host is None:
             host = os.getenv('ERICHOST', 'localhost')
@@ -1989,24 +2098,36 @@
             port = os.getenv('ERICPORT', 42424)
         
         remoteAddress = self.__resolveHost(host)
-        self.connectDebugger(port, remoteAddress, redirect)
+        if progargs:
+            if not progargs[0].startswith("-"):
+                name = os.path.basename(progargs[0])
+            else:
+                name = "debug_client_code"
+        else:
+            name = "debug_client_code"
+        self.connectDebugger(port, remoteAddress, redirect, name=name)
         
         self._fncache = {}
         self.dircache = []
-        sys.argv = progargs[:]
-        sys.argv[0] = os.path.abspath(sys.argv[0])
-        sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
-        if wd == '':
-            os.chdir(sys.path[1])
+        if codeStr:
+            self.running = "<string>"
+            sys.argv = ["<string>"] + progargs[:]
         else:
-            os.chdir(wd)
-        self.running = sys.argv[0]
-        self.__setCoding(self.running)
+            sys.argv = progargs[:]
+            sys.argv[0] = os.path.abspath(sys.argv[0])
+            sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
+            if wd == '':
+                os.chdir(sys.path[1])
+            else:
+                os.chdir(wd)
+            self.running = sys.argv[0]
+            self.__setCoding(self.running)
         self.debugging = True
+        self.multiprocessSupport = multiprocessSupport
         
-        self.passive = True
-        self.sendPassiveStartup(self.running, exceptions)
-        self.__interact()
+        self.passive = passive
+        if passive:
+            self.sendPassiveStartup(self.running, exceptions)
         
         self.attachThread(mainThread=True)
         self.mainThread.tracePythonLibs(tracePython)
@@ -2019,12 +2140,28 @@
         # This will eventually enter a local event loop.
         self.debugMod.__dict__['__file__'] = self.running
         sys.modules['__main__'] = self.debugMod
-        code = self.__compileFileSource(self.running)
+        if codeStr:
+            code = self.__compileCommand(codeStr)
+        elif scriptModule:
+            import runpy
+            modName, modSpec, code = runpy._get_module_details(scriptModule)
+            self.running = code.co_filename
+            self.debugMod.__dict__.clear()
+            self.debugMod.__dict__.update({
+                "__name__": "__main__",
+                "__file__": self.running,
+                "__package__": modSpec.parent,
+                "__loader__": modSpec.loader,
+                "__spec__": modSpec,
+                "__builtins__": __builtins__,
+            })
+        else:
+            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
+            res = 42        # should not happen
+        return res
 
     def run_call(self, scriptname, func, *args):
         """
@@ -2037,7 +2174,7 @@
         """
         self.startDebugger(scriptname, enableTrace=False)
         res = self.mainThread.runcall(func, *args)
-        self.progTerminated(res)
+        self.progTerminated(res, closeSession=False)
         return res
     
     def __resolveHost(self, host):
@@ -2079,6 +2216,10 @@
             tracePython = False
             exceptions = True
             redirect = True
+            passive = True
+            multiprocess = False
+            codeStr = ""
+            scriptModule = ""
             while args[0]:
                 if args[0] == '-h':
                     host = args[1]
@@ -2104,13 +2245,19 @@
                 elif args[0] == '--no-encoding':
                     self.noencoding = True
                     del args[0]
-                elif args[0] == '--fork-child':
-                    self.fork_auto = True
-                    self.fork_child = True
+                elif args[0] == '--no-passive':
+                    passive = False
+                    del args[0]
+                elif args[0] == '--multiprocess':
+                    multiprocess = True
                     del args[0]
-                elif args[0] == '--fork-parent':
-                    self.fork_auto = True
-                    self.fork_child = False
+                elif args[0] in ('-c', '--code'):
+                    codeStr = args[1]
+                    del args[0]
+                    del args[0]
+                elif args[0] in ('-m', '--module'):
+                    scriptModule = args[1]
+                    del args[0]
                     del args[0]
                 elif args[0] == '--':
                     del args[0]
@@ -2120,18 +2267,34 @@
             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 in case a new Python process is created
+                self.startOptions = (
+                    wd, host, port, exceptions, tracePython, redirect,
+                    self.noencoding,
+                )
                 if not self.noencoding:
                     self.__coding = self.defaultCoding
-                self.startProgInDebugger(args, wd, host, port,
-                                         exceptions=exceptions,
-                                         tracePython=tracePython,
-                                         redirect=redirect)
+                patchNewProcessFunctions(multiprocess, self)
+                res = self.startProgInDebugger(
+                    args, wd, host, port, exceptions=exceptions,
+                    tracePython=tracePython, redirect=redirect,
+                    passive=passive, multiprocessSupport=multiprocess,
+                    codeStr=codeStr, scriptModule=scriptModule,
+                )
+                sys.exit(res)
         else:
             if sys.argv[1] == '--no-encoding':
                 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]
             
@@ -2165,39 +2328,20 @@
                 sys.path.insert(0, '')
             
             if port >= 0:
+                # Store options in case a new Python process is created
+                self.startOptions = (
+                    '', remoteAddress, port, True, False, redirect,
+                    self.noencoding,
+                )
                 if not self.noencoding:
                     self.__coding = self.defaultCoding
+                patchNewProcessFunctions(self.multiprocessSupport, self)
                 self.connectDebugger(port, remoteAddress, redirect)
                 self.__interact()
             else:
                 print("No network port given. Aborting...")
                 # __IGNORE_WARNING_M801__
-        
-    def fork(self):
-        """
-        Public method implementing a fork routine deciding which branch
-        to follow.
-        
-        @return process ID (integer)
-        """
-        if not self.fork_auto:
-            self.sendJsonCommand("RequestForkTo", {})
-            self.eventLoop(True)
-        pid = DebugClientOrigFork()
-        if pid == 0:
-            # child
-            if not self.fork_child:
-                sys.settrace(None)
-                sys.setprofile(None)
-                self.sessionClose(False)
-        else:
-            # parent
-            if self.fork_child:
-                sys.settrace(None)
-                sys.setprofile(None)
-                self.sessionClose(False)
-        return pid
-        
+    
     def close(self, fd):
         """
         Public method implementing a close method as a replacement for
@@ -2212,7 +2356,7 @@
             return
         
         DebugClientOrigClose(fd)
-        
+    
     def __getSysPath(self, firstEntry):
         """
         Private slot to calculate a path list including the PYTHONPATH
@@ -2229,3 +2373,18 @@
         sysPath.insert(0, firstEntry)
         sysPath.insert(0, '')
         return sysPath
+    
+    def skipMultiProcessDebugging(self, scriptName):
+        """
+        Public method to check, if the given script is eligible for debugging.
+        
+        @param scriptName name of the script to check
+        @type str
+        @return flag indicating eligibility
+        @rtype bool
+        """
+        for pattern in self.noDebugList:
+            if fnmatch.fnmatch(scriptName, pattern):
+                return True
+        
+        return False

eric ide

mercurial