📄 process.py
字号:
if ' ' in cmd: first, rest = cmd.split(' ', 1) else: first, rest = cmd, "" # Ensure the first arg is a valid path to the appropriate file. import which if os.sep in first: altpath = [os.path.dirname(first)] firstbase = os.path.basename(first) candidates = list(which.which(firstbase, path=altpath)) elif env: altpath = _getPathFromEnv(env) if altpath: candidates = list(which.which(first, altpath.split(os.pathsep))) else: candidates = list(which.which(first)) else: candidates = list(which.which(first)) if candidates: return _joinArgv( [candidates[0]] ) + ' ' + rest else: raise ProcessError("Could not find an appropriate leading command "\ "for: %r" % cmd)if sys.platform.startswith("win"): def _SaferCreateProcess(appName, # app name cmd, # command line processSA, # process security attributes threadSA, # thread security attributes inheritHandles, # are handles are inherited creationFlags, # creation flags env, # environment cwd, # current working directory si): # STARTUPINFO pointer """If CreateProcess fails from environment type inconsistency then fix that and try again. win32process.CreateProcess requires that all environment keys and values either be all ASCII or all unicode. Try to remove this burden from the user of process.py. """ isWin9x = win32api.GetVersionEx()[3] == VER_PLATFORM_WIN32_WINDOWS # On Win9x all keys and values of 'env' must be ASCII (XXX # Actually this is probably only true if the Unicode support # libraries, which are not installed by default, are not # installed). On other Windows flavours all keys and values of # 'env' must all be ASCII *or* all Unicode. We will try to # automatically convert to the appropriate type, issuing a # warning if such an automatic conversion is necessary. #XXX Komodo 2.0 Beta 1 hack. This requirement should be # pushed out to Komodo code using process.py. Or should it? if isWin9x and env: aenv = {} for key, value in env.items(): aenv[str(key)] = str(value) env = aenv log.debug("""\_SaferCreateProcess(appName=%r, cmd=%r, env=%r, cwd=%r) os.getcwd(): %r""", appName, cmd, env, cwd, os.getcwd()) try: hProcess, hThread, processId, threadId\ = win32process.CreateProcess(appName, cmd, processSA, threadSA, inheritHandles, creationFlags, env, cwd, si) except TypeError, ex: if ex.args == ('All dictionary items must be strings, or all must be unicode',): # Try again with an all unicode environment. #XXX Would be nice if didn't have to depend on the error # string to catch this. #XXX Removing this warning for 2.3 release. See bug # 23215. The right fix is to correct the PHPAppInfo # stuff to heed the warning. #import warnings #warnings.warn('env: ' + str(ex), stacklevel=4) if isWin9x and env: aenv = {} try: for key, value in env.items(): aenv[str(key)] = str(value) except UnicodeError, ex: raise ProcessError(str(ex)) env = aenv elif env: uenv = {} for key, val in env.items(): try: uenv[unicode(key)] = unicode(val) # default encoding except UnicodeError: try: uenv[unicode(key, 'iso-8859-1')] = unicode(val, 'iso-8859-1') # backup encoding except UnicodeError: log.warn('Skipping environment variable "%s" in execution process: unable to convert to unicode using either the default encoding or ISO-8859-1' % (key)) env = uenv hProcess, hThread, processId, threadId\ = win32process.CreateProcess(appName, cmd, processSA, threadSA, inheritHandles, creationFlags, env, cwd, si) else: raise return hProcess, hThread, processId, threadId# Maintain references to all spawned ProcessProxy objects to avoid hangs.# Otherwise, if the user lets the a ProcessProxy object go out of# scope before the process has terminated, it is possible to get a# hang (at least it *used* to be so when we had the# win32api.CloseHandle(<stdin handle>) call in the __del__() method).# XXX Is this hang possible on Linux as well?# A reference is removed from this list when the process's .wait or# .kill method is called.# XXX Should an atexit() handler be registered to kill all curently# running processes? Else *could* get hangs, n'est ce pas?def _registerProcess(process): global _processes log.info("_registerprocess(process=%r)", process) # Clean up zombie processes. # If the user does not call .wait() or .kill() on processes then # the ProcessProxy object will not get cleaned up until Python # exits and _processes goes out of scope. Under heavy usage that # is a big memory waste. Cleaning up here alleviates that. for p in _processes[:]: # use copy of _process, because we may modifiy it try: # poll to see if is process still running if sys.platform.startswith("win"): timeout = 0 else: timeout = os.WNOHANG p.wait(timeout) _unregisterProcess(p) except ProcessError, ex: if ex.errno == ProcessProxy.WAIT_TIMEOUT: pass else: raise _processes.append(process)def _unregisterProcess(process): global _processes log.info("_unregisterProcess(process=%r)", process) try: _processes.remove(process) del process except ValueError: passdef _fixupCommand(cmd, env=None): """Fixup the command string so it is launchable via CreateProcess. One cannot just launch, say "python", via CreateProcess. A full path to an executable is required. In general there are two choices: 1. Launch the command string via the shell. The shell will find the fullpath to the appropriate executable. This shell will also be able to execute special shell commands, like "dir", which don't map to an actual executable. 2. Find the fullpath to the appropriate executable manually and launch that exe. Option (1) is preferred because you don't have to worry about not exactly duplicating shell behaviour and you get the added bonus of being able to launch "dir" and friends. However, (1) is not always an option. Doing so when the shell is command.com (as on all Win9x boxes) or when using WinNT's cmd.exe, problems are created with .kill() because these shells seem to eat up Ctrl-C's and Ctrl-Break's sent via win32api.GenerateConsoleCtrlEvent(). Strangely this only happens when spawn via this Python interface. For example, Ctrl-C get through to hang.exe here: C:\> ...\w9xpopen.exe "C:\WINDOWS\COMMAND.COM /c hang.exe" ^C but not here: >>> p = ProcessOpen('hang.exe') # This results in the same command to CreateProcess as # above. >>> p.kill() Hence, for these platforms we fallback to option (2). Cons: - cannot spawn shell commands like 'dir' directly - cannot spawn batch files """ if sys.platform.startswith("win"): # Fixup the command string to spawn. (Lifted from # posixmodule.c::_PyPopenCreateProcess() with some modifications) comspec = os.environ.get("COMSPEC", None) win32Version = win32api.GetVersion() if comspec is None: raise ProcessError("Cannot locate a COMSPEC environment "\ "variable to use as the shell") # Explicitly check if we are using COMMAND.COM. If we # are then use the w9xpopen hack. elif (win32Version & 0x80000000L == 0) and\ (win32Version & 0x5L >= 5) and\ os.path.basename(comspec).lower() != "command.com": # 2000/XP and not using command.com. if '"' in cmd or "'" in cmd: cmd = comspec + ' /c "%s"' % cmd else: cmd = comspec + ' /c ' + cmd elif (win32Version & 0x80000000L == 0) and\ (win32Version & 0x5L < 5) and\ os.path.basename(comspec).lower() != "command.com": # NT and not using command.com. try: cmd = _whichFirstArg(cmd, env) except ProcessError: raise ProcessError("Could not find a suitable executable "\ "to launch for '%s'. On WinNT you must manually prefix "\ "shell commands and batch files with 'cmd.exe /c' to "\ "have the shell run them." % cmd) else: # Oh gag, we're on Win9x and/or using COMMAND.COM. Use the # workaround listed in KB: Q150956 w9xpopen = os.path.join( os.path.dirname(win32api.GetModuleFileName(0)), 'w9xpopen.exe') if not os.path.exists(w9xpopen): # Eeek - file-not-found - possibly an embedding # situation - see if we can locate it in sys.exec_prefix w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix), 'w9xpopen.exe') if not os.path.exists(w9xpopen): raise ProcessError(\ "Can not locate 'w9xpopen.exe' which is needed "\ "for ProcessOpen to work with your shell or "\ "platform.") ## This would be option (1): #cmd = '%s "%s /c %s"'\ # % (w9xpopen, comspec, cmd.replace('"', '\\"')) try: cmd = _whichFirstArg(cmd, env) except ProcessError: raise ProcessError("Could not find a suitable executable "\ "to launch for '%s'. On Win9x you must manually prefix "\ "shell commands and batch files with 'command.com /c' "\ "to have the shell run them." % cmd) cmd = '%s "%s"' % (w9xpopen, cmd.replace('"', '\\"')) return cmdclass _FileWrapper: """Wrap a system file object, hiding some nitpicky details. This class provides a Python file-like interface to either a Python file object (pretty easy job), a file descriptor, or an OS-specific file handle (e.g. Win32 handles to file objects on Windows). Any or all of these object types may be passed to this wrapper. If more than one is specified this wrapper prefers to work with certain one in this order: - file descriptor (because usually this allows for return-immediately-on-read-if-anything-available semantics and also provides text mode translation on Windows) - OS-specific handle (allows for the above read semantics) - file object (buffering can cause difficulty for interacting with spawned programs) It also provides a place where related such objects can be kept alive together to prevent premature ref-counted collection. (E.g. on Windows a Python file object may be associated with a Win32 file handle. If the file handle is not kept alive the Python file object will cease to function.) """ def __init__(self, file=None, descriptor=None, handle=None): self._file = file self._descriptor = descriptor self._handle = handle self._closed = 0 if self._descriptor is not None or self._handle is not None: self._lineBuf = "" # to support .readline() def __del__(self): self.close() def __getattr__(self, name): """Forward to the underlying file object.""" if self._file is not None: return getattr(self._file, name) else: raise ProcessError("no file object to pass '%s' attribute to" % name) def _win32Read(self, nBytes): try: log.info("[%s] _FileWrapper.read: waiting for read on pipe", id(self)) errCode, text = win32file.ReadFile(self._handle, nBytes) except pywintypes.error, ex: # Ignore errors for now, like "The pipe is being closed.", # etc. XXX There *may* be errors we don't want to avoid. log.info("[%s] _FileWrapper.read: error reading from pipe: %s", id(self), ex) return "" assert errCode == 0,\ "Why is 'errCode' from ReadFile non-zero? %r" % errCode if not text: # Empty text signifies that the pipe has been closed on # the parent's end. log.info("[%s] _FileWrapper.read: observed close of parent", id(self)) # Signal the child so it knows to stop listening. self.close() return "" else: log.info("[%s] _FileWrapper.read: read %d bytes from pipe: %r", id(self), len(text), text) return text
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -