1#!/usr/bin/env python 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 16# Description 17# 18# This script is invoked by the build system and does not need to be executed directly by the developer. 19# First, it checks if --release is provided as an argument. This is the only allowed type for stateMgmt 20# that is included in the build image. It then verifies if the node_modules folder exists. If not, npm 21# install is executed. Afterward, npm run build_release is performed, which also generates generateGni.js 22# The files_to_watch.gni file contains a list of input files from tsconfig.base.json. When any of these 23# files are modified, the build system triggers this script to regenerate stateMgmt.js. 24 25import os 26import sys 27import json 28import time 29import shutil 30import subprocess 31from typing import Dict, List 32 33from preprocess import merge_component 34 35 36TARGET_CMD = "arkoala:abc" 37ABC_FILE = "arkoala.abc" 38NPM_REPO = "https://repo.huaweicloud.com/repository/npm/" 39 40 41class Paths: 42 def __init__(self): 43 self.project_path = None 44 self.build_path = None 45 self.node_path = None 46 self.output_target_path = None 47 self.es2panda_path = None 48 self.ohos_ets_api_path = None 49 self.ohos_ets_arkts_path = None 50 self.arklink_path = None 51 self.ets_stdlib_path = None 52 self.innerkits_path = None 53 self.check_install_path = None 54 self.check_bin_path = None 55 self.dist_path = None 56 self.logfile = None 57 self.built_path = None 58 self.check_fast_arktsc = None 59 self.arkui_ohos_path = None 60 61 62def parse_argv(argv) -> Paths: 63 """ 64 parse command line arguments 65 """ 66 if len(argv) < 4: 67 print("Usage: python build.py <path_to_project> <build_path> <node_path> <output_target_path>") 68 sys.exit(1) 69 70 path = Paths() 71 path.project_path = os.path.abspath(argv[1]) 72 path.build_path = os.path.abspath(argv[2]) 73 path.node_path = os.path.abspath(argv[3]) 74 path.output_target_path = os.path.abspath(argv[4]) 75 path.es2panda_path = os.path.abspath(argv[5]) 76 path.ohos_ets_api_path = os.path.abspath(argv[6]) 77 path.ohos_ets_arkts_path = os.path.abspath(argv[7]) 78 path.arklink_path = os.path.abspath(argv[8]) 79 path.ets_stdlib_path = os.path.abspath(argv[9]) 80 path.innerkits_path = os.path.abspath(argv[10]) 81 path.check_install_path = os.path.join(path.project_path, "arkoala-arkts", "node_modules") 82 path.check_bin_path = os.path.join(path.check_install_path, ".bin") 83 path.check_fast_arktsc = os.path.join(path.check_bin_path, "fast-arktsc") 84 path.dist_path = os.path.join(path.build_path, "dist") 85 path.logfile = os.path.join(path.dist_path, "koala_build.log") 86 path.arkui_ohos_path = os.path.join(path.project_path, "arkoala-arkts" ,"arkui-ohos-preprocess") 87 return path 88 89 90def prebuilt_dist(path: Paths): 91 """ 92 create log file and prebuilt dist path 93 """ 94 if os.path.exists(path.logfile): 95 try: 96 os.remove(path.logfile) 97 except OSError as e: 98 print("remove log file filed!") 99 if not os.path.exists(path.dist_path): 100 os.makedirs(path.dist_path) 101 with open(path.logfile, "a+") as f: 102 f.write("koala build:\n") 103 f.write(path.logfile + "\n") 104 f.write("es2panda_path: " + path.es2panda_path + "\n") 105 f.write("arklink_path:" + path.arklink_path + "\n") 106 f.write("ets_stdlib_path:" + path.ets_stdlib_path + "\n") 107 f.close() 108 109 110def change_env_path(env, path: Paths): 111 """ 112 change env path for es2pand and arklink process 113 """ 114 env["PATH"] = f"{path.node_path}:{env['PATH']}" 115 if (path.es2panda_path != ""): 116 env["ES2PANDA_PATH"] = path.es2panda_path 117 if (path.arklink_path != ""): 118 env["ARKLINK_PATH"] = path.arklink_path 119 if (path.ets_stdlib_path != ""): 120 env["ETS_STDLIB_PATH"] = path.ets_stdlib_path 121 try: 122 ret = subprocess.run(["npm", "-v"], capture_output=True, env = env, text=True, check=True) 123 with open(path.logfile, "a+") as f: 124 f.write("\n") 125 f.write("path env:" + env["PATH"] + "\n") 126 f.write("npm version:" + ret.stdout) 127 print(f"npm version: {ret.stdout}") 128 f.close() 129 except subprocess.CalledProcessError as e: 130 with open(path.logfile, "a+") as f: 131 f.write("\n") 132 f.write("error message: "+ e.stderr + "\n") 133 print(f"error message: {e.stderr}") 134 f.close() 135 os.chdir(path.project_path) 136 137 138def check_node_modules(env, path: Paths): 139 """ 140 Check if `node_modules` exists. If yes skip npm install 141 """ 142 if not os.path.exists(path.check_install_path): 143 try: 144 ret = subprocess.run( 145 ["npm", "install", "--registry", NPM_REPO, "--verbose"], 146 capture_output=True, 147 env = env, 148 text=True, 149 check=True 150 ) 151 with open(path.logfile, "a+") as f: 152 f.write("\n") 153 f.write("install log:\n" + ret.stdout) 154 print(f"install log:\n {ret.stdout}") 155 f.close() 156 except subprocess.CalledProcessError as e: 157 with open(path.logfile, "a+") as f: 158 f.write("\n") 159 f.write("error message: "+ e.stderr + "\n") 160 print(f"error message: {e.stderr}") 161 f.close() 162 else: 163 print(f"arkola node_modules directory exists, skip install") 164 with open(path.logfile, "a+") as f: 165 f.write("\n") 166 f.write("arkola node_modules directory exists, skip install\n") 167 f.close() 168 169 170def check_fast_arktsc(path: Paths): 171 """ 172 check fast arktsc 173 """ 174 if not os.path.exists(path.check_fast_arktsc): 175 print(f"fast-arktsc not found!") 176 with open(path.logfile, "a+") as f: 177 f.write("\n") 178 f.write("fast-arktsc not found!\n") 179 f.close() 180 181 182def is_target_file(file_name: str) -> bool: 183 """ 184 Check if the given file name is a target file. 185 """ 186 target_extensions = [".d.ets", ".ets"] 187 return any(file_name.endswith(ext) for ext in target_extensions) 188 189 190def get_key_from_file_name(file_name: str) -> str: 191 """ 192 Extract the key from the given file name. 193 """ 194 if ".d." in file_name: 195 file_name = file_name.replace(".d.", ".") 196 return os.path.splitext(file_name)[0] 197 198 199def scan_directory_for_paths(directory: str) -> Dict[str, List[str]]: 200 """ 201 Scan the specified directory to find all target files and organize their paths by key. 202 """ 203 paths = {} 204 for root, _, files in os.walk(directory): 205 for file in files: 206 if not is_target_file(file): 207 continue 208 file_path = os.path.abspath(os.path.join(root, file)) 209 file_name = get_key_from_file_name(file) 210 file_abs_path = os.path.abspath(os.path.join(root, file_name)) 211 file_rel_path = os.path.relpath(file_abs_path, start=directory) 212 # Split the relative path into components 213 path_components = file_rel_path.split(os.sep) 214 first_level_dir = path_components[0] if len(path_components) > 0 else "" 215 second_level_dir = path_components[1] if len(path_components) > 1 else "" 216 # Determine the key based on directory structure 217 if first_level_dir == "arkui" and second_level_dir == "runtime-api": 218 key = file_name 219 else: 220 key = file_rel_path.replace(os.sep, ".") 221 if key in paths: 222 paths[key].append(file_path) 223 else: 224 paths[key] = [file_path] 225 return paths 226 227 228def generate_new_arkts_config(path: Paths): 229 """ 230 generate new arkts config json 231 """ 232 paths = {} 233 new_paths = {} 234 if path.ohos_ets_api_path == "" or path.ohos_ets_arkts_path == "": 235 print(f"ohos ets api or arkts path not exists") 236 sys.exit(1) 237 else: 238 scan_paths = [path.ohos_ets_api_path, path.ohos_ets_arkts_path] 239 for scan_path in scan_paths: 240 scanned_paths = scan_directory_for_paths(scan_path) 241 for key, value in scanned_paths.items(): 242 if key in paths: 243 paths[key].extend(value) 244 else: 245 paths[key] = value 246 old_arkts_config_path = os.path.join(path.arkui_ohos_path, "arktsconfig-unmemoized.json") 247 new_arkts_config_path = os.path.join(path.arkui_ohos_path, "arktsconfig-unmemoized-merged.json") 248 # need take old arkts config json paths splicing to new arkts config paths 249 with open(old_arkts_config_path, 'r', encoding='utf-8') as f: 250 old_data = json.load(f) 251 old_paths = old_data['compilerOptions']['paths'] 252 for key, value in paths.items(): 253 if key not in old_paths: 254 old_paths[key] = value 255 with open(new_arkts_config_path, 'w', encoding="utf-8") as f: 256 json.dump(old_data, f, indent=2, ensure_ascii=False) 257 258 259def run_build_arkoala(env, path: Paths): 260 """ 261 run build arkoala 262 """ 263 try: 264 ret = subprocess.run( 265 ["npm", "run", TARGET_CMD, "--verbose"], 266 capture_output=True, 267 env = env, 268 text=True, 269 check=True 270 ) 271 with open(path.logfile, "a+") as f: 272 f.write("\n") 273 f.write("build log:\n" + ret.stdout) 274 print(f"build log:\n {ret.stdout}") 275 f.close() 276 except subprocess.CalledProcessError as e: 277 with open(path.logfile, "a+") as f: 278 f.write("\n") 279 f.write("build log: "+ e.stdout + "\n") 280 f.write("error message: "+ e.stderr + "\n") 281 print(f"build log:\n {e.stdout}") 282 print(f"error message: {e.stderr}") 283 f.close() 284 285 286def copy_akoala_abc(path: Paths): 287 """ 288 copy arkoala abc file and move to dist path 289 """ 290 built_path = os.path.join(path.project_path, "arkoala-arkts", "build", ABC_FILE) 291 if not os.path.exists(built_path): 292 print(f"Error: Built file not found at {built_path}") 293 sys.exit(1) 294 dist_file = os.path.join(path.dist_path, ABC_FILE) 295 output_file = os.path.join(path.output_target_path, ABC_FILE) 296 try: 297 shutil.copy(built_path, dist_file) 298 shutil.copy(built_path, output_file) 299 print(f"Arkoala: File successfully copied to {dist_file}") 300 with open(path.logfile, "a+") as f: 301 f.write("\n") 302 f.write("Arkoala: File successfully copied to " + dist_file + " \n") 303 f.write("Arkoala: File successfully copied to " + output_file + " \n") 304 f.close() 305 except Exception as e: 306 print(f"Error: Failed to copy file: {e}") 307 sys.exit(1) 308 309 310def resolve_innerkis_config(path: Paths, config_file, output_file): 311 config_file_path = os.path.join(path.arkui_ohos_path, config_file) 312 with open(path.logfile, "a+") as f: 313 f.write("\n") 314 f.write("resolve config path: " + config_file_path + " \n") 315 f.close() 316 317 try: 318 with open(config_file_path, 'r', encoding='utf-8') as file: 319 with open(path.logfile, "a+") as f: 320 f.write("resolved path file opend \n") 321 f.close() 322 json_data = json.load(file) 323 324 # 获取 baseUrl 325 base_url = json_data['compilerOptions']['baseUrl'] 326 327 # 获取配置文件的目录路径 328 config_dir = os.path.dirname(config_file_path) 329 330 # 将 baseUrl 解析为绝对路径(相对于配置文件) 331 absolute_base_url = os.path.abspath(os.path.join(config_dir, base_url)) 332 333 # 获取 paths 334 paths = json_data['compilerOptions']['paths'] 335 336 # 处理 paths 中的相对路径 337 resolved_paths = {} 338 for key, value in paths.items(): 339 resolved_value = [os.path.abspath(os.path.join(absolute_base_url, path)) for path in value] 340 resolved_paths[key] = resolved_value 341 if not os.path.exists(path.innerkits_path): 342 os.makedirs(path.innerkits_path) 343 with open(output_file, 'w', encoding='utf-8') as f: 344 json.dump({"paths": resolved_paths}, f, indent=2, ensure_ascii=False) 345 with open(path.logfile, "a+") as f: 346 f.write("output_file generated:" + output_file + " \n") 347 f.close() 348 except Exception as e: 349 with open(path.logfile, "a+") as f: 350 f.write("Error: Failed to generate innerkit path file: " + e + " \n") 351 f.close() 352 sys.exit(1) 353 354 355def pre_processing(path: Paths): 356 start_time = time.time() 357 original_path = os.path.join( 358 path.project_path, "arkoala-arkts", "arkui-ohos") 359 target_path = os.path.join( 360 path.project_path, "arkoala-arkts", "arkui-ohos-preprocess") 361 362 if os.path.exists(target_path): 363 shutil.rmtree(target_path) 364 365 def ignore_build_path(src, names): 366 return ["build"] if "build" in names else [] 367 368 shutil.copytree(original_path, target_path, ignore=ignore_build_path, dirs_exist_ok=True) 369 370 handwritten_path = os.path.join(target_path, "src", "handwritten", "component") 371 generated_path = os.path.join(target_path, "src", "component") 372 373 merge_component(handwritten_path, generated_path) 374 375 shutil.rmtree(handwritten_path) 376 end_time = time.time() 377 elapsed_time = end_time - start_time 378 print(f"Arkoala: preprocess time: {elapsed_time:.2f} seconds") 379 return 380 381def main(argv): 382 path = parse_argv(argv) 383 pre_processing(path) 384 prebuilt_dist(path) 385 env = os.environ.copy() 386 env_old_path = env["PATH"] 387 change_env_path(env, path) 388 check_node_modules(env, path) 389 check_fast_arktsc(path) 390 generate_new_arkts_config(path) 391 run_build_arkoala(env, path) 392 env["PATH"] = env_old_path 393 copy_akoala_abc(path) 394 resolve_innerkis_config(path, "arktsconfig-unmemoized-merged.json", os.path.join(path.innerkits_path, "arkts_paths.json")) 395 396 397if __name__ == '__main__': 398 start_time = time.time() 399 main(sys.argv) 400 end_time = time.time() 401 elapsed_time = end_time - start_time 402 print(f"Arkoala: build time: {elapsed_time:.2f} seconds") 403