1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright (c) 2025 Huawei Device Co., Ltd. 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import os 17import shutil 18import subprocess 19from common_utils import ( 20 symlink_src2dest, 21 copy_folder, 22 remove_dest_path, 23 run_cmd_directly, 24 install_hpm, 25 install_hpm_in_other_platform, 26 npm_install, 27 is_system_component, 28 get_code_dir, 29 check_hpm_version, 30 save_data, 31 load_config, 32) 33import re 34import platform 35from collections import OrderedDict 36 37 38class OperateHanlder: 39 global_args = None 40 41 @staticmethod 42 def process_step(process_item: str, step_list: list, unchanged_list: list, processed_dict: dict): 43 process_result_file = os.path.join(OperateHanlder.global_args.code_dir, "prebuilts/.local_data/processed.json") 44 for step in step_list: 45 try: 46 getattr(OperateHanlder, "_" + step.get("type"))(step) 47 except Exception as e: 48 # if the process item is already being processed, but not being recorded(that means prebuilts/processed.json is not exist), 49 # in this situation, we just check if the process item is in unchanged_list, 50 # if it is, then we don't need to process it again, we can just mark it as processed. 51 if process_item in unchanged_list: 52 processed_dict[process_item] = True 53 break 54 # If an error occurs, save the processed status 55 processed_dict[process_item] = False 56 save_data(process_result_file, processed_dict) 57 raise e 58 59 @staticmethod 60 def run(operate_list: list, global_args, unchanged_list: tuple = ()): 61 OperateHanlder.global_args = global_args 62 # read and reset processed record 63 process_result_file = os.path.join(global_args.code_dir, "prebuilts/.local_data/processed.json") 64 if os.path.exists(process_result_file): 65 processed_dict = load_config(process_result_file) 66 else: 67 processed_dict = dict() 68 for key in processed_dict.keys(): 69 if key not in unchanged_list: 70 processed_dict[key] = False 71 72 # group operate_list by process item 73 item_steps_dict = OrderedDict() 74 for current_operate in operate_list: 75 current_process_item = re.match(r"(.*)_\d$", current_operate.get("step_id")).group(1) 76 if current_process_item not in item_steps_dict: 77 item_steps_dict[current_process_item] = [current_operate] 78 else: 79 item_steps_dict[current_process_item].append(current_operate) 80 81 # process each item 82 for process_item, step_list in item_steps_dict.items(): 83 process_item_without_suffix = re.sub(r"(\.[A-Za-z]+)+$", "", process_item).strip("_") 84 # If the process item is in unchanged_list and has been processed, skip it 85 if process_item in unchanged_list: 86 if process_item in processed_dict and processed_dict[process_item]: 87 print(f"==> {process_item_without_suffix} is unchanged, skip") 88 continue 89 print(f"\n==> process {process_item_without_suffix}") 90 processed_dict[process_item] = False 91 OperateHanlder.process_step(process_item, step_list, unchanged_list, processed_dict) 92 processed_dict[process_item] = True 93 # save the processed status of each item 94 save_data(process_result_file, processed_dict) 95 96 @staticmethod 97 def _symlink(operate: dict): 98 src = operate.get("src") 99 dest = operate.get("dest") 100 symlink_src2dest(src, dest) 101 102 @staticmethod 103 def _copy(operate: dict): 104 src = operate.get("src") 105 dest = operate.get("dest") 106 try: 107 shutil.copy2(src, dest) 108 except IsADirectoryError: 109 copy_folder(src, dest) 110 print(f"copy {src} ---> dest: {dest}") 111 112 @staticmethod 113 def _remove(operate: dict): 114 path = operate.get("path") 115 if isinstance(path, list): 116 for p in path: 117 remove_dest_path(p) 118 else: 119 remove_dest_path(path) 120 print(f"remove {path}") 121 122 @staticmethod 123 def _move(operate: dict): 124 src = operate.get("src") 125 dest = operate.get("dest") 126 127 filetype = operate.get("filetype", None) 128 if filetype: 129 file_list = os.listdir(src) 130 for file in file_list: 131 if file.endswith(filetype): 132 file_path = os.path.join(src, file) 133 shutil.move(file_path, dest) 134 print(f"move {file_path} ---> dest: {dest}") 135 else: 136 shutil.move(src, dest) 137 print(f"move {src} ---> dest: {dest}") 138 139 @staticmethod 140 def _shell(operate: dict): 141 cmd = operate.get("cmd") 142 run_cmd_directly(cmd) 143 144 145 @staticmethod 146 def _hpm_download(operate: dict): 147 hpm_path = os.path.join(OperateHanlder.global_args.code_dir, "prebuilts/hpm/node_modules/.bin/hpm") 148 npm_tool_path = os.path.join(OperateHanlder.global_args.code_dir, "prebuilts/build-tools/common/nodejs/current/bin/npm") 149 if check_hpm_version(hpm_path, npm_tool_path): 150 print("hpm version is ok, skip hpm download") 151 return 152 name = operate.get("name") 153 download_dir = operate.get("download_dir") 154 symlink_dest = operate.get("symlink") 155 if "@ohos/hpm-cli" == name: 156 install_hpm(npm_tool_path, download_dir) 157 symlink_src2dest(os.path.join(download_dir, "node_modules"), symlink_dest) 158 return 159 else: 160 install_hpm_in_other_platform(name, operate) 161 162 @staticmethod 163 def _npm_install(operate: dict, max_retry_times=2): 164 if OperateHanlder.global_args.type != "indep": 165 # 若不是系统组件,直接返回 166 if not is_system_component(): 167 return 168 success_installed_npm_config = [] 169 170 for retry_times in range(max_retry_times + 1): 171 try: 172 result, error = npm_install(operate, OperateHanlder.global_args, success_installed_npm_config) 173 if result: 174 return 175 print("npm install error, error info: %s", error) 176 except Exception as e: 177 print("An unexpected error occurred during npm install: %s", str(e)) 178 error = str(e) 179 180 # 重试次数超过最大限制,处理错误日志 181 for error_info in error.split("\n"): 182 if error_info.endswith("debug.log"): 183 log_path = error_info.split()[-1] 184 try: 185 # 读取日志文件内容 186 result = subprocess.run( 187 ["cat", log_path], 188 capture_output=True, 189 text=True, 190 timeout=60 191 ) 192 print("npm debug log content:\n%s", result.stdout) 193 except subprocess.TimeoutExpired: 194 print("Reading npm debug log timed out after 60 seconds.") 195 except Exception as e: 196 print("Error reading npm debug log: %s", str(e)) 197 break 198 199 # 抛出最终异常 200 raise Exception("npm install error with three times, prebuilts download exit") 201 202 @staticmethod 203 def _node_modules_copy(operate: dict): 204 if OperateHanlder.global_args.type != "indep": 205 if not is_system_component(): 206 return 207 208 copy_list = operate.get("copy_list") 209 for copy_config in copy_list: 210 src_dir = copy_config.get("src") 211 if not os.path.exists(src_dir): 212 print(f"{src_dir} not exist, skip node_modules copy.") 213 continue 214 dest_dir = copy_config.get("dest") 215 use_symlink = copy_config.get("use_symlink") 216 if os.path.exists(os.path.dirname(dest_dir)): 217 print("remove", os.path.dirname(dest_dir)) 218 shutil.rmtree(os.path.dirname(dest_dir)) 219 if use_symlink == "True" and OperateHanlder.global_args.enable_symlink == True: 220 os.makedirs(os.path.dirname(dest_dir), exist_ok=True) 221 os.symlink(src_dir, dest_dir) 222 print(f"symlink {src_dir} ---> dest: {dest_dir}") 223 else: 224 shutil.copytree(src_dir, dest_dir, symlinks=True) 225 print(f"copy {src_dir} ---> dest: {dest_dir}") 226 227 @staticmethod 228 def _download_sdk(operate: dict): 229 # 获取操作系统信息 230 system = platform.system() 231 if system == "Linux": 232 host_platform = "linux" 233 elif system == "Darwin": 234 host_platform = "darwin" 235 else: 236 print(f"Unsupported host platform: {system}") 237 exit(1) 238 239 # 获取 CPU 架构信息 240 machine = platform.machine() 241 if machine == "arm64": 242 host_cpu_prefix = "arm64" 243 elif machine == "aarch64": 244 host_cpu_prefix = "aarch64" 245 else: 246 host_cpu_prefix = "x86" 247 248 # 假设 code_dir 是当前目录,可根据实际情况修改 249 code_dir = get_code_dir() 250 prebuilts_python_dir = os.path.join(code_dir, "prebuilts", "python", f"{host_platform}-{host_cpu_prefix}") 251 python_dirs = [os.path.join(prebuilts_python_dir, d) for d in os.listdir(prebuilts_python_dir) if os.path.isdir(os.path.join(prebuilts_python_dir, d))] 252 python_dirs.sort(reverse=True) 253 if python_dirs: 254 python_path = os.path.join(python_dirs[0], "bin") 255 else: 256 raise Exception("python path not exist") 257 ohos_sdk_linux_dir = os.path.join(code_dir, "prebuilts", "ohos-sdk", "linux") 258 if not os.path.isdir(ohos_sdk_linux_dir): 259 python_executable = os.path.join(python_path, "python3") 260 script_path = os.path.join(code_dir, "build", "scripts", "download_sdk.py") 261 try: 262 subprocess.run([python_executable, script_path, "--branch", "master", "--product-name", operate.get("sdk_name"), "--api-version", str(operate.get("version"))], check=True) 263 264 except subprocess.CalledProcessError as e: 265 print(f"Error running download_sdk.py: {e}")