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