aboutsummaryrefslogtreecommitdiff
path: root/pw_presubmit/py/pw_presubmit/ninja_parser.py
blob: 131137df655b3c417ace8c30e2949b58010e14cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# Copyright 2022 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

# Inspired by
# https://fuchsia.googlesource.com/infra/recipes/+/336933647862a1a9718b4ca18f0a67e89c2419f8/recipe_modules/ninja/resources/ninja_wrapper.py
"""Extracts a concise error from a ninja log."""

from pathlib import Path
import re

_RULE_RE = re.compile(r'^\s*\[\d+/\d+\] (\S+)')
_FAILED_RE = re.compile(r'^\s*FAILED: (.*)$')
_FAILED_END_RE = re.compile(r'^\s*ninja: build stopped:.*')


def parse_ninja_stdout(ninja_stdout: Path) -> str:
    """Extract an error summary from ninja output."""

    failure_begins = False
    failure_lines = []
    last_line = ''

    with ninja_stdout.open() as ins:
        for line in ins:
            # Trailing whitespace isn't significant, as it doesn't affect the
            # way the line shows up in the logs. However, leading whitespace may
            # be significant, especially for compiler error messages.
            line = line.rstrip()
            if failure_begins:
                if not _RULE_RE.match(line) and not _FAILED_END_RE.match(line):
                    failure_lines.append(line)
                else:
                    # Output of failed step ends, save its info.
                    failure_begins = False
            else:
                failed_nodes_match = _FAILED_RE.match(line)
                failure_begins = False
                if failed_nodes_match:
                    failure_begins = True
                    failure_lines.extend([last_line, line])
            last_line = line

    # Remove "Requirement already satisfied:" lines since many of those might
    # be printed during Python installation, and they usually have no relevance
    # to the actual error.
    failure_lines = [
        x
        for x in failure_lines
        if not x.lstrip().startswith('Requirement already satisfied:')
    ]

    result = '\n'.join(failure_lines)
    return re.sub(r'\n+', '\n', result)