+
+
+class PHPDebuggerUI(BaseDebuggerUI):
+ DEFAULT_LISTENER_HOST = "127.0.0.1"
+ DEFAULT_LISTENER_PORT = 10001
+ DEFAULT_DBG_MOD_TIMEOUT = 300
+ DEFAULT_DBG_MAX_EXEC_TIME = 240
+ dbgSessSeqId = 1
+
+ def __init__(self, parent, id, command, service):
+ BaseDebuggerUI.__init__(self, parent, id)
+ #Note host and port need to come out of options or a pool.
+ self._dbgHost = PHPDebuggerUI.DEFAULT_LISTENER_HOST
+ self._dbgPort = PHPDebuggerUI.DEFAULT_LISTENER_PORT
+ self._dbgTimeout = PHPDebuggerUI.DEFAULT_DBG_MOD_TIMEOUT
+ self._dbgMaxExecTime = PHPDebuggerUI.DEFAULT_DBG_MAX_EXEC_TIME
+ self._dbgSessId = None
+ self._dbgSessParam = None
+ self._dbgPhpIniFile = None
+ self._callback = PHPDebugger.PHPDebuggerCallback(self, service, self._dbgHost, self._dbgPort)
+ self._executor = Executor(command, self)
+ self._service = service
+ self._stopped = False
+ self._allStopped = False
+
+ self._createPhpDbgSess()
+
+ def showErrorDialog(self, message, title):
+ wx.MessageBox(_(message), _(title))
+ return
+
+ def _createPhpDbgSess(self):
+ currTimeStr = str(time.time())
+ (secStr, usecStr) = currTimeStr.split('.')
+ secLongInt = long(secStr)
+ usecLongInt = long(usecStr)
+ self._dbgSessId = "%06ld%06ld%04d" % (secLongInt, usecLongInt, PHPDebuggerUI.dbgSessSeqId)
+ PHPDebuggerUI.dbgSessSeqId = PHPDebuggerUI.dbgSessSeqId + 1
+ self._dbgSessParam = "DBGSESSID=%s@clienthost:%d" % (self._dbgSessId, self._dbgPort)
+
+ if _VERBOSE:
+ print "phpDbgParam=%s" % self._dbgSessParam
+
+ self._service.SetPhpDbgParam(self._dbgSessParam)
+
+ def _preparePhpIniFile(self):
+ success = False
+
+ phpCgiExec = Executor.GetPHPExecutablePath()
+ phpExec = phpCgiExec.replace("php-cgi", "php")
+ iniPath = self._getPhpIniFromRunningPhp(phpExec)
+
+ try:
+ iniDbgPath = os.path.normpath(iniPath + ".ag_debug_enabled")
+ dbgFile = open(iniDbgPath, "w")
+ oriFile = open(iniPath, "r")
+
+ while True:
+ oneOriLine = oriFile.readline()
+ if oneOriLine == '':
+ break
+
+ if not oneOriLine.startswith("debugger.") and not oneOriLine.startswith("max_execution_time="):
+ dbgFile.write(oneOriLine)
+
+ oriFile.close()
+
+ if _WINDOWS:
+ dbgExtFile = "php_dbg.dll"
+ else:
+ dbgExtFile = "dbg.so"
+
+ #
+ # TODO: we should make all of these options configurable.
+ #
+ configStr = "\n; ===============================================================\n; The followings are added by ActiveGrid IDE PHP Debugger Runtime\n; ===============================================================\n\n; As we are running with the dbg module, it takes a much longer time for each script to run.\nmax_execution_time=%d\n\n[debugger]\nextension=%s\ndebugger.enabled=On\ndebugger.JIT_enabled=On\ndebugger.JIT_host=%s\ndebugger.JIT_port=%d\ndebugger.fail_silently=Off\ndebugger.timeout_seconds=%d\ndebugger.ignore_nops=Off\ndebugger.enable_session_cookie=On\ndebugger.session_nocache=On\ndebugger.profiler_enabled=Off\n" % (self._dbgMaxExecTime, dbgExtFile, self._dbgHost, self._dbgPort, self._dbgTimeout)
+ dbgFile.write(configStr)
+ dbgFile.close()
+ success = True
+ except:
+ #TODO: print stack trace.
+ print "Caught exceptions while minipulating php.ini files"
+
+ if success:
+ self._dbgPhpIniFile = iniDbgPath
+ else:
+ self._dbgPhpIniFile = None
+
+ def _getPhpIniFromRunningPhp(self, phpExec):
+ phpIniPath = None
+
+ cmdEnv = os.environ
+ if cmdEnv.has_key('PYTHONPATH'):
+ del cmdEnv['PYTHONPATH']
+
+ cmdLine = [phpExec, "-r", "phpinfo();"]
+ phpProc = subprocess.Popen(args=cmdLine, bufsize=0, executable=None, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=os.environ, universal_newlines=False, startupinfo=None, creationflags=0)
+ phpOutput = phpProc.stdout
+
+ phpIniPattern = "Configuration File (php.ini) Path => "
+ while True:
+ oneLine = phpOutput.readline()
+ if oneLine == '':
+ break
+
+ if oneLine.startswith(phpIniPattern):
+ if oneLine.endswith("\n"):
+ endIndex = oneLine.index("\n")
+ phpIniPath = oneLine[len(phpIniPattern):endIndex]
+ else:
+ phpIniPath = oneLine[len(phpIniPattern):]
+
+ if phpIniPath and len(phpIniPath) > 0:
+ phpIniPath = os.path.normpath(phpIniPath)
+ break
+
+ phpOutput.close()
+
+ if _VERBOSE:
+ print "php.ini path is: %s" % repr(phpIniPath)
+
+ return phpIniPath
+
+ def Execute(self, initialArgs, startIn, environment, onWebServer = False):
+ self._preparePhpIniFile()
+ self._callback.Start()
+
+ if not onWebServer:
+ if self._dbgPhpIniFile:
+ args = '-c "' + self._dbgPhpIniFile + '" ' + initialArgs
+ else:
+ args = initialArgs
+
+ self._executor.Execute(args, startIn, environment)
+
+ def StopExecution(self, event):
+ # This is a general comment on shutdown for the running and debugged processes. Basically, the
+ # current state of this is the result of trial and error coding. The common problems were memory
+ # access violations and threads that would not exit. Making the OutputReaderThreads daemons seems
+ # to have side-stepped the hung thread issue. Being very careful not to touch things after calling
+ # process.py:ProcessOpen.kill() also seems to have fixed the memory access violations, but if there
+ # were more ugliness discovered I would not be surprised. If anyone has any help/advice, please send
+ # it on to mfryer@activegrid.com.
+ if not self._allStopped:
+ self._stopped = True
+ try:
+ self.DisableAfterStop()
+ except wx._core.PyDeadObjectError:
+ pass
+
+ try:
+ #
+ # If this is called by clicking the "Stop" button, we only stop
+ # the current running php script, and keep the listener
+ # running.
+ #
+ if event:
+ self._callback.ShutdownServer(stopLsnr = False)
+ else:
+ self._callback.ShutdownServer(stopLsnr = True)
+ self._allStopped = True
+ except:
+ tp,val,tb = sys.exc_info()
+ traceback.print_exception(tp, val, tb)
+
+ try:
+ self.DeleteCurrentLineMarkers()
+ except:
+ pass
+
+ try:
+ if self._executor:
+ self._executor.DoStopExecution()
+ self._executor = None
+ except:
+ tp,val,tb = sys.exc_info()
+ traceback.print_exception(tp, val, tb)
+
+ def MakeFramesUI(self, parent, id, debugger):
+ return PHPFramesUI(parent, id, self)
+
+ def LoadPHPFramesList(self, stackList):
+ self.framesTab.LoadFramesList(stackList)
+
+ #
+ # TODO: this is a hack to overwrite BaseDebuggerUI's function. The purpose
+ # is to always push breakpoints no matter if a php is running or not. If
+ # no php is running, an exception will be thrown and handled like nothing
+ # happened.
+ #
+ def BreakPointChange(self):
+ self._callback.PushBreakpoints()
+ self.framesTab.PopulateBPList()
+
+
+class PythonDebuggerUI(BaseDebuggerUI):
+ debuggerPortList = None
+
+ def GetAvailablePort():
+ for index in range( 0, len(PythonDebuggerUI.debuggerPortList)):
+ port = PythonDebuggerUI.debuggerPortList[index]
+ if PythonDebuggerUI.PortAvailable(port):
+ PythonDebuggerUI.debuggerPortList.pop(index)
+ return port
+ wx.MessageBox(_("Out of ports for debugging! Please restart the application builder.\nIf that does not work, check for and remove running instances of python."), _("Out of Ports"))
+ assert False, "Out of ports for debugger."
+
+ GetAvailablePort = staticmethod(GetAvailablePort)
+
+ def ReturnPortToPool(port):
+ config = wx.ConfigBase_Get()
+ startingPort = config.ReadInt("DebuggerStartingPort", DEFAULT_PORT)
+ val = int(startingPort) + int(PORT_COUNT)
+ if int(port) >= startingPort and (int(port) <= val):
+ PythonDebuggerUI.debuggerPortList.append(int(port))
+
+ ReturnPortToPool = staticmethod(ReturnPortToPool)
+
+ def PortAvailable(port):
+ config = wx.ConfigBase_Get()
+ hostname = config.Read("DebuggerHostName", DEFAULT_HOST)
+ try:
+ server = AGXMLRPCServer((hostname, port))
+ server.server_close()
+ if _VERBOSE: print "Port ", str(port), " available."
+ return True
+ except:
+ tp,val,tb = sys.exc_info()
+ if _VERBOSE: traceback.print_exception(tp, val, tb)
+ if _VERBOSE: print "Port ", str(port), " unavailable."
+ return False
+
+ PortAvailable = staticmethod(PortAvailable)
+
+ def NewPortRange():
+ config = wx.ConfigBase_Get()
+ startingPort = config.ReadInt("DebuggerStartingPort", DEFAULT_PORT)
+ PythonDebuggerUI.debuggerPortList = range(startingPort, startingPort + PORT_COUNT)
+ NewPortRange = staticmethod(NewPortRange)
+
+ def __init__(self, parent, id, command, service, autoContinue=True):
+ # Check for ports before creating the panel.
+ if not PythonDebuggerUI.debuggerPortList:
+ PythonDebuggerUI.NewPortRange()
+ self._debuggerPort = str(PythonDebuggerUI.GetAvailablePort())
+ self._guiPort = str(PythonDebuggerUI.GetAvailablePort())
+ self._debuggerBreakPort = str(PythonDebuggerUI.GetAvailablePort())
+ BaseDebuggerUI.__init__(self, parent, id)
+ self._command = command
+ self._service = service
+ config = wx.ConfigBase_Get()
+ self._debuggerHost = self._guiHost = config.Read("DebuggerHostName", DEFAULT_HOST)
+
+ url = 'http://' + self._debuggerHost + ':' + self._debuggerPort + '/'
+ self._breakURL = 'http://' + self._debuggerHost + ':' + self._debuggerBreakPort + '/'
+ self._callback = PythonDebuggerCallback(self._guiHost, self._guiPort, url, self._breakURL, self, autoContinue)
+ if DebuggerHarness.__file__.find('library.zip') > 0:
+ try:
+ fname = DebuggerHarness.__file__
+ parts = fname.split('library.zip')
+ path = os.path.join(parts[0],'activegrid', 'tool', 'DebuggerHarness.py')
+ except:
+ tp, val, tb = sys.exc_info()
+ traceback.print_exception(tp, val, tb)
+
+ else:
+ print "Starting debugger on these ports: %s, %s, %s" % (str(self._debuggerPort) , str(self._guiPort) , str(self._debuggerBreakPort))
+ path = DebuggerService.ExpandPath(DebuggerHarness.__file__)
+ self._executor = Executor(path, self, self._debuggerHost, \
+ self._debuggerPort, self._debuggerBreakPort, self._guiHost, self._guiPort, self._command, callbackOnExit=self.ExecutorFinished)
+
+ self._stopped = False
+
+ def LoadPythonFramesList(self, framesXML):
+ self.framesTab.LoadFramesList(framesXML)
+
+ def Execute(self, initialArgs, startIn, environment, onWebServer = False):
+ self._callback.Start()
+ self._executor.Execute(initialArgs, startIn, environment)
+ self._callback.WaitForRPC()
+
+
+ def StopExecution(self, event):
+ # This is a general comment on shutdown for the running and debugged processes. Basically, the
+ # current state of this is the result of trial and error coding. The common problems were memory
+ # access violations and threads that would not exit. Making the OutputReaderThreads daemons seems
+ # to have side-stepped the hung thread issue. Being very careful not to touch things after calling
+ # process.py:ProcessOpen.kill() also seems to have fixed the memory access violations, but if there
+ # were more ugliness discovered I would not be surprised. If anyone has any help/advice, please send
+ # it on to mfryer@activegrid.com.
+ if not self._stopped:
+ self._stopped = True
+ try:
+ self.DisableAfterStop()
+ except wx._core.PyDeadObjectError:
+ pass
+ try:
+ self._callback.ShutdownServer()
+ except:
+ tp,val,tb = sys.exc_info()
+ traceback.print_exception(tp, val, tb)
+
+ try:
+ self.DeleteCurrentLineMarkers()
+ except:
+ pass
+ try:
+ PythonDebuggerUI.ReturnPortToPool(self._debuggerPort)
+ PythonDebuggerUI.ReturnPortToPool(self._guiPort)
+ PythonDebuggerUI.ReturnPortToPool(self._debuggerBreakPort)
+ except:
+ pass
+ try:
+ if self._executor:
+ self._executor.DoStopExecution()
+ self._executor = None
+ except:
+ tp,val,tb = sys.exc_info()
+ traceback.print_exception(tp, val, tb)
+
+
+ def MakeFramesUI(self, parent, id, debugger):
+ panel = PythonFramesUI(parent, id, self)
+ return panel
+
+