# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
from __future__ import print_function
import sys
from ctest_log_parser import BuildError, BuildWarning, CTestLogParser
from six import StringIO
import llnl.util.tty as tty
from llnl.util.tty.color import cescape, colorize
__all__ = ['parse_log_events', 'make_log_context']
[docs]def parse_log_events(stream, context=6, jobs=None, profile=False):
"""Extract interesting events from a log file as a list of LogEvent.
Args:
stream (str or typing.IO): 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
profile (bool): print out profile information for parsing
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 = CTestLogParser(profile=profile)
result = parse_log_events.ctest_parser.parse(stream, context, jobs)
if profile:
parse_log_events.ctest_parser.print_timings()
return result
#: lazily constructed CTest log parser
parse_log_events.ctest_parser = None # type: ignore[attr-defined]
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
[docs]def make_log_context(log_events, width=None):
"""Get error context from a log file.
Args:
log_events (list): 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 '>>' and
with red highlighting (if color is enabled).
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:
_, width = tty.terminal_size()
if 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 isinstance(event, BuildError):
color = 'R'
elif isinstance(event, BuildWarning):
color = 'Y'
else:
color = 'W'
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 + ln for ln in lines[1:]]
wrapped_line = line_fmt % (i, '\n'.join(lines))
if i in error_lines:
out.write(colorize(
' @%s{>> %s}\n' % (color, cescape(wrapped_line))))
else:
out.write(' %s\n' % wrapped_line)
next_line = event.end
return out.getvalue()