1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3 4# 5# Copyright (c) 2023 Huawei Device Co., Ltd. 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19import sys 20import re 21import os 22 23from containers.colors import Colors 24from hb.helper.no_instance import NoInstance 25from resources.global_var import STATUS_FILE 26from util.io_util import IoUtil 27from exceptions.ohos_exception import OHOSException 28 29 30class LogLevel(): 31 INFO = 0 32 WARNING = 1 33 ERROR = 2 34 DEBUG = 3 35 36 37class LogUtil(metaclass=NoInstance): 38 # static member for store current stage 39 stage = "" 40 41 @staticmethod 42 def set_stage(stage): 43 LogUtil.stage = stage 44 45 @staticmethod 46 def clear_stage(): 47 LogUtil.stage = "" 48 49 @staticmethod 50 def hb_info(msg, stage='', mode='normal'): 51 level = 'info' 52 if not stage: 53 stage = LogUtil.stage 54 if mode == 'silent': 55 for line in str(msg).splitlines(): 56 sys.stdout.write('\033[K') 57 sys.stdout.write( 58 '\r' + (LogUtil.message(level, line, stage)).strip('\n')) 59 sys.stdout.flush() 60 elif mode == 'normal': 61 level = 'info' 62 for line in str(msg).splitlines(): 63 sys.stdout.write(LogUtil.message(level, line, stage)) 64 sys.stdout.flush() 65 66 @staticmethod 67 def hb_warning(msg, stage=''): 68 level = 'warning' 69 if not stage: 70 stage = LogUtil.stage 71 for line in str(msg).splitlines(): 72 sys.stderr.write(LogUtil.message(level, line, stage)) 73 sys.stderr.flush() 74 75 @staticmethod 76 def hb_error(msg, stage=''): 77 level = 'error' 78 if not stage: 79 stage = LogUtil.stage 80 sys.stderr.write('\n') 81 for line in str(msg).splitlines(): 82 sys.stderr.write(LogUtil.message(level, line, stage)) 83 sys.stderr.flush() 84 85 @staticmethod 86 def message(level, msg, stage=''): 87 if isinstance(msg, str) and not msg.endswith('\n'): 88 msg += '\n' 89 if level == 'error': 90 msg = msg.replace('error:', f'{Colors.ERROR}error{Colors.END}:') 91 return f'{Colors.ERROR}[OHOS {level.upper()}]{Colors.END} {stage} {msg}' 92 elif level == 'info': 93 return f'[OHOS {level.upper()}] {stage} {msg}' 94 else: 95 return f'{Colors.WARNING}[OHOS {level.upper()}]{Colors.END} {stage} {msg}' 96 97 @staticmethod 98 def write_log(log_path, msg, level): 99 os.makedirs(os.path.dirname(log_path), exist_ok=True) 100 sys.stderr.write('\n') 101 with open(log_path, 'at', encoding='utf-8') as log_file: 102 for line in str(msg).splitlines(): 103 sys.stderr.write(LogUtil.message(level, line, LogUtil.stage)) 104 sys.stderr.flush() 105 log_file.write(LogUtil.message(level, line, LogUtil.stage)) 106 107 @staticmethod 108 def analyze_build_error(error_log, status_code_prefix): 109 with open(error_log, 'rt', encoding='utf-8') as log_file: 110 data = log_file.read() 111 status_file = IoUtil.read_json_file(STATUS_FILE) 112 choices = [] 113 status_map = {} 114 for status_code, status in status_file.items(): 115 if not status_code.startswith(status_code_prefix): 116 continue 117 if isinstance(status, dict) and status.get('pattern'): 118 choices.append(status['pattern']) 119 status_map[status['pattern']] = status.get('code') 120 best_match = None 121 best_ratio = 0 122 for choice in choices: 123 pattern = re.compile(choice, re.DOTALL) 124 match = pattern.search(data) 125 if not match: 126 continue 127 ratio = len(match.group()) / len(data) 128 if ratio > best_ratio: 129 best_ratio = ratio 130 best_match = choice 131 return_status_code = status_map.get( 132 best_match) if best_match else f'{status_code_prefix}000' 133 return return_status_code 134 135 @staticmethod 136 def get_gn_failed_log(log_path): 137 error_log = os.path.join(os.path.dirname(log_path), 'error.log') 138 is_gn_failed = False 139 with open(log_path, 'rt', encoding='utf-8') as log_file: 140 lines = log_file.readlines() 141 error_lines = [] 142 for i, line in enumerate(lines): 143 if line.startswith('ERROR at'): 144 error_lines.extend(lines[i: i + 50]) 145 is_gn_failed = True 146 break 147 for log in error_lines[:50]: 148 LogUtil.hb_error(log) 149 with open(error_log, 'at', encoding='utf-8') as log_file: 150 log_file.write(log + '\n') 151 if is_gn_failed: 152 return_status_code = LogUtil.analyze_build_error(error_log, '3') 153 raise OHOSException( 154 'GN Failed! Please check error in {}, and for more build information in {}'.format( 155 error_log, log_path), return_status_code) 156 157 @staticmethod 158 def get_ninja_failed_log(log_path): 159 error_log = os.path.join(os.path.dirname(log_path), 'error.log') 160 is_ninja_failed = False 161 with open(log_path, 'rt', encoding='utf-8') as log_file: 162 data = log_file.read() 163 failed_pattern = re.compile(r'(ninja: error:.*?)\n', re.DOTALL) 164 failed_log = failed_pattern.findall(data) 165 if failed_log: 166 is_ninja_failed = True 167 for log in failed_log: 168 LogUtil.hb_error(log) 169 with open(error_log, 'at', encoding='utf-8') as log_file: 170 log_file.write(log) 171 if is_ninja_failed: 172 return_status_code = LogUtil.analyze_build_error(error_log, '4') 173 raise OHOSException( 174 'NINJA Failed! Please check error in {}, and for more build information in {}'.format( 175 error_log, log_path), return_status_code) 176 177 @staticmethod 178 def get_compiler_failed_log(log_path): 179 error_log = os.path.join(os.path.dirname(log_path), 'error.log') 180 is_compiler_failed = False 181 with open(log_path, 'rt', encoding='utf-8') as log_file: 182 data = log_file.read() 183 failed_pattern = re.compile( 184 r'(\[\d+/\d+\].*?)(?=\[\d+/\d+\]|' 185 'ninja: build stopped)', re.DOTALL) 186 failed_log = failed_pattern.findall(data) 187 if failed_log: 188 is_compiler_failed = True 189 for log in failed_log: 190 if 'FAILED:' in log: 191 LogUtil.hb_error(log) 192 with open(error_log, 'at', encoding='utf-8') as log_file: 193 log_file.write(log) 194 if is_compiler_failed: 195 return_status_code = LogUtil.analyze_build_error(error_log, '4') 196 raise OHOSException( 197 'COMPILE Failed! Please check error in {}, and for more build information in {}'.format( 198 error_log, log_path), return_status_code) 199 200 @staticmethod 201 def get_failed_log(log_path): 202 last_error_log = os.path.join(os.path.dirname(log_path), 'error.log') 203 if os.path.exists(last_error_log): 204 mtime = os.stat(last_error_log).st_mtime 205 os.rename( 206 last_error_log, '{}/error.{}.log'.format(os.path.dirname(last_error_log), mtime)) 207 LogUtil.get_gn_failed_log(log_path) 208 LogUtil.get_ninja_failed_log(log_path) 209 LogUtil.get_compiler_failed_log(log_path) 210 raise OHOSException( 211 'BUILD Failed! Please check build log for more information: {}'.format(log_path)) 212