Source code for spack.util.log_parse
# Copyright Spack Project Developers. See COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import io
import shutil
import sys
from typing import Optional, TextIO, Union
from spack.llnl.util.tty.color import cescape, colorize
from spack.util.ctest_log_parser import BuildError, BuildWarning, CTestLogParser
__all__ = ["parse_log_events", "make_log_context"]
[docs]
def parse_log_events(
stream: Union[str, TextIO], context: int = 6, jobs: Optional[int] = None, profile: bool = False
):
"""Extract interesting events from a log file as a list of LogEvent.
Args:
stream: build log name or file object
context: lines of context to extract around each log event
jobs: number of jobs to parse with; default ncpus
profile: print out profile information for parsing
Returns:
two lists containing :class:`~spack.util.ctest_log_parser.BuildError` and
:class:`~spack.util.ctest_log_parser.BuildWarning` objects.
This is a wrapper around :class:`~spack.util.ctest_log_parser.CTestLogParser` that
lazily constructs a single ``CTestLogParser`` object. This ensures
that all the regex compilation is only done once.
"""
parser = getattr(parse_log_events, "ctest_parser", None)
if parser is None:
parser = CTestLogParser(profile=profile)
setattr(parse_log_events, "ctest_parser", parser)
result = parser.parse(stream, context, jobs)
if profile:
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 = shutil.get_terminal_size().columns
if width <= 0:
width = sys.maxsize
wrap_width = width - num_width - 6
out = io.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()