15f9996aaSopenharmony_ci#!/usr/bin/env python3
25f9996aaSopenharmony_ci# -*- coding: utf-8 -*-
35f9996aaSopenharmony_ci
45f9996aaSopenharmony_ci#
55f9996aaSopenharmony_ci# Copyright (c) 2023 Huawei Device Co., Ltd.
65f9996aaSopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License");
75f9996aaSopenharmony_ci# you may not use this file except in compliance with the License.
85f9996aaSopenharmony_ci# You may obtain a copy of the License at
95f9996aaSopenharmony_ci#
105f9996aaSopenharmony_ci#     http://www.apache.org/licenses/LICENSE-2.0
115f9996aaSopenharmony_ci#
125f9996aaSopenharmony_ci# Unless required by applicable law or agreed to in writing, software
135f9996aaSopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS,
145f9996aaSopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
155f9996aaSopenharmony_ci# See the License for the specific language governing permissions and
165f9996aaSopenharmony_ci# limitations under the License.
175f9996aaSopenharmony_ci#
185f9996aaSopenharmony_ci
195f9996aaSopenharmony_ciimport sys
205f9996aaSopenharmony_ciimport re
215f9996aaSopenharmony_ciimport os
225f9996aaSopenharmony_ci
235f9996aaSopenharmony_cifrom containers.colors import Colors
245f9996aaSopenharmony_cifrom hb.helper.no_instance import NoInstance
255f9996aaSopenharmony_cifrom resources.global_var import STATUS_FILE
265f9996aaSopenharmony_cifrom util.io_util import IoUtil
275f9996aaSopenharmony_cifrom exceptions.ohos_exception import OHOSException
285f9996aaSopenharmony_ci
295f9996aaSopenharmony_ci
305f9996aaSopenharmony_ciclass LogLevel():
315f9996aaSopenharmony_ci    INFO = 0
325f9996aaSopenharmony_ci    WARNING = 1
335f9996aaSopenharmony_ci    ERROR = 2
345f9996aaSopenharmony_ci    DEBUG = 3
355f9996aaSopenharmony_ci
365f9996aaSopenharmony_ci
375f9996aaSopenharmony_ciclass LogUtil(metaclass=NoInstance):
385f9996aaSopenharmony_ci
395f9996aaSopenharmony_ci    @staticmethod
405f9996aaSopenharmony_ci    def hb_info(msg, mode='normal'):
415f9996aaSopenharmony_ci        level = 'info'
425f9996aaSopenharmony_ci        if mode == 'silent':
435f9996aaSopenharmony_ci            for line in str(msg).splitlines():
445f9996aaSopenharmony_ci                sys.stdout.write('\033[K')
455f9996aaSopenharmony_ci                sys.stdout.write(
465f9996aaSopenharmony_ci                    '\r' + (LogUtil.message(level, line)).strip('\n'))
475f9996aaSopenharmony_ci                sys.stdout.flush()
485f9996aaSopenharmony_ci        elif mode == 'normal':
495f9996aaSopenharmony_ci            level = 'info'
505f9996aaSopenharmony_ci            for line in str(msg).splitlines():
515f9996aaSopenharmony_ci                sys.stdout.write(LogUtil.message(level, line))
525f9996aaSopenharmony_ci                sys.stdout.flush()
535f9996aaSopenharmony_ci
545f9996aaSopenharmony_ci    @staticmethod
555f9996aaSopenharmony_ci    def hb_warning(msg):
565f9996aaSopenharmony_ci        level = 'warning'
575f9996aaSopenharmony_ci        for line in str(msg).splitlines():
585f9996aaSopenharmony_ci            sys.stderr.write(LogUtil.message(level, line))
595f9996aaSopenharmony_ci            sys.stderr.flush()
605f9996aaSopenharmony_ci
615f9996aaSopenharmony_ci    @staticmethod
625f9996aaSopenharmony_ci    def hb_error(msg):
635f9996aaSopenharmony_ci        level = 'error'
645f9996aaSopenharmony_ci        sys.stderr.write('\n')
655f9996aaSopenharmony_ci        for line in str(msg).splitlines():
665f9996aaSopenharmony_ci            sys.stderr.write(LogUtil.message(level, line))
675f9996aaSopenharmony_ci            sys.stderr.flush()
685f9996aaSopenharmony_ci
695f9996aaSopenharmony_ci    @staticmethod
705f9996aaSopenharmony_ci    def message(level, msg):
715f9996aaSopenharmony_ci        if isinstance(msg, str) and not msg.endswith('\n'):
725f9996aaSopenharmony_ci            msg += '\n'
735f9996aaSopenharmony_ci        if level == 'error':
745f9996aaSopenharmony_ci            msg = msg.replace('error:', f'{Colors.ERROR}error{Colors.END}:')
755f9996aaSopenharmony_ci            return f'{Colors.ERROR}[OHOS {level.upper()}]{Colors.END} {msg}'
765f9996aaSopenharmony_ci        elif level == 'info':
775f9996aaSopenharmony_ci            return f'[OHOS {level.upper()}] {msg}'
785f9996aaSopenharmony_ci        else:
795f9996aaSopenharmony_ci            return f'{Colors.WARNING}[OHOS {level.upper()}]{Colors.END} {msg}'
805f9996aaSopenharmony_ci
815f9996aaSopenharmony_ci    @staticmethod
825f9996aaSopenharmony_ci    def write_log(log_path, msg, level):
835f9996aaSopenharmony_ci        os.makedirs(os.path.dirname(log_path), exist_ok=True)
845f9996aaSopenharmony_ci        sys.stderr.write('\n')
855f9996aaSopenharmony_ci        with open(log_path, 'at', encoding='utf-8') as log_file:
865f9996aaSopenharmony_ci            for line in str(msg).splitlines():
875f9996aaSopenharmony_ci                sys.stderr.write(LogUtil.message(level, line))
885f9996aaSopenharmony_ci                sys.stderr.flush()
895f9996aaSopenharmony_ci                log_file.write(LogUtil.message(level, line))
905f9996aaSopenharmony_ci
915f9996aaSopenharmony_ci    @staticmethod
925f9996aaSopenharmony_ci    def analyze_build_error(error_log, status_code_prefix):
935f9996aaSopenharmony_ci        with open(error_log, 'rt', encoding='utf-8') as log_file:
945f9996aaSopenharmony_ci            data = log_file.read()
955f9996aaSopenharmony_ci            status_file = IoUtil.read_json_file(STATUS_FILE)
965f9996aaSopenharmony_ci            choices = []
975f9996aaSopenharmony_ci            status_map = {}
985f9996aaSopenharmony_ci            for status_code, status in status_file.items():
995f9996aaSopenharmony_ci                if not status_code.startswith(status_code_prefix):
1005f9996aaSopenharmony_ci                    continue
1015f9996aaSopenharmony_ci                if isinstance(status, dict) and status.get('pattern'):
1025f9996aaSopenharmony_ci                    choices.append(status['pattern'])
1035f9996aaSopenharmony_ci                    status_map[status['pattern']] = status.get('code')
1045f9996aaSopenharmony_ci            best_match = None
1055f9996aaSopenharmony_ci            best_ratio = 0
1065f9996aaSopenharmony_ci            for choice in choices:
1075f9996aaSopenharmony_ci                pattern = re.compile(choice, re.DOTALL)
1085f9996aaSopenharmony_ci                match = pattern.search(data)
1095f9996aaSopenharmony_ci                if not match:
1105f9996aaSopenharmony_ci                    continue
1115f9996aaSopenharmony_ci                ratio = len(match.group()) / len(data)
1125f9996aaSopenharmony_ci                if ratio > best_ratio:
1135f9996aaSopenharmony_ci                    best_ratio = ratio
1145f9996aaSopenharmony_ci                    best_match = choice
1155f9996aaSopenharmony_ci            return_status_code = status_map.get(
1165f9996aaSopenharmony_ci                best_match) if best_match else f'{status_code_prefix}000'
1175f9996aaSopenharmony_ci        return return_status_code
1185f9996aaSopenharmony_ci
1195f9996aaSopenharmony_ci    @staticmethod
1205f9996aaSopenharmony_ci    def get_gn_failed_log(log_path):
1215f9996aaSopenharmony_ci        error_log = os.path.join(os.path.dirname(log_path), 'error.log')
1225f9996aaSopenharmony_ci        is_gn_failed = False
1235f9996aaSopenharmony_ci        with open(log_path, 'rt', encoding='utf-8') as log_file:
1245f9996aaSopenharmony_ci            lines = log_file.readlines()
1255f9996aaSopenharmony_ci        error_lines = []
1265f9996aaSopenharmony_ci        for i, line in enumerate(lines):
1275f9996aaSopenharmony_ci            if line.startswith('ERROR at'):
1285f9996aaSopenharmony_ci                error_lines.extend(lines[i: i + 50])
1295f9996aaSopenharmony_ci                is_gn_failed = True
1305f9996aaSopenharmony_ci                break
1315f9996aaSopenharmony_ci        for log in error_lines[:50]:
1325f9996aaSopenharmony_ci            LogUtil.hb_error(log)
1335f9996aaSopenharmony_ci            with open(error_log, 'at', encoding='utf-8') as log_file:
1345f9996aaSopenharmony_ci                log_file.write(log + '\n')
1355f9996aaSopenharmony_ci        if is_gn_failed:
1365f9996aaSopenharmony_ci            return_status_code = LogUtil.analyze_build_error(error_log, '3')
1375f9996aaSopenharmony_ci            raise OHOSException(
1385f9996aaSopenharmony_ci                'GN Failed! Please check error in {}, and for more build information in {}'.format(
1395f9996aaSopenharmony_ci                    error_log, log_path), return_status_code)
1405f9996aaSopenharmony_ci
1415f9996aaSopenharmony_ci    @staticmethod
1425f9996aaSopenharmony_ci    def get_ninja_failed_log(log_path):
1435f9996aaSopenharmony_ci        error_log = os.path.join(os.path.dirname(log_path), 'error.log')
1445f9996aaSopenharmony_ci        is_ninja_failed = False
1455f9996aaSopenharmony_ci        with open(log_path, 'rt', encoding='utf-8') as log_file:
1465f9996aaSopenharmony_ci            data = log_file.read()
1475f9996aaSopenharmony_ci        failed_pattern = re.compile(r'(ninja: error:.*?)\n', re.DOTALL)
1485f9996aaSopenharmony_ci        failed_log = failed_pattern.findall(data)
1495f9996aaSopenharmony_ci        if failed_log:
1505f9996aaSopenharmony_ci            is_ninja_failed = True
1515f9996aaSopenharmony_ci        for log in failed_log:
1525f9996aaSopenharmony_ci            LogUtil.hb_error(log)
1535f9996aaSopenharmony_ci            with open(error_log, 'at', encoding='utf-8') as log_file:
1545f9996aaSopenharmony_ci                log_file.write(log)
1555f9996aaSopenharmony_ci        if is_ninja_failed:
1565f9996aaSopenharmony_ci            return_status_code = LogUtil.analyze_build_error(error_log, '4')
1575f9996aaSopenharmony_ci            raise OHOSException(
1585f9996aaSopenharmony_ci                'NINJA Failed! Please check error in {}, and for more build information in {}'.format(
1595f9996aaSopenharmony_ci                    error_log, log_path), return_status_code)
1605f9996aaSopenharmony_ci
1615f9996aaSopenharmony_ci    @staticmethod
1625f9996aaSopenharmony_ci    def get_compiler_failed_log(log_path):
1635f9996aaSopenharmony_ci        error_log = os.path.join(os.path.dirname(log_path), 'error.log')
1645f9996aaSopenharmony_ci        is_compiler_failed = False
1655f9996aaSopenharmony_ci        with open(log_path, 'rt', encoding='utf-8') as log_file:
1665f9996aaSopenharmony_ci            data = log_file.read()
1675f9996aaSopenharmony_ci        failed_pattern = re.compile(
1685f9996aaSopenharmony_ci            r'(\[\d+/\d+\].*?)(?=\[\d+/\d+\]|'
1695f9996aaSopenharmony_ci            'ninja: build stopped)', re.DOTALL)
1705f9996aaSopenharmony_ci        failed_log = failed_pattern.findall(data)
1715f9996aaSopenharmony_ci        if failed_log:
1725f9996aaSopenharmony_ci            is_compiler_failed = True
1735f9996aaSopenharmony_ci        for log in failed_log:
1745f9996aaSopenharmony_ci            if 'FAILED:' in log:
1755f9996aaSopenharmony_ci                LogUtil.hb_error(log)
1765f9996aaSopenharmony_ci                with open(error_log, 'at', encoding='utf-8') as log_file:
1775f9996aaSopenharmony_ci                    log_file.write(log)
1785f9996aaSopenharmony_ci        if is_compiler_failed:
1795f9996aaSopenharmony_ci            return_status_code = LogUtil.analyze_build_error(error_log, '4')
1805f9996aaSopenharmony_ci            raise OHOSException(
1815f9996aaSopenharmony_ci                'COMPILE Failed! Please check error in {}, and for more build information in {}'.format(
1825f9996aaSopenharmony_ci                    error_log, log_path), return_status_code)
1835f9996aaSopenharmony_ci
1845f9996aaSopenharmony_ci    @staticmethod
1855f9996aaSopenharmony_ci    def get_failed_log(log_path):
1865f9996aaSopenharmony_ci        last_error_log = os.path.join(os.path.dirname(log_path), 'error.log')
1875f9996aaSopenharmony_ci        if os.path.exists(last_error_log):
1885f9996aaSopenharmony_ci            mtime = os.stat(last_error_log).st_mtime
1895f9996aaSopenharmony_ci            os.rename(
1905f9996aaSopenharmony_ci                last_error_log, '{}/error.{}.log'.format(os.path.dirname(last_error_log), mtime))
1915f9996aaSopenharmony_ci        LogUtil.get_gn_failed_log(log_path)
1925f9996aaSopenharmony_ci        LogUtil.get_ninja_failed_log(log_path)
1935f9996aaSopenharmony_ci        LogUtil.get_compiler_failed_log(log_path)
1945f9996aaSopenharmony_ci        raise OHOSException(
1955f9996aaSopenharmony_ci            'BUILD Failed! Please check build log for more information: {}'.format(log_path))
196