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