• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2022 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14
15# Inspired by
16# https://fuchsia.googlesource.com/infra/recipes/+/336933647862a1a9718b4ca18f0a67e89c2419f8/recipe_modules/ninja/resources/ninja_wrapper.py
17"""Extracts a concise error from a ninja log."""
18
19from pathlib import Path
20import re
21
22_RULE_RE = re.compile(r'^\s*\[\d+/\d+\] (\S+)')
23_FAILED_RE = re.compile(r'^\s*FAILED: (.*)$')
24_FAILED_END_RE = re.compile(r'^\s*ninja: build stopped:.*')
25
26
27def parse_ninja_stdout(ninja_stdout: Path) -> str:
28    """Extract an error summary from ninja output."""
29
30    failure_begins = False
31    failure_lines = []
32    last_line = ''
33
34    with ninja_stdout.open() as ins:
35        for line in ins:
36            # Trailing whitespace isn't significant, as it doesn't affect the
37            # way the line shows up in the logs. However, leading whitespace may
38            # be significant, especially for compiler error messages.
39            line = line.rstrip()
40            if failure_begins:
41                if not _RULE_RE.match(line) and not _FAILED_END_RE.match(line):
42                    failure_lines.append(line)
43                else:
44                    # Output of failed step ends, save its info.
45                    failure_begins = False
46            else:
47                failed_nodes_match = _FAILED_RE.match(line)
48                failure_begins = False
49                if failed_nodes_match:
50                    failure_begins = True
51                    failure_lines.extend([last_line, line])
52            last_line = line
53
54    # Remove "Requirement already satisfied:" lines since many of those might
55    # be printed during Python installation, and they usually have no relevance
56    # to the actual error.
57    failure_lines = [
58        x
59        for x in failure_lines
60        if not x.lstrip().startswith('Requirement already satisfied:')
61    ]
62
63    result = '\n'.join(failure_lines)
64    return re.sub(r'\n+', '\n', result)
65