diff options
Diffstat (limited to 'lib/spack/llnl/util/tty/log.py')
-rw-r--r-- | lib/spack/llnl/util/tty/log.py | 145 |
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 |