1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# Copyright (c) 2025 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 shutil 19import subprocess 20import sys 21import tarfile 22import time 23 24 25def copy_files(source_path, dest_path, is_file=False): 26 try: 27 if is_file: 28 if not os.path.exists(os.path.dirname(dest_path)): 29 os.makedirs(os.path.dirname(dest_path), exist_ok=True) 30 shutil.copy(source_path, dest_path) 31 else: 32 shutil.copytree(source_path, dest_path, dirs_exist_ok=True, 33 symlinks=True) 34 except Exception as err: 35 raise Exception("Copy files failed. Error: " + str(err)) from err 36 37 38def run_cmd(cmd, execution_path=None): 39 if (cmd and cmd[0].strip().endswith('npm')): 40 cmd.append('--registry') 41 cmd.append('https://repo.huaweicloud.com/repository/npm/') 42 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, 43 stdin=subprocess.PIPE, 44 stderr=subprocess.PIPE, 45 cwd=execution_path) 46 stdout, stderr = proc.communicate(timeout=600) 47 if proc.returncode != 0: 48 raise Exception(stderr.decode()) 49 return stdout 50 51 52def run_cmd_with_retry(max_retries, wait_time, cmd, execution_path=None): 53 retry_count = 0 54 last_exception = None 55 while retry_count < max_retries: 56 try: 57 run_cmd(cmd, execution_path) 58 break 59 except Exception as e: 60 last_exception = e 61 retry_count += 1 62 time.sleep(wait_time) 63 if retry_count >= max_retries: 64 raise Exception( 65 "Command failed after {r} attempts. Cmd: {c}. Error: {e}" 66 .format( 67 r=max_retries, 68 c=" ".join(cmd), 69 e=last_exception 70 ) 71 ) 72 73 74def is_npm_newer_than_6(options): 75 cmd = [options.npm, '-v'] 76 stdout = run_cmd(cmd, options.source_path) 77 version_str = stdout.decode('utf-8').strip() 78 # get npm major version(i.e. "6.14.15" -> 6) 79 major_version = int(version_str.split('.')[0]) 80 if major_version is not None: 81 if major_version <= 6: 82 return False 83 else: 84 return True 85 # default set to lower than v7 which can compatible with v7+ 86 return False 87 88 89def build(options): 90 build_cmd = [options.npm, 'run', 'build'] 91 pack_cmd = [options.npm, 'pack'] 92 run_cmd(build_cmd, options.source_path) 93 run_cmd(pack_cmd, options.source_path) 94 95 96def copy_output(options): 97 run_cmd(['rm', '-rf', options.output_path]) 98 src = os.path.join(options.source_path, 'panda-tslinter-{}.tgz'.format(options.version)) 99 dest = os.path.join(options.output_path, 'panda-tslinter-{}.tgz'.format(options.version)) 100 copy_files(src, dest, True) 101 try: 102 with tarfile.open(dest, 'r:gz') as tar: 103 tar.extractall(path=options.output_path) 104 except tarfile.TarError as e: 105 raise Exception("Error extracting files") from e 106 copy_files(os.path.join(options.output_path, 'package'), options.output_path) 107 run_cmd(['rm', '-rf', os.path.join(options.output_path, 'package')]) 108 run_cmd(['rm', '-rf', dest]) 109 src = os.path.join(options.source_path, 'tsconfig.json') 110 dest = os.path.join(options.output_path, 'tsconfig.json') 111 copy_files(src, dest, True) 112 113 114def install_typescript(options): 115 new_npm = is_npm_newer_than_6(options) 116 tsc_file = 'file:' + options.typescript 117 if new_npm: 118 cmd = [options.npm, 'install', '--no-save', tsc_file, '--legacy-peer-deps', '--offline'] 119 else: 120 cmd = [options.npm, 'install', '--no-save', tsc_file] 121 run_cmd(cmd, options.source_path) 122 123 124def find_files_by_prefix_suffix(directory, prefix, suffix): 125 matched_files = [] 126 for filename in os.listdir(directory): 127 if filename.startswith(prefix) and filename.endswith(suffix): 128 matched_files.append(os.path.join(directory, filename)) 129 return sorted(matched_files, key=os.path.getctime, reverse=True) 130 131 132def clean_old_packages(directory, prefix, suffix): 133 res = True 134 matched_files = find_files_by_prefix_suffix(directory, prefix, suffix) 135 if (matched_files): 136 for file in matched_files: 137 try: 138 os.remove(file) 139 except Exception: 140 res = False 141 return res 142 143 144def backup_package_files(source_path): 145 package_name = 'package.json' 146 package_back_name = 'package.json.bak' 147 aa_path = os.path.join(source_path, 'arkanalyzer') 148 hc_path = os.path.join(source_path, 'homecheck') 149 linter_path = source_path 150 copy_files(os.path.join(aa_path, package_name), os.path.join(aa_path, package_back_name), True) 151 copy_files(os.path.join(hc_path, package_name), os.path.join(hc_path, package_back_name), True) 152 copy_files(os.path.join(linter_path, package_name), os.path.join(linter_path, package_back_name), True) 153 154 155def clean_env(source_path): 156 package_name = 'package.json' 157 package_back_name = 'package.json.bak' 158 package_lock_name = 'package-lock.json' 159 aa_path = os.path.join(source_path, 'arkanalyzer') 160 hc_path = os.path.join(source_path, 'homecheck') 161 linter_path = source_path 162 try: 163 copy_files(os.path.join(aa_path, package_back_name), os.path.join(aa_path, package_name), True) 164 copy_files(os.path.join(hc_path, package_back_name), os.path.join(hc_path, package_name), True) 165 copy_files(os.path.join(linter_path, package_back_name), os.path.join(linter_path, package_name), True) 166 os.remove(os.path.join(hc_path, package_lock_name)) 167 os.remove(os.path.join(linter_path, package_lock_name)) 168 os.remove(os.path.join(aa_path, package_back_name)) 169 os.remove(os.path.join(hc_path, package_back_name)) 170 os.remove(os.path.join(linter_path, package_back_name)) 171 except Exception: 172 return False 173 return True 174 175 176def aa_copy_lib_files(options): 177 aa_path = os.path.join(options.source_path, 'arkanalyzer') 178 source_file_1 = os.path.join(aa_path, 'node_modules', 'ohos-typescript', 'lib', 'lib.es5.d.ts') 179 dest_path = os.path.join(aa_path, 'builtIn', 'typescript', 'api', '@internal') 180 copy_files(source_file_1, dest_path, True) 181 source_file_2 = os.path.join(aa_path, 'node_modules', 'ohos-typescript', 'lib', 'lib.es2015.collection.d.ts') 182 copy_files(source_file_2, dest_path, True) 183 184 185def hc_copy_lib_files(options): 186 hc_path = os.path.join(options.source_path, 'homecheck') 187 source_file = os.path.join(hc_path, 'node_modules', 'ohos-typescript', 'lib', 'lib.es5.d.ts') 188 dest_path = os.path.join(hc_path, 'resources', 'internalSdk', '@internal') 189 copy_files(source_file, dest_path, True) 190 191 192def pack_arkanalyzer(options, new_npm): 193 aa_path = os.path.join(options.source_path, 'arkanalyzer') 194 tsc_file = 'file:' + options.typescript 195 pack_prefix = 'arkanalyzer-' 196 pack_suffix = '.tgz' 197 clean_old_packages(aa_path, pack_prefix, pack_suffix) 198 199 if new_npm: 200 ts_install_cmd = [options.npm, 'install', '--no-save', tsc_file, '--legacy-peer-deps', '--offline'] 201 else: 202 ts_install_cmd = [options.npm, 'install', '--no-save', tsc_file] 203 compile_cmd = [options.npm, 'run', 'compile'] 204 pack_cmd = [options.npm, 'pack'] 205 run_cmd(ts_install_cmd, aa_path) 206 aa_copy_lib_files(options) 207 run_cmd(compile_cmd, aa_path) 208 run_cmd(pack_cmd, aa_path) 209 210 211def install_homecheck(options, max_retries, wait_time): 212 new_npm = is_npm_newer_than_6(options) 213 pack_arkanalyzer(options, new_npm) 214 aa_path = os.path.join(options.source_path, 'arkanalyzer') 215 hc_path = os.path.join(options.source_path, 'homecheck') 216 aa_pack_prefix = 'arkanalyzer-' 217 hc_pack_prefix = 'homecheck-' 218 pack_suffix = '.tgz' 219 exist_aa_packs = find_files_by_prefix_suffix(aa_path, aa_pack_prefix, pack_suffix) 220 if (exist_aa_packs): 221 aa_file = 'file:' + exist_aa_packs[0] 222 if new_npm: 223 aa_install_cmd = [options.npm, 'install', aa_file, '--legacy-peer-deps', '--offline'] 224 else: 225 aa_install_cmd = [options.npm, 'install', aa_file] 226 run_cmd_with_retry(max_retries, wait_time, aa_install_cmd, hc_path) 227 else: 228 raise Exception('Failed to find arkanalyzer npm package') 229 230 clean_old_packages(hc_path, hc_pack_prefix, pack_suffix) 231 tsc_file = 'file:' + options.typescript 232 if new_npm: 233 ts_install_cmd = [options.npm, 'install', '--no-save', tsc_file, '--legacy-peer-deps', '--offline'] 234 else: 235 ts_install_cmd = [options.npm, 'install', '--no-save', tsc_file] 236 pack_cmd = [options.npm, 'pack'] 237 compile_cmd = [options.npm, 'run', 'compile'] 238 run_cmd_with_retry(max_retries, wait_time, ts_install_cmd, hc_path) 239 hc_copy_lib_files(options) 240 run_cmd(compile_cmd, hc_path) 241 run_cmd(pack_cmd, hc_path) 242 exist_hc_packs = find_files_by_prefix_suffix(hc_path, hc_pack_prefix, pack_suffix) 243 if (exist_hc_packs): 244 hc_file = 'file:' + exist_hc_packs[0] 245 if new_npm: 246 hc_install_cmd = [options.npm, 'install', hc_file, '--legacy-peer-deps', '--offline'] 247 else: 248 hc_install_cmd = [options.npm, 'install', hc_file] 249 run_cmd_with_retry(max_retries, wait_time, hc_install_cmd, options.source_path) 250 else: 251 raise Exception('Failed to find homecheck npm package') 252 253 254def extract(package_path, dest_path, package_name): 255 try: 256 with tarfile.open(package_path, 'r:gz') as tar: 257 tar.extractall(path=dest_path) 258 except tarfile.TarError as e: 259 raise Exception("Error extracting files") from e 260 dest_package_path = os.path.join(dest_path, package_name) 261 if (os.path.exists(dest_package_path)): 262 shutil.rmtree(dest_package_path) 263 os.rename(os.path.join(dest_path, 'package'), dest_package_path) 264 265 266def parse_args(): 267 parser = argparse.ArgumentParser() 268 parser.add_argument('--npm', help='path to a npm exetuable') 269 parser.add_argument('--source-path', help='path to build system source') 270 parser.add_argument('--output-path', help='path to output') 271 parser.add_argument('--typescript', help='path to typescript') 272 parser.add_argument('--version', help='linter version') 273 274 options = parser.parse_args() 275 return options 276 277 278def main(): 279 options = parse_args() 280 backup_package_files(options.source_path) 281 install_homecheck(options, 5, 3) 282 install_typescript(options) 283 node_modules_path = os.path.join(options.source_path, "node_modules") 284 extract(options.typescript, node_modules_path, "typescript") 285 build(options) 286 copy_output(options) 287 clean_env(options.source_path) 288 289 290if __name__ == '__main__': 291 sys.exit(main()) 292