##// END OF EJS Templates
Merge pull request #13242 from Kojoley/remove-unused-_find_cmd...
Matthias Bussonnier -
r26994:76b369ef merge
parent child Browse files
Show More
@@ -1,78 +1,70 b''
1 1 """cli-specific implementation of process utilities.
2 2
3 3 cli - Common Language Infrastructure for IronPython. Code
4 4 can run on any operating system. Check os.name for os-
5 5 specific settings.
6 6
7 7 This file is only meant to be imported by process.py, not by end-users.
8 8
9 9 This file is largely untested. To become a full drop-in process
10 10 interface for IronPython will probably require you to help fill
11 11 in the details.
12 12 """
13 13
14 14 # Import cli libraries:
15 15 import clr
16 16 import System
17 17
18 18 # Import Python libraries:
19 19 import os
20 20
21 21 # Import IPython libraries:
22 22 from IPython.utils import py3compat
23 23 from ._process_common import arg_split
24 24
25 def _find_cmd(cmd):
26 """Find the full path to a command using which."""
27 paths = System.Environment.GetEnvironmentVariable("PATH").Split(os.pathsep)
28 for path in paths:
29 filename = os.path.join(path, cmd)
30 if System.IO.File.Exists(filename):
31 return py3compat.decode(filename)
32 raise OSError("command %r not found" % cmd)
33 25
34 26 def system(cmd):
35 27 """
36 28 system(cmd) should work in a cli environment on Mac OSX, Linux,
37 29 and Windows
38 30 """
39 31 psi = System.Diagnostics.ProcessStartInfo(cmd)
40 32 psi.RedirectStandardOutput = True
41 33 psi.RedirectStandardError = True
42 34 psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal
43 35 psi.UseShellExecute = False
44 36 # Start up process:
45 37 reg = System.Diagnostics.Process.Start(psi)
46 38
47 39 def getoutput(cmd):
48 40 """
49 41 getoutput(cmd) should work in a cli environment on Mac OSX, Linux,
50 42 and Windows
51 43 """
52 44 psi = System.Diagnostics.ProcessStartInfo(cmd)
53 45 psi.RedirectStandardOutput = True
54 46 psi.RedirectStandardError = True
55 47 psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal
56 48 psi.UseShellExecute = False
57 49 # Start up process:
58 50 reg = System.Diagnostics.Process.Start(psi)
59 51 myOutput = reg.StandardOutput
60 52 output = myOutput.ReadToEnd()
61 53 myError = reg.StandardError
62 54 error = myError.ReadToEnd()
63 55 return output
64 56
65 57 def check_pid(pid):
66 58 """
67 59 Check if a process with the given PID (pid) exists
68 60 """
69 61 try:
70 62 System.Diagnostics.Process.GetProcessById(pid)
71 63 # process with given pid is running
72 64 return True
73 65 except System.InvalidOperationException:
74 66 # process wasn't started by this object (but is running)
75 67 return True
76 68 except System.ArgumentException:
77 69 # process with given pid isn't running
78 70 return False
@@ -1,225 +1,217 b''
1 1 """Posix-specific implementation of process utilities.
2 2
3 3 This file is only meant to be imported by process.py, not by end-users.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2010-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 # Stdlib
18 18 import errno
19 19 import os
20 20 import subprocess as sp
21 21 import sys
22 22
23 23 import pexpect
24 24
25 25 # Our own
26 26 from ._process_common import getoutput, arg_split
27 27 from IPython.utils import py3compat
28 28 from IPython.utils.encoding import DEFAULT_ENCODING
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # Function definitions
32 32 #-----------------------------------------------------------------------------
33 33
34 def _find_cmd(cmd):
35 """Find the full path to a command using which."""
36
37 path = sp.Popen(['/usr/bin/env', 'which', cmd],
38 stdout=sp.PIPE, stderr=sp.PIPE).communicate()[0]
39 return py3compat.decode(path)
40
41
42 34 class ProcessHandler(object):
43 35 """Execute subprocesses under the control of pexpect.
44 36 """
45 37 # Timeout in seconds to wait on each reading of the subprocess' output.
46 38 # This should not be set too low to avoid cpu overusage from our side,
47 39 # since we read in a loop whose period is controlled by this timeout.
48 40 read_timeout = 0.05
49 41
50 42 # Timeout to give a process if we receive SIGINT, between sending the
51 43 # SIGINT to the process and forcefully terminating it.
52 44 terminate_timeout = 0.2
53 45
54 46 # File object where stdout and stderr of the subprocess will be written
55 47 logfile = None
56 48
57 49 # Shell to call for subprocesses to execute
58 50 _sh = None
59 51
60 52 @property
61 53 def sh(self):
62 54 if self._sh is None:
63 55 shell_name = os.environ.get("SHELL", "sh")
64 56 self._sh = pexpect.which(shell_name)
65 57 if self._sh is None:
66 58 raise OSError('"{}" shell not found'.format(shell_name))
67 59
68 60 return self._sh
69 61
70 62 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
71 63 """Arguments are used for pexpect calls."""
72 64 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
73 65 None else read_timeout)
74 66 self.terminate_timeout = (ProcessHandler.terminate_timeout if
75 67 terminate_timeout is None else
76 68 terminate_timeout)
77 69 self.logfile = sys.stdout if logfile is None else logfile
78 70
79 71 def getoutput(self, cmd):
80 72 """Run a command and return its stdout/stderr as a string.
81 73
82 74 Parameters
83 75 ----------
84 76 cmd : str
85 77 A command to be executed in the system shell.
86 78
87 79 Returns
88 80 -------
89 81 output : str
90 82 A string containing the combination of stdout and stderr from the
91 83 subprocess, in whatever order the subprocess originally wrote to its
92 84 file descriptors (so the order of the information in this string is the
93 85 correct order as would be seen if running the command in a terminal).
94 86 """
95 87 try:
96 88 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
97 89 except KeyboardInterrupt:
98 90 print('^C', file=sys.stderr, end='')
99 91
100 92 def getoutput_pexpect(self, cmd):
101 93 """Run a command and return its stdout/stderr as a string.
102 94
103 95 Parameters
104 96 ----------
105 97 cmd : str
106 98 A command to be executed in the system shell.
107 99
108 100 Returns
109 101 -------
110 102 output : str
111 103 A string containing the combination of stdout and stderr from the
112 104 subprocess, in whatever order the subprocess originally wrote to its
113 105 file descriptors (so the order of the information in this string is the
114 106 correct order as would be seen if running the command in a terminal).
115 107 """
116 108 try:
117 109 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
118 110 except KeyboardInterrupt:
119 111 print('^C', file=sys.stderr, end='')
120 112
121 113 def system(self, cmd):
122 114 """Execute a command in a subshell.
123 115
124 116 Parameters
125 117 ----------
126 118 cmd : str
127 119 A command to be executed in the system shell.
128 120
129 121 Returns
130 122 -------
131 123 int : child's exitstatus
132 124 """
133 125 # Get likely encoding for the output.
134 126 enc = DEFAULT_ENCODING
135 127
136 128 # Patterns to match on the output, for pexpect. We read input and
137 129 # allow either a short timeout or EOF
138 130 patterns = [pexpect.TIMEOUT, pexpect.EOF]
139 131 # the index of the EOF pattern in the list.
140 132 # even though we know it's 1, this call means we don't have to worry if
141 133 # we change the above list, and forget to change this value:
142 134 EOF_index = patterns.index(pexpect.EOF)
143 135 # The size of the output stored so far in the process output buffer.
144 136 # Since pexpect only appends to this buffer, each time we print we
145 137 # record how far we've printed, so that next time we only print *new*
146 138 # content from the buffer.
147 139 out_size = 0
148 140 try:
149 141 # Since we're not really searching the buffer for text patterns, we
150 142 # can set pexpect's search window to be tiny and it won't matter.
151 143 # We only search for the 'patterns' timeout or EOF, which aren't in
152 144 # the text itself.
153 145 #child = pexpect.spawn(pcmd, searchwindowsize=1)
154 146 if hasattr(pexpect, 'spawnb'):
155 147 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
156 148 else:
157 149 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
158 150 flush = sys.stdout.flush
159 151 while True:
160 152 # res is the index of the pattern that caused the match, so we
161 153 # know whether we've finished (if we matched EOF) or not
162 154 res_idx = child.expect_list(patterns, self.read_timeout)
163 155 print(child.before[out_size:].decode(enc, 'replace'), end='')
164 156 flush()
165 157 if res_idx==EOF_index:
166 158 break
167 159 # Update the pointer to what we've already printed
168 160 out_size = len(child.before)
169 161 except KeyboardInterrupt:
170 162 # We need to send ^C to the process. The ascii code for '^C' is 3
171 163 # (the character is known as ETX for 'End of Text', see
172 164 # curses.ascii.ETX).
173 165 child.sendline(chr(3))
174 166 # Read and print any more output the program might produce on its
175 167 # way out.
176 168 try:
177 169 out_size = len(child.before)
178 170 child.expect_list(patterns, self.terminate_timeout)
179 171 print(child.before[out_size:].decode(enc, 'replace'), end='')
180 172 sys.stdout.flush()
181 173 except KeyboardInterrupt:
182 174 # Impatient users tend to type it multiple times
183 175 pass
184 176 finally:
185 177 # Ensure the subprocess really is terminated
186 178 child.terminate(force=True)
187 179 # add isalive check, to ensure exitstatus is set:
188 180 child.isalive()
189 181
190 182 # We follow the subprocess pattern, returning either the exit status
191 183 # as a positive number, or the terminating signal as a negative
192 184 # number.
193 185 # on Linux, sh returns 128+n for signals terminating child processes on Linux
194 186 # on BSD (OS X), the signal code is set instead
195 187 if child.exitstatus is None:
196 188 # on WIFSIGNALED, pexpect sets signalstatus, leaving exitstatus=None
197 189 if child.signalstatus is None:
198 190 # this condition may never occur,
199 191 # but let's be certain we always return an integer.
200 192 return 0
201 193 return -child.signalstatus
202 194 if child.exitstatus > 128:
203 195 return -(child.exitstatus - 128)
204 196 return child.exitstatus
205 197
206 198
207 199 # Make system() with a functional interface for outside use. Note that we use
208 200 # getoutput() from the _common utils, which is built on top of popen(). Using
209 201 # pexpect to get subprocess output produces difficult to parse output, since
210 202 # programs think they are talking to a tty and produce highly formatted output
211 203 # (ls is a good example) that makes them hard.
212 204 system = ProcessHandler().system
213 205
214 206 def check_pid(pid):
215 207 try:
216 208 os.kill(pid, 0)
217 209 except OSError as err:
218 210 if err.errno == errno.ESRCH:
219 211 return False
220 212 elif err.errno == errno.EPERM:
221 213 # Don't have permission to signal the process - probably means it exists
222 214 return True
223 215 raise
224 216 else:
225 217 return True
@@ -1,205 +1,184 b''
1 1 """Windows-specific implementation of process utilities.
2 2
3 3 This file is only meant to be imported by process.py, not by end-users.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2010-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 # stdlib
18 18 import os
19 19 import sys
20 20 import ctypes
21 21 import time
22 22
23 23 from ctypes import c_int, POINTER
24 24 from ctypes.wintypes import LPCWSTR, HLOCAL
25 25 from subprocess import STDOUT, TimeoutExpired
26 26 from threading import Thread
27 27
28 28 # our own imports
29 29 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
30 30 from . import py3compat
31 31 from .encoding import DEFAULT_ENCODING
32 32
33 33 #-----------------------------------------------------------------------------
34 34 # Function definitions
35 35 #-----------------------------------------------------------------------------
36 36
37 37 class AvoidUNCPath(object):
38 38 """A context manager to protect command execution from UNC paths.
39 39
40 40 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
41 41 This context manager temporarily changes directory to the 'C:' drive on
42 42 entering, and restores the original working directory on exit.
43 43
44 44 The context manager returns the starting working directory *if* it made a
45 45 change and None otherwise, so that users can apply the necessary adjustment
46 46 to their system calls in the event of a change.
47 47
48 48 Examples
49 49 --------
50 50 ::
51 51 cmd = 'dir'
52 52 with AvoidUNCPath() as path:
53 53 if path is not None:
54 54 cmd = '"pushd %s &&"%s' % (path, cmd)
55 55 os.system(cmd)
56 56 """
57 57 def __enter__(self):
58 58 self.path = os.getcwd()
59 59 self.is_unc_path = self.path.startswith(r"\\")
60 60 if self.is_unc_path:
61 61 # change to c drive (as cmd.exe cannot handle UNC addresses)
62 62 os.chdir("C:")
63 63 return self.path
64 64 else:
65 65 # We return None to signal that there was no change in the working
66 66 # directory
67 67 return None
68 68
69 69 def __exit__(self, exc_type, exc_value, traceback):
70 70 if self.is_unc_path:
71 71 os.chdir(self.path)
72 72
73 73
74 def _find_cmd(cmd):
75 """Find the full path to a .bat or .exe using the win32api module."""
76 try:
77 from win32api import SearchPath
78 except ImportError as e:
79 raise ImportError('you need to have pywin32 installed for this to work') from e
80 else:
81 PATH = os.environ['PATH']
82 extensions = ['.exe', '.com', '.bat', '.py']
83 path = None
84 for ext in extensions:
85 try:
86 path = SearchPath(PATH, cmd, ext)[0]
87 except:
88 pass
89 if path is None:
90 raise OSError("command %r not found" % cmd)
91 else:
92 return path
93
94
95 74 def _system_body(p):
96 75 """Callback for _system."""
97 76 enc = DEFAULT_ENCODING
98 77
99 78 def stdout_read():
100 79 for line in read_no_interrupt(p.stdout).splitlines():
101 80 line = line.decode(enc, 'replace')
102 81 print(line, file=sys.stdout)
103 82
104 83 def stderr_read():
105 84 for line in read_no_interrupt(p.stderr).splitlines():
106 85 line = line.decode(enc, 'replace')
107 86 print(line, file=sys.stderr)
108 87
109 88 Thread(target=stdout_read).start()
110 89 Thread(target=stderr_read).start()
111 90
112 91 # Wait to finish for returncode. Unfortunately, Python has a bug where
113 92 # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in
114 93 # a loop instead of just doing `return p.wait()`.
115 94 while True:
116 95 result = p.poll()
117 96 if result is None:
118 97 time.sleep(0.01)
119 98 else:
120 99 return result
121 100
122 101
123 102 def system(cmd):
124 103 """Win32 version of os.system() that works with network shares.
125 104
126 105 Note that this implementation returns None, as meant for use in IPython.
127 106
128 107 Parameters
129 108 ----------
130 109 cmd : str or list
131 110 A command to be executed in the system shell.
132 111
133 112 Returns
134 113 -------
135 114 int : child process' exit code.
136 115 """
137 116 # The controller provides interactivity with both
138 117 # stdin and stdout
139 118 #import _process_win32_controller
140 119 #_process_win32_controller.system(cmd)
141 120
142 121 with AvoidUNCPath() as path:
143 122 if path is not None:
144 123 cmd = '"pushd %s &&"%s' % (path, cmd)
145 124 return process_handler(cmd, _system_body)
146 125
147 126 def getoutput(cmd):
148 127 """Return standard output of executing cmd in a shell.
149 128
150 129 Accepts the same arguments as os.system().
151 130
152 131 Parameters
153 132 ----------
154 133 cmd : str or list
155 134 A command to be executed in the system shell.
156 135
157 136 Returns
158 137 -------
159 138 stdout : str
160 139 """
161 140
162 141 with AvoidUNCPath() as path:
163 142 if path is not None:
164 143 cmd = '"pushd %s &&"%s' % (path, cmd)
165 144 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
166 145
167 146 if out is None:
168 147 out = b''
169 148 return py3compat.decode(out)
170 149
171 150 try:
172 151 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
173 152 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
174 153 CommandLineToArgvW.restype = POINTER(LPCWSTR)
175 154 LocalFree = ctypes.windll.kernel32.LocalFree
176 155 LocalFree.res_type = HLOCAL
177 156 LocalFree.arg_types = [HLOCAL]
178 157
179 158 def arg_split(commandline, posix=False, strict=True):
180 159 """Split a command line's arguments in a shell-like manner.
181 160
182 161 This is a special version for windows that use a ctypes call to CommandLineToArgvW
183 162 to do the argv splitting. The posix parameter is ignored.
184 163
185 164 If strict=False, process_common.arg_split(...strict=False) is used instead.
186 165 """
187 166 #CommandLineToArgvW returns path to executable if called with empty string.
188 167 if commandline.strip() == "":
189 168 return []
190 169 if not strict:
191 170 # not really a cl-arg, fallback on _process_common
192 171 return py_arg_split(commandline, posix=posix, strict=strict)
193 172 argvn = c_int()
194 173 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
195 174 result_array_type = LPCWSTR * argvn.value
196 175 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
197 176 retval = LocalFree(result_pointer)
198 177 return result
199 178 except AttributeError:
200 179 arg_split = py_arg_split
201 180
202 181 def check_pid(pid):
203 182 # OpenProcess returns 0 if no such process (of ours) exists
204 183 # positive int otherwise
205 184 return bool(ctypes.windll.kernel32.OpenProcess(1,0,pid))
General Comments 0
You need to be logged in to leave comments. Login now