1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4# Copyright (c) 2023 Huawei Device Co., Ltd.
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18The tool for making hmp.
19
20positional arguments:
21  -pn PACKAGE_NAME, --package_name PACKAGE_NAME
22                        Module package name.
23  -op OUT_PACKAGE, --out_package OUT_PACKAGE
24                        Out package file path.
25  -pi PACK_INFO, --pack_info PACK_INFO
26                        Pack info file path.
27  -mf MODULE_FILES, --module_files MODULE_FILES
28                        Module files path.
29"""
30import os
31import sys
32import argparse
33import zipfile
34import io
35import logging
36
37
38# 1000000: max number of function recursion depth
39MAXIMUM_RECURSION_DEPTH = 1000000
40sys.setrecursionlimit(MAXIMUM_RECURSION_DEPTH)
41
42
43def package_name_check(arg):
44    """
45    Argument check, which is used to check whether the specified arg is none.
46    :param arg: the arg to check
47    :return:  Check result, which is False if the arg is invalid.
48    """
49    if arg is None:
50        UPDATE_LOGGER.print_log(
51            "Package name error: %s" % arg, UPDATE_LOGGER.ERROR_LOG)
52        return False
53    return arg
54
55
56def check_out_package(arg):
57    """
58    Argument check, which is used to check whether
59    the update package path exists.
60    :param arg: The arg to check.
61    :return: Check result
62    """
63    make_dir_path = None
64    if os.path.exists(arg):
65        if os.path.isfile(arg):
66            UPDATE_LOGGER.print_log(
67                "Out package must be a dir path, not a file path. "
68                "path: %s" % arg, UPDATE_LOGGER.ERROR_LOG)
69            return False
70    else:
71        try:
72            UPDATE_LOGGER.print_log(
73                "Out package path does not exist. The dir will be created!"
74                "path: %s" % arg, UPDATE_LOGGER.WARNING_LOG)
75            os.makedirs(arg)
76            make_dir_path = arg
77        except OSError:
78            UPDATE_LOGGER.print_log(
79                "Make out package path dir failed! "
80                "path: %s" % arg, UPDATE_LOGGER.ERROR_LOG)
81            return False
82    return arg
83
84
85def pack_info_check(arg):
86    """
87    Argument check, which is used to check whether
88    the specified arg is a pack info.
89    :param arg: the arg to check
90    :return: Check result, which is False if the arg is invalid.
91    """
92    if not os.path.isfile(arg):
93        UPDATE_LOGGER.print_log(
94            "FileNotFoundError, path: %s" % arg, UPDATE_LOGGER.ERROR_LOG)
95        return False
96    return arg
97
98
99class UpdateToolLogger:
100    """
101    Global log class
102    """
103    INFO_LOG = 'INFO_LOG'
104    WARNING_LOG = 'WARNING_LOG'
105    ERROR_LOG = 'ERROR_LOG'
106    LOG_TYPE = (INFO_LOG, WARNING_LOG, ERROR_LOG)
107
108    def __init__(self, output_type='console'):
109        self.__logger_obj = self.__get_logger_obj(output_type=output_type)
110
111    @staticmethod
112    def __get_logger_obj(output_type='console'):
113        ota_logger = logging.getLogger(__name__)
114        ota_logger.setLevel(level=logging.INFO)
115        formatter = logging.Formatter(
116            '%(asctime)s %(levelname)s : %(message)s',
117            "%Y-%m-%d %H:%M:%S")
118        if output_type == 'console':
119            console_handler = logging.StreamHandler()
120            console_handler.setLevel(logging.INFO)
121            console_handler.setFormatter(formatter)
122            ota_logger.addHandler(console_handler)
123        elif output_type == 'file':
124            file_handler = logging.FileHandler("UpdateToolLog.txt")
125            file_handler.setLevel(logging.INFO)
126            file_handler.setFormatter(formatter)
127            ota_logger.addHandler(file_handler)
128        return ota_logger
129
130    def print_log(self, msg, log_type=INFO_LOG):
131        """
132        Print log information.
133        :param msg: log information
134        :param log_type: log type
135        :return:
136        """
137        if log_type == self.LOG_TYPE[0]:
138            self.__logger_obj.info(msg)
139        elif log_type == self.LOG_TYPE[1]:
140            self.__logger_obj.warning(msg)
141        elif log_type == self.LOG_TYPE[2]:
142            self.__logger_obj.error(msg)
143        else:
144            self.__logger_obj.error("Unknown log type! %s", log_type)
145            return False
146        return True
147
148    def print_uncaught_exception_msg(self, msg, exc_info):
149        """
150        Print log when an uncaught exception occurs.
151        :param msg: Uncaught exception
152        :param exc_info: information about the uncaught exception
153        """
154        self.__logger_obj.error(msg, exc_info=exc_info)
155
156
157UPDATE_LOGGER = UpdateToolLogger()
158
159
160def build_hmp(package_name, out_package, pack_info, module_files):
161    out_package_path = os.path.join(
162        out_package, '%s.zip' % package_name)
163    zip_file = zipfile.ZipFile(out_package_path, 'w', zipfile.ZIP_DEFLATED, allowZip64=True)
164    for module_file in module_files:
165        zip_file.write(module_file, os.path.basename(module_file))
166    zip_file.write(pack_info, os.path.basename(pack_info))
167    zip_file.close()
168    return True
169
170
171def main(argv):
172    """
173    Entry function.
174    """
175    parser = argparse.ArgumentParser()
176
177    parser.add_argument("-pn", "--package_name", type=package_name_check,
178                        default=None, help="Module package name.")
179    parser.add_argument("-op", "--out_package", type=check_out_package,
180                        default=None, help="Out package file path.")
181    parser.add_argument("-pi", "--pack_info", type=pack_info_check,
182                        default=None, help="Pack info file path.")
183    parser.add_argument("-mf", "--module_files", nargs='+',
184                        default=None, help="Module files path.")
185
186    args = parser.parse_args(argv)
187
188    # Generate the hmp.
189    build_re = build_hmp(args.package_name, args.out_package, args.pack_info, args.module_files)
190
191if __name__ == '__main__':
192    main(sys.argv[1:])
193