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