##// END OF EJS Templates
pytype: stop excluding procutil.py...
Matt Harbison -
r49318:333a2656 default
parent child Browse files
Show More
@@ -1,905 +1,910 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 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@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 BadFile(io.RawIOBase):
53 class BadFile(io.RawIOBase):
54 """Dummy file object to simulate closed stdio behavior"""
54 """Dummy file object to simulate closed stdio behavior"""
55
55
56 def readinto(self, b):
56 def readinto(self, b):
57 raise IOError(errno.EBADF, 'Bad file descriptor')
57 raise IOError(errno.EBADF, 'Bad file descriptor')
58
58
59 def write(self, b):
59 def write(self, b):
60 raise IOError(errno.EBADF, 'Bad file descriptor')
60 raise IOError(errno.EBADF, 'Bad file descriptor')
61
61
62
62
63 class LineBufferedWrapper(object):
63 class LineBufferedWrapper(object):
64 def __init__(self, orig):
64 def __init__(self, orig):
65 self.orig = orig
65 self.orig = orig
66
66
67 def __getattr__(self, attr):
67 def __getattr__(self, attr):
68 return getattr(self.orig, attr)
68 return getattr(self.orig, attr)
69
69
70 def write(self, s):
70 def write(self, s):
71 orig = self.orig
71 orig = self.orig
72 res = orig.write(s)
72 res = orig.write(s)
73 if s.endswith(b'\n'):
73 if s.endswith(b'\n'):
74 orig.flush()
74 orig.flush()
75 return res
75 return res
76
76
77
77
78 # pytype: disable=attribute-error
78 io.BufferedIOBase.register(LineBufferedWrapper)
79 io.BufferedIOBase.register(LineBufferedWrapper)
80 # pytype: enable=attribute-error
79
81
80
82
81 def make_line_buffered(stream):
83 def make_line_buffered(stream):
82 if pycompat.ispy3 and not isinstance(stream, io.BufferedIOBase):
84 if pycompat.ispy3 and not isinstance(stream, io.BufferedIOBase):
83 # On Python 3, buffered streams can be expected to subclass
85 # On Python 3, buffered streams can be expected to subclass
84 # BufferedIOBase. This is definitively the case for the streams
86 # BufferedIOBase. This is definitively the case for the streams
85 # initialized by the interpreter. For unbuffered streams, we don't need
87 # initialized by the interpreter. For unbuffered streams, we don't need
86 # to emulate line buffering.
88 # to emulate line buffering.
87 return stream
89 return stream
88 if isinstance(stream, LineBufferedWrapper):
90 if isinstance(stream, LineBufferedWrapper):
89 return stream
91 return stream
90 return LineBufferedWrapper(stream)
92 return LineBufferedWrapper(stream)
91
93
92
94
93 def unwrap_line_buffered(stream):
95 def unwrap_line_buffered(stream):
94 if isinstance(stream, LineBufferedWrapper):
96 if isinstance(stream, LineBufferedWrapper):
95 assert not isinstance(stream.orig, LineBufferedWrapper)
97 assert not isinstance(stream.orig, LineBufferedWrapper)
96 return stream.orig
98 return stream.orig
97 return stream
99 return stream
98
100
99
101
100 class WriteAllWrapper(object):
102 class WriteAllWrapper(object):
101 def __init__(self, orig):
103 def __init__(self, orig):
102 self.orig = orig
104 self.orig = orig
103
105
104 def __getattr__(self, attr):
106 def __getattr__(self, attr):
105 return getattr(self.orig, attr)
107 return getattr(self.orig, attr)
106
108
107 def write(self, s):
109 def write(self, s):
108 write1 = self.orig.write
110 write1 = self.orig.write
109 m = memoryview(s)
111 m = memoryview(s)
110 total_to_write = len(s)
112 total_to_write = len(s)
111 total_written = 0
113 total_written = 0
112 while total_written < total_to_write:
114 while total_written < total_to_write:
113 total_written += write1(m[total_written:])
115 total_written += write1(m[total_written:])
114 return total_written
116 return total_written
115
117
116
118
119 # pytype: disable=attribute-error
117 io.IOBase.register(WriteAllWrapper)
120 io.IOBase.register(WriteAllWrapper)
121 # pytype: enable=attribute-error
118
122
119
123
120 def _make_write_all(stream):
124 def _make_write_all(stream):
121 assert pycompat.ispy3
125 assert pycompat.ispy3
122 if isinstance(stream, WriteAllWrapper):
126 if isinstance(stream, WriteAllWrapper):
123 return stream
127 return stream
124 if isinstance(stream, io.BufferedIOBase):
128 if isinstance(stream, io.BufferedIOBase):
125 # The io.BufferedIOBase.write() contract guarantees that all data is
129 # The io.BufferedIOBase.write() contract guarantees that all data is
126 # written.
130 # written.
127 return stream
131 return stream
128 # In general, the write() method of streams is free to write only part of
132 # In general, the write() method of streams is free to write only part of
129 # the data.
133 # the data.
130 return WriteAllWrapper(stream)
134 return WriteAllWrapper(stream)
131
135
132
136
133 if pycompat.ispy3:
137 if pycompat.ispy3:
134 # Python 3 implements its own I/O streams. Unlike stdio of C library,
138 # Python 3 implements its own I/O streams. Unlike stdio of C library,
135 # sys.stdin/stdout/stderr may be None if underlying fd is closed.
139 # sys.stdin/stdout/stderr may be None if underlying fd is closed.
136
140
137 # TODO: .buffer might not exist if std streams were replaced; we'll need
141 # TODO: .buffer might not exist if std streams were replaced; we'll need
138 # a silly wrapper to make a bytes stream backed by a unicode one.
142 # a silly wrapper to make a bytes stream backed by a unicode one.
139
143
140 if sys.stdin is None:
144 if sys.stdin is None:
141 stdin = BadFile()
145 stdin = BadFile()
142 else:
146 else:
143 stdin = sys.stdin.buffer
147 stdin = sys.stdin.buffer
144 if sys.stdout is None:
148 if sys.stdout is None:
145 stdout = BadFile()
149 stdout = BadFile()
146 else:
150 else:
147 stdout = _make_write_all(sys.stdout.buffer)
151 stdout = _make_write_all(sys.stdout.buffer)
148 if sys.stderr is None:
152 if sys.stderr is None:
149 stderr = BadFile()
153 stderr = BadFile()
150 else:
154 else:
151 stderr = _make_write_all(sys.stderr.buffer)
155 stderr = _make_write_all(sys.stderr.buffer)
152
156
153 if pycompat.iswindows:
157 if pycompat.iswindows:
154 # Work around Windows bugs.
158 # Work around Windows bugs.
155 stdout = platform.winstdout(stdout) # pytype: disable=module-attr
159 stdout = platform.winstdout(stdout) # pytype: disable=module-attr
156 stderr = platform.winstdout(stderr) # pytype: disable=module-attr
160 stderr = platform.winstdout(stderr) # pytype: disable=module-attr
157 if isatty(stdout):
161 if isatty(stdout):
158 # The standard library doesn't offer line-buffered binary streams.
162 # The standard library doesn't offer line-buffered binary streams.
159 stdout = make_line_buffered(stdout)
163 stdout = make_line_buffered(stdout)
160 else:
164 else:
161 # Python 2 uses the I/O streams provided by the C library.
165 # Python 2 uses the I/O streams provided by the C library.
162 stdin = sys.stdin
166 stdin = sys.stdin
163 stdout = sys.stdout
167 stdout = sys.stdout
164 stderr = sys.stderr
168 stderr = sys.stderr
165 if pycompat.iswindows:
169 if pycompat.iswindows:
166 # Work around Windows bugs.
170 # Work around Windows bugs.
167 stdout = platform.winstdout(stdout) # pytype: disable=module-attr
171 stdout = platform.winstdout(stdout) # pytype: disable=module-attr
168 stderr = platform.winstdout(stderr) # pytype: disable=module-attr
172 stderr = platform.winstdout(stderr) # pytype: disable=module-attr
169 if isatty(stdout):
173 if isatty(stdout):
170 if pycompat.iswindows:
174 if pycompat.iswindows:
171 # The Windows C runtime library doesn't support line buffering.
175 # The Windows C runtime library doesn't support line buffering.
172 stdout = make_line_buffered(stdout)
176 stdout = make_line_buffered(stdout)
173 else:
177 else:
174 # glibc determines buffering on first write to stdout - if we
178 # glibc determines buffering on first write to stdout - if we
175 # replace a TTY destined stdout with a pipe destined stdout (e.g.
179 # replace a TTY destined stdout with a pipe destined stdout (e.g.
176 # pager), we want line buffering.
180 # pager), we want line buffering.
177 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
181 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
178
182
179
183
180 findexe = platform.findexe
184 findexe = platform.findexe
181 _gethgcmd = platform.gethgcmd
185 _gethgcmd = platform.gethgcmd
182 getuser = platform.getuser
186 getuser = platform.getuser
183 getpid = os.getpid
187 getpid = os.getpid
184 hidewindow = platform.hidewindow
188 hidewindow = platform.hidewindow
185 readpipe = platform.readpipe
189 readpipe = platform.readpipe
186 setbinary = platform.setbinary
190 setbinary = platform.setbinary
187 setsignalhandler = platform.setsignalhandler
191 setsignalhandler = platform.setsignalhandler
188 shellquote = platform.shellquote
192 shellquote = platform.shellquote
189 shellsplit = platform.shellsplit
193 shellsplit = platform.shellsplit
190 spawndetached = platform.spawndetached
194 spawndetached = platform.spawndetached
191 sshargs = platform.sshargs
195 sshargs = platform.sshargs
192 testpid = platform.testpid
196 testpid = platform.testpid
193
197
194 try:
198 try:
195 setprocname = osutil.setprocname
199 setprocname = osutil.setprocname
196 except AttributeError:
200 except AttributeError:
197 pass
201 pass
198 try:
202 try:
199 unblocksignal = osutil.unblocksignal
203 unblocksignal = osutil.unblocksignal
200 except AttributeError:
204 except AttributeError:
201 pass
205 pass
202
206
203 closefds = pycompat.isposix
207 closefds = pycompat.isposix
204
208
205
209
206 def explainexit(code):
210 def explainexit(code):
207 """return a message describing a subprocess status
211 """return a message describing a subprocess status
208 (codes from kill are negative - not os.system/wait encoding)"""
212 (codes from kill are negative - not os.system/wait encoding)"""
209 if code >= 0:
213 if code >= 0:
210 return _(b"exited with status %d") % code
214 return _(b"exited with status %d") % code
211 return _(b"killed by signal %d") % -code
215 return _(b"killed by signal %d") % -code
212
216
213
217
214 class _pfile(object):
218 class _pfile(object):
215 """File-like wrapper for a stream opened by subprocess.Popen()"""
219 """File-like wrapper for a stream opened by subprocess.Popen()"""
216
220
217 def __init__(self, proc, fp):
221 def __init__(self, proc, fp):
218 self._proc = proc
222 self._proc = proc
219 self._fp = fp
223 self._fp = fp
220
224
221 def close(self):
225 def close(self):
222 # unlike os.popen(), this returns an integer in subprocess coding
226 # unlike os.popen(), this returns an integer in subprocess coding
223 self._fp.close()
227 self._fp.close()
224 return self._proc.wait()
228 return self._proc.wait()
225
229
226 def __iter__(self):
230 def __iter__(self):
227 return iter(self._fp)
231 return iter(self._fp)
228
232
229 def __getattr__(self, attr):
233 def __getattr__(self, attr):
230 return getattr(self._fp, attr)
234 return getattr(self._fp, attr)
231
235
232 def __enter__(self):
236 def __enter__(self):
233 return self
237 return self
234
238
235 def __exit__(self, exc_type, exc_value, exc_tb):
239 def __exit__(self, exc_type, exc_value, exc_tb):
236 self.close()
240 self.close()
237
241
238
242
239 def popen(cmd, mode=b'rb', bufsize=-1):
243 def popen(cmd, mode=b'rb', bufsize=-1):
240 if mode == b'rb':
244 if mode == b'rb':
241 return _popenreader(cmd, bufsize)
245 return _popenreader(cmd, bufsize)
242 elif mode == b'wb':
246 elif mode == b'wb':
243 return _popenwriter(cmd, bufsize)
247 return _popenwriter(cmd, bufsize)
244 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
248 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
245
249
246
250
247 def _popenreader(cmd, bufsize):
251 def _popenreader(cmd, bufsize):
248 p = subprocess.Popen(
252 p = subprocess.Popen(
249 tonativestr(cmd),
253 tonativestr(cmd),
250 shell=True,
254 shell=True,
251 bufsize=bufsize,
255 bufsize=bufsize,
252 close_fds=closefds,
256 close_fds=closefds,
253 stdout=subprocess.PIPE,
257 stdout=subprocess.PIPE,
254 )
258 )
255 return _pfile(p, p.stdout)
259 return _pfile(p, p.stdout)
256
260
257
261
258 def _popenwriter(cmd, bufsize):
262 def _popenwriter(cmd, bufsize):
259 p = subprocess.Popen(
263 p = subprocess.Popen(
260 tonativestr(cmd),
264 tonativestr(cmd),
261 shell=True,
265 shell=True,
262 bufsize=bufsize,
266 bufsize=bufsize,
263 close_fds=closefds,
267 close_fds=closefds,
264 stdin=subprocess.PIPE,
268 stdin=subprocess.PIPE,
265 )
269 )
266 return _pfile(p, p.stdin)
270 return _pfile(p, p.stdin)
267
271
268
272
269 def popen2(cmd, env=None):
273 def popen2(cmd, env=None):
270 # Setting bufsize to -1 lets the system decide the buffer size.
274 # Setting bufsize to -1 lets the system decide the buffer size.
271 # The default for bufsize is 0, meaning unbuffered. This leads to
275 # The default for bufsize is 0, meaning unbuffered. This leads to
272 # poor performance on Mac OS X: http://bugs.python.org/issue4194
276 # poor performance on Mac OS X: http://bugs.python.org/issue4194
273 p = subprocess.Popen(
277 p = subprocess.Popen(
274 tonativestr(cmd),
278 tonativestr(cmd),
275 shell=True,
279 shell=True,
276 bufsize=-1,
280 bufsize=-1,
277 close_fds=closefds,
281 close_fds=closefds,
278 stdin=subprocess.PIPE,
282 stdin=subprocess.PIPE,
279 stdout=subprocess.PIPE,
283 stdout=subprocess.PIPE,
280 env=tonativeenv(env),
284 env=tonativeenv(env),
281 )
285 )
282 return p.stdin, p.stdout
286 return p.stdin, p.stdout
283
287
284
288
285 def popen3(cmd, env=None):
289 def popen3(cmd, env=None):
286 stdin, stdout, stderr, p = popen4(cmd, env)
290 stdin, stdout, stderr, p = popen4(cmd, env)
287 return stdin, stdout, stderr
291 return stdin, stdout, stderr
288
292
289
293
290 def popen4(cmd, env=None, bufsize=-1):
294 def popen4(cmd, env=None, bufsize=-1):
291 p = subprocess.Popen(
295 p = subprocess.Popen(
292 tonativestr(cmd),
296 tonativestr(cmd),
293 shell=True,
297 shell=True,
294 bufsize=bufsize,
298 bufsize=bufsize,
295 close_fds=closefds,
299 close_fds=closefds,
296 stdin=subprocess.PIPE,
300 stdin=subprocess.PIPE,
297 stdout=subprocess.PIPE,
301 stdout=subprocess.PIPE,
298 stderr=subprocess.PIPE,
302 stderr=subprocess.PIPE,
299 env=tonativeenv(env),
303 env=tonativeenv(env),
300 )
304 )
301 return p.stdin, p.stdout, p.stderr, p
305 return p.stdin, p.stdout, p.stderr, p
302
306
303
307
304 def pipefilter(s, cmd):
308 def pipefilter(s, cmd):
305 '''filter string S through command CMD, returning its output'''
309 '''filter string S through command CMD, returning its output'''
306 p = subprocess.Popen(
310 p = subprocess.Popen(
307 tonativestr(cmd),
311 tonativestr(cmd),
308 shell=True,
312 shell=True,
309 close_fds=closefds,
313 close_fds=closefds,
310 stdin=subprocess.PIPE,
314 stdin=subprocess.PIPE,
311 stdout=subprocess.PIPE,
315 stdout=subprocess.PIPE,
312 )
316 )
313 pout, perr = p.communicate(s)
317 pout, perr = p.communicate(s)
314 return pout
318 return pout
315
319
316
320
317 def tempfilter(s, cmd):
321 def tempfilter(s, cmd):
318 """filter string S through a pair of temporary files with CMD.
322 """filter string S through a pair of temporary files with CMD.
319 CMD is used as a template to create the real command to be run,
323 CMD is used as a template to create the real command to be run,
320 with the strings INFILE and OUTFILE replaced by the real names of
324 with the strings INFILE and OUTFILE replaced by the real names of
321 the temporary files generated."""
325 the temporary files generated."""
322 inname, outname = None, None
326 inname, outname = None, None
323 try:
327 try:
324 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
328 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
325 fp = os.fdopen(infd, 'wb')
329 fp = os.fdopen(infd, 'wb')
326 fp.write(s)
330 fp.write(s)
327 fp.close()
331 fp.close()
328 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
332 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
329 os.close(outfd)
333 os.close(outfd)
330 cmd = cmd.replace(b'INFILE', inname)
334 cmd = cmd.replace(b'INFILE', inname)
331 cmd = cmd.replace(b'OUTFILE', outname)
335 cmd = cmd.replace(b'OUTFILE', outname)
332 code = system(cmd)
336 code = system(cmd)
333 if pycompat.sysplatform == b'OpenVMS' and code & 1:
337 if pycompat.sysplatform == b'OpenVMS' and code & 1:
334 code = 0
338 code = 0
335 if code:
339 if code:
336 raise error.Abort(
340 raise error.Abort(
337 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
341 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
338 )
342 )
339 with open(outname, b'rb') as fp:
343 with open(outname, b'rb') as fp:
340 return fp.read()
344 return fp.read()
341 finally:
345 finally:
342 try:
346 try:
343 if inname:
347 if inname:
344 os.unlink(inname)
348 os.unlink(inname)
345 except OSError:
349 except OSError:
346 pass
350 pass
347 try:
351 try:
348 if outname:
352 if outname:
349 os.unlink(outname)
353 os.unlink(outname)
350 except OSError:
354 except OSError:
351 pass
355 pass
352
356
353
357
354 _filtertable = {
358 _filtertable = {
355 b'tempfile:': tempfilter,
359 b'tempfile:': tempfilter,
356 b'pipe:': pipefilter,
360 b'pipe:': pipefilter,
357 }
361 }
358
362
359
363
360 def filter(s, cmd):
364 def filter(s, cmd):
361 """filter a string through a command that transforms its input to its
365 """filter a string through a command that transforms its input to its
362 output"""
366 output"""
363 for name, fn in pycompat.iteritems(_filtertable):
367 for name, fn in pycompat.iteritems(_filtertable):
364 if cmd.startswith(name):
368 if cmd.startswith(name):
365 return fn(s, cmd[len(name) :].lstrip())
369 return fn(s, cmd[len(name) :].lstrip())
366 return pipefilter(s, cmd)
370 return pipefilter(s, cmd)
367
371
368
372
369 _hgexecutable = None
373 _hgexecutable = None
370
374
371
375
372 def hgexecutable():
376 def hgexecutable():
373 """return location of the 'hg' executable.
377 """return location of the 'hg' executable.
374
378
375 Defaults to $HG or 'hg' in the search path.
379 Defaults to $HG or 'hg' in the search path.
376 """
380 """
377 if _hgexecutable is None:
381 if _hgexecutable is None:
378 hg = encoding.environ.get(b'HG')
382 hg = encoding.environ.get(b'HG')
379 mainmod = sys.modules['__main__']
383 mainmod = sys.modules['__main__']
380 if hg:
384 if hg:
381 _sethgexecutable(hg)
385 _sethgexecutable(hg)
382 elif resourceutil.mainfrozen():
386 elif resourceutil.mainfrozen():
383 if getattr(sys, 'frozen', None) == 'macosx_app':
387 if getattr(sys, 'frozen', None) == 'macosx_app':
384 # Env variable set by py2app
388 # Env variable set by py2app
385 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
389 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
386 else:
390 else:
387 _sethgexecutable(pycompat.sysexecutable)
391 _sethgexecutable(pycompat.sysexecutable)
388 elif (
392 elif (
389 not pycompat.iswindows
393 not pycompat.iswindows
390 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
394 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
391 ):
395 ):
392 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
396 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
393 else:
397 else:
394 _sethgexecutable(
398 _sethgexecutable(
395 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
399 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
396 )
400 )
397 return _hgexecutable
401 return _hgexecutable
398
402
399
403
400 def _sethgexecutable(path):
404 def _sethgexecutable(path):
401 """set location of the 'hg' executable"""
405 """set location of the 'hg' executable"""
402 global _hgexecutable
406 global _hgexecutable
403 _hgexecutable = path
407 _hgexecutable = path
404
408
405
409
406 def _testfileno(f, stdf):
410 def _testfileno(f, stdf):
407 fileno = getattr(f, 'fileno', None)
411 fileno = getattr(f, 'fileno', None)
408 try:
412 try:
409 return fileno and fileno() == stdf.fileno()
413 return fileno and fileno() == stdf.fileno()
410 except io.UnsupportedOperation:
414 except io.UnsupportedOperation:
411 return False # fileno() raised UnsupportedOperation
415 return False # fileno() raised UnsupportedOperation
412
416
413
417
414 def isstdin(f):
418 def isstdin(f):
415 return _testfileno(f, sys.__stdin__)
419 return _testfileno(f, sys.__stdin__)
416
420
417
421
418 def isstdout(f):
422 def isstdout(f):
419 return _testfileno(f, sys.__stdout__)
423 return _testfileno(f, sys.__stdout__)
420
424
421
425
422 def protectstdio(uin, uout):
426 def protectstdio(uin, uout):
423 """Duplicate streams and redirect original if (uin, uout) are stdio
427 """Duplicate streams and redirect original if (uin, uout) are stdio
424
428
425 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
429 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
426 redirected to stderr so the output is still readable.
430 redirected to stderr so the output is still readable.
427
431
428 Returns (fin, fout) which point to the original (uin, uout) fds, but
432 Returns (fin, fout) which point to the original (uin, uout) fds, but
429 may be copy of (uin, uout). The returned streams can be considered
433 may be copy of (uin, uout). The returned streams can be considered
430 "owned" in that print(), exec(), etc. never reach to them.
434 "owned" in that print(), exec(), etc. never reach to them.
431 """
435 """
432 uout.flush()
436 uout.flush()
433 fin, fout = uin, uout
437 fin, fout = uin, uout
434 if _testfileno(uin, stdin):
438 if _testfileno(uin, stdin):
435 newfd = os.dup(uin.fileno())
439 newfd = os.dup(uin.fileno())
436 nullfd = os.open(os.devnull, os.O_RDONLY)
440 nullfd = os.open(os.devnull, os.O_RDONLY)
437 os.dup2(nullfd, uin.fileno())
441 os.dup2(nullfd, uin.fileno())
438 os.close(nullfd)
442 os.close(nullfd)
439 fin = os.fdopen(newfd, 'rb')
443 fin = os.fdopen(newfd, 'rb')
440 if _testfileno(uout, stdout):
444 if _testfileno(uout, stdout):
441 newfd = os.dup(uout.fileno())
445 newfd = os.dup(uout.fileno())
442 os.dup2(stderr.fileno(), uout.fileno())
446 os.dup2(stderr.fileno(), uout.fileno())
443 fout = os.fdopen(newfd, 'wb')
447 fout = os.fdopen(newfd, 'wb')
444 return fin, fout
448 return fin, fout
445
449
446
450
447 def restorestdio(uin, uout, fin, fout):
451 def restorestdio(uin, uout, fin, fout):
448 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
452 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
449 uout.flush()
453 uout.flush()
450 for f, uif in [(fin, uin), (fout, uout)]:
454 for f, uif in [(fin, uin), (fout, uout)]:
451 if f is not uif:
455 if f is not uif:
452 os.dup2(f.fileno(), uif.fileno())
456 os.dup2(f.fileno(), uif.fileno())
453 f.close()
457 f.close()
454
458
455
459
456 def shellenviron(environ=None):
460 def shellenviron(environ=None):
457 """return environ with optional override, useful for shelling out"""
461 """return environ with optional override, useful for shelling out"""
458
462
459 def py2shell(val):
463 def py2shell(val):
460 """convert python object into string that is useful to shell"""
464 """convert python object into string that is useful to shell"""
461 if val is None or val is False:
465 if val is None or val is False:
462 return b'0'
466 return b'0'
463 if val is True:
467 if val is True:
464 return b'1'
468 return b'1'
465 return pycompat.bytestr(val)
469 return pycompat.bytestr(val)
466
470
467 env = dict(encoding.environ)
471 env = dict(encoding.environ)
468 if environ:
472 if environ:
469 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
473 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
470 env[b'HG'] = hgexecutable()
474 env[b'HG'] = hgexecutable()
471 return env
475 return env
472
476
473
477
474 if pycompat.iswindows:
478 if pycompat.iswindows:
475
479
476 def shelltonative(cmd, env):
480 def shelltonative(cmd, env):
477 return platform.shelltocmdexe( # pytype: disable=module-attr
481 return platform.shelltocmdexe( # pytype: disable=module-attr
478 cmd, shellenviron(env)
482 cmd, shellenviron(env)
479 )
483 )
480
484
481 tonativestr = encoding.strfromlocal
485 tonativestr = encoding.strfromlocal
482 else:
486 else:
483
487
484 def shelltonative(cmd, env):
488 def shelltonative(cmd, env):
485 return cmd
489 return cmd
486
490
487 tonativestr = pycompat.identity
491 tonativestr = pycompat.identity
488
492
489
493
490 def tonativeenv(env):
494 def tonativeenv(env):
491 """convert the environment from bytes to strings suitable for Popen(), etc."""
495 """convert the environment from bytes to strings suitable for Popen(), etc."""
492 return pycompat.rapply(tonativestr, env)
496 return pycompat.rapply(tonativestr, env)
493
497
494
498
495 def system(cmd, environ=None, cwd=None, out=None):
499 def system(cmd, environ=None, cwd=None, out=None):
496 """enhanced shell command execution.
500 """enhanced shell command execution.
497 run with environment maybe modified, maybe in different dir.
501 run with environment maybe modified, maybe in different dir.
498
502
499 if out is specified, it is assumed to be a file-like object that has a
503 if out is specified, it is assumed to be a file-like object that has a
500 write() method. stdout and stderr will be redirected to out."""
504 write() method. stdout and stderr will be redirected to out."""
501 try:
505 try:
502 stdout.flush()
506 stdout.flush()
503 except Exception:
507 except Exception:
504 pass
508 pass
505 env = shellenviron(environ)
509 env = shellenviron(environ)
506 if out is None or isstdout(out):
510 if out is None or isstdout(out):
507 rc = subprocess.call(
511 rc = subprocess.call(
508 tonativestr(cmd),
512 tonativestr(cmd),
509 shell=True,
513 shell=True,
510 close_fds=closefds,
514 close_fds=closefds,
511 env=tonativeenv(env),
515 env=tonativeenv(env),
512 cwd=pycompat.rapply(tonativestr, cwd),
516 cwd=pycompat.rapply(tonativestr, cwd),
513 )
517 )
514 else:
518 else:
515 proc = subprocess.Popen(
519 proc = subprocess.Popen(
516 tonativestr(cmd),
520 tonativestr(cmd),
517 shell=True,
521 shell=True,
518 close_fds=closefds,
522 close_fds=closefds,
519 env=tonativeenv(env),
523 env=tonativeenv(env),
520 cwd=pycompat.rapply(tonativestr, cwd),
524 cwd=pycompat.rapply(tonativestr, cwd),
521 stdout=subprocess.PIPE,
525 stdout=subprocess.PIPE,
522 stderr=subprocess.STDOUT,
526 stderr=subprocess.STDOUT,
523 )
527 )
524 for line in iter(proc.stdout.readline, b''):
528 for line in iter(proc.stdout.readline, b''):
525 out.write(line)
529 out.write(line)
526 proc.wait()
530 proc.wait()
527 rc = proc.returncode
531 rc = proc.returncode
528 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
532 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
529 rc = 0
533 rc = 0
530 return rc
534 return rc
531
535
532
536
533 _is_gui = None
537 _is_gui = None
534
538
535
539
536 def _gui():
540 def _gui():
537 '''Are we running in a GUI?'''
541 '''Are we running in a GUI?'''
538 if pycompat.isdarwin:
542 if pycompat.isdarwin:
539 if b'SSH_CONNECTION' in encoding.environ:
543 if b'SSH_CONNECTION' in encoding.environ:
540 # handle SSH access to a box where the user is logged in
544 # handle SSH access to a box where the user is logged in
541 return False
545 return False
542 elif getattr(osutil, 'isgui', None):
546 elif getattr(osutil, 'isgui', None):
543 # check if a CoreGraphics session is available
547 # check if a CoreGraphics session is available
544 return osutil.isgui()
548 return osutil.isgui()
545 else:
549 else:
546 # pure build; use a safe default
550 # pure build; use a safe default
547 return True
551 return True
548 else:
552 else:
549 return (
553 return (
550 pycompat.iswindows
554 pycompat.iswindows
551 or encoding.environ.get(b"DISPLAY")
555 or encoding.environ.get(b"DISPLAY")
552 or encoding.environ.get(b"WAYLAND_DISPLAY")
556 or encoding.environ.get(b"WAYLAND_DISPLAY")
553 )
557 )
554
558
555
559
556 def gui():
560 def gui():
557 global _is_gui
561 global _is_gui
558 if _is_gui is None:
562 if _is_gui is None:
559 _is_gui = _gui()
563 _is_gui = _gui()
560 return _is_gui
564 return _is_gui
561
565
562
566
563 def hgcmd():
567 def hgcmd():
564 """Return the command used to execute current hg
568 """Return the command used to execute current hg
565
569
566 This is different from hgexecutable() because on Windows we want
570 This is different from hgexecutable() because on Windows we want
567 to avoid things opening new shell windows like batch files, so we
571 to avoid things opening new shell windows like batch files, so we
568 get either the python call or current executable.
572 get either the python call or current executable.
569 """
573 """
570 if resourceutil.mainfrozen():
574 if resourceutil.mainfrozen():
571 if getattr(sys, 'frozen', None) == 'macosx_app':
575 if getattr(sys, 'frozen', None) == 'macosx_app':
572 # Env variable set by py2app
576 # Env variable set by py2app
573 return [encoding.environ[b'EXECUTABLEPATH']]
577 return [encoding.environ[b'EXECUTABLEPATH']]
574 else:
578 else:
575 return [pycompat.sysexecutable]
579 return [pycompat.sysexecutable]
576 return _gethgcmd()
580 return _gethgcmd()
577
581
578
582
579 def rundetached(args, condfn):
583 def rundetached(args, condfn):
580 """Execute the argument list in a detached process.
584 """Execute the argument list in a detached process.
581
585
582 condfn is a callable which is called repeatedly and should return
586 condfn is a callable which is called repeatedly and should return
583 True once the child process is known to have started successfully.
587 True once the child process is known to have started successfully.
584 At this point, the child process PID is returned. If the child
588 At this point, the child process PID is returned. If the child
585 process fails to start or finishes before condfn() evaluates to
589 process fails to start or finishes before condfn() evaluates to
586 True, return -1.
590 True, return -1.
587 """
591 """
588 # Windows case is easier because the child process is either
592 # Windows case is easier because the child process is either
589 # successfully starting and validating the condition or exiting
593 # successfully starting and validating the condition or exiting
590 # on failure. We just poll on its PID. On Unix, if the child
594 # on failure. We just poll on its PID. On Unix, if the child
591 # process fails to start, it will be left in a zombie state until
595 # process fails to start, it will be left in a zombie state until
592 # the parent wait on it, which we cannot do since we expect a long
596 # the parent wait on it, which we cannot do since we expect a long
593 # running process on success. Instead we listen for SIGCHLD telling
597 # running process on success. Instead we listen for SIGCHLD telling
594 # us our child process terminated.
598 # us our child process terminated.
595 terminated = set()
599 terminated = set()
596
600
597 def handler(signum, frame):
601 def handler(signum, frame):
598 terminated.add(os.wait())
602 terminated.add(os.wait())
599
603
600 prevhandler = None
604 prevhandler = None
601 SIGCHLD = getattr(signal, 'SIGCHLD', None)
605 SIGCHLD = getattr(signal, 'SIGCHLD', None)
602 if SIGCHLD is not None:
606 if SIGCHLD is not None:
603 prevhandler = signal.signal(SIGCHLD, handler)
607 prevhandler = signal.signal(SIGCHLD, handler)
604 try:
608 try:
605 pid = spawndetached(args)
609 pid = spawndetached(args)
606 while not condfn():
610 while not condfn():
607 if (pid in terminated or not testpid(pid)) and not condfn():
611 if (pid in terminated or not testpid(pid)) and not condfn():
608 return -1
612 return -1
609 time.sleep(0.1)
613 time.sleep(0.1)
610 return pid
614 return pid
611 finally:
615 finally:
612 if prevhandler is not None:
616 if prevhandler is not None:
613 signal.signal(signal.SIGCHLD, prevhandler)
617 signal.signal(signal.SIGCHLD, prevhandler)
614
618
615
619
616 @contextlib.contextmanager
620 @contextlib.contextmanager
617 def uninterruptible(warn):
621 def uninterruptible(warn):
618 """Inhibit SIGINT handling on a region of code.
622 """Inhibit SIGINT handling on a region of code.
619
623
620 Note that if this is called in a non-main thread, it turns into a no-op.
624 Note that if this is called in a non-main thread, it turns into a no-op.
621
625
622 Args:
626 Args:
623 warn: A callable which takes no arguments, and returns True if the
627 warn: A callable which takes no arguments, and returns True if the
624 previous signal handling should be restored.
628 previous signal handling should be restored.
625 """
629 """
626
630
627 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
631 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
628 shouldbail = []
632 shouldbail = []
629
633
630 def disabledsiginthandler(*args):
634 def disabledsiginthandler(*args):
631 if warn():
635 if warn():
632 signal.signal(signal.SIGINT, oldsiginthandler[0])
636 signal.signal(signal.SIGINT, oldsiginthandler[0])
633 del oldsiginthandler[0]
637 del oldsiginthandler[0]
634 shouldbail.append(True)
638 shouldbail.append(True)
635
639
636 try:
640 try:
637 try:
641 try:
638 signal.signal(signal.SIGINT, disabledsiginthandler)
642 signal.signal(signal.SIGINT, disabledsiginthandler)
639 except ValueError:
643 except ValueError:
640 # wrong thread, oh well, we tried
644 # wrong thread, oh well, we tried
641 del oldsiginthandler[0]
645 del oldsiginthandler[0]
642 yield
646 yield
643 finally:
647 finally:
644 if oldsiginthandler:
648 if oldsiginthandler:
645 signal.signal(signal.SIGINT, oldsiginthandler[0])
649 signal.signal(signal.SIGINT, oldsiginthandler[0])
646 if shouldbail:
650 if shouldbail:
647 raise KeyboardInterrupt
651 raise KeyboardInterrupt
648
652
649
653
650 if pycompat.iswindows:
654 if pycompat.iswindows:
651 # no fork on Windows, but we can create a detached process
655 # no fork on Windows, but we can create a detached process
652 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
656 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
653 # No stdlib constant exists for this value
657 # No stdlib constant exists for this value
654 DETACHED_PROCESS = 0x00000008
658 DETACHED_PROCESS = 0x00000008
655 # Following creation flags might create a console GUI window.
659 # Following creation flags might create a console GUI window.
656 # Using subprocess.CREATE_NEW_CONSOLE might helps.
660 # Using subprocess.CREATE_NEW_CONSOLE might helps.
657 # See https://phab.mercurial-scm.org/D1701 for discussion
661 # See https://phab.mercurial-scm.org/D1701 for discussion
658 _creationflags = (
662 _creationflags = (
659 DETACHED_PROCESS
663 DETACHED_PROCESS
660 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
664 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
661 )
665 )
662
666
663 def runbgcommand(
667 def runbgcommand(
664 script,
668 script,
665 env,
669 env,
666 shell=False,
670 shell=False,
667 stdout=None,
671 stdout=None,
668 stderr=None,
672 stderr=None,
669 ensurestart=True,
673 ensurestart=True,
670 record_wait=None,
674 record_wait=None,
671 stdin_bytes=None,
675 stdin_bytes=None,
672 ):
676 ):
673 '''Spawn a command without waiting for it to finish.'''
677 '''Spawn a command without waiting for it to finish.'''
674 # we can't use close_fds *and* redirect stdin. I'm not sure that we
678 # we can't use close_fds *and* redirect stdin. I'm not sure that we
675 # need to because the detached process has no console connection.
679 # need to because the detached process has no console connection.
676
680
677 try:
681 try:
678 stdin = None
682 stdin = None
679 if stdin_bytes is not None:
683 if stdin_bytes is not None:
680 stdin = pycompat.unnamedtempfile()
684 stdin = pycompat.unnamedtempfile()
681 stdin.write(stdin_bytes)
685 stdin.write(stdin_bytes)
682 stdin.flush()
686 stdin.flush()
683 stdin.seek(0)
687 stdin.seek(0)
684
688
685 p = subprocess.Popen(
689 p = subprocess.Popen(
686 pycompat.rapply(tonativestr, script),
690 pycompat.rapply(tonativestr, script),
687 shell=shell,
691 shell=shell,
688 env=tonativeenv(env),
692 env=tonativeenv(env),
689 close_fds=True,
693 close_fds=True,
690 creationflags=_creationflags,
694 creationflags=_creationflags,
691 stdin=stdin,
695 stdin=stdin,
692 stdout=stdout,
696 stdout=stdout,
693 stderr=stderr,
697 stderr=stderr,
694 )
698 )
695 if record_wait is not None:
699 if record_wait is not None:
696 record_wait(p.wait)
700 record_wait(p.wait)
697 finally:
701 finally:
698 if stdin is not None:
702 if stdin is not None:
699 stdin.close()
703 stdin.close()
700
704
701
705
702 else:
706 else:
703
707
704 def runbgcommandpy3(
708 def runbgcommandpy3(
705 cmd,
709 cmd,
706 env,
710 env,
707 shell=False,
711 shell=False,
708 stdout=None,
712 stdout=None,
709 stderr=None,
713 stderr=None,
710 ensurestart=True,
714 ensurestart=True,
711 record_wait=None,
715 record_wait=None,
712 stdin_bytes=None,
716 stdin_bytes=None,
713 ):
717 ):
714 """Spawn a command without waiting for it to finish.
718 """Spawn a command without waiting for it to finish.
715
719
716
720
717 When `record_wait` is not None, the spawned process will not be fully
721 When `record_wait` is not None, the spawned process will not be fully
718 detached and the `record_wait` argument will be called with a the
722 detached and the `record_wait` argument will be called with a the
719 `Subprocess.wait` function for the spawned process. This is mostly
723 `Subprocess.wait` function for the spawned process. This is mostly
720 useful for developers that need to make sure the spawned process
724 useful for developers that need to make sure the spawned process
721 finished before a certain point. (eg: writing test)"""
725 finished before a certain point. (eg: writing test)"""
722 if pycompat.isdarwin:
726 if pycompat.isdarwin:
723 # avoid crash in CoreFoundation in case another thread
727 # avoid crash in CoreFoundation in case another thread
724 # calls gui() while we're calling fork().
728 # calls gui() while we're calling fork().
725 gui()
729 gui()
726
730
727 if shell:
731 if shell:
728 script = cmd
732 script = cmd
729 else:
733 else:
730 if isinstance(cmd, bytes):
734 if isinstance(cmd, bytes):
731 cmd = [cmd]
735 cmd = [cmd]
732 script = b' '.join(shellquote(x) for x in cmd)
736 script = b' '.join(shellquote(x) for x in cmd)
733 if record_wait is None:
737 if record_wait is None:
734 # double-fork to completely detach from the parent process
738 # double-fork to completely detach from the parent process
735 script = b'( %s ) &' % script
739 script = b'( %s ) &' % script
736 start_new_session = True
740 start_new_session = True
737 else:
741 else:
738 start_new_session = False
742 start_new_session = False
739 ensurestart = True
743 ensurestart = True
740
744
741 try:
745 try:
742 if stdin_bytes is None:
746 if stdin_bytes is None:
743 stdin = subprocess.DEVNULL
747 stdin = subprocess.DEVNULL
744 else:
748 else:
745 stdin = pycompat.unnamedtempfile()
749 stdin = pycompat.unnamedtempfile()
746 stdin.write(stdin_bytes)
750 stdin.write(stdin_bytes)
747 stdin.flush()
751 stdin.flush()
748 stdin.seek(0)
752 stdin.seek(0)
749 if stdout is None:
753 if stdout is None:
750 stdout = subprocess.DEVNULL
754 stdout = subprocess.DEVNULL
751 if stderr is None:
755 if stderr is None:
752 stderr = subprocess.DEVNULL
756 stderr = subprocess.DEVNULL
753
757
754 p = subprocess.Popen(
758 p = subprocess.Popen(
755 script,
759 script,
756 shell=True,
760 shell=True,
757 env=env,
761 env=env,
758 close_fds=True,
762 close_fds=True,
759 stdin=stdin,
763 stdin=stdin,
760 stdout=stdout,
764 stdout=stdout,
761 stderr=stderr,
765 stderr=stderr,
762 start_new_session=start_new_session,
766 start_new_session=start_new_session,
763 )
767 )
764 except Exception:
768 except Exception:
765 if record_wait is not None:
769 if record_wait is not None:
766 record_wait(255)
770 record_wait(255)
767 raise
771 raise
768 finally:
772 finally:
769 if stdin_bytes is not None:
773 if stdin_bytes is not None:
774 assert not isinstance(stdin, int)
770 stdin.close()
775 stdin.close()
771 if not ensurestart:
776 if not ensurestart:
772 # Even though we're not waiting on the child process,
777 # Even though we're not waiting on the child process,
773 # we still must call waitpid() on it at some point so
778 # we still must call waitpid() on it at some point so
774 # it's not a zombie/defunct. This is especially relevant for
779 # it's not a zombie/defunct. This is especially relevant for
775 # chg since the parent process won't die anytime soon.
780 # chg since the parent process won't die anytime soon.
776 # We use a thread to make the overhead tiny.
781 # We use a thread to make the overhead tiny.
777 t = threading.Thread(target=lambda: p.wait)
782 t = threading.Thread(target=lambda: p.wait)
778 t.daemon = True
783 t.daemon = True
779 t.start()
784 t.start()
780 else:
785 else:
781 returncode = p.wait
786 returncode = p.wait
782 if record_wait is not None:
787 if record_wait is not None:
783 record_wait(returncode)
788 record_wait(returncode)
784
789
785 def runbgcommandpy2(
790 def runbgcommandpy2(
786 cmd,
791 cmd,
787 env,
792 env,
788 shell=False,
793 shell=False,
789 stdout=None,
794 stdout=None,
790 stderr=None,
795 stderr=None,
791 ensurestart=True,
796 ensurestart=True,
792 record_wait=None,
797 record_wait=None,
793 stdin_bytes=None,
798 stdin_bytes=None,
794 ):
799 ):
795 """Spawn a command without waiting for it to finish.
800 """Spawn a command without waiting for it to finish.
796
801
797
802
798 When `record_wait` is not None, the spawned process will not be fully
803 When `record_wait` is not None, the spawned process will not be fully
799 detached and the `record_wait` argument will be called with a the
804 detached and the `record_wait` argument will be called with a the
800 `Subprocess.wait` function for the spawned process. This is mostly
805 `Subprocess.wait` function for the spawned process. This is mostly
801 useful for developers that need to make sure the spawned process
806 useful for developers that need to make sure the spawned process
802 finished before a certain point. (eg: writing test)"""
807 finished before a certain point. (eg: writing test)"""
803 if pycompat.isdarwin:
808 if pycompat.isdarwin:
804 # avoid crash in CoreFoundation in case another thread
809 # avoid crash in CoreFoundation in case another thread
805 # calls gui() while we're calling fork().
810 # calls gui() while we're calling fork().
806 gui()
811 gui()
807
812
808 # double-fork to completely detach from the parent process
813 # double-fork to completely detach from the parent process
809 # based on http://code.activestate.com/recipes/278731
814 # based on http://code.activestate.com/recipes/278731
810 if record_wait is None:
815 if record_wait is None:
811 pid = os.fork()
816 pid = os.fork()
812 if pid:
817 if pid:
813 if not ensurestart:
818 if not ensurestart:
814 # Even though we're not waiting on the child process,
819 # Even though we're not waiting on the child process,
815 # we still must call waitpid() on it at some point so
820 # we still must call waitpid() on it at some point so
816 # it's not a zombie/defunct. This is especially relevant for
821 # it's not a zombie/defunct. This is especially relevant for
817 # chg since the parent process won't die anytime soon.
822 # chg since the parent process won't die anytime soon.
818 # We use a thread to make the overhead tiny.
823 # We use a thread to make the overhead tiny.
819 def _do_wait():
824 def _do_wait():
820 os.waitpid(pid, 0)
825 os.waitpid(pid, 0)
821
826
822 t = threading.Thread(target=_do_wait)
827 t = threading.Thread(target=_do_wait)
823 t.daemon = True
828 t.daemon = True
824 t.start()
829 t.start()
825 return
830 return
826 # Parent process
831 # Parent process
827 (_pid, status) = os.waitpid(pid, 0)
832 (_pid, status) = os.waitpid(pid, 0)
828 if os.WIFEXITED(status):
833 if os.WIFEXITED(status):
829 returncode = os.WEXITSTATUS(status)
834 returncode = os.WEXITSTATUS(status)
830 else:
835 else:
831 returncode = -(os.WTERMSIG(status))
836 returncode = -(os.WTERMSIG(status))
832 if returncode != 0:
837 if returncode != 0:
833 # The child process's return code is 0 on success, an errno
838 # The child process's return code is 0 on success, an errno
834 # value on failure, or 255 if we don't have a valid errno
839 # value on failure, or 255 if we don't have a valid errno
835 # value.
840 # value.
836 #
841 #
837 # (It would be slightly nicer to return the full exception info
842 # (It would be slightly nicer to return the full exception info
838 # over a pipe as the subprocess module does. For now it
843 # over a pipe as the subprocess module does. For now it
839 # doesn't seem worth adding that complexity here, though.)
844 # doesn't seem worth adding that complexity here, though.)
840 if returncode == 255:
845 if returncode == 255:
841 returncode = errno.EINVAL
846 returncode = errno.EINVAL
842 raise OSError(
847 raise OSError(
843 returncode,
848 returncode,
844 b'error running %r: %s'
849 b'error running %r: %s'
845 % (cmd, os.strerror(returncode)),
850 % (cmd, os.strerror(returncode)),
846 )
851 )
847 return
852 return
848
853
849 returncode = 255
854 returncode = 255
850 try:
855 try:
851 if record_wait is None:
856 if record_wait is None:
852 # Start a new session
857 # Start a new session
853 os.setsid()
858 os.setsid()
854 # connect stdin to devnull to make sure the subprocess can't
859 # connect stdin to devnull to make sure the subprocess can't
855 # muck up that stream for mercurial.
860 # muck up that stream for mercurial.
856 if stdin_bytes is None:
861 if stdin_bytes is None:
857 stdin = open(os.devnull, b'r')
862 stdin = open(os.devnull, b'r')
858 else:
863 else:
859 stdin = pycompat.unnamedtempfile()
864 stdin = pycompat.unnamedtempfile()
860 stdin.write(stdin_bytes)
865 stdin.write(stdin_bytes)
861 stdin.flush()
866 stdin.flush()
862 stdin.seek(0)
867 stdin.seek(0)
863
868
864 if stdout is None:
869 if stdout is None:
865 stdout = open(os.devnull, b'w')
870 stdout = open(os.devnull, b'w')
866 if stderr is None:
871 if stderr is None:
867 stderr = open(os.devnull, b'w')
872 stderr = open(os.devnull, b'w')
868
873
869 p = subprocess.Popen(
874 p = subprocess.Popen(
870 cmd,
875 cmd,
871 shell=shell,
876 shell=shell,
872 env=env,
877 env=env,
873 close_fds=True,
878 close_fds=True,
874 stdin=stdin,
879 stdin=stdin,
875 stdout=stdout,
880 stdout=stdout,
876 stderr=stderr,
881 stderr=stderr,
877 )
882 )
878 if record_wait is not None:
883 if record_wait is not None:
879 record_wait(p.wait)
884 record_wait(p.wait)
880 returncode = 0
885 returncode = 0
881 except EnvironmentError as ex:
886 except EnvironmentError as ex:
882 returncode = ex.errno & 0xFF
887 returncode = ex.errno & 0xFF
883 if returncode == 0:
888 if returncode == 0:
884 # This shouldn't happen, but just in case make sure the
889 # This shouldn't happen, but just in case make sure the
885 # return code is never 0 here.
890 # return code is never 0 here.
886 returncode = 255
891 returncode = 255
887 except Exception:
892 except Exception:
888 returncode = 255
893 returncode = 255
889 finally:
894 finally:
890 # mission accomplished, this child needs to exit and not
895 # mission accomplished, this child needs to exit and not
891 # continue the hg process here.
896 # continue the hg process here.
892 stdin.close()
897 stdin.close()
893 if record_wait is None:
898 if record_wait is None:
894 os._exit(returncode)
899 os._exit(returncode)
895
900
896 if pycompat.ispy3:
901 if pycompat.ispy3:
897 # This branch is more robust, because it avoids running python
902 # This branch is more robust, because it avoids running python
898 # code (hence gc finalizers, like sshpeer.__del__, which
903 # code (hence gc finalizers, like sshpeer.__del__, which
899 # blocks). But we can't easily do the equivalent in py2,
904 # blocks). But we can't easily do the equivalent in py2,
900 # because of the lack of start_new_session=True flag. Given
905 # because of the lack of start_new_session=True flag. Given
901 # that the py2 branch should die soon, the short-lived
906 # that the py2 branch should die soon, the short-lived
902 # duplication seems acceptable.
907 # duplication seems acceptable.
903 runbgcommand = runbgcommandpy3
908 runbgcommand = runbgcommandpy3
904 else:
909 else:
905 runbgcommand = runbgcommandpy2
910 runbgcommand = runbgcommandpy2
@@ -1,81 +1,79 b''
1 #require pytype py3 slow
1 #require pytype py3 slow
2
2
3 $ cd $RUNTESTDIR/..
3 $ cd $RUNTESTDIR/..
4
4
5 Many of the individual files that are excluded here confuse pytype
5 Many of the individual files that are excluded here confuse pytype
6 because they do a mix of Python 2 and Python 3 things
6 because they do a mix of Python 2 and Python 3 things
7 conditionally. There's no good way to help it out with that as far as
7 conditionally. There's no good way to help it out with that as far as
8 I can tell, so let's just hide those files from it for now. We should
8 I can tell, so let's just hide those files from it for now. We should
9 endeavor to empty this list out over time, as some of these are
9 endeavor to empty this list out over time, as some of these are
10 probably hiding real problems.
10 probably hiding real problems.
11
11
12 mercurial/bundlerepo.py # no vfs and ui attrs on bundlerepo
12 mercurial/bundlerepo.py # no vfs and ui attrs on bundlerepo
13 mercurial/context.py # many [attribute-error]
13 mercurial/context.py # many [attribute-error]
14 mercurial/crecord.py # tons of [attribute-error], [module-attr]
14 mercurial/crecord.py # tons of [attribute-error], [module-attr]
15 mercurial/debugcommands.py # [wrong-arg-types]
15 mercurial/debugcommands.py # [wrong-arg-types]
16 mercurial/dispatch.py # initstdio: No attribute ... on TextIO [attribute-error]
16 mercurial/dispatch.py # initstdio: No attribute ... on TextIO [attribute-error]
17 mercurial/exchange.py # [attribute-error]
17 mercurial/exchange.py # [attribute-error]
18 mercurial/hgweb/hgweb_mod.py # [attribute-error], [name-error], [wrong-arg-types]
18 mercurial/hgweb/hgweb_mod.py # [attribute-error], [name-error], [wrong-arg-types]
19 mercurial/hgweb/server.py # [attribute-error], [name-error], [module-attr]
19 mercurial/hgweb/server.py # [attribute-error], [name-error], [module-attr]
20 mercurial/hgweb/wsgicgi.py # confused values in os.environ
20 mercurial/hgweb/wsgicgi.py # confused values in os.environ
21 mercurial/httppeer.py # [attribute-error], [wrong-arg-types]
21 mercurial/httppeer.py # [attribute-error], [wrong-arg-types]
22 mercurial/interfaces # No attribute 'capabilities' on peer [attribute-error]
22 mercurial/interfaces # No attribute 'capabilities' on peer [attribute-error]
23 mercurial/keepalive.py # [attribute-error]
23 mercurial/keepalive.py # [attribute-error]
24 mercurial/localrepo.py # [attribute-error]
24 mercurial/localrepo.py # [attribute-error]
25 mercurial/manifest.py # [unsupported-operands], [wrong-arg-types]
25 mercurial/manifest.py # [unsupported-operands], [wrong-arg-types]
26 mercurial/minirst.py # [unsupported-operands], [attribute-error]
26 mercurial/minirst.py # [unsupported-operands], [attribute-error]
27 mercurial/pure/osutil.py # [invalid-typevar], [not-callable]
27 mercurial/pure/osutil.py # [invalid-typevar], [not-callable]
28 mercurial/pure/parsers.py # [attribute-error]
28 mercurial/pure/parsers.py # [attribute-error]
29 mercurial/pycompat.py # bytes vs str issues
29 mercurial/pycompat.py # bytes vs str issues
30 mercurial/repoview.py # [attribute-error]
30 mercurial/repoview.py # [attribute-error]
31 mercurial/sslutil.py # [attribute-error]
31 mercurial/sslutil.py # [attribute-error]
32 mercurial/testing/storage.py # tons of [attribute-error]
32 mercurial/testing/storage.py # tons of [attribute-error]
33 mercurial/ui.py # [attribute-error], [wrong-arg-types]
33 mercurial/ui.py # [attribute-error], [wrong-arg-types]
34 mercurial/unionrepo.py # ui, svfs, unfiltered [attribute-error]
34 mercurial/unionrepo.py # ui, svfs, unfiltered [attribute-error]
35 mercurial/utils/procutil.py # [attribute-error], [module-attr], [bad-return-type]
36 mercurial/utils/memorytop.py # not 3.6 compatible
35 mercurial/utils/memorytop.py # not 3.6 compatible
37 mercurial/win32.py # [not-callable]
36 mercurial/win32.py # [not-callable]
38 mercurial/wireprotoframing.py # [unsupported-operands], [attribute-error], [import-error]
37 mercurial/wireprotoframing.py # [unsupported-operands], [attribute-error], [import-error]
39 mercurial/wireprotoserver.py # line 253, in _availableapis: No attribute '__iter__' on Callable[[Any, Any], Any] [attribute-error]
38 mercurial/wireprotoserver.py # line 253, in _availableapis: No attribute '__iter__' on Callable[[Any, Any], Any] [attribute-error]
40 mercurial/wireprotov1peer.py # [attribute-error]
39 mercurial/wireprotov1peer.py # [attribute-error]
41 mercurial/wireprotov1server.py # BUG?: BundleValueError handler accesses subclass's attrs
40 mercurial/wireprotov1server.py # BUG?: BundleValueError handler accesses subclass's attrs
42
41
43 TODO: use --no-cache on test server? Caching the files locally helps during
42 TODO: use --no-cache on test server? Caching the files locally helps during
44 development, but may be a hinderance for CI testing.
43 development, but may be a hinderance for CI testing.
45
44
46 $ pytype -V 3.6 --keep-going --jobs auto mercurial \
45 $ pytype -V 3.6 --keep-going --jobs auto mercurial \
47 > -x mercurial/bundlerepo.py \
46 > -x mercurial/bundlerepo.py \
48 > -x mercurial/context.py \
47 > -x mercurial/context.py \
49 > -x mercurial/crecord.py \
48 > -x mercurial/crecord.py \
50 > -x mercurial/debugcommands.py \
49 > -x mercurial/debugcommands.py \
51 > -x mercurial/dispatch.py \
50 > -x mercurial/dispatch.py \
52 > -x mercurial/exchange.py \
51 > -x mercurial/exchange.py \
53 > -x mercurial/hgweb/hgweb_mod.py \
52 > -x mercurial/hgweb/hgweb_mod.py \
54 > -x mercurial/hgweb/server.py \
53 > -x mercurial/hgweb/server.py \
55 > -x mercurial/hgweb/wsgicgi.py \
54 > -x mercurial/hgweb/wsgicgi.py \
56 > -x mercurial/httppeer.py \
55 > -x mercurial/httppeer.py \
57 > -x mercurial/interfaces \
56 > -x mercurial/interfaces \
58 > -x mercurial/keepalive.py \
57 > -x mercurial/keepalive.py \
59 > -x mercurial/localrepo.py \
58 > -x mercurial/localrepo.py \
60 > -x mercurial/manifest.py \
59 > -x mercurial/manifest.py \
61 > -x mercurial/minirst.py \
60 > -x mercurial/minirst.py \
62 > -x mercurial/pure/osutil.py \
61 > -x mercurial/pure/osutil.py \
63 > -x mercurial/pure/parsers.py \
62 > -x mercurial/pure/parsers.py \
64 > -x mercurial/pycompat.py \
63 > -x mercurial/pycompat.py \
65 > -x mercurial/repoview.py \
64 > -x mercurial/repoview.py \
66 > -x mercurial/sslutil.py \
65 > -x mercurial/sslutil.py \
67 > -x mercurial/testing/storage.py \
66 > -x mercurial/testing/storage.py \
68 > -x mercurial/thirdparty \
67 > -x mercurial/thirdparty \
69 > -x mercurial/ui.py \
68 > -x mercurial/ui.py \
70 > -x mercurial/unionrepo.py \
69 > -x mercurial/unionrepo.py \
71 > -x mercurial/utils/procutil.py \
72 > -x mercurial/utils/memorytop.py \
70 > -x mercurial/utils/memorytop.py \
73 > -x mercurial/win32.py \
71 > -x mercurial/win32.py \
74 > -x mercurial/wireprotoframing.py \
72 > -x mercurial/wireprotoframing.py \
75 > -x mercurial/wireprotoserver.py \
73 > -x mercurial/wireprotoserver.py \
76 > -x mercurial/wireprotov1peer.py \
74 > -x mercurial/wireprotov1peer.py \
77 > -x mercurial/wireprotov1server.py \
75 > -x mercurial/wireprotov1server.py \
78 > > $TESTTMP/pytype-output.txt || cat $TESTTMP/pytype-output.txt
76 > > $TESTTMP/pytype-output.txt || cat $TESTTMP/pytype-output.txt
79
77
80 Only show the results on a failure, because the output on success is also
78 Only show the results on a failure, because the output on success is also
81 voluminous and variable.
79 voluminous and variable.
General Comments 0
You need to be logged in to leave comments. Login now