• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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