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