##// END OF EJS Templates
py3: make stdout line-buffered if connected to a TTY...
Manuel Jacob -
r45477:f9734b2d default
parent child Browse files
Show More
@@ -1,677 +1,701 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 errno
13 import errno
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 threading
19 import threading
20 import time
20 import time
21
21
22 from ..i18n import _
22 from ..i18n import _
23 from ..pycompat import (
23 from ..pycompat import (
24 getattr,
24 getattr,
25 open,
25 open,
26 )
26 )
27
27
28 from .. import (
28 from .. import (
29 encoding,
29 encoding,
30 error,
30 error,
31 policy,
31 policy,
32 pycompat,
32 pycompat,
33 )
33 )
34
34
35 # Import like this to keep import-checker happy
35 # Import like this to keep import-checker happy
36 from ..utils import resourceutil
36 from ..utils import resourceutil
37
37
38 osutil = policy.importmod('osutil')
38 osutil = policy.importmod('osutil')
39
39
40 stderr = pycompat.stderr
40 stderr = pycompat.stderr
41 stdin = pycompat.stdin
41 stdin = pycompat.stdin
42 stdout = pycompat.stdout
42 stdout = pycompat.stdout
43
43
44
44
45 def isatty(fp):
45 def isatty(fp):
46 try:
46 try:
47 return fp.isatty()
47 return fp.isatty()
48 except AttributeError:
48 except AttributeError:
49 return False
49 return False
50
50
51
51
52 if pycompat.ispy3:
53
54 class LineBufferedWrapper(object):
55 def __init__(self, orig):
56 self.orig = orig
57
58 def __getattr__(self, attr):
59 return getattr(self.orig, attr)
60
61 def write(self, s):
62 orig = self.orig
63 res = orig.write(s)
64 if s.endswith(b'\n'):
65 orig.flush()
66 return res
67
68 io.BufferedIOBase.register(LineBufferedWrapper)
69
70
52 # glibc determines buffering on first write to stdout - if we replace a TTY
71 # glibc determines buffering on first write to stdout - if we replace a TTY
53 # destined stdout with a pipe destined stdout (e.g. pager), we want line
72 # destined stdout with a pipe destined stdout (e.g. pager), we want line
54 # buffering (or unbuffered, on Windows)
73 # buffering (or unbuffered, on Windows)
55 if isatty(stdout):
74 if isatty(stdout):
56 if pycompat.iswindows:
75 if pycompat.iswindows:
57 # Windows doesn't support line buffering
76 # Windows doesn't support line buffering
58 stdout = os.fdopen(stdout.fileno(), 'wb', 0)
77 stdout = os.fdopen(stdout.fileno(), 'wb', 0)
59 elif not pycompat.ispy3:
78 elif pycompat.ispy3:
60 # on Python 3, stdout (sys.stdout.buffer) is already line buffered and
79 # On Python 3, buffered binary streams can't be set line-buffered.
61 # buffering=1 is not handled in binary mode
80 # Therefore we have a wrapper that implements line buffering.
81 if isinstance(stdout, io.BufferedIOBase) and not isinstance(
82 stdout, LineBufferedWrapper
83 ):
84 stdout = LineBufferedWrapper(stdout)
85 else:
62 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
86 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
63
87
64 if pycompat.iswindows:
88 if pycompat.iswindows:
65 from .. import windows as platform
89 from .. import windows as platform
66
90
67 stdout = platform.winstdout(stdout)
91 stdout = platform.winstdout(stdout)
68 else:
92 else:
69 from .. import posix as platform
93 from .. import posix as platform
70
94
71 findexe = platform.findexe
95 findexe = platform.findexe
72 _gethgcmd = platform.gethgcmd
96 _gethgcmd = platform.gethgcmd
73 getuser = platform.getuser
97 getuser = platform.getuser
74 getpid = os.getpid
98 getpid = os.getpid
75 hidewindow = platform.hidewindow
99 hidewindow = platform.hidewindow
76 readpipe = platform.readpipe
100 readpipe = platform.readpipe
77 setbinary = platform.setbinary
101 setbinary = platform.setbinary
78 setsignalhandler = platform.setsignalhandler
102 setsignalhandler = platform.setsignalhandler
79 shellquote = platform.shellquote
103 shellquote = platform.shellquote
80 shellsplit = platform.shellsplit
104 shellsplit = platform.shellsplit
81 spawndetached = platform.spawndetached
105 spawndetached = platform.spawndetached
82 sshargs = platform.sshargs
106 sshargs = platform.sshargs
83 testpid = platform.testpid
107 testpid = platform.testpid
84
108
85 try:
109 try:
86 setprocname = osutil.setprocname
110 setprocname = osutil.setprocname
87 except AttributeError:
111 except AttributeError:
88 pass
112 pass
89 try:
113 try:
90 unblocksignal = osutil.unblocksignal
114 unblocksignal = osutil.unblocksignal
91 except AttributeError:
115 except AttributeError:
92 pass
116 pass
93
117
94 closefds = pycompat.isposix
118 closefds = pycompat.isposix
95
119
96
120
97 def explainexit(code):
121 def explainexit(code):
98 """return a message describing a subprocess status
122 """return a message describing a subprocess status
99 (codes from kill are negative - not os.system/wait encoding)"""
123 (codes from kill are negative - not os.system/wait encoding)"""
100 if code >= 0:
124 if code >= 0:
101 return _(b"exited with status %d") % code
125 return _(b"exited with status %d") % code
102 return _(b"killed by signal %d") % -code
126 return _(b"killed by signal %d") % -code
103
127
104
128
105 class _pfile(object):
129 class _pfile(object):
106 """File-like wrapper for a stream opened by subprocess.Popen()"""
130 """File-like wrapper for a stream opened by subprocess.Popen()"""
107
131
108 def __init__(self, proc, fp):
132 def __init__(self, proc, fp):
109 self._proc = proc
133 self._proc = proc
110 self._fp = fp
134 self._fp = fp
111
135
112 def close(self):
136 def close(self):
113 # unlike os.popen(), this returns an integer in subprocess coding
137 # unlike os.popen(), this returns an integer in subprocess coding
114 self._fp.close()
138 self._fp.close()
115 return self._proc.wait()
139 return self._proc.wait()
116
140
117 def __iter__(self):
141 def __iter__(self):
118 return iter(self._fp)
142 return iter(self._fp)
119
143
120 def __getattr__(self, attr):
144 def __getattr__(self, attr):
121 return getattr(self._fp, attr)
145 return getattr(self._fp, attr)
122
146
123 def __enter__(self):
147 def __enter__(self):
124 return self
148 return self
125
149
126 def __exit__(self, exc_type, exc_value, exc_tb):
150 def __exit__(self, exc_type, exc_value, exc_tb):
127 self.close()
151 self.close()
128
152
129
153
130 def popen(cmd, mode=b'rb', bufsize=-1):
154 def popen(cmd, mode=b'rb', bufsize=-1):
131 if mode == b'rb':
155 if mode == b'rb':
132 return _popenreader(cmd, bufsize)
156 return _popenreader(cmd, bufsize)
133 elif mode == b'wb':
157 elif mode == b'wb':
134 return _popenwriter(cmd, bufsize)
158 return _popenwriter(cmd, bufsize)
135 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
159 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
136
160
137
161
138 def _popenreader(cmd, bufsize):
162 def _popenreader(cmd, bufsize):
139 p = subprocess.Popen(
163 p = subprocess.Popen(
140 tonativestr(cmd),
164 tonativestr(cmd),
141 shell=True,
165 shell=True,
142 bufsize=bufsize,
166 bufsize=bufsize,
143 close_fds=closefds,
167 close_fds=closefds,
144 stdout=subprocess.PIPE,
168 stdout=subprocess.PIPE,
145 )
169 )
146 return _pfile(p, p.stdout)
170 return _pfile(p, p.stdout)
147
171
148
172
149 def _popenwriter(cmd, bufsize):
173 def _popenwriter(cmd, bufsize):
150 p = subprocess.Popen(
174 p = subprocess.Popen(
151 tonativestr(cmd),
175 tonativestr(cmd),
152 shell=True,
176 shell=True,
153 bufsize=bufsize,
177 bufsize=bufsize,
154 close_fds=closefds,
178 close_fds=closefds,
155 stdin=subprocess.PIPE,
179 stdin=subprocess.PIPE,
156 )
180 )
157 return _pfile(p, p.stdin)
181 return _pfile(p, p.stdin)
158
182
159
183
160 def popen2(cmd, env=None):
184 def popen2(cmd, env=None):
161 # Setting bufsize to -1 lets the system decide the buffer size.
185 # Setting bufsize to -1 lets the system decide the buffer size.
162 # The default for bufsize is 0, meaning unbuffered. This leads to
186 # The default for bufsize is 0, meaning unbuffered. This leads to
163 # poor performance on Mac OS X: http://bugs.python.org/issue4194
187 # poor performance on Mac OS X: http://bugs.python.org/issue4194
164 p = subprocess.Popen(
188 p = subprocess.Popen(
165 tonativestr(cmd),
189 tonativestr(cmd),
166 shell=True,
190 shell=True,
167 bufsize=-1,
191 bufsize=-1,
168 close_fds=closefds,
192 close_fds=closefds,
169 stdin=subprocess.PIPE,
193 stdin=subprocess.PIPE,
170 stdout=subprocess.PIPE,
194 stdout=subprocess.PIPE,
171 env=tonativeenv(env),
195 env=tonativeenv(env),
172 )
196 )
173 return p.stdin, p.stdout
197 return p.stdin, p.stdout
174
198
175
199
176 def popen3(cmd, env=None):
200 def popen3(cmd, env=None):
177 stdin, stdout, stderr, p = popen4(cmd, env)
201 stdin, stdout, stderr, p = popen4(cmd, env)
178 return stdin, stdout, stderr
202 return stdin, stdout, stderr
179
203
180
204
181 def popen4(cmd, env=None, bufsize=-1):
205 def popen4(cmd, env=None, bufsize=-1):
182 p = subprocess.Popen(
206 p = subprocess.Popen(
183 tonativestr(cmd),
207 tonativestr(cmd),
184 shell=True,
208 shell=True,
185 bufsize=bufsize,
209 bufsize=bufsize,
186 close_fds=closefds,
210 close_fds=closefds,
187 stdin=subprocess.PIPE,
211 stdin=subprocess.PIPE,
188 stdout=subprocess.PIPE,
212 stdout=subprocess.PIPE,
189 stderr=subprocess.PIPE,
213 stderr=subprocess.PIPE,
190 env=tonativeenv(env),
214 env=tonativeenv(env),
191 )
215 )
192 return p.stdin, p.stdout, p.stderr, p
216 return p.stdin, p.stdout, p.stderr, p
193
217
194
218
195 def pipefilter(s, cmd):
219 def pipefilter(s, cmd):
196 '''filter string S through command CMD, returning its output'''
220 '''filter string S through command CMD, returning its output'''
197 p = subprocess.Popen(
221 p = subprocess.Popen(
198 tonativestr(cmd),
222 tonativestr(cmd),
199 shell=True,
223 shell=True,
200 close_fds=closefds,
224 close_fds=closefds,
201 stdin=subprocess.PIPE,
225 stdin=subprocess.PIPE,
202 stdout=subprocess.PIPE,
226 stdout=subprocess.PIPE,
203 )
227 )
204 pout, perr = p.communicate(s)
228 pout, perr = p.communicate(s)
205 return pout
229 return pout
206
230
207
231
208 def tempfilter(s, cmd):
232 def tempfilter(s, cmd):
209 '''filter string S through a pair of temporary files with CMD.
233 '''filter string S through a pair of temporary files with CMD.
210 CMD is used as a template to create the real command to be run,
234 CMD is used as a template to create the real command to be run,
211 with the strings INFILE and OUTFILE replaced by the real names of
235 with the strings INFILE and OUTFILE replaced by the real names of
212 the temporary files generated.'''
236 the temporary files generated.'''
213 inname, outname = None, None
237 inname, outname = None, None
214 try:
238 try:
215 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
239 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
216 fp = os.fdopen(infd, 'wb')
240 fp = os.fdopen(infd, 'wb')
217 fp.write(s)
241 fp.write(s)
218 fp.close()
242 fp.close()
219 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
243 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
220 os.close(outfd)
244 os.close(outfd)
221 cmd = cmd.replace(b'INFILE', inname)
245 cmd = cmd.replace(b'INFILE', inname)
222 cmd = cmd.replace(b'OUTFILE', outname)
246 cmd = cmd.replace(b'OUTFILE', outname)
223 code = system(cmd)
247 code = system(cmd)
224 if pycompat.sysplatform == b'OpenVMS' and code & 1:
248 if pycompat.sysplatform == b'OpenVMS' and code & 1:
225 code = 0
249 code = 0
226 if code:
250 if code:
227 raise error.Abort(
251 raise error.Abort(
228 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
252 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
229 )
253 )
230 with open(outname, b'rb') as fp:
254 with open(outname, b'rb') as fp:
231 return fp.read()
255 return fp.read()
232 finally:
256 finally:
233 try:
257 try:
234 if inname:
258 if inname:
235 os.unlink(inname)
259 os.unlink(inname)
236 except OSError:
260 except OSError:
237 pass
261 pass
238 try:
262 try:
239 if outname:
263 if outname:
240 os.unlink(outname)
264 os.unlink(outname)
241 except OSError:
265 except OSError:
242 pass
266 pass
243
267
244
268
245 _filtertable = {
269 _filtertable = {
246 b'tempfile:': tempfilter,
270 b'tempfile:': tempfilter,
247 b'pipe:': pipefilter,
271 b'pipe:': pipefilter,
248 }
272 }
249
273
250
274
251 def filter(s, cmd):
275 def filter(s, cmd):
252 """filter a string through a command that transforms its input to its
276 """filter a string through a command that transforms its input to its
253 output"""
277 output"""
254 for name, fn in pycompat.iteritems(_filtertable):
278 for name, fn in pycompat.iteritems(_filtertable):
255 if cmd.startswith(name):
279 if cmd.startswith(name):
256 return fn(s, cmd[len(name) :].lstrip())
280 return fn(s, cmd[len(name) :].lstrip())
257 return pipefilter(s, cmd)
281 return pipefilter(s, cmd)
258
282
259
283
260 _hgexecutable = None
284 _hgexecutable = None
261
285
262
286
263 def hgexecutable():
287 def hgexecutable():
264 """return location of the 'hg' executable.
288 """return location of the 'hg' executable.
265
289
266 Defaults to $HG or 'hg' in the search path.
290 Defaults to $HG or 'hg' in the search path.
267 """
291 """
268 if _hgexecutable is None:
292 if _hgexecutable is None:
269 hg = encoding.environ.get(b'HG')
293 hg = encoding.environ.get(b'HG')
270 mainmod = sys.modules['__main__']
294 mainmod = sys.modules['__main__']
271 if hg:
295 if hg:
272 _sethgexecutable(hg)
296 _sethgexecutable(hg)
273 elif resourceutil.mainfrozen():
297 elif resourceutil.mainfrozen():
274 if getattr(sys, 'frozen', None) == 'macosx_app':
298 if getattr(sys, 'frozen', None) == 'macosx_app':
275 # Env variable set by py2app
299 # Env variable set by py2app
276 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
300 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
277 else:
301 else:
278 _sethgexecutable(pycompat.sysexecutable)
302 _sethgexecutable(pycompat.sysexecutable)
279 elif (
303 elif (
280 not pycompat.iswindows
304 not pycompat.iswindows
281 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
305 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
282 ):
306 ):
283 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
307 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
284 else:
308 else:
285 _sethgexecutable(
309 _sethgexecutable(
286 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
310 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
287 )
311 )
288 return _hgexecutable
312 return _hgexecutable
289
313
290
314
291 def _sethgexecutable(path):
315 def _sethgexecutable(path):
292 """set location of the 'hg' executable"""
316 """set location of the 'hg' executable"""
293 global _hgexecutable
317 global _hgexecutable
294 _hgexecutable = path
318 _hgexecutable = path
295
319
296
320
297 def _testfileno(f, stdf):
321 def _testfileno(f, stdf):
298 fileno = getattr(f, 'fileno', None)
322 fileno = getattr(f, 'fileno', None)
299 try:
323 try:
300 return fileno and fileno() == stdf.fileno()
324 return fileno and fileno() == stdf.fileno()
301 except io.UnsupportedOperation:
325 except io.UnsupportedOperation:
302 return False # fileno() raised UnsupportedOperation
326 return False # fileno() raised UnsupportedOperation
303
327
304
328
305 def isstdin(f):
329 def isstdin(f):
306 return _testfileno(f, sys.__stdin__)
330 return _testfileno(f, sys.__stdin__)
307
331
308
332
309 def isstdout(f):
333 def isstdout(f):
310 return _testfileno(f, sys.__stdout__)
334 return _testfileno(f, sys.__stdout__)
311
335
312
336
313 def protectstdio(uin, uout):
337 def protectstdio(uin, uout):
314 """Duplicate streams and redirect original if (uin, uout) are stdio
338 """Duplicate streams and redirect original if (uin, uout) are stdio
315
339
316 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
340 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
317 redirected to stderr so the output is still readable.
341 redirected to stderr so the output is still readable.
318
342
319 Returns (fin, fout) which point to the original (uin, uout) fds, but
343 Returns (fin, fout) which point to the original (uin, uout) fds, but
320 may be copy of (uin, uout). The returned streams can be considered
344 may be copy of (uin, uout). The returned streams can be considered
321 "owned" in that print(), exec(), etc. never reach to them.
345 "owned" in that print(), exec(), etc. never reach to them.
322 """
346 """
323 uout.flush()
347 uout.flush()
324 fin, fout = uin, uout
348 fin, fout = uin, uout
325 if _testfileno(uin, stdin):
349 if _testfileno(uin, stdin):
326 newfd = os.dup(uin.fileno())
350 newfd = os.dup(uin.fileno())
327 nullfd = os.open(os.devnull, os.O_RDONLY)
351 nullfd = os.open(os.devnull, os.O_RDONLY)
328 os.dup2(nullfd, uin.fileno())
352 os.dup2(nullfd, uin.fileno())
329 os.close(nullfd)
353 os.close(nullfd)
330 fin = os.fdopen(newfd, 'rb')
354 fin = os.fdopen(newfd, 'rb')
331 if _testfileno(uout, stdout):
355 if _testfileno(uout, stdout):
332 newfd = os.dup(uout.fileno())
356 newfd = os.dup(uout.fileno())
333 os.dup2(stderr.fileno(), uout.fileno())
357 os.dup2(stderr.fileno(), uout.fileno())
334 fout = os.fdopen(newfd, 'wb')
358 fout = os.fdopen(newfd, 'wb')
335 return fin, fout
359 return fin, fout
336
360
337
361
338 def restorestdio(uin, uout, fin, fout):
362 def restorestdio(uin, uout, fin, fout):
339 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
363 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
340 uout.flush()
364 uout.flush()
341 for f, uif in [(fin, uin), (fout, uout)]:
365 for f, uif in [(fin, uin), (fout, uout)]:
342 if f is not uif:
366 if f is not uif:
343 os.dup2(f.fileno(), uif.fileno())
367 os.dup2(f.fileno(), uif.fileno())
344 f.close()
368 f.close()
345
369
346
370
347 def shellenviron(environ=None):
371 def shellenviron(environ=None):
348 """return environ with optional override, useful for shelling out"""
372 """return environ with optional override, useful for shelling out"""
349
373
350 def py2shell(val):
374 def py2shell(val):
351 """convert python object into string that is useful to shell"""
375 """convert python object into string that is useful to shell"""
352 if val is None or val is False:
376 if val is None or val is False:
353 return b'0'
377 return b'0'
354 if val is True:
378 if val is True:
355 return b'1'
379 return b'1'
356 return pycompat.bytestr(val)
380 return pycompat.bytestr(val)
357
381
358 env = dict(encoding.environ)
382 env = dict(encoding.environ)
359 if environ:
383 if environ:
360 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
384 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
361 env[b'HG'] = hgexecutable()
385 env[b'HG'] = hgexecutable()
362 return env
386 return env
363
387
364
388
365 if pycompat.iswindows:
389 if pycompat.iswindows:
366
390
367 def shelltonative(cmd, env):
391 def shelltonative(cmd, env):
368 return platform.shelltocmdexe( # pytype: disable=module-attr
392 return platform.shelltocmdexe( # pytype: disable=module-attr
369 cmd, shellenviron(env)
393 cmd, shellenviron(env)
370 )
394 )
371
395
372 tonativestr = encoding.strfromlocal
396 tonativestr = encoding.strfromlocal
373 else:
397 else:
374
398
375 def shelltonative(cmd, env):
399 def shelltonative(cmd, env):
376 return cmd
400 return cmd
377
401
378 tonativestr = pycompat.identity
402 tonativestr = pycompat.identity
379
403
380
404
381 def tonativeenv(env):
405 def tonativeenv(env):
382 '''convert the environment from bytes to strings suitable for Popen(), etc.
406 '''convert the environment from bytes to strings suitable for Popen(), etc.
383 '''
407 '''
384 return pycompat.rapply(tonativestr, env)
408 return pycompat.rapply(tonativestr, env)
385
409
386
410
387 def system(cmd, environ=None, cwd=None, out=None):
411 def system(cmd, environ=None, cwd=None, out=None):
388 '''enhanced shell command execution.
412 '''enhanced shell command execution.
389 run with environment maybe modified, maybe in different dir.
413 run with environment maybe modified, maybe in different dir.
390
414
391 if out is specified, it is assumed to be a file-like object that has a
415 if out is specified, it is assumed to be a file-like object that has a
392 write() method. stdout and stderr will be redirected to out.'''
416 write() method. stdout and stderr will be redirected to out.'''
393 try:
417 try:
394 stdout.flush()
418 stdout.flush()
395 except Exception:
419 except Exception:
396 pass
420 pass
397 env = shellenviron(environ)
421 env = shellenviron(environ)
398 if out is None or isstdout(out):
422 if out is None or isstdout(out):
399 rc = subprocess.call(
423 rc = subprocess.call(
400 tonativestr(cmd),
424 tonativestr(cmd),
401 shell=True,
425 shell=True,
402 close_fds=closefds,
426 close_fds=closefds,
403 env=tonativeenv(env),
427 env=tonativeenv(env),
404 cwd=pycompat.rapply(tonativestr, cwd),
428 cwd=pycompat.rapply(tonativestr, cwd),
405 )
429 )
406 else:
430 else:
407 proc = subprocess.Popen(
431 proc = subprocess.Popen(
408 tonativestr(cmd),
432 tonativestr(cmd),
409 shell=True,
433 shell=True,
410 close_fds=closefds,
434 close_fds=closefds,
411 env=tonativeenv(env),
435 env=tonativeenv(env),
412 cwd=pycompat.rapply(tonativestr, cwd),
436 cwd=pycompat.rapply(tonativestr, cwd),
413 stdout=subprocess.PIPE,
437 stdout=subprocess.PIPE,
414 stderr=subprocess.STDOUT,
438 stderr=subprocess.STDOUT,
415 )
439 )
416 for line in iter(proc.stdout.readline, b''):
440 for line in iter(proc.stdout.readline, b''):
417 out.write(line)
441 out.write(line)
418 proc.wait()
442 proc.wait()
419 rc = proc.returncode
443 rc = proc.returncode
420 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
444 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
421 rc = 0
445 rc = 0
422 return rc
446 return rc
423
447
424
448
425 _is_gui = None
449 _is_gui = None
426
450
427
451
428 def _gui():
452 def _gui():
429 '''Are we running in a GUI?'''
453 '''Are we running in a GUI?'''
430 if pycompat.isdarwin:
454 if pycompat.isdarwin:
431 if b'SSH_CONNECTION' in encoding.environ:
455 if b'SSH_CONNECTION' in encoding.environ:
432 # handle SSH access to a box where the user is logged in
456 # handle SSH access to a box where the user is logged in
433 return False
457 return False
434 elif getattr(osutil, 'isgui', None):
458 elif getattr(osutil, 'isgui', None):
435 # check if a CoreGraphics session is available
459 # check if a CoreGraphics session is available
436 return osutil.isgui()
460 return osutil.isgui()
437 else:
461 else:
438 # pure build; use a safe default
462 # pure build; use a safe default
439 return True
463 return True
440 else:
464 else:
441 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
465 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
442
466
443
467
444 def gui():
468 def gui():
445 global _is_gui
469 global _is_gui
446 if _is_gui is None:
470 if _is_gui is None:
447 _is_gui = _gui()
471 _is_gui = _gui()
448 return _is_gui
472 return _is_gui
449
473
450
474
451 def hgcmd():
475 def hgcmd():
452 """Return the command used to execute current hg
476 """Return the command used to execute current hg
453
477
454 This is different from hgexecutable() because on Windows we want
478 This is different from hgexecutable() because on Windows we want
455 to avoid things opening new shell windows like batch files, so we
479 to avoid things opening new shell windows like batch files, so we
456 get either the python call or current executable.
480 get either the python call or current executable.
457 """
481 """
458 if resourceutil.mainfrozen():
482 if resourceutil.mainfrozen():
459 if getattr(sys, 'frozen', None) == 'macosx_app':
483 if getattr(sys, 'frozen', None) == 'macosx_app':
460 # Env variable set by py2app
484 # Env variable set by py2app
461 return [encoding.environ[b'EXECUTABLEPATH']]
485 return [encoding.environ[b'EXECUTABLEPATH']]
462 else:
486 else:
463 return [pycompat.sysexecutable]
487 return [pycompat.sysexecutable]
464 return _gethgcmd()
488 return _gethgcmd()
465
489
466
490
467 def rundetached(args, condfn):
491 def rundetached(args, condfn):
468 """Execute the argument list in a detached process.
492 """Execute the argument list in a detached process.
469
493
470 condfn is a callable which is called repeatedly and should return
494 condfn is a callable which is called repeatedly and should return
471 True once the child process is known to have started successfully.
495 True once the child process is known to have started successfully.
472 At this point, the child process PID is returned. If the child
496 At this point, the child process PID is returned. If the child
473 process fails to start or finishes before condfn() evaluates to
497 process fails to start or finishes before condfn() evaluates to
474 True, return -1.
498 True, return -1.
475 """
499 """
476 # Windows case is easier because the child process is either
500 # Windows case is easier because the child process is either
477 # successfully starting and validating the condition or exiting
501 # successfully starting and validating the condition or exiting
478 # on failure. We just poll on its PID. On Unix, if the child
502 # on failure. We just poll on its PID. On Unix, if the child
479 # process fails to start, it will be left in a zombie state until
503 # process fails to start, it will be left in a zombie state until
480 # the parent wait on it, which we cannot do since we expect a long
504 # the parent wait on it, which we cannot do since we expect a long
481 # running process on success. Instead we listen for SIGCHLD telling
505 # running process on success. Instead we listen for SIGCHLD telling
482 # us our child process terminated.
506 # us our child process terminated.
483 terminated = set()
507 terminated = set()
484
508
485 def handler(signum, frame):
509 def handler(signum, frame):
486 terminated.add(os.wait())
510 terminated.add(os.wait())
487
511
488 prevhandler = None
512 prevhandler = None
489 SIGCHLD = getattr(signal, 'SIGCHLD', None)
513 SIGCHLD = getattr(signal, 'SIGCHLD', None)
490 if SIGCHLD is not None:
514 if SIGCHLD is not None:
491 prevhandler = signal.signal(SIGCHLD, handler)
515 prevhandler = signal.signal(SIGCHLD, handler)
492 try:
516 try:
493 pid = spawndetached(args)
517 pid = spawndetached(args)
494 while not condfn():
518 while not condfn():
495 if (pid in terminated or not testpid(pid)) and not condfn():
519 if (pid in terminated or not testpid(pid)) and not condfn():
496 return -1
520 return -1
497 time.sleep(0.1)
521 time.sleep(0.1)
498 return pid
522 return pid
499 finally:
523 finally:
500 if prevhandler is not None:
524 if prevhandler is not None:
501 signal.signal(signal.SIGCHLD, prevhandler)
525 signal.signal(signal.SIGCHLD, prevhandler)
502
526
503
527
504 @contextlib.contextmanager
528 @contextlib.contextmanager
505 def uninterruptible(warn):
529 def uninterruptible(warn):
506 """Inhibit SIGINT handling on a region of code.
530 """Inhibit SIGINT handling on a region of code.
507
531
508 Note that if this is called in a non-main thread, it turns into a no-op.
532 Note that if this is called in a non-main thread, it turns into a no-op.
509
533
510 Args:
534 Args:
511 warn: A callable which takes no arguments, and returns True if the
535 warn: A callable which takes no arguments, and returns True if the
512 previous signal handling should be restored.
536 previous signal handling should be restored.
513 """
537 """
514
538
515 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
539 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
516 shouldbail = []
540 shouldbail = []
517
541
518 def disabledsiginthandler(*args):
542 def disabledsiginthandler(*args):
519 if warn():
543 if warn():
520 signal.signal(signal.SIGINT, oldsiginthandler[0])
544 signal.signal(signal.SIGINT, oldsiginthandler[0])
521 del oldsiginthandler[0]
545 del oldsiginthandler[0]
522 shouldbail.append(True)
546 shouldbail.append(True)
523
547
524 try:
548 try:
525 try:
549 try:
526 signal.signal(signal.SIGINT, disabledsiginthandler)
550 signal.signal(signal.SIGINT, disabledsiginthandler)
527 except ValueError:
551 except ValueError:
528 # wrong thread, oh well, we tried
552 # wrong thread, oh well, we tried
529 del oldsiginthandler[0]
553 del oldsiginthandler[0]
530 yield
554 yield
531 finally:
555 finally:
532 if oldsiginthandler:
556 if oldsiginthandler:
533 signal.signal(signal.SIGINT, oldsiginthandler[0])
557 signal.signal(signal.SIGINT, oldsiginthandler[0])
534 if shouldbail:
558 if shouldbail:
535 raise KeyboardInterrupt
559 raise KeyboardInterrupt
536
560
537
561
538 if pycompat.iswindows:
562 if pycompat.iswindows:
539 # no fork on Windows, but we can create a detached process
563 # no fork on Windows, but we can create a detached process
540 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
564 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
541 # No stdlib constant exists for this value
565 # No stdlib constant exists for this value
542 DETACHED_PROCESS = 0x00000008
566 DETACHED_PROCESS = 0x00000008
543 # Following creation flags might create a console GUI window.
567 # Following creation flags might create a console GUI window.
544 # Using subprocess.CREATE_NEW_CONSOLE might helps.
568 # Using subprocess.CREATE_NEW_CONSOLE might helps.
545 # See https://phab.mercurial-scm.org/D1701 for discussion
569 # See https://phab.mercurial-scm.org/D1701 for discussion
546 _creationflags = (
570 _creationflags = (
547 DETACHED_PROCESS
571 DETACHED_PROCESS
548 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
572 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
549 )
573 )
550
574
551 def runbgcommand(
575 def runbgcommand(
552 script,
576 script,
553 env,
577 env,
554 shell=False,
578 shell=False,
555 stdout=None,
579 stdout=None,
556 stderr=None,
580 stderr=None,
557 ensurestart=True,
581 ensurestart=True,
558 record_wait=None,
582 record_wait=None,
559 ):
583 ):
560 '''Spawn a command without waiting for it to finish.'''
584 '''Spawn a command without waiting for it to finish.'''
561 # we can't use close_fds *and* redirect stdin. I'm not sure that we
585 # we can't use close_fds *and* redirect stdin. I'm not sure that we
562 # need to because the detached process has no console connection.
586 # need to because the detached process has no console connection.
563 p = subprocess.Popen(
587 p = subprocess.Popen(
564 tonativestr(script),
588 tonativestr(script),
565 shell=shell,
589 shell=shell,
566 env=tonativeenv(env),
590 env=tonativeenv(env),
567 close_fds=True,
591 close_fds=True,
568 creationflags=_creationflags,
592 creationflags=_creationflags,
569 stdout=stdout,
593 stdout=stdout,
570 stderr=stderr,
594 stderr=stderr,
571 )
595 )
572 if record_wait is not None:
596 if record_wait is not None:
573 record_wait(p.wait)
597 record_wait(p.wait)
574
598
575
599
576 else:
600 else:
577
601
578 def runbgcommand(
602 def runbgcommand(
579 cmd,
603 cmd,
580 env,
604 env,
581 shell=False,
605 shell=False,
582 stdout=None,
606 stdout=None,
583 stderr=None,
607 stderr=None,
584 ensurestart=True,
608 ensurestart=True,
585 record_wait=None,
609 record_wait=None,
586 ):
610 ):
587 '''Spawn a command without waiting for it to finish.
611 '''Spawn a command without waiting for it to finish.
588
612
589
613
590 When `record_wait` is not None, the spawned process will not be fully
614 When `record_wait` is not None, the spawned process will not be fully
591 detached and the `record_wait` argument will be called with a the
615 detached and the `record_wait` argument will be called with a the
592 `Subprocess.wait` function for the spawned process. This is mostly
616 `Subprocess.wait` function for the spawned process. This is mostly
593 useful for developers that need to make sure the spawned process
617 useful for developers that need to make sure the spawned process
594 finished before a certain point. (eg: writing test)'''
618 finished before a certain point. (eg: writing test)'''
595 if pycompat.isdarwin:
619 if pycompat.isdarwin:
596 # avoid crash in CoreFoundation in case another thread
620 # avoid crash in CoreFoundation in case another thread
597 # calls gui() while we're calling fork().
621 # calls gui() while we're calling fork().
598 gui()
622 gui()
599
623
600 # double-fork to completely detach from the parent process
624 # double-fork to completely detach from the parent process
601 # based on http://code.activestate.com/recipes/278731
625 # based on http://code.activestate.com/recipes/278731
602 if record_wait is None:
626 if record_wait is None:
603 pid = os.fork()
627 pid = os.fork()
604 if pid:
628 if pid:
605 if not ensurestart:
629 if not ensurestart:
606 # Even though we're not waiting on the child process,
630 # Even though we're not waiting on the child process,
607 # we still must call waitpid() on it at some point so
631 # we still must call waitpid() on it at some point so
608 # it's not a zombie/defunct. This is especially relevant for
632 # it's not a zombie/defunct. This is especially relevant for
609 # chg since the parent process won't die anytime soon.
633 # chg since the parent process won't die anytime soon.
610 # We use a thread to make the overhead tiny.
634 # We use a thread to make the overhead tiny.
611 def _do_wait():
635 def _do_wait():
612 os.waitpid(pid, 0)
636 os.waitpid(pid, 0)
613
637
614 threading.Thread(target=_do_wait, daemon=True).start()
638 threading.Thread(target=_do_wait, daemon=True).start()
615 return
639 return
616 # Parent process
640 # Parent process
617 (_pid, status) = os.waitpid(pid, 0)
641 (_pid, status) = os.waitpid(pid, 0)
618 if os.WIFEXITED(status):
642 if os.WIFEXITED(status):
619 returncode = os.WEXITSTATUS(status)
643 returncode = os.WEXITSTATUS(status)
620 else:
644 else:
621 returncode = -(os.WTERMSIG(status))
645 returncode = -(os.WTERMSIG(status))
622 if returncode != 0:
646 if returncode != 0:
623 # The child process's return code is 0 on success, an errno
647 # The child process's return code is 0 on success, an errno
624 # value on failure, or 255 if we don't have a valid errno
648 # value on failure, or 255 if we don't have a valid errno
625 # value.
649 # value.
626 #
650 #
627 # (It would be slightly nicer to return the full exception info
651 # (It would be slightly nicer to return the full exception info
628 # over a pipe as the subprocess module does. For now it
652 # over a pipe as the subprocess module does. For now it
629 # doesn't seem worth adding that complexity here, though.)
653 # doesn't seem worth adding that complexity here, though.)
630 if returncode == 255:
654 if returncode == 255:
631 returncode = errno.EINVAL
655 returncode = errno.EINVAL
632 raise OSError(
656 raise OSError(
633 returncode,
657 returncode,
634 b'error running %r: %s'
658 b'error running %r: %s'
635 % (cmd, os.strerror(returncode)),
659 % (cmd, os.strerror(returncode)),
636 )
660 )
637 return
661 return
638
662
639 returncode = 255
663 returncode = 255
640 try:
664 try:
641 if record_wait is None:
665 if record_wait is None:
642 # Start a new session
666 # Start a new session
643 os.setsid()
667 os.setsid()
644
668
645 stdin = open(os.devnull, b'r')
669 stdin = open(os.devnull, b'r')
646 if stdout is None:
670 if stdout is None:
647 stdout = open(os.devnull, b'w')
671 stdout = open(os.devnull, b'w')
648 if stderr is None:
672 if stderr is None:
649 stderr = open(os.devnull, b'w')
673 stderr = open(os.devnull, b'w')
650
674
651 # connect stdin to devnull to make sure the subprocess can't
675 # connect stdin to devnull to make sure the subprocess can't
652 # muck up that stream for mercurial.
676 # muck up that stream for mercurial.
653 p = subprocess.Popen(
677 p = subprocess.Popen(
654 cmd,
678 cmd,
655 shell=shell,
679 shell=shell,
656 env=env,
680 env=env,
657 close_fds=True,
681 close_fds=True,
658 stdin=stdin,
682 stdin=stdin,
659 stdout=stdout,
683 stdout=stdout,
660 stderr=stderr,
684 stderr=stderr,
661 )
685 )
662 if record_wait is not None:
686 if record_wait is not None:
663 record_wait(p.wait)
687 record_wait(p.wait)
664 returncode = 0
688 returncode = 0
665 except EnvironmentError as ex:
689 except EnvironmentError as ex:
666 returncode = ex.errno & 0xFF
690 returncode = ex.errno & 0xFF
667 if returncode == 0:
691 if returncode == 0:
668 # This shouldn't happen, but just in case make sure the
692 # This shouldn't happen, but just in case make sure the
669 # return code is never 0 here.
693 # return code is never 0 here.
670 returncode = 255
694 returncode = 255
671 except Exception:
695 except Exception:
672 returncode = 255
696 returncode = 255
673 finally:
697 finally:
674 # mission accomplished, this child needs to exit and not
698 # mission accomplished, this child needs to exit and not
675 # continue the hg process here.
699 # continue the hg process here.
676 if record_wait is None:
700 if record_wait is None:
677 os._exit(returncode)
701 os._exit(returncode)
General Comments 0
You need to be logged in to leave comments. Login now