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