| """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 |