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