summaryrefslogtreecommitdiff
path: root/lib/spack/llnl/util/tty/log.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/spack/llnl/util/tty/log.py')
-rw-r--r--lib/spack/llnl/util/tty/log.py145
1 files changed, 79 insertions, 66 deletions
diff --git a/lib/spack/llnl/util/tty/log.py b/lib/spack/llnl/util/tty/log.py
index 51b9caa332..e155fa1d26 100644
--- a/lib/spack/llnl/util/tty/log.py
+++ b/lib/spack/llnl/util/tty/log.py
@@ -31,21 +31,22 @@ import llnl.util.tty as tty
termios = None # type: Optional[ModuleType]
try:
import termios as term_mod
+
termios = term_mod
except ImportError:
pass
# Use this to strip escape sequences
-_escape = re.compile(r'\x1b[^m]*m|\x1b\[?1034h|\x1b\][0-9]+;[^\x07]*\x07')
+_escape = re.compile(r"\x1b[^m]*m|\x1b\[?1034h|\x1b\][0-9]+;[^\x07]*\x07")
# control characters for enabling/disabling echo
#
# We use control characters to ensure that echo enable/disable are inline
# with the other output. We always follow these with a newline to ensure
# one per line the following newline is ignored in output.
-xon, xoff = '\x11\n', '\x13\n'
-control = re.compile('(\x11\n|\x13\n)')
+xon, xoff = "\x11\n", "\x13\n"
+control = re.compile("(\x11\n|\x13\n)")
@contextmanager
@@ -59,17 +60,13 @@ def ignore_signal(signum):
def _is_background_tty(stream):
- """True if the stream is a tty and calling process is in the background.
- """
- return (
- stream.isatty() and
- os.getpgrp() != os.tcgetpgrp(stream.fileno())
- )
+ """True if the stream is a tty and calling process is in the background."""
+ return stream.isatty() and os.getpgrp() != os.tcgetpgrp(stream.fileno())
def _strip(line):
"""Strip color and control characters from a line."""
- return _escape.sub('', line)
+ return _escape.sub("", line)
class keyboard_input(object):
@@ -147,6 +144,7 @@ class keyboard_input(object):
a TTY, ``keyboard_input`` has no effect.
"""
+
def __init__(self, stream):
"""Create a context manager that will enable keyboard input on stream.
@@ -204,7 +202,7 @@ class keyboard_input(object):
bg = self._is_background()
# restore sanity if flags are amiss -- see diagram in class docs
- if not bg and any(flags): # fg, but input not enabled
+ if not bg and any(flags): # fg, but input not enabled
self._enable_keyboard_input()
elif bg and not all(flags): # bg, but input enabled
self._restore_default_terminal_settings()
@@ -228,8 +226,7 @@ class keyboard_input(object):
# Install a signal handler to disable/enable keyboard input
# when the process moves between foreground and background.
- self.old_handlers[signal.SIGTSTP] = signal.signal(
- signal.SIGTSTP, self._tstp_handler)
+ self.old_handlers[signal.SIGTSTP] = signal.signal(signal.SIGTSTP, self._tstp_handler)
# add an atexit handler to ensure the terminal is restored
atexit.register(self._restore_default_terminal_settings)
@@ -258,6 +255,7 @@ class Unbuffered(object):
This is implemented by forcing a flush after each write.
"""
+
def __init__(self, stream):
self.stream = stream
@@ -302,6 +300,7 @@ class FileWrapper(object):
yet), or neither. When unwrapped, it returns an open file (or file-like)
object.
"""
+
def __init__(self, file_like):
# This records whether the file-like object returned by "unwrap" is
# purely in-memory. In that case a subprocess will need to explicitly
@@ -325,9 +324,9 @@ class FileWrapper(object):
if self.open:
if self.file_like:
if sys.version_info < (3,):
- self.file = open(self.file_like, 'w')
+ self.file = open(self.file_like, "w")
else:
- self.file = open(self.file_like, 'w', encoding='utf-8') # novm
+ self.file = open(self.file_like, "w", encoding="utf-8") # novm
else:
self.file = StringIO()
return self.file
@@ -343,8 +342,9 @@ class FileWrapper(object):
class MultiProcessFd(object):
"""Return an object which stores a file descriptor and can be passed as an
- argument to a function run with ``multiprocessing.Process``, such that
- the file descriptor is available in the subprocess."""
+ argument to a function run with ``multiprocessing.Process``, such that
+ the file descriptor is available in the subprocess."""
+
def __init__(self, fd):
self._connection = None
self._fd = None
@@ -434,7 +434,7 @@ def log_output(*args, **kwargs):
This method is actually a factory serving a per platform
(unix vs windows) log_output class
"""
- if sys.platform == 'win32':
+ if sys.platform == "win32":
return winlog(*args, **kwargs)
else:
return nixlog(*args, **kwargs)
@@ -454,8 +454,9 @@ class nixlog(object):
work within test frameworks like nose and pytest.
"""
- def __init__(self, file_like=None, echo=False, debug=0, buffer=False,
- env=None, filter_fn=None):
+ def __init__(
+ self, file_like=None, echo=False, debug=0, buffer=False, env=None, filter_fn=None
+ ):
"""Create a new output log context manager.
Args:
@@ -524,8 +525,7 @@ class nixlog(object):
raise RuntimeError("Can't re-enter the same log_output!")
if self.file_like is None:
- raise RuntimeError(
- "file argument must be set by either __init__ or __call__")
+ raise RuntimeError("file argument must be set by either __init__ or __call__")
# set up a stream for the daemon to write to
self.log_file = FileWrapper(self.file_like)
@@ -555,9 +555,7 @@ class nixlog(object):
input_multiprocess_fd = None
try:
if sys.stdin.isatty():
- input_multiprocess_fd = MultiProcessFd(
- os.dup(sys.stdin.fileno())
- )
+ input_multiprocess_fd = MultiProcessFd(os.dup(sys.stdin.fileno()))
except BaseException:
# just don't forward input if this fails
pass
@@ -566,9 +564,14 @@ class nixlog(object):
self.process = multiprocessing.Process(
target=_writer_daemon,
args=(
- input_multiprocess_fd, read_multiprocess_fd, write_fd,
- self.echo, self.log_file, child_pipe, self.filter_fn
- )
+ input_multiprocess_fd,
+ read_multiprocess_fd,
+ write_fd,
+ self.echo,
+ self.log_file,
+ child_pipe,
+ self.filter_fn,
+ ),
)
self.process.daemon = True # must set before start()
self.process.start()
@@ -609,7 +612,7 @@ class nixlog(object):
self._saved_stderr = sys.stderr
# create a file object for the pipe; redirect to it.
- pipe_fd_out = os.fdopen(write_fd, 'w')
+ pipe_fd_out = os.fdopen(write_fd, "w")
sys.stdout = pipe_fd_out
sys.stderr = pipe_fd_out
@@ -674,8 +677,7 @@ class nixlog(object):
def force_echo(self):
"""Context manager to force local echo, even if echo is off."""
if not self._active:
- raise RuntimeError(
- "Can't call force_echo() outside log_output region!")
+ raise RuntimeError("Can't call force_echo() outside log_output region!")
# This uses the xon/xoff to highlight regions to be echoed in the
# output. We us these control characters rather than, say, a
@@ -691,25 +693,26 @@ class nixlog(object):
class StreamWrapper:
- """ Wrapper class to handle redirection of io streams """
+ """Wrapper class to handle redirection of io streams"""
+
def __init__(self, sys_attr):
self.sys_attr = sys_attr
self.saved_stream = None
- if sys.platform.startswith('win32'):
+ if sys.platform.startswith("win32"):
if sys.version_info < (3, 5):
- libc = ctypes.CDLL(ctypes.util.find_library('c'))
+ libc = ctypes.CDLL(ctypes.util.find_library("c"))
else:
- if hasattr(sys, 'gettotalrefcount'): # debug build
- libc = ctypes.CDLL('ucrtbased')
+ if hasattr(sys, "gettotalrefcount"): # debug build
+ libc = ctypes.CDLL("ucrtbased")
else:
- libc = ctypes.CDLL('api-ms-win-crt-stdio-l1-1-0')
+ libc = ctypes.CDLL("api-ms-win-crt-stdio-l1-1-0")
- kernel32 = ctypes.WinDLL('kernel32')
+ kernel32 = ctypes.WinDLL("kernel32")
# https://docs.microsoft.com/en-us/windows/console/getstdhandle
- if self.sys_attr == 'stdout':
+ if self.sys_attr == "stdout":
STD_HANDLE = -11
- elif self.sys_attr == 'stderr':
+ elif self.sys_attr == "stderr":
STD_HANDLE = -12
else:
raise KeyError(self.sys_attr)
@@ -728,7 +731,7 @@ class StreamWrapper:
def redirect_stream(self, to_fd):
"""Redirect stdout to the given file descriptor."""
# Flush the C-level buffer stream
- if sys.platform.startswith('win32'):
+ if sys.platform.startswith("win32"):
self.libc.fflush(None)
else:
self.libc.fflush(self.c_stream)
@@ -739,13 +742,13 @@ class StreamWrapper:
# Make orig_stream_fd point to the same file as to_fd
os.dup2(to_fd, self.orig_stream_fd)
# Set sys_stream to a new stream that points to the redirected fd
- new_buffer = open(self.orig_stream_fd, 'wb')
+ new_buffer = open(self.orig_stream_fd, "wb")
new_stream = io.TextIOWrapper(new_buffer)
setattr(sys, self.sys_attr, new_stream)
self.sys_stream = getattr(sys, self.sys_attr)
def flush(self):
- if sys.platform.startswith('win32'):
+ if sys.platform.startswith("win32"):
self.libc.fflush(None)
else:
self.libc.fflush(self.c_stream)
@@ -768,14 +771,16 @@ class winlog(object):
Does not support the use of 'v' toggling as nixlog does.
"""
- def __init__(self, file_like=None, echo=False, debug=0, buffer=False,
- env=None, filter_fn=None):
+
+ def __init__(
+ self, file_like=None, echo=False, debug=0, buffer=False, env=None, filter_fn=None
+ ):
self.env = env
self.debug = debug
self.echo = echo
self.logfile = file_like
- self.stdout = StreamWrapper('stdout')
- self.stderr = StreamWrapper('stderr')
+ self.stdout = StreamWrapper("stdout")
+ self.stderr = StreamWrapper("stderr")
self._active = False
self._ioflag = False
self.old_stdout = sys.stdout
@@ -786,8 +791,7 @@ class winlog(object):
raise RuntimeError("Can't re-enter the same log_output!")
if self.logfile is None:
- raise RuntimeError(
- "file argument must be set by __init__ ")
+ raise RuntimeError("file argument must be set by __init__ ")
# Open both write and reading on logfile
if type(self.logfile) == StringIO:
@@ -796,8 +800,8 @@ class winlog(object):
sys.stdout = self.logfile
sys.stderr = self.logfile
else:
- self.writer = open(self.logfile, mode='wb+')
- self.reader = open(self.logfile, mode='rb+')
+ self.writer = open(self.logfile, mode="wb+")
+ self.reader = open(self.logfile, mode="rb+")
# Dup stdout so we can still write to it after redirection
self.echo_writer = open(os.dup(sys.stdout.fileno()), "w")
@@ -811,7 +815,7 @@ class winlog(object):
# if echo: write line to user
try:
while True:
- is_killed = _kill.wait(.1)
+ is_killed = _kill.wait(0.1)
# Flush buffered build output to file
# stdout/err fds refer to log file
self.stderr.flush()
@@ -819,7 +823,7 @@ class winlog(object):
line = reader.readline()
if self.echo and line:
- echo_writer.write('{0}'.format(line.decode()))
+ echo_writer.write("{0}".format(line.decode()))
echo_writer.flush()
if is_killed:
@@ -829,8 +833,9 @@ class winlog(object):
self._active = True
with replace_environment(self.env):
- self._thread = Thread(target=background_reader,
- args=(self.reader, self.echo_writer, self._kill))
+ self._thread = Thread(
+ target=background_reader, args=(self.reader, self.echo_writer, self._kill)
+ )
self._thread.start()
return self
@@ -854,13 +859,19 @@ class winlog(object):
def force_echo(self):
"""Context manager to force local echo, even if echo is off."""
if not self._active:
- raise RuntimeError(
- "Can't call force_echo() outside log_output region!")
+ raise RuntimeError("Can't call force_echo() outside log_output region!")
yield
-def _writer_daemon(stdin_multiprocess_fd, read_multiprocess_fd, write_fd, echo,
- log_file_wrapper, control_pipe, filter_fn):
+def _writer_daemon(
+ stdin_multiprocess_fd,
+ read_multiprocess_fd,
+ write_fd,
+ echo,
+ log_file_wrapper,
+ control_pipe,
+ filter_fn,
+):
"""Daemon used by ``log_output`` to write to a log file and to ``stdout``.
The daemon receives output from the parent process and writes it both
@@ -913,16 +924,16 @@ def _writer_daemon(stdin_multiprocess_fd, read_multiprocess_fd, write_fd, echo,
# write_fd to terminate the reading loop, so we close the file descriptor
# here. Forking is the process spawning method everywhere except Mac OS
# for Python >= 3.8 and on Windows
- if sys.version_info < (3, 8) or sys.platform != 'darwin':
+ if sys.version_info < (3, 8) or sys.platform != "darwin":
os.close(write_fd)
# Use line buffering (3rd param = 1) since Python 3 has a bug
# that prevents unbuffered text I/O.
if sys.version_info < (3,):
- in_pipe = os.fdopen(read_multiprocess_fd.fd, 'r', 1)
+ in_pipe = os.fdopen(read_multiprocess_fd.fd, "r", 1)
else:
# Python 3.x before 3.7 does not open with UTF-8 encoding by default
- in_pipe = os.fdopen(read_multiprocess_fd.fd, 'r', 1, encoding='utf-8')
+ in_pipe = os.fdopen(read_multiprocess_fd.fd, "r", 1, encoding="utf-8")
if stdin_multiprocess_fd:
stdin = os.fdopen(stdin_multiprocess_fd.fd)
@@ -931,7 +942,7 @@ def _writer_daemon(stdin_multiprocess_fd, read_multiprocess_fd, write_fd, echo,
# list of streams to select from
istreams = [in_pipe, stdin] if stdin else [in_pipe]
- force_echo = False # parent can force echo for certain output
+ force_echo = False # parent can force echo for certain output
log_file = log_file_wrapper.unwrap()
@@ -954,7 +965,7 @@ def _writer_daemon(stdin_multiprocess_fd, read_multiprocess_fd, write_fd, echo,
# check and the read, so we ignore SIGTTIN here.
with ignore_signal(signal.SIGTTIN):
try:
- if stdin.read(1) == 'v':
+ if stdin.read(1) == "v":
echo = not echo
except IOError as e:
# If SIGTTIN is ignored, the system gives EIO
@@ -972,14 +983,14 @@ def _writer_daemon(stdin_multiprocess_fd, read_multiprocess_fd, write_fd, echo,
line = _retry(in_pipe.readline)()
except UnicodeDecodeError:
# installs like --test=root gpgme produce non-UTF8 logs
- line = '<line lost: output was not encoded as UTF-8>\n'
+ line = "<line lost: output was not encoded as UTF-8>\n"
if not line:
return
line_count += 1
# find control characters and strip them.
- clean_line, num_controls = control.subn('', line)
+ clean_line, num_controls = control.subn("", line)
# Echo to stdout if requested or forced.
if echo or force_echo:
@@ -1043,6 +1054,7 @@ def _retry(function):
relevant for this file.
"""
+
def wrapped(*args, **kwargs):
while True:
try:
@@ -1055,6 +1067,7 @@ def _retry(function):
if e.args[0] == errno.EINTR:
continue
raise
+
return wrapped