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