• 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
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