1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright (c) 2023 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 argparse 17import os 18import sys 19import subprocess 20import shutil 21import json5 22 23from util import build_utils 24from util import file_utils 25 26 27def parse_args(args): 28 parser = argparse.ArgumentParser() 29 build_utils.add_depfile_option(parser) 30 31 parser.add_argument('--nodejs', help='nodejs path') 32 parser.add_argument('--cwd', help='app project directory') 33 parser.add_argument('--sdk-home', help='sdk home') 34 parser.add_argument('--hvigor-home', help='hvigor home') 35 parser.add_argument('--enable-debug', action='store_true', help='if enable debuggable') 36 parser.add_argument('--build-level', default='project', help='module or project') 37 parser.add_argument('--assemble-type', default='assembleApp', help='assemble type') 38 parser.add_argument('--output-file', help='output file') 39 parser.add_argument('--build-profile', help='build profile file') 40 parser.add_argument('--system-lib-module-info-list', nargs='+', help='system lib module info list') 41 parser.add_argument('--ohos-app-abi', help='ohos app abi') 42 parser.add_argument('--ohpm-registry', help='ohpm registry', nargs='?') 43 parser.add_argument('--hap-out-dir', help='hap out dir') 44 parser.add_argument('--hap-name', help='hap name') 45 parser.add_argument('--test-hap', help='build ohosTest if enable', action='store_true') 46 parser.add_argument('--test-module', help='specify the module within ohosTest', default='entry') 47 parser.add_argument('--module-libs-dir', help='', default='entry') 48 parser.add_argument('--sdk-type-name', help='sdk type name', nargs='+', default=['sdk.dir']) 49 parser.add_argument('--build-modules', help='build modules', nargs='+', default=[]) 50 parser.add_argument('--use-hvigor-cache', help='use hvigor cache', action='store_true') 51 parser.add_argument('--hvigor-obfuscation', help='hvigor obfuscation', action='store_true') 52 parser.add_argument('--ohos-app-enable-asan', help='hvigor enable asan', action='store_true') 53 parser.add_argument('--ohos-app-enable-tsan', help='hvigor enable tsan', action='store_true') 54 parser.add_argument('--ohos-app-enable-ubsan', help='hvigor enable ubsan', action='store_true') 55 parser.add_argument('--target-out-dir', help='base output dir') 56 parser.add_argument('--target-app-dir', help='target output dir') 57 parser.add_argument('--product', help='set product value of hvigor cmd, default or others') 58 parser.add_argument('--module-target', help='set module target of unsigned hap path') 59 parser.add_argument('--modules-filter', help='if enable filter unsigned hap or hsp packages', action='store_true') 60 parser.add_argument('--ohos-test-coverage', help='enable test coverage when compile hap', action='store_true') 61 62 options = parser.parse_args(args) 63 return options 64 65 66def get_root_dir(): 67 current_dir = os.path.dirname(__file__) 68 while True: 69 check_path = os.path.join(current_dir, ".gn") 70 if os.path.exists(check_path): 71 return current_dir 72 else: 73 new_dir = os.path.dirname(current_dir) 74 if new_dir == current_dir: 75 raise Exception(f"file {__file__} not in ohos source directory") 76 else: 77 current_dir = new_dir 78 79 80def make_env(build_profile: str, cwd: str, ohpm_registry: str, options): 81 ''' 82 Set up the application compilation environment and run "ohpm install" 83 :param build_profile: module compilation information file 84 :param cwd: app project directory 85 :param ohpm_registry: ohpm registry 86 :return: None 87 ''' 88 print(f"build_profile:{build_profile}; cwd:{cwd}") 89 cur_dir = os.getcwd() 90 root_dir = get_root_dir() 91 ohpm_path = os.path.join(root_dir, "prebuilts/tool/command-line-tools/ohpm/bin/ohpm") 92 if not os.path.exists(ohpm_path): 93 ohpm_path = "ohpm" 94 with open(build_profile, 'r') as input_f: 95 build_info = json5.load(input_f) 96 modules_list = build_info.get('modules') 97 ohpm_install_cmd = [ohpm_path, 'install'] 98 if ohpm_registry: 99 ohpm_install_cmd.append('--registry=' + ohpm_registry) 100 env = { 101 'PATH': f"{os.path.dirname(os.path.abspath(options.nodejs))}:{os.environ.get('PATH')}", 102 'NODE_HOME': os.path.dirname(os.path.abspath(options.nodejs)), 103 } 104 os.chdir(cwd) 105 if os.path.exists(os.path.join(cwd, 'hvigorw')): 106 subprocess.run(['chmod', '+x', 'hvigorw']) 107 if os.path.exists(os.path.join(cwd, '.arkui-x/android/gradlew')): 108 subprocess.run(['chmod', '+x', '.arkui-x/android/gradlew']) 109 proc = subprocess.Popen(ohpm_install_cmd, 110 stdout=subprocess.PIPE, 111 stderr=subprocess.PIPE, 112 env=env, 113 encoding='utf-8') 114 stdout, stderr = proc.communicate() 115 if proc.returncode: 116 raise Exception('ReturnCode:{}. ohpm install failed. {}'.format( 117 proc.returncode, stdout)) 118 os.chdir(cur_dir) 119 120 121def get_integrated_project_config(cwd: str): 122 print(f"[0/0] project dir: {cwd}") 123 with open(os.path.join(cwd, 'hvigor/hvigor-config.json5'), 'r') as input_f: 124 hvigor_info = json5.load(input_f) 125 model_version = hvigor_info.get('modelVersion') 126 return model_version 127 128 129def get_hvigor_version(cwd: str): 130 print(f"[0/0] project dir: {cwd}") 131 with open(os.path.join(cwd, 'hvigor/hvigor-config.json5'), 'r') as input_f: 132 hvigor_info = json5.load(input_f) 133 hvigor_version = hvigor_info.get('hvigorVersion') 134 return hvigor_version 135 136 137def get_unsigned_hap_path(project_name: str, src_path: str, cwd: str, options): 138 hvigor_version = get_hvigor_version(cwd) 139 model_version = get_integrated_project_config(cwd) 140 product_value = options.product 141 if product_value is None: 142 product_value = 'default' 143 if options.test_hap: 144 if options.target_app_dir and ((hvigor_version and float(hvigor_version[:3]) > 4.1) or model_version): 145 new_src_path = os.path.join(options.target_out_dir, options.target_app_dir, project_name, src_path) 146 unsigned_hap_path = os.path.join( 147 new_src_path, 'build/default/outputs/ohosTest') 148 else: 149 unsigned_hap_path = os.path.join( 150 cwd, src_path, 'build/default/outputs/ohosTest') 151 else: 152 module_target = options.module_target 153 if options.target_app_dir and ((hvigor_version and float(hvigor_version[:3]) > 4.1) or model_version): 154 new_src_path = os.path.join(options.target_out_dir, options.target_app_dir, project_name, src_path) 155 unsigned_hap_path = os.path.join( 156 new_src_path, f'build/{product_value}/outputs/{module_target}') 157 else: 158 unsigned_hap_path = os.path.join( 159 cwd, src_path, f'build/{product_value}/outputs/{module_target}') 160 return unsigned_hap_path 161 162 163def gen_unsigned_hap_path_json(build_profile: str, cwd: str, options): 164 ''' 165 Generate unsigned_hap_path_list 166 :param build_profile: module compilation information file 167 :param cwd: app project directory 168 :return: None 169 ''' 170 unsigned_hap_path_json = {} 171 unsigned_hap_path_list = [] 172 with open(build_profile, 'r') as input_f: 173 build_info = json5.load(input_f) 174 modules_list = build_info.get('modules') 175 for module in modules_list: 176 if options.modules_filter and module.get('name') not in options.build_modules: 177 continue 178 src_path = module.get('srcPath') 179 project_name = options.build_profile.replace("/build-profile.json5", "").split("/")[-1] 180 unsigned_hap_path = get_unsigned_hap_path(project_name, src_path, cwd, options) 181 hap_file = build_utils.find_in_directory( 182 unsigned_hap_path, '*-unsigned.hap') 183 hsp_file = build_utils.find_in_directory( 184 unsigned_hap_path, '*-unsigned.hsp') 185 unsigned_hap_path_list.extend(hap_file) 186 unsigned_hap_path_list.extend(hsp_file) 187 unsigned_hap_path_json['unsigned_hap_path_list'] = unsigned_hap_path_list 188 file_utils.write_json_file(options.output_file, unsigned_hap_path_json) 189 190 191def copy_libs(cwd: str, system_lib_module_info_list: list, ohos_app_abi: str, module_libs_dir: str): 192 ''' 193 Obtain the output location of system library .so by reading the module compilation information file, 194 and copy it to the app project directory 195 :param cwd: app project directory 196 :param system_lib_module_info_list: system library module compilation information file 197 :param ohos_app_abi: app abi 198 :return: None 199 ''' 200 for _lib_info in system_lib_module_info_list: 201 lib_info = file_utils.read_json_file(_lib_info) 202 lib_path = lib_info.get('source') 203 if os.path.exists(lib_path): 204 lib_name = os.path.basename(lib_path) 205 dest = os.path.join(cwd, f'{module_libs_dir}/libs', ohos_app_abi, lib_name) 206 if not os.path.exists(os.path.dirname(dest)): 207 os.makedirs(os.path.dirname(dest), exist_ok=True) 208 shutil.copyfile(lib_path, dest) 209 210 211def hvigor_write_log(cmd, cwd, env): 212 proc = subprocess.Popen(cmd, 213 cwd=cwd, 214 env=env, 215 stdout=subprocess.PIPE, 216 stderr=subprocess.PIPE, 217 encoding='utf-8') 218 stdout, stderr = proc.communicate() 219 for line in stdout.splitlines(): 220 print(f"[1/1] Hvigor info: {line}") 221 for line in stderr.splitlines(): 222 print(f"[2/2] Hvigor warning: {line}") 223 os.makedirs(os.path.join(cwd, 'build'), exist_ok=True) 224 with open(os.path.join(cwd, 'build', 'build.log'), 'w') as f: 225 f.write(f'{stdout}\n') 226 f.write(f'{stderr}\n') 227 if proc.returncode or "ERROR: BUILD FAILED" in stderr or "ERROR: BUILD FAILED" in stdout: 228 raise Exception('ReturnCode:{}. Hvigor build failed: {}'.format(proc.returncode, stderr)) 229 print("[0/0] Hvigor build end") 230 231 232def build_hvigor_cmd(cwd: str, model_version: str, options): 233 cmd = ['bash'] 234 hvigor_version = get_hvigor_version(cwd) 235 if options.hvigor_home: 236 cmd.extend([f'{os.path.abspath(options.hvigor_home)}/bin/hvigorw']) 237 elif model_version: 238 code_home = os.path.dirname(os.path.dirname(options.sdk_home)) 239 hvigor_home = f"{code_home}/tool/command-line-tools/bin" 240 cmd.extend([f'{hvigor_home}/hvigorw']) 241 else: 242 cmd.extend(['./hvigorw']) 243 244 if options.ohos_app_enable_asan: 245 cmd.extend(['-p', 'ohos-debug-asan=true']) 246 elif options.ohos_app_enable_tsan: 247 cmd.extend(['-p', 'ohos-enable-tsan=true']) 248 elif options.ohos_app_enable_ubsan: 249 cmd.extend(['-p', 'ohos-enable-ubsan=true']) 250 251 product_value = options.product if options.product else 'default' 252 if options.test_hap: 253 cmd.extend(['--mode', 'module', '-p', 254 f'module={options.test_module}@ohosTest', 'assembleHap']) 255 elif options.build_modules: 256 cmd.extend(['assembleHap', '--mode', 257 'module', '-p', f'product={product_value}', '-p', 'module=' + ','.join(options.build_modules)]) 258 else: 259 cmd.extend(['--mode', 260 options.build_level, '-p', f'product={product_value}', options.assemble_type]) 261 262 if options.enable_debug: 263 cmd.extend(['-p', 'debuggable=true']) 264 else: 265 cmd.extend(['-p', 'debuggable=false']) 266 267 if options.use_hvigor_cache and os.environ.get('CACHE_BASE'): 268 hvigor_cache_dir = os.path.join(os.environ.get('CACHE_BASE'), 'hvigor_cache', options.cwd) 269 os.makedirs(hvigor_cache_dir, exist_ok=True) 270 cmd.extend(['-p', f'build-cache-dir={hvigor_cache_dir}']) 271 272 if options.hvigor_obfuscation: 273 cmd.extend(['-p', 'buildMode=release']) 274 else: 275 cmd.extend(['-p', 'hvigor-obfuscation=false']) 276 277 if options.ohos_test_coverage: 278 cmd.extend(['-p', 'ohos-test-coverage=true']) 279 280 if options.target_app_dir and options.target_app_dir != "": 281 if (hvigor_version and float(hvigor_version[:3]) > 4.1) or model_version: 282 target_out_dir = os.path.abspath(options.target_out_dir) 283 output_dir = os.path.join(target_out_dir, options.target_app_dir) 284 cmd.extend(['-c', f'properties.ohos.buildDir="{output_dir}"']) 285 286 cmd.extend(['--no-daemon']) 287 288 print("[0/0] hvigor cmd: " + ' '.join(cmd)) 289 return cmd 290 291 292def set_sdk_path(cwd: str, model_version: str, options, env): 293 if 'sdk.dir' not in options.sdk_type_name and model_version: 294 write_env_sdk(options, env) 295 else: 296 write_local_properties(cwd, options) 297 298 299def write_local_properties(cwd: str, options): 300 sdk_dir = options.sdk_home 301 nodejs_dir = os.path.abspath( 302 os.path.dirname(os.path.dirname(options.nodejs))) 303 with open(os.path.join(cwd, 'local.properties'), 'w') as f: 304 for sdk_type in options.sdk_type_name: 305 f.write(f'{sdk_type}={sdk_dir}\n') 306 f.write(f'nodejs.dir={nodejs_dir}\n') 307 308 309def write_env_sdk(options, env): 310 sdk_dir = options.sdk_home 311 env['DEVECO_SDK_HOME'] = sdk_dir 312 313 314def hvigor_sync(cwd: str, model_version: str, env): 315 if not model_version: 316 subprocess.run(['bash', './hvigorw', '--sync', '--no-daemon'], 317 cwd=cwd, 318 env=env, 319 stdout=subprocess.DEVNULL, 320 stderr=subprocess.DEVNULL) 321 322 323def hvigor_build(cwd: str, options): 324 ''' 325 Run hvigorw to build the app or hap 326 :param cwd: app project directory 327 :param options: command line parameters 328 :return: None 329 ''' 330 model_version = get_integrated_project_config(cwd) 331 print(f"[0/0] model_version: {model_version}") 332 333 cmd = build_hvigor_cmd(cwd, model_version, options) 334 335 print("[0/0] Hvigor clean start") 336 env = os.environ.copy() 337 env['CI'] = 'true' 338 env['PATH'] = f"{os.path.dirname(os.path.abspath(options.nodejs))}:{os.environ.get('PATH')}" 339 env['NODE_HOME'] = os.path.dirname(os.path.dirname(os.path.abspath(options.nodejs))) 340 library_path = os.path.join(os.path.abspath(options.sdk_home), '20/ets/ets1.2/build-tools/ets2panda/lib/') 341 env['LD_LIBRARY_PATH'] = library_path 342 set_sdk_path(cwd, model_version, options, env) 343 344 hvigor_sync(cwd, model_version, env) 345 346 print("[0/0] Hvigor build start") 347 hvigor_write_log(cmd, cwd, env) 348 349 350def main(args): 351 options = parse_args(args) 352 cwd = os.path.abspath(options.cwd) 353 354 # copy system lib deps to app libs dir 355 if options.system_lib_module_info_list: 356 copy_libs(cwd, options.system_lib_module_info_list, 357 options.ohos_app_abi, options.module_libs_dir) 358 359 os.environ['PATH'] = '{}:{}'.format(os.path.dirname( 360 os.path.abspath(options.nodejs)), os.environ.get('PATH')) 361 362 # add arkui-x to PATH 363 os.environ['PATH'] = f'{cwd}/.arkui-x/android:{os.environ.get("PATH")}' 364 365 # generate unsigned_hap_path_list and run ohpm install 366 make_env(options.build_profile, cwd, options.ohpm_registry, options) 367 368 # invoke hvigor to build hap or app 369 hvigor_build(cwd, options) 370 371 # generate a json file to record the path of all unsigned haps, and When signing hap later, 372 # this json file will serve as input to provide path information for each unsigned hap. 373 gen_unsigned_hap_path_json(options.build_profile, cwd, options) 374 375if __name__ == '__main__': 376 sys.exit(main(sys.argv[1:])) 377