1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# Copyright (c) 2021 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 optparse 17import os 18import sys 19import json 20 21from zipfile import ZipFile # noqa: E402 22from util import build_utils # noqa: E402 23 24 25def parse_args(args): 26 args = build_utils.expand_file_args(args) 27 28 parser = optparse.OptionParser() 29 build_utils.add_depfile_option(parser) 30 parser.add_option('--output', help='stamp file') 31 parser.add_option('--js-assets-dir', help='js assets directory') 32 parser.add_option('--ets-assets-dir', help='ets assets directory') 33 parser.add_option('--js-forms-dir', help='js forms directory') 34 parser.add_option('--testrunner-dir', help='testrunner directory') 35 parser.add_option('--nodejs-path', help='path to nodejs app') 36 parser.add_option('--webpack-js', help='path to js webpack.js') 37 parser.add_option('--webpack-ets', help='path to ets webpack.js') 38 parser.add_option('--webpack-config-js', help='path to webpack.config.js') 39 parser.add_option('--webpack-config-ets', help='path to webpack.rich.config.js') 40 parser.add_option('--hap-profile', help='path to hap profile') 41 parser.add_option('--build-mode', help='debug mode or release mode') 42 parser.add_option('--js-sources-file', help='path to js sources file') 43 parser.add_option('--js2abc', 44 action='store_true', 45 default=False, 46 help='whether to transform js to ark bytecode') 47 parser.add_option('--ets2abc', 48 action='store_true', 49 default=False, 50 help='whether to transform ets to ark bytecode') 51 parser.add_option('--ark-es2abc-dir', help='path to ark es2abc dir') 52 parser.add_option('--ace-loader-home', help='path to ace-loader dir.') 53 parser.add_option('--ets-loader-home', help='path to ets-loader dir.') 54 parser.add_option('--app-profile', default=False, help='path to app-profile.') 55 parser.add_option('--manifest-file-path', help='path to manifest.json dir.') 56 57 options, _ = parser.parse_args(args) 58 options.js_assets_dir = build_utils.parse_gn_list(options.js_assets_dir) 59 options.ets_assets_dir = build_utils.parse_gn_list(options.ets_assets_dir) 60 options.js_forms_dir = build_utils.parse_gn_list(options.js_forms_dir) 61 options.testrunner_dir = build_utils.parse_gn_list(options.testrunner_dir) 62 return options 63 64 65def make_my_env(options, js2abc: bool) -> dict: 66 out_dir = os.path.abspath(os.path.dirname(options.output)) 67 gen_dir = os.path.join(out_dir, "gen") 68 assets_dir = os.path.join(out_dir, "assets") 69 if options.app_profile: 70 if js2abc: 71 assets_dir = os.path.join(assets_dir, "js") 72 else: 73 assets_dir = os.path.join(assets_dir, "ets") 74 my_env = { 75 "aceModuleBuild": assets_dir, 76 "buildMode": options.build_mode, 77 "PATH": os.environ.get('PATH'), 78 "appResource": os.path.join(gen_dir, "ResourceTable.txt"), 79 "xtsMode": 'true' 80 } 81 if options.app_profile: 82 my_env["aceProfilePath"] = os.path.join(gen_dir, "resources/base/profile") 83 my_env["aceModuleJsonPath"] = os.path.abspath(options.hap_profile) 84 return my_env 85 86 87def make_manifest_data(config: dict, options, js2abc: bool, asset_index: int, assets_cnt: int, src_path: str) -> dict: 88 data = dict() 89 data['appID'] = config['app']['bundleName'] 90 if options.app_profile: 91 data['versionName'] = config['app']['versionName'] 92 data['versionCode'] = config['app']['versionCode'] 93 data['pages'] = config['module']['pages'] 94 data['deviceType'] = config['module']['deviceTypes'] 95 else: 96 data['appName'] = config['module']['abilities'][asset_index].get('label') 97 data['versionName'] = config['app']['version']['name'] 98 data['versionCode'] = config['app']['version']['code'] 99 data['deviceType'] = config['module']['deviceType'] 100 for js_module in config['module']['js']: 101 js_module_name = js_module.get('name').split('.')[-1] 102 103 # According to the page name and ability entry match the corresponding page for ability 104 # Compatibility with mismatches due to "MainAbility" and "default" 105 if js_module_name == src_path or (js_module_name == 'MainAbility' and src_path == 'default') \ 106 or (js_module_name == 'default' and src_path == 'MainAbility'): 107 data['pages'] = js_module.get('pages') 108 data['window'] = js_module.get('window') 109 if js_module.get('type') == 'form' and options.js_forms_dir: 110 data['type'] = 'form' 111 if not js2abc: 112 data['mode'] = js_module.get('mode') 113 return data 114 115 116def build_ace(cmd: str, options, js2abc: bool, loader_home: str, assets_dir: str, assets_name: str): 117 my_env = make_my_env(options, js2abc) 118 gen_dir = my_env.get("aceModuleBuild") 119 assets_cnt = len(assets_dir) 120 use_compile_cache = os.environ.get("USE_COMPILE_CACHE", "false").lower() == "true" 121 for asset_index in range(assets_cnt): 122 ability_dir = os.path.relpath(assets_dir[asset_index], loader_home) 123 my_env["aceModuleRoot"] = ability_dir 124 if options.js_sources_file: 125 with open(options.js_sources_file, 'wb') as js_sources_file: 126 sources = get_all_js_sources(ability_dir) 127 js_sources_file.write('\n'.join(sources).encode()) 128 src_path = os.path.basename(assets_dir[asset_index]) 129 130 # Create a fixed directory for manifest.json 131 if js2abc: 132 build_dir = os.path.abspath(os.path.join(options.manifest_file_path, 'js', src_path)) 133 my_env.update({"cachePath": os.path.join(build_dir, ".cache")}) 134 else: 135 build_dir = os.path.abspath(os.path.join(options.manifest_file_path, 'ets', src_path)) 136 if not os.path.exists(build_dir): 137 os.makedirs(build_dir, exist_ok=True) 138 manifest = os.path.join(build_dir, 'manifest.json') 139 140 # Determine abilityType according to config.json 141 if assets_name == 'testrunner_dir': 142 my_env["abilityType"] = 'testrunner' 143 elif assets_name != 'js_forms_dir' and not options.app_profile and assets_cnt > 1: 144 with open(options.hap_profile) as profile: 145 config = json.load(profile) 146 if config['module']['abilities'][asset_index].__contains__('forms'): 147 my_env["abilityType"] = 'form' 148 else: 149 my_env["abilityType"] = config['module']['abilities'][asset_index]['type'] 150 else: 151 my_env["abilityType"] = 'page' 152 153 # Generate manifest.json only when abilityType is page 154 data = dict() 155 if my_env["abilityType"] == 'page': 156 with open(options.hap_profile) as profile: 157 config = json.load(profile) 158 data = make_manifest_data(config, options, js2abc, asset_index, assets_cnt, src_path) 159 build_utils.write_json(data, manifest) 160 161 # If missing page, skip it 162 if not data.__contains__('pages'): 163 print('Warning: There is no page matching this {}'.format(src_path)) 164 continue 165 166 if not options.app_profile: 167 my_env["aceManifestPath"] = manifest 168 my_env["aceModuleBuild"] = os.path.join(gen_dir, src_path) 169 170 enable_compile_cache(asset_index, assets_cnt, my_env, use_compile_cache) 171 build_utils.check_output(cmd, cwd=loader_home, env=my_env) 172 173 if options.app_profile: 174 gen_dir = os.path.dirname(gen_dir) 175 build_utils.zip_dir(options.output, gen_dir) 176 else: 177 build_utils.zip_dir(options.output, gen_dir, zip_prefix_path='assets/js/') 178 179 180def enable_compile_cache(asset_index: int, assets_cnt: int, env: dict, use_compile_cache: bool): 181 if use_compile_cache: 182 env["useCompileCache"] = "true" 183 if assets_cnt > 1 and asset_index == assets_cnt - 1: 184 env["addTestRunner"] = "true" 185 186 187def get_all_js_sources(base) -> list: 188 sources = [] 189 for root, _, files in os.walk(base): 190 for file in files: 191 if file[-3:] in ('.js', '.ts'): 192 sources.append(os.path.join(root, file)) 193 194 return sources 195 196 197def main(args): 198 options = parse_args(args) 199 200 inputs = [ 201 options.nodejs_path, options.webpack_js, options.webpack_ets, 202 options.webpack_config_js, options.webpack_config_ets 203 ] 204 depfiles = [] 205 if not options.js_assets_dir and not options.ets_assets_dir: 206 with ZipFile(options.output, 'w') as file: 207 return 208 209 if options.ark_es2abc_dir: 210 depfiles.extend(build_utils.get_all_files(options.ark_es2abc_dir)) 211 212 depfiles.append(options.webpack_js) 213 depfiles.append(options.webpack_ets) 214 depfiles.append(options.webpack_config_js) 215 depfiles.append(options.webpack_config_ets) 216 depfiles.extend(build_utils.get_all_files(options.ace_loader_home)) 217 depfiles.extend(build_utils.get_all_files(options.ets_loader_home)) 218 219 node_js = os.path.relpath(options.nodejs_path, options.ace_loader_home) 220 assets_dict = dict() 221 if options.js_assets_dir: 222 assets_dict['js_assets_dir'] = options.js_assets_dir 223 if options.ets_assets_dir: 224 assets_dict['ets_assets_dir'] = options.ets_assets_dir 225 if options.js_forms_dir: 226 assets_dict['js_forms_dir'] = options.js_forms_dir 227 if options.testrunner_dir: 228 assets_dict['testrunner_dir'] = options.testrunner_dir 229 230 for assets_name, assets_dir in assets_dict.items(): 231 for asset in assets_dir: 232 depfiles.extend(build_utils.get_all_files(asset)) 233 if assets_name == 'ets_assets_dir': 234 js2abc = False 235 loader_home = options.ets_loader_home 236 webpack_config = options.webpack_config_ets 237 webpack_path = options.webpack_ets 238 else: 239 js2abc = True 240 loader_home = options.ace_loader_home 241 webpack_config = options.webpack_config_js 242 webpack_path = options.webpack_js 243 cmd = [ 244 node_js, 245 os.path.relpath(webpack_path, loader_home), 246 '--config', 247 os.path.relpath(webpack_config, loader_home) 248 ] 249 ark_es2abc_dir = os.path.relpath(options.ark_es2abc_dir, loader_home) 250 if options.app_profile: 251 cmd.extend(['--env', 'buildMode={}'.format(options.build_mode), 'compilerType=ark', 252 'arkFrontendDir={}'.format(ark_es2abc_dir), 'nodeJs={}'.format(node_js)]) 253 else: 254 cmd.extend(['--env', 'compilerType=ark', 255 'arkFrontendDir={}'.format(ark_es2abc_dir), 'nodeJs={}'.format(node_js)]) 256 build_utils.call_and_write_depfile_if_stale( 257 lambda: build_ace(cmd, options, js2abc, loader_home, assets_dir, assets_name), 258 options, 259 depfile_deps=depfiles, 260 input_paths=depfiles + inputs, 261 input_strings=cmd + [options.build_mode], 262 output_paths=([options.output]), 263 force=False, 264 add_pydeps=False) 265 266if __name__ == '__main__': 267 sys.exit(main(sys.argv[1:])) 268