--- a/src/eric7/Debugger/DebuggerInterfacePython.py Wed Feb 07 15:28:08 2024 +0100 +++ b/src/eric7/Debugger/DebuggerInterfacePython.py Fri Feb 09 19:54:15 2024 +0100 @@ -50,6 +50,17 @@ self.__autoContinued = [] self.__isStepCommand = False + self.__ericServerDebugging = False # are we debugging via the eric-ide server? + try: + self.__ericServerDebuggerInterface = ericApp().getObject( + "EricServer" + ).getServiceInterface("Debugger") + self.__ericServerDebuggerInterface.debugClientResponse.connect( + lambda jsonStr: self.handleJsonCommand(jsonStr, None) + ) + except KeyError: + self.__ericServerDebuggerInterface = None + self.debugServer = debugServer self.passive = passive self.process = None @@ -88,8 +99,8 @@ @param fn filename to be translated @type str @param remote2local flag indicating the direction of translation - (False = local to remote, True = remote to local [default]) - @type bool + (False = local to remote, True = remote to local) (defaults to True) + @type bool (optional) @return translated filename @rtype str """ @@ -102,8 +113,8 @@ @param fn filename to be translated @type str @param remote2local flag indicating the direction of translation - (False = local to remote, True = remote to local [default]) - @type bool + (False = local to remote, True = remote to local) (defaults to True) + @type bool (optional) @return translated filename @rtype str """ @@ -118,7 +129,24 @@ return path - def __startProcess(self, program, arguments, environment=None, workingDir=None): + def __ericServerTranslation(self, fn, remote2local=True): + """ + Private method to perform the eric-ide server path translation. + + @param fn filename to be translated + @type str + @param remote2local flag indicating the direction of translation + (False = local to remote, True = remote to local) (defaults to True) + @type bool (optional) + @return translated filename + @rtype str + """ + if remote2local: + return FileSystemUtilities.remoteFileName(fn) + else: + return FileSystemUtilities.plainFileName(fn) + + def __startProcess(self, program, arguments, environment=None, workingDir=""): """ Private method to start the debugger client process. @@ -154,8 +182,9 @@ runInConsole, venvName, originalPathString, - workingDir=None, + workingDir="", configOverride=None, + startRemote=False, ): """ Public method to start a remote Python interpreter. @@ -169,41 +198,54 @@ @type str @param originalPathString original PATH environment variable @type str - @param workingDir directory to start the debugger client in - @type str - @param configOverride dictionary containing the global config override - data - @type dict + @param workingDir directory to start the debugger client in (defaults to "") + @type str (optional) + @param configOverride dictionary containing the global config override data + (defaults to None) + @type dict (optional) + @param startRemote flag indicating to start the client via an eric-ide server + (defaults to False) + @type bool (optional) @return client process object, a flag to indicate a network connection and the name of the interpreter in case of a local execution @rtype tuple of (QProcess, bool, str) """ global origPathEnv - if not venvName: - venvName = Preferences.getDebugger("Python3VirtualEnv") - if venvName == self.debugServer.getProjectEnvironmentString(): - project = ericApp().getObject("Project") - venvName = project.getProjectVenv() - execPath = project.getProjectExecPath() - interpreter = project.getProjectInterpreter() + if startRemote or venvName == self.debugServer.getEricServerEnvironmentString(): + # TODO change this once server environment definitions are supported + startRemote = True + venvName = self.debugServer.getEricServerEnvironmentString() + interpreter = "" else: - venvManager = ericApp().getObject("VirtualEnvManager") - interpreter = venvManager.getVirtualenvInterpreter(venvName) - execPath = venvManager.getVirtualenvExecPath(venvName) - if interpreter == "": - # use the interpreter used to run eric for identical variants - interpreter = PythonUtilities.getPythonExecutable() - if interpreter == "": - EricMessageBox.critical( - None, - self.tr("Start Debugger"), - self.tr("""<p>No suitable Python3 environment configured.</p>"""), - ) - return None, False, "" + if not venvName: + venvName = Preferences.getDebugger("Python3VirtualEnv") + if venvName == self.debugServer.getProjectEnvironmentString(): + project = ericApp().getObject("Project") + venvName = project.getProjectVenv() + execPath = project.getProjectExecPath() + interpreter = project.getProjectInterpreter() + else: + venvManager = ericApp().getObject("VirtualEnvManager") + interpreter = venvManager.getVirtualenvInterpreter(venvName) + execPath = venvManager.getVirtualenvExecPath(venvName) + if interpreter == "": + # use the interpreter used to run eric for identical variants + interpreter = PythonUtilities.getPythonExecutable() + if interpreter == "": + EricMessageBox.critical( + None, + self.tr("Start Debugger"), + self.tr("""<p>No suitable Python3 environment configured.</p>"""), + ) + return None, False, "" self.__inShutdown = False + self.__ericServerDebugging = False + self.__ericServerDebuggerInterface.stopClient() + self.__mainDebugger = None + redirect = ( str(configOverride["redirect"]) if configOverride and configOverride["enable"] @@ -290,6 +332,28 @@ ) return None, False, "" + elif startRemote and self.__ericServerDebuggerInterface is not None: + # debugging via an eric-ide server + ##self.__ericServerDebuggerInterface.stopClient() + ##self.__mainDebugger = None +## + self.translate = self.__ericServerTranslation + self.__ericServerDebugging = True + + args = [] + if noencoding: + args.append(noencoding) + if multiprocessEnabled: + args.append(multiprocessEnabled) + if callTraceOptimization: + args.append(callTraceOptimization) + self.__ericServerDebuggerInterface.startClient( + interpreter, originalPathString, args, workingDir=workingDir, + ) + self.__startedVenv = venvName + + return None, self.__isNetworked, "" + else: # local debugging code below debugClient = self.__determineDebugClient() @@ -398,6 +462,7 @@ originalPathString, workingDir=None, configOverride=None, + startRemote=False, ): """ Public method to start a remote Python interpreter for a project. @@ -416,6 +481,9 @@ @param configOverride dictionary containing the global config override data @type dict + @param startRemote flag indicating to start the client via an eric-ide server + (defaults to False) + @type bool (optional) @return client process object, a flag to indicate a network connection and the name of the interpreter in case of a local execution @rtype tuple of (QProcess, bool, str) @@ -460,6 +528,10 @@ self.__inShutdown = False + self.__ericServerDebugging = False + self.__ericServerDebuggerInterface.stopClient() + self.__mainDebugger = None + if project.getDebugProperty("REMOTEDEBUGGER"): # remote debugging code ipaddr = self.debugServer.getHostAddress(False) @@ -517,6 +589,8 @@ # remote shell command is missing return None, self.__isNetworked, "" + # TODO: add server debugging for projects + else: # local debugging code below debugClient = project.getDebugProperty("DEBUGCLIENT") @@ -619,7 +693,7 @@ """ self.__pendingConnections.append(sock) - sock.readyRead.connect(lambda: self.__parseClientLine(sock)) + sock.readyRead.connect(lambda: self.__receiveJson(sock)) sock.disconnected.connect(lambda: self.__socketDisconnected(sock)) return True @@ -634,30 +708,30 @@ @param debuggerId id of the connected debug client @type str """ - if sock in self.__pendingConnections: + if sock and sock in self.__pendingConnections: self.__connections[debuggerId] = sock self.__pendingConnections.remove(sock) - if self.__mainDebugger is None: - self.__mainDebugger = debuggerId - # Get the remote clients capabilities - self.remoteCapabilities(debuggerId) + if self.__mainDebugger is None: + self.__mainDebugger = debuggerId + # Get the remote clients capabilities + self.remoteCapabilities(debuggerId) - self.debugServer.signalClientDebuggerId(debuggerId) + self.debugServer.signalClientDebuggerId(debuggerId) - if debuggerId == self.__mainDebugger: - self.__flush() - self.debugServer.mainClientConnected() + if debuggerId == self.__mainDebugger: + self.__flush() + self.debugServer.mainClientConnected() - self.debugServer.initializeClient(debuggerId) + self.debugServer.initializeClient(debuggerId) - # perform auto-continue except for main - if ( - debuggerId != self.__mainDebugger - and self.__autoContinue - and not self.__isStepCommand - ): - QTimer.singleShot(0, lambda: self.remoteContinue(debuggerId)) + # perform auto-continue except for main + if ( + debuggerId != self.__mainDebugger + and self.__autoContinue + and not self.__isStepCommand + ): + QTimer.singleShot(0, lambda: self.remoteContinue(debuggerId)) def __socketDisconnected(self, sock): """ @@ -707,9 +781,15 @@ """ if self.__mainDebugger: # Send commands that were waiting for the connection. - conn = self.__connections[self.__mainDebugger] - for jsonStr in self.__commandQueue: - self.__writeJsonCommandToSocket(jsonStr, conn) + if self.__ericServerDebugging: + for jsonStr in self.__commandQueue: + self.__ericServerDebuggerInterface.sendClientCommand( + self.__mainDebugger, jsonStr + ) + else: + conn = self.__connections[self.__mainDebugger] + for jsonStr in self.__commandQueue: + self.__writeJsonCommandToSocket(jsonStr, conn) self.__commandQueue.clear() @@ -1396,7 +1476,7 @@ debuggerId, ) - def __parseClientLine(self, sock): + def __receiveJson(self, sock): """ Private method to handle data from the client. @@ -1423,11 +1503,11 @@ logging.debug("<Debug-Server> %s", jsonStr) ##print("Server: ", jsonStr) ## debug # __IGNORE_WARNING_M891__ - self.__handleJsonCommand(jsonStr, sock) + self.handleJsonCommand(jsonStr, sock) - def __handleJsonCommand(self, jsonStr, sock): + def handleJsonCommand(self, jsonStr, sock): """ - Private method to handle a command or response serialized as a + Public method to handle a command or response serialized as a JSON string. @param jsonStr string containing the command or response received @@ -1663,14 +1743,25 @@ } jsonStr = json.dumps(commandDict) - if debuggerId and debuggerId in self.__connections: - sock = self.__connections[debuggerId] - elif sock is None and self.__mainDebugger is not None: - sock = self.__connections[self.__mainDebugger] - if sock is not None: - self.__writeJsonCommandToSocket(jsonStr, sock) + if self.__ericServerDebugging: + # Debugging via the eric-ide server -> pass the command on to it + if self.__mainDebugger is None: + # debugger has not connected yet -> queue the command + self.__commandQueue.append(jsonStr) + else: + self.__ericServerDebuggerInterface.sendClientCommand( + debuggerId, jsonStr + ) else: - self.__commandQueue.append(jsonStr) + # Local debugging -> send the command to the client + if debuggerId and debuggerId in self.__connections: + sock = self.__connections[debuggerId] + elif sock is None and self.__mainDebugger is not None: + sock = self.__connections[self.__mainDebugger] + if sock is not None: + self.__writeJsonCommandToSocket(jsonStr, sock) + else: + self.__commandQueue.append(jsonStr) def __writeJsonCommandToSocket(self, jsonCommand, sock): """