# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other # Spack Project Developers. See the spack top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) from __future__ import print_function import sys from io import StringIO from . import ctest_log_parser __all__ = ['parse_log_events', 'make_log_context'] def parse_log_events(stream, context=3, jobs=None): """Extract interesting events from a log file as a list of LogEvent. Args: stream (str or fileobject): build log name or file object context (int): lines of context to extract around each log event jobs (int): number of jobs to parse with; default ncpus Returns: (tuple): two lists containig ``BuildError`` and ``BuildWarning`` objects. This is a wrapper around ``ctest_log_parser.CTestLogParser`` that lazily constructs a single ``CTestLogParser`` object. This ensures that all the regex compilation is only done once. """ if parse_log_events.ctest_parser is None: parse_log_events.ctest_parser = ctest_log_parser.CTestLogParser() result = parse_log_events.ctest_parser.parse(stream, context, jobs) return result #: lazily constructed CTest log parser parse_log_events.ctest_parser = None def _wrap(text, width): """Break text into lines of specific width.""" lines = [] pos = 0 while pos < len(text): lines.append(text[pos:pos + width]) pos += width return lines def make_log_context(log_events, width=None): """Get error context from a log file. Args: log_events (list of LogEvent): list of events created by ``ctest_log_parser.parse()`` width (int or None): wrap width; ``0`` for no limit; ``None`` to auto-size for terminal Returns: str: context from the build log with errors highlighted Parses the log file for lines containing errors, and prints them out with line numbers and context. Errors are highlighted with '>>'. Events are sorted by line number before they are displayed. """ error_lines = set(e.line_no for e in log_events) log_events = sorted(log_events, key=lambda e: e.line_no) num_width = len(str(max(error_lines or [0]))) + 4 line_fmt = '%%-%dd%%s' % num_width indent = ' ' * (5 + num_width) if width is None or width <= 0: width = sys.maxsize wrap_width = width - num_width - 6 out = StringIO() next_line = 1 for event in log_events: start = event.start if next_line != 1 and start > next_line: out.write('\n ...\n\n') if start < next_line: start = next_line for i in range(start, event.end): # wrap to width lines = _wrap(event[i], wrap_width) lines[1:] = [indent + l for l in lines[1:]] wrapped_line = line_fmt % (i, '\n'.join(lines)) if i in error_lines: out.write(' >> %s\n' % wrapped_line) else: out.write(' %s\n' % wrapped_line) next_line = event.end return out.getvalue()