##// END OF EJS Templates
procutil: drop unused 'newlines' option from popen*() (API)...
Yuya Nishihara -
r37482:632b9289 default
parent child Browse files
Show More
@@ -1,409 +1,407 b''
1 # procutil.py - utility for managing processes and executable environment
1 # procutil.py - utility for managing processes and executable environment
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import contextlib
12 import contextlib
13 import imp
13 import imp
14 import io
14 import io
15 import os
15 import os
16 import signal
16 import signal
17 import subprocess
17 import subprocess
18 import sys
18 import sys
19 import tempfile
19 import tempfile
20 import time
20 import time
21
21
22 from ..i18n import _
22 from ..i18n import _
23
23
24 from .. import (
24 from .. import (
25 encoding,
25 encoding,
26 error,
26 error,
27 policy,
27 policy,
28 pycompat,
28 pycompat,
29 )
29 )
30
30
31 osutil = policy.importmod(r'osutil')
31 osutil = policy.importmod(r'osutil')
32
32
33 stderr = pycompat.stderr
33 stderr = pycompat.stderr
34 stdin = pycompat.stdin
34 stdin = pycompat.stdin
35 stdout = pycompat.stdout
35 stdout = pycompat.stdout
36
36
37 def isatty(fp):
37 def isatty(fp):
38 try:
38 try:
39 return fp.isatty()
39 return fp.isatty()
40 except AttributeError:
40 except AttributeError:
41 return False
41 return False
42
42
43 # glibc determines buffering on first write to stdout - if we replace a TTY
43 # glibc determines buffering on first write to stdout - if we replace a TTY
44 # destined stdout with a pipe destined stdout (e.g. pager), we want line
44 # destined stdout with a pipe destined stdout (e.g. pager), we want line
45 # buffering
45 # buffering
46 if isatty(stdout):
46 if isatty(stdout):
47 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
47 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
48
48
49 if pycompat.iswindows:
49 if pycompat.iswindows:
50 from .. import windows as platform
50 from .. import windows as platform
51 stdout = platform.winstdout(stdout)
51 stdout = platform.winstdout(stdout)
52 else:
52 else:
53 from .. import posix as platform
53 from .. import posix as platform
54
54
55 findexe = platform.findexe
55 findexe = platform.findexe
56 _gethgcmd = platform.gethgcmd
56 _gethgcmd = platform.gethgcmd
57 getuser = platform.getuser
57 getuser = platform.getuser
58 getpid = os.getpid
58 getpid = os.getpid
59 hidewindow = platform.hidewindow
59 hidewindow = platform.hidewindow
60 quotecommand = platform.quotecommand
60 quotecommand = platform.quotecommand
61 readpipe = platform.readpipe
61 readpipe = platform.readpipe
62 setbinary = platform.setbinary
62 setbinary = platform.setbinary
63 setsignalhandler = platform.setsignalhandler
63 setsignalhandler = platform.setsignalhandler
64 shellquote = platform.shellquote
64 shellquote = platform.shellquote
65 shellsplit = platform.shellsplit
65 shellsplit = platform.shellsplit
66 spawndetached = platform.spawndetached
66 spawndetached = platform.spawndetached
67 sshargs = platform.sshargs
67 sshargs = platform.sshargs
68 testpid = platform.testpid
68 testpid = platform.testpid
69
69
70 try:
70 try:
71 setprocname = osutil.setprocname
71 setprocname = osutil.setprocname
72 except AttributeError:
72 except AttributeError:
73 pass
73 pass
74 try:
74 try:
75 unblocksignal = osutil.unblocksignal
75 unblocksignal = osutil.unblocksignal
76 except AttributeError:
76 except AttributeError:
77 pass
77 pass
78
78
79 closefds = pycompat.isposix
79 closefds = pycompat.isposix
80
80
81 def explainexit(code):
81 def explainexit(code):
82 """return a message describing a subprocess status
82 """return a message describing a subprocess status
83 (codes from kill are negative - not os.system/wait encoding)"""
83 (codes from kill are negative - not os.system/wait encoding)"""
84 if code >= 0:
84 if code >= 0:
85 return _("exited with status %d") % code
85 return _("exited with status %d") % code
86 return _("killed by signal %d") % -code
86 return _("killed by signal %d") % -code
87
87
88 class _pfile(object):
88 class _pfile(object):
89 """File-like wrapper for a stream opened by subprocess.Popen()"""
89 """File-like wrapper for a stream opened by subprocess.Popen()"""
90
90
91 def __init__(self, proc, fp):
91 def __init__(self, proc, fp):
92 self._proc = proc
92 self._proc = proc
93 self._fp = fp
93 self._fp = fp
94
94
95 def close(self):
95 def close(self):
96 # unlike os.popen(), this returns an integer in subprocess coding
96 # unlike os.popen(), this returns an integer in subprocess coding
97 self._fp.close()
97 self._fp.close()
98 return self._proc.wait()
98 return self._proc.wait()
99
99
100 def __iter__(self):
100 def __iter__(self):
101 return iter(self._fp)
101 return iter(self._fp)
102
102
103 def __getattr__(self, attr):
103 def __getattr__(self, attr):
104 return getattr(self._fp, attr)
104 return getattr(self._fp, attr)
105
105
106 def __enter__(self):
106 def __enter__(self):
107 return self
107 return self
108
108
109 def __exit__(self, exc_type, exc_value, exc_tb):
109 def __exit__(self, exc_type, exc_value, exc_tb):
110 self.close()
110 self.close()
111
111
112 def popen(cmd, mode='rb', bufsize=-1):
112 def popen(cmd, mode='rb', bufsize=-1):
113 if mode == 'rb':
113 if mode == 'rb':
114 return _popenreader(cmd, bufsize)
114 return _popenreader(cmd, bufsize)
115 elif mode == 'wb':
115 elif mode == 'wb':
116 return _popenwriter(cmd, bufsize)
116 return _popenwriter(cmd, bufsize)
117 raise error.ProgrammingError('unsupported mode: %r' % mode)
117 raise error.ProgrammingError('unsupported mode: %r' % mode)
118
118
119 def _popenreader(cmd, bufsize):
119 def _popenreader(cmd, bufsize):
120 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
120 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
121 close_fds=closefds,
121 close_fds=closefds,
122 stdout=subprocess.PIPE)
122 stdout=subprocess.PIPE)
123 return _pfile(p, p.stdout)
123 return _pfile(p, p.stdout)
124
124
125 def _popenwriter(cmd, bufsize):
125 def _popenwriter(cmd, bufsize):
126 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
126 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
127 close_fds=closefds,
127 close_fds=closefds,
128 stdin=subprocess.PIPE)
128 stdin=subprocess.PIPE)
129 return _pfile(p, p.stdin)
129 return _pfile(p, p.stdin)
130
130
131 def popen2(cmd, env=None, newlines=False):
131 def popen2(cmd, env=None):
132 # Setting bufsize to -1 lets the system decide the buffer size.
132 # Setting bufsize to -1 lets the system decide the buffer size.
133 # The default for bufsize is 0, meaning unbuffered. This leads to
133 # The default for bufsize is 0, meaning unbuffered. This leads to
134 # poor performance on Mac OS X: http://bugs.python.org/issue4194
134 # poor performance on Mac OS X: http://bugs.python.org/issue4194
135 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
135 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
136 close_fds=closefds,
136 close_fds=closefds,
137 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
137 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
138 universal_newlines=newlines,
139 env=env)
138 env=env)
140 return p.stdin, p.stdout
139 return p.stdin, p.stdout
141
140
142 def popen3(cmd, env=None, newlines=False):
141 def popen3(cmd, env=None):
143 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
142 stdin, stdout, stderr, p = popen4(cmd, env)
144 return stdin, stdout, stderr
143 return stdin, stdout, stderr
145
144
146 def popen4(cmd, env=None, newlines=False, bufsize=-1):
145 def popen4(cmd, env=None, bufsize=-1):
147 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
146 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
148 close_fds=closefds,
147 close_fds=closefds,
149 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
148 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
150 stderr=subprocess.PIPE,
149 stderr=subprocess.PIPE,
151 universal_newlines=newlines,
152 env=env)
150 env=env)
153 return p.stdin, p.stdout, p.stderr, p
151 return p.stdin, p.stdout, p.stderr, p
154
152
155 def pipefilter(s, cmd):
153 def pipefilter(s, cmd):
156 '''filter string S through command CMD, returning its output'''
154 '''filter string S through command CMD, returning its output'''
157 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
155 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
158 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
156 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
159 pout, perr = p.communicate(s)
157 pout, perr = p.communicate(s)
160 return pout
158 return pout
161
159
162 def tempfilter(s, cmd):
160 def tempfilter(s, cmd):
163 '''filter string S through a pair of temporary files with CMD.
161 '''filter string S through a pair of temporary files with CMD.
164 CMD is used as a template to create the real command to be run,
162 CMD is used as a template to create the real command to be run,
165 with the strings INFILE and OUTFILE replaced by the real names of
163 with the strings INFILE and OUTFILE replaced by the real names of
166 the temporary files generated.'''
164 the temporary files generated.'''
167 inname, outname = None, None
165 inname, outname = None, None
168 try:
166 try:
169 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
167 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
170 fp = os.fdopen(infd, r'wb')
168 fp = os.fdopen(infd, r'wb')
171 fp.write(s)
169 fp.write(s)
172 fp.close()
170 fp.close()
173 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
171 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
174 os.close(outfd)
172 os.close(outfd)
175 cmd = cmd.replace('INFILE', inname)
173 cmd = cmd.replace('INFILE', inname)
176 cmd = cmd.replace('OUTFILE', outname)
174 cmd = cmd.replace('OUTFILE', outname)
177 code = system(cmd)
175 code = system(cmd)
178 if pycompat.sysplatform == 'OpenVMS' and code & 1:
176 if pycompat.sysplatform == 'OpenVMS' and code & 1:
179 code = 0
177 code = 0
180 if code:
178 if code:
181 raise error.Abort(_("command '%s' failed: %s") %
179 raise error.Abort(_("command '%s' failed: %s") %
182 (cmd, explainexit(code)))
180 (cmd, explainexit(code)))
183 with open(outname, 'rb') as fp:
181 with open(outname, 'rb') as fp:
184 return fp.read()
182 return fp.read()
185 finally:
183 finally:
186 try:
184 try:
187 if inname:
185 if inname:
188 os.unlink(inname)
186 os.unlink(inname)
189 except OSError:
187 except OSError:
190 pass
188 pass
191 try:
189 try:
192 if outname:
190 if outname:
193 os.unlink(outname)
191 os.unlink(outname)
194 except OSError:
192 except OSError:
195 pass
193 pass
196
194
197 _filtertable = {
195 _filtertable = {
198 'tempfile:': tempfilter,
196 'tempfile:': tempfilter,
199 'pipe:': pipefilter,
197 'pipe:': pipefilter,
200 }
198 }
201
199
202 def filter(s, cmd):
200 def filter(s, cmd):
203 "filter a string through a command that transforms its input to its output"
201 "filter a string through a command that transforms its input to its output"
204 for name, fn in _filtertable.iteritems():
202 for name, fn in _filtertable.iteritems():
205 if cmd.startswith(name):
203 if cmd.startswith(name):
206 return fn(s, cmd[len(name):].lstrip())
204 return fn(s, cmd[len(name):].lstrip())
207 return pipefilter(s, cmd)
205 return pipefilter(s, cmd)
208
206
209 def mainfrozen():
207 def mainfrozen():
210 """return True if we are a frozen executable.
208 """return True if we are a frozen executable.
211
209
212 The code supports py2exe (most common, Windows only) and tools/freeze
210 The code supports py2exe (most common, Windows only) and tools/freeze
213 (portable, not much used).
211 (portable, not much used).
214 """
212 """
215 return (pycompat.safehasattr(sys, "frozen") or # new py2exe
213 return (pycompat.safehasattr(sys, "frozen") or # new py2exe
216 pycompat.safehasattr(sys, "importers") or # old py2exe
214 pycompat.safehasattr(sys, "importers") or # old py2exe
217 imp.is_frozen(u"__main__")) # tools/freeze
215 imp.is_frozen(u"__main__")) # tools/freeze
218
216
219 _hgexecutable = None
217 _hgexecutable = None
220
218
221 def hgexecutable():
219 def hgexecutable():
222 """return location of the 'hg' executable.
220 """return location of the 'hg' executable.
223
221
224 Defaults to $HG or 'hg' in the search path.
222 Defaults to $HG or 'hg' in the search path.
225 """
223 """
226 if _hgexecutable is None:
224 if _hgexecutable is None:
227 hg = encoding.environ.get('HG')
225 hg = encoding.environ.get('HG')
228 mainmod = sys.modules[r'__main__']
226 mainmod = sys.modules[r'__main__']
229 if hg:
227 if hg:
230 _sethgexecutable(hg)
228 _sethgexecutable(hg)
231 elif mainfrozen():
229 elif mainfrozen():
232 if getattr(sys, 'frozen', None) == 'macosx_app':
230 if getattr(sys, 'frozen', None) == 'macosx_app':
233 # Env variable set by py2app
231 # Env variable set by py2app
234 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
232 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
235 else:
233 else:
236 _sethgexecutable(pycompat.sysexecutable)
234 _sethgexecutable(pycompat.sysexecutable)
237 elif (os.path.basename(
235 elif (os.path.basename(
238 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
236 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
239 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
237 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
240 else:
238 else:
241 exe = findexe('hg') or os.path.basename(sys.argv[0])
239 exe = findexe('hg') or os.path.basename(sys.argv[0])
242 _sethgexecutable(exe)
240 _sethgexecutable(exe)
243 return _hgexecutable
241 return _hgexecutable
244
242
245 def _sethgexecutable(path):
243 def _sethgexecutable(path):
246 """set location of the 'hg' executable"""
244 """set location of the 'hg' executable"""
247 global _hgexecutable
245 global _hgexecutable
248 _hgexecutable = path
246 _hgexecutable = path
249
247
250 def _testfileno(f, stdf):
248 def _testfileno(f, stdf):
251 fileno = getattr(f, 'fileno', None)
249 fileno = getattr(f, 'fileno', None)
252 try:
250 try:
253 return fileno and fileno() == stdf.fileno()
251 return fileno and fileno() == stdf.fileno()
254 except io.UnsupportedOperation:
252 except io.UnsupportedOperation:
255 return False # fileno() raised UnsupportedOperation
253 return False # fileno() raised UnsupportedOperation
256
254
257 def isstdin(f):
255 def isstdin(f):
258 return _testfileno(f, sys.__stdin__)
256 return _testfileno(f, sys.__stdin__)
259
257
260 def isstdout(f):
258 def isstdout(f):
261 return _testfileno(f, sys.__stdout__)
259 return _testfileno(f, sys.__stdout__)
262
260
263 def protectstdio(uin, uout):
261 def protectstdio(uin, uout):
264 """Duplicate streams and redirect original if (uin, uout) are stdio
262 """Duplicate streams and redirect original if (uin, uout) are stdio
265
263
266 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
264 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
267 redirected to stderr so the output is still readable.
265 redirected to stderr so the output is still readable.
268
266
269 Returns (fin, fout) which point to the original (uin, uout) fds, but
267 Returns (fin, fout) which point to the original (uin, uout) fds, but
270 may be copy of (uin, uout). The returned streams can be considered
268 may be copy of (uin, uout). The returned streams can be considered
271 "owned" in that print(), exec(), etc. never reach to them.
269 "owned" in that print(), exec(), etc. never reach to them.
272 """
270 """
273 uout.flush()
271 uout.flush()
274 fin, fout = uin, uout
272 fin, fout = uin, uout
275 if uin is stdin:
273 if uin is stdin:
276 newfd = os.dup(uin.fileno())
274 newfd = os.dup(uin.fileno())
277 nullfd = os.open(os.devnull, os.O_RDONLY)
275 nullfd = os.open(os.devnull, os.O_RDONLY)
278 os.dup2(nullfd, uin.fileno())
276 os.dup2(nullfd, uin.fileno())
279 os.close(nullfd)
277 os.close(nullfd)
280 fin = os.fdopen(newfd, r'rb')
278 fin = os.fdopen(newfd, r'rb')
281 if uout is stdout:
279 if uout is stdout:
282 newfd = os.dup(uout.fileno())
280 newfd = os.dup(uout.fileno())
283 os.dup2(stderr.fileno(), uout.fileno())
281 os.dup2(stderr.fileno(), uout.fileno())
284 fout = os.fdopen(newfd, r'wb')
282 fout = os.fdopen(newfd, r'wb')
285 return fin, fout
283 return fin, fout
286
284
287 def restorestdio(uin, uout, fin, fout):
285 def restorestdio(uin, uout, fin, fout):
288 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
286 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
289 uout.flush()
287 uout.flush()
290 for f, uif in [(fin, uin), (fout, uout)]:
288 for f, uif in [(fin, uin), (fout, uout)]:
291 if f is not uif:
289 if f is not uif:
292 os.dup2(f.fileno(), uif.fileno())
290 os.dup2(f.fileno(), uif.fileno())
293 f.close()
291 f.close()
294
292
295 @contextlib.contextmanager
293 @contextlib.contextmanager
296 def protectedstdio(uin, uout):
294 def protectedstdio(uin, uout):
297 """Run code block with protected standard streams"""
295 """Run code block with protected standard streams"""
298 fin, fout = protectstdio(uin, uout)
296 fin, fout = protectstdio(uin, uout)
299 try:
297 try:
300 yield fin, fout
298 yield fin, fout
301 finally:
299 finally:
302 restorestdio(uin, uout, fin, fout)
300 restorestdio(uin, uout, fin, fout)
303
301
304 def shellenviron(environ=None):
302 def shellenviron(environ=None):
305 """return environ with optional override, useful for shelling out"""
303 """return environ with optional override, useful for shelling out"""
306 def py2shell(val):
304 def py2shell(val):
307 'convert python object into string that is useful to shell'
305 'convert python object into string that is useful to shell'
308 if val is None or val is False:
306 if val is None or val is False:
309 return '0'
307 return '0'
310 if val is True:
308 if val is True:
311 return '1'
309 return '1'
312 return pycompat.bytestr(val)
310 return pycompat.bytestr(val)
313 env = dict(encoding.environ)
311 env = dict(encoding.environ)
314 if environ:
312 if environ:
315 env.update((k, py2shell(v)) for k, v in environ.iteritems())
313 env.update((k, py2shell(v)) for k, v in environ.iteritems())
316 env['HG'] = hgexecutable()
314 env['HG'] = hgexecutable()
317 return env
315 return env
318
316
319 def system(cmd, environ=None, cwd=None, out=None):
317 def system(cmd, environ=None, cwd=None, out=None):
320 '''enhanced shell command execution.
318 '''enhanced shell command execution.
321 run with environment maybe modified, maybe in different dir.
319 run with environment maybe modified, maybe in different dir.
322
320
323 if out is specified, it is assumed to be a file-like object that has a
321 if out is specified, it is assumed to be a file-like object that has a
324 write() method. stdout and stderr will be redirected to out.'''
322 write() method. stdout and stderr will be redirected to out.'''
325 try:
323 try:
326 stdout.flush()
324 stdout.flush()
327 except Exception:
325 except Exception:
328 pass
326 pass
329 cmd = quotecommand(cmd)
327 cmd = quotecommand(cmd)
330 env = shellenviron(environ)
328 env = shellenviron(environ)
331 if out is None or isstdout(out):
329 if out is None or isstdout(out):
332 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
330 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
333 env=env, cwd=cwd)
331 env=env, cwd=cwd)
334 else:
332 else:
335 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
333 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
336 env=env, cwd=cwd, stdout=subprocess.PIPE,
334 env=env, cwd=cwd, stdout=subprocess.PIPE,
337 stderr=subprocess.STDOUT)
335 stderr=subprocess.STDOUT)
338 for line in iter(proc.stdout.readline, ''):
336 for line in iter(proc.stdout.readline, ''):
339 out.write(line)
337 out.write(line)
340 proc.wait()
338 proc.wait()
341 rc = proc.returncode
339 rc = proc.returncode
342 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
340 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
343 rc = 0
341 rc = 0
344 return rc
342 return rc
345
343
346 def gui():
344 def gui():
347 '''Are we running in a GUI?'''
345 '''Are we running in a GUI?'''
348 if pycompat.isdarwin:
346 if pycompat.isdarwin:
349 if 'SSH_CONNECTION' in encoding.environ:
347 if 'SSH_CONNECTION' in encoding.environ:
350 # handle SSH access to a box where the user is logged in
348 # handle SSH access to a box where the user is logged in
351 return False
349 return False
352 elif getattr(osutil, 'isgui', None):
350 elif getattr(osutil, 'isgui', None):
353 # check if a CoreGraphics session is available
351 # check if a CoreGraphics session is available
354 return osutil.isgui()
352 return osutil.isgui()
355 else:
353 else:
356 # pure build; use a safe default
354 # pure build; use a safe default
357 return True
355 return True
358 else:
356 else:
359 return pycompat.iswindows or encoding.environ.get("DISPLAY")
357 return pycompat.iswindows or encoding.environ.get("DISPLAY")
360
358
361 def hgcmd():
359 def hgcmd():
362 """Return the command used to execute current hg
360 """Return the command used to execute current hg
363
361
364 This is different from hgexecutable() because on Windows we want
362 This is different from hgexecutable() because on Windows we want
365 to avoid things opening new shell windows like batch files, so we
363 to avoid things opening new shell windows like batch files, so we
366 get either the python call or current executable.
364 get either the python call or current executable.
367 """
365 """
368 if mainfrozen():
366 if mainfrozen():
369 if getattr(sys, 'frozen', None) == 'macosx_app':
367 if getattr(sys, 'frozen', None) == 'macosx_app':
370 # Env variable set by py2app
368 # Env variable set by py2app
371 return [encoding.environ['EXECUTABLEPATH']]
369 return [encoding.environ['EXECUTABLEPATH']]
372 else:
370 else:
373 return [pycompat.sysexecutable]
371 return [pycompat.sysexecutable]
374 return _gethgcmd()
372 return _gethgcmd()
375
373
376 def rundetached(args, condfn):
374 def rundetached(args, condfn):
377 """Execute the argument list in a detached process.
375 """Execute the argument list in a detached process.
378
376
379 condfn is a callable which is called repeatedly and should return
377 condfn is a callable which is called repeatedly and should return
380 True once the child process is known to have started successfully.
378 True once the child process is known to have started successfully.
381 At this point, the child process PID is returned. If the child
379 At this point, the child process PID is returned. If the child
382 process fails to start or finishes before condfn() evaluates to
380 process fails to start or finishes before condfn() evaluates to
383 True, return -1.
381 True, return -1.
384 """
382 """
385 # Windows case is easier because the child process is either
383 # Windows case is easier because the child process is either
386 # successfully starting and validating the condition or exiting
384 # successfully starting and validating the condition or exiting
387 # on failure. We just poll on its PID. On Unix, if the child
385 # on failure. We just poll on its PID. On Unix, if the child
388 # process fails to start, it will be left in a zombie state until
386 # process fails to start, it will be left in a zombie state until
389 # the parent wait on it, which we cannot do since we expect a long
387 # the parent wait on it, which we cannot do since we expect a long
390 # running process on success. Instead we listen for SIGCHLD telling
388 # running process on success. Instead we listen for SIGCHLD telling
391 # us our child process terminated.
389 # us our child process terminated.
392 terminated = set()
390 terminated = set()
393 def handler(signum, frame):
391 def handler(signum, frame):
394 terminated.add(os.wait())
392 terminated.add(os.wait())
395 prevhandler = None
393 prevhandler = None
396 SIGCHLD = getattr(signal, 'SIGCHLD', None)
394 SIGCHLD = getattr(signal, 'SIGCHLD', None)
397 if SIGCHLD is not None:
395 if SIGCHLD is not None:
398 prevhandler = signal.signal(SIGCHLD, handler)
396 prevhandler = signal.signal(SIGCHLD, handler)
399 try:
397 try:
400 pid = spawndetached(args)
398 pid = spawndetached(args)
401 while not condfn():
399 while not condfn():
402 if ((pid in terminated or not testpid(pid))
400 if ((pid in terminated or not testpid(pid))
403 and not condfn()):
401 and not condfn()):
404 return -1
402 return -1
405 time.sleep(0.1)
403 time.sleep(0.1)
406 return pid
404 return pid
407 finally:
405 finally:
408 if prevhandler is not None:
406 if prevhandler is not None:
409 signal.signal(signal.SIGCHLD, prevhandler)
407 signal.signal(signal.SIGCHLD, prevhandler)
General Comments 0
You need to be logged in to leave comments. Login now