blob: d1c4d92dca45174627523ce7bdfb1f8d7585c42d [file] [log] [blame] [edit]
"""Compilation of utility functions for working with ninja."""
from __future__ import absolute_import
import re
# Example: [123/9876]
NINJA_PROGRESS_PREFIX = r'^\[\d+/\d+\]'
NINJA_FAILED_PREFIX = '^FAILED: '
NINJA_BUILD_STOPPED = '^ninja: build stopped: '
# Example: 1 error generated.
# Example: 15 errors generated.
NINJA_ERRORS_GENERATED = r'^\d+ error(s?) generated\.$'
NINJA_FILE_INCLUDED_FROM = '^In file included from '
NINJA_CLANG = r'third_party/llvm-build/Release\+Asserts/bin/clang\+\+'
# Example: prebuilt/toolchain/mips32r1/bin/mipsel-buildroot-linux-uclibc-g++
# Example: prebuilt/toolchain/armv7a/bin/armv7a-cros-linux-gnueabi-g++
NINJA_GXX = r'prebuilt/toolchain/(\S+)(g\+\+)'
NINJA_TOO_MANY_ERRORS_MSG = 'Too many errors! See the logs for the rest.'
def ninja_failure_to_comments(ninja_output, max_errors=None):
"""Convert a ninja failure message to comments for a gerrit review.
In general, a log is from ninja is expected to look somewhat like:
[...snip...]
[123/9876] CXX obj/path/to/foo.o
[124/9876] CXX obj/path/to/bar.o
FAILED: obj/path/to/bar.o
path/to/compiler/clang++ -FLAG -FLAG ... ... ...
path/to/foo:503:27: error: error was here
More specific error stuff
^~~~~
1 error generated.
[125/9876] STAMP obj/path/to/baz.o
[126/9876] ACTION path/to/script
ninja: build stopped: subcommand failed
[...snip...]
And this extracts out the FAILED block from the rest of the log.
Args:
ninja_output: output from a ninja build command.
(Note: this can also include output from other tools in
addition to the ninja output, in case ninja is a subcommand
of another tool, such as make.)
max_errors: The maximum number of errors to include. If not None,
then the returned list will contain at most N error messages.
If more than N errors were generated, then the list will also
contain an N+1'th message indicating errors were left out.
Returns:
A list of multi-line strings. Each string represents the text of a
single "FAILED" message from ninja.
An empty list is returned if no comments are found.
"""
comments = set()
comment, in_failed_block = '', False
for line in ninja_output.splitlines(True):
# Found the start of a failure block
if re.search(NINJA_FAILED_PREFIX, line):
in_failed_block = True
continue
# Haven't found the start of a failure block yet,
# so this line is part of a success. Skip it.
if not in_failed_block:
continue
# Found the end of a failure block.
if (re.search(NINJA_BUILD_STOPPED, line) or
re.search(NINJA_ERRORS_GENERATED, line) or
re.search(NINJA_PROGRESS_PREFIX, line)):
comments.add(comment.strip('\n'))
comment, in_failed_block = '', False
continue
# Ignore these lines, as they are long and do not add enough
# useful information compared to other lines
if (re.search(NINJA_FILE_INCLUDED_FROM, line) or
re.search(NINJA_CLANG, line) or
re.search(NINJA_GXX, line)):
continue
# Found a line within a failure block.
comment += line
comments = list(comments) # pylint: disable=redefined-variable-type
if max_errors and isinstance(max_errors, int):
max_errors = int(max_errors)
if len(comments) > max_errors:
comments = comments[0:max_errors] + [NINJA_TOO_MANY_ERRORS_MSG]
return comments