import argparse
import os
import sys
import stat
import subprocess
import shutil
import json
import json5
import uuid
from util import build_utils
from util import file_utils
def parse_args(args):
parser = argparse.ArgumentParser()
build_utils.add_depfile_option(parser)
parser.add_argument('--nodejs', help='nodejs path')
parser.add_argument('--cwd', help='app project directory')
parser.add_argument('--sdk-home', help='sdk home')
parser.add_argument('--hvigor-home', help='hvigor home')
parser.add_argument('--enable-debug', action='store_true', help='if enable debuggable')
parser.add_argument('--build-level', default='project', help='module or project')
parser.add_argument('--assemble-type', default='assembleApp', help='assemble type')
parser.add_argument('--output-file', help='output file')
parser.add_argument('--build-profile', help='build profile file')
parser.add_argument('--system-lib-module-info-list', nargs='+', help='system lib module info list')
parser.add_argument('--ohos-app-abi', help='ohos app abi')
parser.add_argument('--ohpm-registry', help='ohpm registry', nargs='?')
parser.add_argument('--hap-out-dir', help='hap out dir')
parser.add_argument('--hap-name', help='hap name')
parser.add_argument('--test-hap', help='build ohosTest if enable', action='store_true')
parser.add_argument('--test-module', help='specify the module within ohosTest', default='entry')
parser.add_argument('--module-libs-dir', help='', default='entry')
parser.add_argument('--sdk-type-name', help='sdk type name', nargs='+', default=['sdk.dir'])
parser.add_argument('--build-modules', help='build modules', nargs='+', default=[])
parser.add_argument('--use-hvigor-cache', help='use hvigor cache', action='store_true')
parser.add_argument('--hvigor-obfuscation', help='hvigor obfuscation', action='store_true')
parser.add_argument('--ohos-app-enable-asan', help='hvigor enable asan', action='store_true')
parser.add_argument('--ohos-app-enable-tsan', help='hvigor enable tsan', action='store_true')
parser.add_argument('--ohos-app-enable-ubsan', help='hvigor enable ubsan', action='store_true')
parser.add_argument('--target-out-dir', help='base output dir')
parser.add_argument('--target-app-dir', help='target output dir')
parser.add_argument('--ohos-test-coverage', help='enable test coverage when compile hap', action='store_true')
parser.add_argument('--product', help='set product value of hvigor cmd, default or others')
parser.add_argument('--host-os', help='host os')
options = parser.parse_args(args)
return options
def get_root_dir():
current_dir = os.path.dirname(__file__)
while True:
check_path = os.path.join(current_dir, ".gn")
if os.path.exists(check_path):
return current_dir
else:
new_dir = os.path.dirname(current_dir)
if new_dir == current_dir:
raise Exception(f"file {__file__} not in ohos source directory")
else:
current_dir = new_dir
def make_env(build_profile: str, cwd: str, ohpm_registry: str, options, hash_value: str, node_home: str):
'''
Set up the application compilation environment and run "ohpm install"
:param build_profile: module compilation information file
:param cwd: app project directory
:param ohpm_registry: ohpm registry
:return: None
'''
print(f"[{hash_value}] build_profile:{build_profile}; cwd:{cwd}")
cur_dir = os.getcwd()
root_dir = get_root_dir()
ohpm_path = os.path.join(root_dir, "prebuilts/tool/command-line-tools/ohpm/bin/ohpm")
if not os.path.exists(ohpm_path):
ohpm_path = "ohpm"
with open(build_profile, 'r') as input_f:
build_info = json5.load(input_f)
modules_list = build_info.get('modules')
ohpm_install_cmd = [ohpm_path, 'install']
if ohpm_registry:
ohpm_install_cmd.append('--registry=' + ohpm_registry)
env = {
'PATH': f"{os.path.dirname(os.path.abspath(node_home))}:{os.environ.get('PATH')}",
'NODE_HOME': os.path.dirname(os.path.abspath(node_home)),
}
os.chdir(cwd)
if os.path.exists(os.path.join(cwd, 'oh_modules')):
shutil.rmtree(os.path.join(cwd, 'oh_modules'), ignore_errors=True)
if os.path.exists(os.path.join(cwd, 'hvigorw')):
subprocess.run(['chmod', '+x', 'hvigorw'])
if os.path.exists(os.path.join(cwd, '.arkui-x/android/gradlew')):
subprocess.run(['chmod', '+x', '.arkui-x/android/gradlew'])
proc = subprocess.Popen(ohpm_install_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
encoding='utf-8')
stdout, stderr = proc.communicate()
if proc.returncode:
raise Exception('ReturnCode:{}. ohpm install failed. {}'.format(
proc.returncode, stdout))
os.chdir(cur_dir)
def get_integrated_project_config(cwd: str, hash_value: str):
print(f"[0/0] [{hash_value}] project dir: {cwd}")
with open(os.path.join(cwd, 'hvigor/hvigor-config.json5'), 'r') as input_f:
hvigor_info = json5.load(input_f)
model_version = hvigor_info.get('modelVersion')
return model_version
def get_hvigor_version(cwd: str, hash_value: str):
print(f"[0/0] [{hash_value}] project dir: {cwd}")
with open(os.path.join(cwd, 'hvigor/hvigor-config.json5'), 'r') as input_f:
hvigor_info = json5.load(input_f)
hvigor_version = hvigor_info.get('hvigorVersion')
return hvigor_version
def get_unsigned_hap_path(module: str, src_path: str, cwd: str, options, hash_value: str):
hvigor_version = get_hvigor_version(cwd, hash_value)
product_name = options.build_profile.replace("/build-profile.json5", "").split("/")[-1]
model_version = get_integrated_project_config(cwd, hash_value)
product_value = options.product if options.product else 'default'
if product_value is None:
product_value = 'default'
if options.test_hap:
if options.target_app_dir and ((hvigor_version and float(hvigor_version[:3]) > 4.1) or model_version):
new_src_path = os.path.join(options.target_out_dir, options.target_app_dir, project_name, src_path)
unsigned_hap_path = os.path.join(
new_src_path, 'build/default/outputs/ohosTest')
else:
unsigned_hap_path = os.path.join(
cwd, src_path, 'build/default/outputs/ohosTest')
else:
module_target = get_target_by_module(module, options)
if options.target_app_dir and ((hvigor_version and float(hvigor_version[:3]) > 4.1) or model_version):
new_src_path = os.path.join(options.target_out_dir, options.target_app_dir, project_name, src_path)
unsigned_hap_path = os.path.join(
new_src_path, f'build/{product_value}/outputs/{module_target}')
else:
unsigned_hap_path = os.path.join(
cwd, src_path, f'build/{product_value}/outputs/{module_target}')
return unsigned_hap_path
def split_build_modules(build_modules: list):
new_build_modules = []
if not build_modules:
return new_build_modules
for item in build_modules:
target = "default"
module = item
if "@" in item:
split_item = item.split("@")
module = split_item[0]
target = split_item[1]
new_build_modules.append({"module": module, "target": target})
return new_build_modules
def get_target_by_module(module: str, options):
new_build_modules = split_build_modules(options.build_modules)
for item in new_build_modules:
if item["module"] == module:
return item["target"]
return "default"
def gen_unsigned_hap_path_json(build_profile: str, cwd: str, options, hash_value: str):
'''
Generate unsigned_hap_path_list
:param build_profile: module compilation information file
:param cwd: app project directory
:return: None
'''
unsigned_hap_path_json = {}
unsigned_hap_path_list = []
with open(build_profile, 'r') as input_f:
build_info = json5.load(input_f)
modules_list = build_info.get('modules')
for module in modules_list:
src_path = module.get('srcPath')
module_name = module.get("name")
unsigned_hap_path = get_unsigned_hap_path(module_name, src_path, cwd, options, hash_value)
hap_file = build_utils.find_in_directory(
unsigned_hap_path, '*-unsigned.hap')
if hap_file:
unsigned_hap_path_list.extend(hap_file)
hsp_file = build_utils.find_in_directory(
unsigned_hap_path, '*-unsigned.hsp')
if hsp_file:
unsigned_hap_path_list.extend(hsp_file)
unsigned_hap_path_json['unsigned_hap_path_list'] = unsigned_hap_path_list
file_utils.write_json_file(options.output_file, unsigned_hap_path_json)
def copy_libs(cwd: str, system_lib_module_info_list: list, ohos_app_abi: str, module_libs_dir: str):
'''
Obtain the output location of system library .so by reading the module compilation information file,
and copy it to the app project directory
:param cwd: app project directory
:param system_lib_module_info_list: system library module compilation information file
:param ohos_app_abi: app abi
:return: None
'''
for _lib_info in system_lib_module_info_list:
lib_info = file_utils.read_json_file(_lib_info)
lib_path = lib_info.get('source')
if os.path.exists(lib_path):
lib_name = os.path.basename(lib_path)
dest = os.path.join(cwd, f'{module_libs_dir}/libs', ohos_app_abi, lib_name)
if not os.path.exists(os.path.dirname(dest)):
os.makedirs(os.path.dirname(dest), exist_ok=True)
shutil.copyfile(lib_path, dest)
def hvigor_write_log(cmd, cwd, env, hash_value):
proc = subprocess.Popen(cmd,
cwd=cwd,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding='utf-8')
stdout, stderr = proc.communicate()
for line in stdout.splitlines():
print(f"[1/1] [{hash_value}] Hvigor info: {line}")
for line in stderr.splitlines():
print(f"[2/2] [{hash_value}] Hvigor warning: {line}")
os.makedirs(os.path.join(cwd, 'build'), exist_ok=True)
with os.fdopen(os.open(os.path.join(cwd, 'build', 'build.log'),
os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH),
'w') as f:
f.write(f'{stdout}\n')
f.write(f'{stderr}\n')
if proc.returncode or "ERROR: BUILD FAILED" in stderr or "ERROR: BUILD FAILED" in stdout:
raise Exception('ReturnCode:{}. Hvigor build failed: {}'.format(proc.returncode, stderr))
print(f"[0/0] [{hash_value}] Hvigor build end")
def get_hvigor_home_from_config(model_version, config):
parts = model_version.split('.')
major_version = parts[0]
minor_version = parts[1] if len(parts) > 1 else None
if model_version in config:
return config[model_version].get('hvigor_home'), config[model_version].get('nodejs_version')
key = f"{major_version}.x"
if key in config:
specific_config = config[key].get(model_version, {})
if 'hvigor_home' in specific_config:
return specific_config['hvigor_home'], specific_config['nodejs_version']
return config[key].get('default', {}).get('hvigor_home'), config[key].get('default', {}).get('nodejs_version')
return None, None
def get_node_path(nodejs_home_config: str, options):
if nodejs_home_config == None:
return None
host_os = options.host_os
code_home = os.path.dirname(os.path.dirname(os.path.dirname(options.sdk_home)))
if host_os == "mac":
return os.path.join(code_home, f"prebuilts/build-tools/common/nodejs/node-v{nodejs_home_config}-darwin-x64/bin/node")
elif host_os == "linux" and host_os == "arm64":
return os.path.join(code_home, f"prebuilts/build-tools/common/nodejs/node-v{nodejs_home_config}-linux-aarch64/bin/node")
else:
return os.path.join(code_home, f"prebuilts/build-tools/common/nodejs/node-v{nodejs_home_config}-{host_os}-x64/bin/node")
def get_hvigor_cache_dir(options, cache_base: str) -> str:
real_cwd = os.path.realpath(options.cwd)
real_root = os.path.realpath(options.root_dir)
if os.path.commonpath([real_root, real_cwd]) == real_root:
rel_cwd = os.path.relpath(real_cwd, real_root)
else:
rel_cwd = uuid.uuid5(uuid.NAMESPACE_URL, real_cwd).hex
hvigor_cache_dir = os.path.join(cache_base, 'hvigor_cache', rel_cwd)
os.makedirs(hvigor_cache_dir, exist_ok=True)
return hvigor_cache_dir
def build_hvigor_cmd(cwd: str, model_version: str, options, hash_val: str):
cmd = ['bash']
hvigor_version = get_hvigor_version(cwd, hash_val)
nodejs_home = None
code_home = os.path.dirname(os.path.dirname(os.path.dirname(options.sdk_home)))
with open(os.path.join(code_home, "build/scripts/app_config.json"), 'r') as file:
config = json.load(file)
if options.hvigor_home:
cmd.extend([f'{os.path.abspath(options.hvigor_home)}/bin/hvigorw'])
elif model_version:
hvigor_home_config, node_home_config = get_hvigor_home_from_config(model_version, config)
prebuilts_home = os.path.dirname(os.path.dirname(options.sdk_home))
if hvigor_home_config:
hvigor_home = os.path.join(os.path.dirname(prebuilts_home), hvigor_home_config)
else:
hvigor_home = f"{prebuilts_home}/tool/command-line-tools"
cmd.extend([f'{hvigor_home}/bin/hvigorw'])
else:
cmd.extend(['./hvigorw'])
if options.ohos_app_enable_asan:
cmd.extend(['-p', 'ohos-debug-asan=true'])
elif options.ohos_app_enable_tsan:
cmd.extend(['-p', 'ohos-enable-tsan=true'])
elif options.ohos_app_enable_ubsan:
cmd.extend(['-p', 'ohos-enable-ubsan=true'])
product_value = options.product if options.product else 'default'
if options.test_hap:
cmd.extend(['--mode', 'module', '-p',
f'module={options.test_module}@ohosTest', 'assembleHap'])
elif options.build_modules:
cmd.extend([options.assemble_type, '--mode',
'module', '-p', f'product={product_value}', '-p', 'module=' + ','.join(options.build_modules)])
else:
cmd.extend(['--mode',
options.build_level, '-p', f'product={product_value}', options.assemble_type])
if options.enable_debug:
cmd.extend(['-p', 'debuggable=true'])
else:
cmd.extend(['-p', 'debuggable=false'])
if options.use_hvigor_cache and os.environ.get('CACHE_BASE'):
hvigor_cache_dir = get_hvigor_cache_dir(options, os.environ.get('CACHE_BASE'))
cmd.extend(['-p', f'build-cache-dir={hvigor_cache_dir}'])
if options.hvigor_obfuscation:
cmd.extend(['-p', 'buildMode=release'])
else:
cmd.extend(['-p', 'hvigor-obfuscation=false'])
if options.ohos_test_coverage:
cmd.extend(['-p', 'ohos-test-coverage=true'])
if options.target_app_dir and options.target_app_dir != "":
if (hvigor_version and float(hvigor_version[:3]) > 4.1) or model_version:
target_out_dir = os.path.abspath(options.target_out_dir)
output_dir = os.path.join(target_out_dir, options.target_app_dir)
cmd.extend(['-c', f'properties.ohos.buildDir="{output_dir}"'])
cmd.extend(['--no-daemon'])
print(f"[0/0] [{hash_val}] hvigor cmd: " + ' '.join(cmd))
return cmd
def set_sdk_path(cwd: str, model_version: str, options, env, node_home):
if 'sdk.dir' not in options.sdk_type_name and model_version:
write_env_sdk(options, env)
else:
write_local_properties(cwd, options, node_home)
def write_local_properties(cwd: str, options, node_home: str):
sdk_dir = options.sdk_home
nodejs_dir = os.path.abspath(
os.path.dirname(os.path.dirname(node_home)))
with os.fdopen(os.open(os.path.join(cwd, 'local.properties'),
os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH),
'w') as f:
for sdk_type in options.sdk_type_name:
f.write(f'{sdk_type}={sdk_dir}\n')
f.write(f'nodejs.dir={nodejs_dir}\n')
def write_env_sdk(options, env):
sdk_dir = options.sdk_home
env['DEVECO_SDK_HOME'] = sdk_dir
def hvigor_sync(cwd: str, model_version: str, env):
if not model_version:
subprocess.run(['bash', './hvigorw', '--sync', '--no-daemon'],
cwd=cwd,
env=env,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
def hvigor_build(cwd: str, options, hash_value: str, node_home: str):
'''
Run hvigorw to build the app or hap
:param cwd: app project directory
:param options: command line parameters
:return: None
'''
model_version = get_integrated_project_config(cwd, hash_value)
print(f"[0/0] [{hash_value}] model_version: {model_version}")
cmd = build_hvigor_cmd(cwd, model_version, options, hash_value)
print(f"[0/0] [{hash_value}] Hvigor clean start")
env = os.environ.copy()
env['CI'] = 'true'
env['PATH'] = f"{os.path.dirname(os.path.abspath(node_home))}:{os.environ.get('PATH')}"
env['NODE_HOME'] = os.path.dirname(os.path.dirname(os.path.abspath(node_home)))
library_path = os.path.join(os.path.abspath(options.sdk_home), '20/ets/static/build-tools/ets2panda/lib/')
env['LD_LIBRARY_PATH'] = library_path
set_sdk_path(cwd, model_version, options, env, node_home)
hvigor_sync(cwd, model_version, env)
print(f"[0/0] [{hash_value}] Hvigor build start")
hvigor_write_log(cmd, cwd, env, hash_value)
def main(args):
options = parse_args(args)
cwd = os.path.abspath(options.cwd)
hash_result = uuid.uuid4()
hash_value = str(hash_result).split("-")[0]
model_version = get_integrated_project_config(cwd, hash_value)
print(model_version)
code_home = os.path.dirname(os.path.dirname(os.path.dirname(options.sdk_home)))
if model_version:
with open(os.path.join(code_home, "build/scripts/app_config.json"), 'r') as file:
config = json.load(file)
hvigor_home_config, node_home_config = get_hvigor_home_from_config(model_version, config)
nodejs_home = get_node_path(node_home_config, options) if node_home_config else options.nodejs
else:
nodejs_home = options.nodejs
if options.system_lib_module_info_list:
copy_libs(cwd, options.system_lib_module_info_list,
options.ohos_app_abi, options.module_libs_dir)
os.environ['PATH'] = '{}:{}'.format(os.path.dirname(
os.path.abspath(options.nodejs)), os.environ.get('PATH'))
os.environ['PATH'] = f'{cwd}/.arkui-x/android:{os.environ.get("PATH")}'
make_env(options.build_profile, cwd, options.ohpm_registry, options, hash_value, nodejs_home)
hvigor_build(cwd, options, hash_value, nodejs_home)
gen_unsigned_hap_path_json(options.build_profile, cwd, options, hash_value)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))