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