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