1/* 2 * Copyright (c) 2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import path from 'path'; 17import fs from 'fs'; 18import type sourceMap from 'source-map'; 19 20import { minify, MinifyOutput } from 'terser'; 21import { 22 deleteLineInfoForNameString, 23 MemoryUtils, 24 nameCacheMap, 25 unobfuscationNamesObj 26} from 'arkguard'; 27import { 28 OH_MODULES, 29 SEPARATOR_AT, 30 SEPARATOR_BITWISE_AND, 31 SEPARATOR_SLASH 32} from './fast_build/ark_compiler/common/ark_define'; 33import { 34 ARKTS_MODULE_NAME, 35 PACKAGES, 36 TEMPORARY, 37 ZERO, 38 ONE, 39 EXTNAME_JS, 40 EXTNAME_TS, 41 EXTNAME_MJS, 42 EXTNAME_CJS, 43 EXTNAME_ABC, 44 EXTNAME_ETS, 45 EXTNAME_TS_MAP, 46 EXTNAME_JS_MAP, 47 ESMODULE, 48 FAIL, 49 TS2ABC, 50 ES2ABC, 51 EXTNAME_PROTO_BIN, 52 NATIVE_MODULE 53} from './pre_define'; 54import { 55 isMac, 56 isWindows, 57 isPackageModulesFile, 58 genTemporaryPath, 59 getExtensionIfUnfullySpecifiedFilepath, 60 mkdirsSync, 61 toUnixPath, 62 validateFilePathLength, 63 harFilesRecord, 64 getProjectRootPath 65} from './utils'; 66import type { GeneratedFileInHar } from './utils'; 67import { 68 extendSdkConfigs, 69 projectConfig, 70 sdkConfigPrefix 71} from '../main'; 72import { getRelativeSourcePath, mangleFilePath } from './fast_build/ark_compiler/common/ob_config_resolver'; 73import { moduleRequestCallback } from './fast_build/system_api/api_check_utils'; 74import { performancePrinter } from 'arkguard/lib/ArkObfuscator'; 75import { SourceMapGenerator } from './fast_build/ark_compiler/generate_sourcemap'; 76import { sourceFileBelongProject } from './fast_build/ark_compiler/module/module_source_file'; 77 78const red: string = '\u001b[31m'; 79const reset: string = '\u001b[39m'; 80const IDENTIFIER_CACHE: string = 'IdentifierCache'; 81 82export const SRC_MAIN: string = 'src/main'; 83 84export let newSourceMaps: Object = {}; 85 86export const packageCollection: Map<string, Array<string>> = new Map(); 87// Splicing ohmurl or record name based on filePath and context information table. 88export function getNormalizedOhmUrlByFilepath(filePath: string, projectConfig: Object, logger: Object, 89 pkgParams: Object, importerFile: string): string { 90 const { pkgName, pkgPath, isRecordName } = pkgParams; 91 // rollup uses commonjs plugin to handle commonjs files, 92 // the commonjs files are prefixed with '\x00' and need to be removed. 93 if (filePath.startsWith('\x00')) { 94 filePath = filePath.replace('\x00', ''); 95 } 96 let unixFilePath: string = toUnixPath(filePath); 97 unixFilePath = unixFilePath.substring(0, filePath.lastIndexOf('.')); // remove extension 98 let projectFilePath: string = unixFilePath.replace(toUnixPath(pkgPath), ''); 99 // case1: /entry/src/main/ets/xxx/yyy 100 // case2: /entry/src/ohosTest/ets/xxx/yyy 101 // case3: /node_modules/xxx/yyy 102 // case4: /entry/node_modules/xxx/yyy 103 // case5: /library/node_modules/xxx/yyy 104 // case6: /library/index.ts 105 // ---> @normalized:N&<moduleName>&<bunldName>&<packageName>/entry/ets/xxx/yyy&<version> 106 let pkgInfo = projectConfig.pkgContextInfo[pkgName]; 107 if (pkgInfo === undefined) { 108 logger.error(red, 'ArkTS:ERROR Failed to resolve OhmUrl.\n' + 109 `Error Message: Failed to get a resolved OhmUrl for "${filePath}" imported by "${importerFile}".\n` + 110 `Solutions: > Check whether the module which ${filePath} belongs to is correctly configured.` + 111 '> Check the corresponding file name is correct(including case-sensitivity).', reset); 112 } 113 let recordName = `${pkgInfo.bundleName}&${pkgName}${projectFilePath}&${pkgInfo.version}`; 114 if (isRecordName) { 115 // record name style: <bunldName>&<packageName>/entry/ets/xxx/yyy&<version> 116 return recordName; 117 } 118 return `${pkgInfo.isSO ? 'Y' : 'N'}&${pkgInfo.moduleName}&${recordName}`; 119} 120 121export function getOhmUrlByFilepath(filePath: string, projectConfig: Object, logger: Object, namespace?: string, 122 importerFile?: string): string { 123 // remove '\x00' from the rollup virtual commonjs file's filePath 124 if (filePath.startsWith('\x00')) { 125 filePath = filePath.replace('\x00', ''); 126 } 127 let unixFilePath: string = toUnixPath(filePath); 128 unixFilePath = unixFilePath.substring(0, filePath.lastIndexOf('.')); // remove extension 129 const REG_PROJECT_SRC: RegExp = /(\S+)\/src\/(?:main|ohosTest)\/(ets|js|mock)\/(\S+)/; 130 131 const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath); 132 const bundleName: string = packageInfo[0]; 133 const moduleName: string = packageInfo[1]; 134 const moduleRootPath: string = toUnixPath(projectConfig.modulePathMap[moduleName]); 135 const projectRootPath: string = toUnixPath(getProjectRootPath(filePath, projectConfig, projectConfig?.rootPathSet)); 136 // case1: /entry/src/main/ets/xxx/yyy ---> @bundle:<bundleName>/entry/ets/xxx/yyy 137 // case2: /entry/src/ohosTest/ets/xxx/yyy ---> @bundle:<bundleName>/entry_test@entry/ets/xxx/yyy 138 // case3: /node_modules/xxx/yyy ---> @package:pkg_modules/xxx/yyy 139 // case4: /entry/node_modules/xxx/yyy ---> @package:pkg_modules@entry/xxx/yyy 140 // case5: /library/node_modules/xxx/yyy ---> @package:pkg_modules@library/xxx/yyy 141 // case6: /library/index.ts ---> @bundle:<bundleName>/library/index 142 const projectFilePath: string = unixFilePath.replace(projectRootPath, ''); 143 const packageDir: string = projectConfig.packageDir; 144 const result: RegExpMatchArray | null = projectFilePath.match(REG_PROJECT_SRC); 145 if (result && result[1].indexOf(packageDir) === -1) { 146 const relativePath = processSrcMain(result, projectFilePath); 147 if (namespace && moduleName !== namespace) { 148 return `${bundleName}/${moduleName}@${namespace}/${relativePath}`; 149 } 150 return `${bundleName}/${moduleName}/${relativePath}`; 151 } 152 153 const processParams: Object = { 154 projectFilePath, 155 unixFilePath, 156 packageDir, 157 projectRootPath, 158 moduleRootPath, 159 projectConfig, 160 namespace, 161 logger, 162 importerFile, 163 originalFilePath: filePath 164 }; 165 return processPackageDir(processParams); 166} 167 168function processSrcMain(result: RegExpMatchArray | null, projectFilePath: string): string { 169 let langType: string = result[2]; 170 let relativePath: string = result[3]; 171 // case7: /entry/src/main/ets/xxx/src/main/js/yyy ---> @bundle:<bundleName>/entry/ets/xxx/src/main/js/yyy 172 const REG_SRC_MAIN: RegExp = /src\/(?:main|ohosTest)\/(ets|js)\//; 173 const srcMainIndex: number = result[1].search(REG_SRC_MAIN); 174 if (srcMainIndex !== -1) { 175 relativePath = projectFilePath.substring(srcMainIndex).replace(REG_SRC_MAIN, ''); 176 langType = projectFilePath.replace(relativePath, '').match(REG_SRC_MAIN)[1]; 177 } 178 return `${langType}/${relativePath}`; 179} 180 181function processPackageDir(params: Object): string { 182 const { projectFilePath, unixFilePath, packageDir, projectRootPath, moduleRootPath, 183 projectConfig, namespace, logger, importerFile, originalFilePath } = params; 184 if (projectFilePath.indexOf(packageDir) !== -1) { 185 if (compileToolIsRollUp()) { 186 const tryProjectPkg: string = toUnixPath(path.join(projectRootPath, packageDir)); 187 if (unixFilePath.indexOf(tryProjectPkg) !== -1) { 188 return unixFilePath.replace(tryProjectPkg, `${packageDir}`).replace(new RegExp(packageDir, 'g'), PACKAGES); 189 } 190 191 // iterate the modulePathMap to find the module which contains the pkg_module's file 192 for (const moduleName in projectConfig.modulePathMap) { 193 const modulePath: string = projectConfig.modulePathMap[moduleName]; 194 const tryModulePkg: string = toUnixPath(path.resolve(modulePath, packageDir)); 195 if (unixFilePath.indexOf(tryModulePkg) !== -1) { 196 return unixFilePath.replace(tryModulePkg, `${packageDir}@${moduleName}`).replace(new RegExp(packageDir, 'g'), PACKAGES); 197 } 198 } 199 200 logger.error(red, 'ArkTS:ERROR Failed to resolve OhmUrl.\n' + 201 `Error Message: Failed to get a resolved OhmUrl for "${originalFilePath}" imported by "${importerFile}".\n` + 202 `Solutions: > Check whether the module which ${originalFilePath} belongs to is correctly configured.` + 203 '> Check the corresponding file name is correct(including case-sensitivity).', reset); 204 return originalFilePath; 205 } 206 207 // webpack with old implematation 208 const tryProjectPkg: string = toUnixPath(path.join(projectRootPath, packageDir)); 209 if (unixFilePath.indexOf(tryProjectPkg) !== -1) { 210 return unixFilePath.replace(tryProjectPkg, `${packageDir}/${ONE}`).replace(new RegExp(packageDir, 'g'), PACKAGES); 211 } 212 213 const tryModulePkg: string = toUnixPath(path.join(moduleRootPath, packageDir)); 214 if (unixFilePath.indexOf(tryModulePkg) !== -1) { 215 return unixFilePath.replace(tryModulePkg, `${packageDir}/${ZERO}`).replace(new RegExp(packageDir, 'g'), PACKAGES); 216 } 217 } 218 219 const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath); 220 const bundleName: string = packageInfo[0]; 221 const moduleName: string = packageInfo[1]; 222 for (const key in projectConfig.modulePathMap) { 223 const moduleRootPath: string = toUnixPath(projectConfig.modulePathMap[key]); 224 if (unixFilePath.indexOf(moduleRootPath + '/') !== -1) { 225 const relativeModulePath: string = unixFilePath.replace(moduleRootPath + '/', ''); 226 if (namespace && moduleName !== namespace) { 227 return `${bundleName}/${moduleName}@${namespace}/${relativeModulePath}`; 228 } 229 return `${bundleName}/${moduleName}/${relativeModulePath}`; 230 } 231 } 232 233 logger.error(red, 'ArkTS:ERROR Failed to resolve OhmUrl.\n' + 234 `Error Message: Failed to get a resolved OhmUrl for "${originalFilePath}" imported by "${importerFile}".\n` + 235 `Solutions: > Check whether the module which ${originalFilePath} belongs to is correctly configured.` + 236 '> Check the corresponding file name is correct(including case-sensitivity).', reset); 237 return originalFilePath; 238} 239 240 241export function getOhmUrlBySystemApiOrLibRequest(moduleRequest: string, config?: Object, logger?: Object, 242 importerFile?: string, useNormalizedOHMUrl: boolean = false): string { 243 // 'arkui-x' represents cross platform related APIs, processed as 'ohos' 244 const REG_SYSTEM_MODULE: RegExp = new RegExp(`@(${sdkConfigPrefix})\\.(\\S+)`); 245 const REG_LIB_SO: RegExp = /lib(\S+)\.so/; 246 247 if (REG_SYSTEM_MODULE.test(moduleRequest.trim())) { 248 return moduleRequest.replace(REG_SYSTEM_MODULE, (_, moduleType, systemKey) => { 249 let moduleRequestStr = ''; 250 if (extendSdkConfigs) { 251 moduleRequestStr = moduleRequestCallback(moduleRequest, _, moduleType, systemKey); 252 } 253 if (moduleRequestStr !== '') { 254 return moduleRequestStr; 255 } 256 const systemModule: string = `${moduleType}.${systemKey}`; 257 if (NATIVE_MODULE.has(systemModule)) { 258 return `@native:${systemModule}`; 259 } else if (moduleType === ARKTS_MODULE_NAME) { 260 // @arkts.xxx -> @ohos:arkts.xxx 261 return `@ohos:${systemModule}`; 262 } else { 263 return `@ohos:${systemKey}`; 264 }; 265 }); 266 } 267 if (REG_LIB_SO.test(moduleRequest.trim())) { 268 if (useNormalizedOHMUrl) { 269 const pkgInfo = config.pkgContextInfo[moduleRequest]; 270 if (pkgInfo === undefined) { 271 logger?.error(red, `ArkTS:INTERNAL ERROR: Can not get pkgContextInfo of package '${moduleRequest}' ` + 272 `which being imported by '${importerFile}'`, reset); 273 } 274 const isSo = pkgInfo.isSO ? 'Y' : 'N'; 275 return `@normalized:${isSo}&${pkgInfo.moduleName}&${pkgInfo.bundleName}&${moduleRequest}&${pkgInfo.version}`; 276 } 277 return moduleRequest.replace(REG_LIB_SO, (_, libsoKey) => { 278 return `@app:${projectConfig.bundleName}/${projectConfig.moduleName}/${libsoKey}`; 279 }); 280 } 281 return undefined; 282} 283 284export function genSourceMapFileName(temporaryFile: string): string { 285 let abcFile: string = temporaryFile; 286 if (temporaryFile.endsWith(EXTNAME_TS)) { 287 abcFile = temporaryFile.replace(/\.ts$/, EXTNAME_TS_MAP); 288 } else { 289 abcFile = temporaryFile.replace(/\.js$/, EXTNAME_JS_MAP); 290 } 291 return abcFile; 292} 293 294export function getBuildModeInLowerCase(projectConfig: Object): string { 295 return (compileToolIsRollUp() ? projectConfig.buildMode : projectConfig.buildArkMode).toLowerCase(); 296} 297 298/** 299 * This Api only used by webpack compiling process - js-loader 300 * @param sourcePath The path in build cache dir 301 * @param sourceCode The intermediate js source code 302 */ 303export function writeFileSyncByString(sourcePath: string, sourceCode: string, projectConfig: Object, logger: Object): void { 304 const filePath: string = genTemporaryPath(sourcePath, projectConfig.projectPath, process.env.cachePath, 305 projectConfig, undefined); 306 if (filePath.length === 0) { 307 return; 308 } 309 mkdirsSync(path.dirname(filePath)); 310 if (/\.js$/.test(sourcePath)) { 311 sourceCode = transformModuleSpecifier(sourcePath, sourceCode, projectConfig); 312 if (projectConfig.buildArkMode === 'debug') { 313 fs.writeFileSync(filePath, sourceCode); 314 return; 315 } 316 writeObfuscatedSourceCode({content: sourceCode, buildFilePath: filePath, relativeSourceFilePath: ''}, 317 logger, projectConfig); 318 } 319 if (/\.json$/.test(sourcePath)) { 320 fs.writeFileSync(filePath, sourceCode); 321 } 322} 323 324export function transformModuleSpecifier(sourcePath: string, sourceCode: string, projectConfig: Object): string { 325 // replace relative moduleSpecifier with ohmURl 326 const REG_RELATIVE_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]((?:\.\/|\.\.\/)[^'"]+|(?:\.\/?|\.\.\/?))['"]/g; 327 const REG_HAR_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]([^\.\/][^'"]+)['"]/g; 328 // replace requireNapi and requireNativeModule with import 329 const REG_REQUIRE_NATIVE_MODULE: RegExp = /var (\S+) = globalThis.requireNativeModule\(['"](\S+)['"]\);/g; 330 const REG_REQUIRE_NAPI_APP: RegExp = /var (\S+) = globalThis.requireNapi\(['"](\S+)['"], true, ['"](\S+)['"]\);/g; 331 const REG_REQUIRE_NAPI_OHOS: RegExp = /var (\S+) = globalThis.requireNapi\(['"](\S+)['"]\);/g; 332 333 return sourceCode.replace(REG_HAR_DEPENDENCY, (item, moduleRequest) => { 334 return replaceHarDependency(item, moduleRequest, projectConfig); 335 }).replace(REG_RELATIVE_DEPENDENCY, (item, moduleRequest) => { 336 return replaceRelativeDependency(item, moduleRequest, toUnixPath(sourcePath), projectConfig); 337 }).replace(REG_REQUIRE_NATIVE_MODULE, (_, moduleRequest, moduleName) => { 338 return `import ${moduleRequest} from '@native:${moduleName}';`; 339 }).replace(REG_REQUIRE_NAPI_APP, (_, moduleRequest, soName, moudlePath) => { 340 return `import ${moduleRequest} from '@app:${moudlePath}/${soName}';`; 341 }).replace(REG_REQUIRE_NAPI_OHOS, (_, moduleRequest, moduleName) => { 342 return `import ${moduleRequest} from '@ohos:${moduleName}';`; 343 }); 344} 345 346function removeSuffix(filePath: string): string { 347 const SUFFIX_REG = /\.(?:d\.)?(ets|ts|mjs|cjs|js)$/; 348 return filePath.split(path.sep).join('/').replace(SUFFIX_REG, ''); 349} 350 351export function getNormalizedOhmUrlByAliasName(aliasName: string, projectConfig: Object, 352 logger?: Object, filePath?: string): string { 353 let pkgName: string = aliasName; 354 const aliasPkgNameMap: Map<string, string> = projectConfig.dependencyAliasMap; 355 if (aliasPkgNameMap.has(aliasName)) { 356 pkgName = aliasPkgNameMap.get(aliasName); 357 } 358 const pkgInfo: Object = projectConfig.pkgContextInfo[pkgName]; 359 if (!pkgInfo) { 360 logger.error(red, `ArkTS:INTERNAL ERROR: Failed to find package '${pkgName}'.\n` + 361 `Error Message: Failed to obtain package '${pkgName}' from the package context information.`, reset); 362 } 363 let normalizedPath: string = ''; 364 if (!filePath) { 365 if (!pkgInfo.entryPath) { 366 logger.error(red, `ArkTS:INTERNAL ERROR: Failed to find entry file of '${pkgName}'.\n` + 367 `Error Message: Failed to obtain the entry file information of '${pkgName}' from the package context information.`, reset); 368 } 369 normalizedPath = `${pkgName}/${toUnixPath(pkgInfo.entryPath)}`; 370 normalizedPath = removeSuffix(normalizedPath); 371 } else { 372 const relativePath = toUnixPath(filePath).replace(aliasName, ''); 373 normalizedPath = `${pkgName}${relativePath}`; 374 } 375 const isSo = pkgInfo.isSO ? 'Y' : 'N'; 376 return `@normalized:${isSo}&${pkgInfo.moduleName}&${pkgInfo.bundleName}&${normalizedPath}&${pkgInfo.version}`; 377} 378 379export function getOhmUrlByByteCodeHar(moduleRequest: string, projectConfig: Object, logger?: Object): 380 string | undefined { 381 if (projectConfig.byteCodeHarInfo) { 382 if (Object.prototype.hasOwnProperty.call(projectConfig.byteCodeHarInfo, moduleRequest)) { 383 return getNormalizedOhmUrlByAliasName(moduleRequest, projectConfig, logger); 384 } 385 for (const byteCodeHarName in projectConfig.byteCodeHarInfo) { 386 if (moduleRequest.startsWith(byteCodeHarName + '/')) { 387 return getNormalizedOhmUrlByAliasName(byteCodeHarName, projectConfig, logger, moduleRequest); 388 } 389 } 390 } 391 return undefined; 392} 393 394export function getOhmUrlByExternalPackage(moduleRequest: string, projectConfig: Object, logger?: Object, 395 useNormalizedOHMUrl: boolean = false): string | undefined { 396 // The externalPkgMap store the ohmurl with the alias of hsp package and the hars depended on bytecode har. 397 let externalPkgMap: Object = Object.assign({}, projectConfig.hspNameOhmMap, projectConfig.harNameOhmMap); 398 if (Object.keys(externalPkgMap).length !== 0) { 399 if (Object.prototype.hasOwnProperty.call(externalPkgMap, moduleRequest)) { 400 if (useNormalizedOHMUrl) { 401 return getNormalizedOhmUrlByAliasName(moduleRequest, projectConfig, logger); 402 } 403 // case1: "@ohos/lib" ---> "@bundle:bundleName/lib/ets/index" 404 return externalPkgMap[moduleRequest]; 405 } 406 407 for (const externalPkgName in externalPkgMap) { 408 if (moduleRequest.startsWith(externalPkgName + '/')) { 409 if (useNormalizedOHMUrl) { 410 return getNormalizedOhmUrlByAliasName(externalPkgName, projectConfig, logger, moduleRequest); 411 } 412 // case2: "@ohos/lib/src/main/ets/pages/page1" ---> "@bundle:bundleName/lib/ets/pages/page1" 413 const idx: number = externalPkgMap[externalPkgName].split('/', 2).join('/').length; 414 const ohmName: string = externalPkgMap[externalPkgName].substring(0, idx); 415 if (moduleRequest.indexOf(externalPkgName + '/' + SRC_MAIN) === 0) { 416 return moduleRequest.replace(externalPkgName + '/' + SRC_MAIN, ohmName); 417 } else { 418 return moduleRequest.replace(externalPkgName, ohmName); 419 } 420 } 421 } 422 } 423 return undefined; 424} 425 426function replaceHarDependency(item: string, moduleRequest: string, projectConfig: Object): string { 427 const hspOhmUrl: string | undefined = getOhmUrlByExternalPackage(moduleRequest, projectConfig); 428 if (hspOhmUrl !== undefined) { 429 return item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { 430 return quotation + hspOhmUrl + quotation; 431 }); 432 } 433 return item; 434} 435 436function locateActualFilePathWithModuleRequest(absolutePath: string): string { 437 if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isDirectory()) { 438 return absolutePath; 439 } 440 441 const filePath: string = absolutePath + getExtensionIfUnfullySpecifiedFilepath(absolutePath); 442 if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { 443 return absolutePath; 444 } 445 446 return path.join(absolutePath, 'index'); 447} 448 449function replaceRelativeDependency(item: string, moduleRequest: string, sourcePath: string, projectConfig: Object): string { 450 if (sourcePath && projectConfig.compileMode === ESMODULE) { 451 // remove file extension from moduleRequest 452 const SUFFIX_REG: RegExp = /\.(?:[cm]?js|[e]?ts|json)$/; 453 moduleRequest = moduleRequest.replace(SUFFIX_REG, ''); 454 455 // normalize the moduleRequest 456 item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { 457 let normalizedModuleRequest: string = toUnixPath(path.normalize(moduleRequest)); 458 if (moduleRequest.startsWith('./')) { 459 normalizedModuleRequest = './' + normalizedModuleRequest; 460 } 461 return quotation + normalizedModuleRequest + quotation; 462 }); 463 464 const filePath: string = 465 locateActualFilePathWithModuleRequest(path.resolve(path.dirname(sourcePath), moduleRequest)); 466 const result: RegExpMatchArray | null = 467 filePath.match(/(\S+)(\/|\\)src(\/|\\)(?:main|ohosTest)(\/|\\)(ets|js)(\/|\\)(\S+)/); 468 if (result && projectConfig.aceModuleJsonPath) { 469 const npmModuleIdx: number = result[1].search(/(\/|\\)node_modules(\/|\\)/); 470 const projectRootPath: string = projectConfig.projectRootPath; 471 if (npmModuleIdx === -1 || npmModuleIdx === projectRootPath.search(/(\/|\\)node_modules(\/|\\)/)) { 472 const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath); 473 const bundleName: string = packageInfo[0]; 474 const moduleName: string = packageInfo[1]; 475 moduleRequest = `@bundle:${bundleName}/${moduleName}/${result[5]}/${toUnixPath(result[7])}`; 476 item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => { 477 return quotation + moduleRequest + quotation; 478 }); 479 } 480 } 481 } 482 return item; 483} 484 485interface ModuleInfo { 486 content: string, 487 /** 488 * the path in build cache dir 489 */ 490 buildFilePath: string, 491 /** 492 * the `originSourceFilePath` relative to project root dir. 493 */ 494 relativeSourceFilePath: string, 495 /** 496 * the origin source file path will be set with rollup moduleId when obfuscate intermediate js source code, 497 * whereas be set with tsc node.fileName when obfuscate intermediate ts source code. 498 */ 499 originSourceFilePath?: string, 500 rollupModuleId?: string 501} 502 503export async function writeObfuscatedSourceCode(moduleInfo: ModuleInfo, logger: Object, 504 projectConfig: Object, rollupNewSourceMaps: Object = {}): Promise<void> { 505 if (compileToolIsRollUp() && projectConfig.arkObfuscator) { 506 MemoryUtils.tryGC(); 507 performancePrinter?.filesPrinter?.startEvent(moduleInfo.buildFilePath); 508 await writeArkguardObfuscatedSourceCode(moduleInfo, logger, projectConfig, rollupNewSourceMaps); 509 performancePrinter?.filesPrinter?.endEvent(moduleInfo.buildFilePath, undefined, true); 510 MemoryUtils.tryGC(); 511 return; 512 } 513 mkdirsSync(path.dirname(moduleInfo.buildFilePath)); 514 if (!compileToolIsRollUp()) { 515 await writeMinimizedSourceCode(moduleInfo.content, moduleInfo.buildFilePath, logger, projectConfig.compileHar); 516 return; 517 } 518 519 if (moduleInfo.originSourceFilePath) { 520 const originSourceFilePath = toUnixPath(moduleInfo.originSourceFilePath); 521 let genFileInHar: GeneratedFileInHar = harFilesRecord.get(originSourceFilePath); 522 523 if (!genFileInHar) { 524 genFileInHar = { sourcePath: originSourceFilePath }; 525 } 526 if (!genFileInHar.sourceCachePath) { 527 genFileInHar.sourceCachePath = toUnixPath(moduleInfo.buildFilePath); 528 } 529 harFilesRecord.set(originSourceFilePath, genFileInHar); 530 } 531 532 fs.writeFileSync(moduleInfo.buildFilePath, moduleInfo.content); 533} 534 535/** 536 * This Api only be used by rollup compiling process & only be 537 * exported for unit test. 538 */ 539export async function writeArkguardObfuscatedSourceCode(moduleInfo: ModuleInfo, logger: Object, 540 projectConfig: Object, rollupNewSourceMaps: Object = {}): Promise<void> { 541 const arkObfuscator = projectConfig.arkObfuscator; 542 const isDeclaration = (/\.d\.e?ts$/).test(moduleInfo.buildFilePath); 543 const packageDir = projectConfig.packageDir; 544 const projectRootPath = projectConfig.projectRootPath; 545 const useNormalized = projectConfig.useNormalizedOHMUrl; 546 const localPackageSet = projectConfig.localPackageSet; 547 const useTsHar = projectConfig.useTsHar; 548 const sourceMapGeneratorInstance = SourceMapGenerator.getInstance(); 549 550 let previousStageSourceMap: sourceMap.RawSourceMap | undefined = undefined; 551 if (moduleInfo.relativeSourceFilePath.length > 0 && !isDeclaration) { 552 const selectedFilePath = sourceMapGeneratorInstance.isNewSourceMaps() ? moduleInfo.rollupModuleId! : moduleInfo.relativeSourceFilePath; 553 previousStageSourceMap = sourceMapGeneratorInstance.getSpecifySourceMap(rollupNewSourceMaps, selectedFilePath) as sourceMap.RawSourceMap; 554 } 555 556 let historyNameCache = new Map<string, string>(); 557 let namecachePath = moduleInfo.relativeSourceFilePath; 558 if (isDeclaration) { 559 namecachePath = getRelativeSourcePath(moduleInfo.originSourceFilePath, projectRootPath, 560 sourceFileBelongProject.get(toUnixPath(moduleInfo.originSourceFilePath))); 561 } 562 if (nameCacheMap) { 563 let identifierCache = nameCacheMap.get(namecachePath)?.[IDENTIFIER_CACHE]; 564 deleteLineInfoForNameString(historyNameCache, identifierCache); 565 } 566 567 let mixedInfo: { content: string, sourceMap?: Object, nameCache?: Object, unobfuscationNameMap?: Map<string, Set<string>>}; 568 let projectInfo: { 569 packageDir: string, 570 projectRootPath: string, 571 localPackageSet: Set<string>, 572 useNormalized: boolean, 573 useTsHar: boolean 574 } = { packageDir, projectRootPath, localPackageSet, useNormalized, useTsHar }; 575 let filePathObj = { buildFilePath: moduleInfo.buildFilePath, relativeFilePath: moduleInfo.relativeSourceFilePath }; 576 try { 577 mixedInfo = await arkObfuscator.obfuscate(moduleInfo.content, filePathObj, previousStageSourceMap, 578 historyNameCache, moduleInfo.originSourceFilePath, projectInfo); 579 } catch (err) { 580 logger.error(red, `ArkTS:INTERNAL ERROR: Failed to obfuscate file '${moduleInfo.relativeSourceFilePath}' with arkguard. ${err}`); 581 } 582 583 if (mixedInfo.sourceMap && !isDeclaration) { 584 const selectedFilePath = sourceMapGeneratorInstance.isNewSourceMaps() ? moduleInfo.rollupModuleId! : moduleInfo.relativeSourceFilePath; 585 mixedInfo.sourceMap.sources = [moduleInfo.relativeSourceFilePath]; 586 sourceMapGeneratorInstance.fillSourceMapPackageInfo(moduleInfo.rollupModuleId!, mixedInfo.sourceMap); 587 sourceMapGeneratorInstance.updateSpecifySourceMap(rollupNewSourceMaps, selectedFilePath, mixedInfo.sourceMap); 588 } 589 590 if (mixedInfo.nameCache && !isDeclaration) { 591 let obfName: string = moduleInfo.relativeSourceFilePath; 592 let isOhModule = isPackageModulesFile(moduleInfo.originSourceFilePath, projectConfig); 593 if (projectConfig.obfuscationMergedObConfig?.options.enableFileNameObfuscation && !isOhModule) { 594 obfName = mangleFilePath(moduleInfo.relativeSourceFilePath); 595 } 596 mixedInfo.nameCache.obfName = obfName; 597 nameCacheMap.set(moduleInfo.relativeSourceFilePath, mixedInfo.nameCache); 598 } 599 600 if (mixedInfo.unobfuscationNameMap && !isDeclaration) { 601 let arrayObject: Record<string, string[]> = {}; 602 // The type of unobfuscationNameMap's value is Set, convert Set to Array. 603 mixedInfo.unobfuscationNameMap.forEach((value: Set<string>, key: string) => { 604 let array: string[] = Array.from(value); 605 arrayObject[key] = array; 606 }); 607 unobfuscationNamesObj[moduleInfo.relativeSourceFilePath] = arrayObject; 608 } 609 610 const newFilePath: string = tryMangleFileName(moduleInfo.buildFilePath, projectConfig, moduleInfo.originSourceFilePath); 611 if (newFilePath !== moduleInfo.buildFilePath && !isDeclaration) { 612 sourceMapGeneratorInstance.saveKeyMappingForObfFileName(moduleInfo.rollupModuleId!); 613 } 614 mkdirsSync(path.dirname(newFilePath)); 615 fs.writeFileSync(newFilePath, mixedInfo.content ?? ''); 616} 617 618export function tryMangleFileName(filePath: string, projectConfig: Object, originalFilePath: string): string { 619 originalFilePath = toUnixPath(originalFilePath); 620 let isOhModule = isPackageModulesFile(originalFilePath, projectConfig); 621 let genFileInHar: GeneratedFileInHar = harFilesRecord.get(originalFilePath); 622 if (!genFileInHar) { 623 genFileInHar = { sourcePath: originalFilePath }; 624 harFilesRecord.set(originalFilePath, genFileInHar); 625 } 626 627 if (projectConfig.obfuscationMergedObConfig?.options?.enableFileNameObfuscation && !isOhModule) { 628 const mangledFilePath: string = mangleFilePath(filePath); 629 if ((/\.d\.e?ts$/).test(filePath)) { 630 genFileInHar.obfuscatedDeclarationCachePath = mangledFilePath; 631 } else { 632 genFileInHar.obfuscatedSourceCachePath = mangledFilePath; 633 } 634 filePath = mangledFilePath; 635 } else if (!(/\.d\.e?ts$/).test(filePath)) { 636 genFileInHar.sourceCachePath = filePath; 637 } 638 return filePath; 639} 640 641export async function mangleDeclarationFileName(logger: Object, projectConfig: Object, 642 sourceFileBelongProject: Map<string, string>): Promise<void> { 643 for (const [sourcePath, genFilesInHar] of harFilesRecord) { 644 if (genFilesInHar.originalDeclarationCachePath && genFilesInHar.originalDeclarationContent) { 645 let filePath = genFilesInHar.originalDeclarationCachePath; 646 let relativeSourceFilePath = getRelativeSourcePath(filePath, 647 projectConfig.projectRootPath, sourceFileBelongProject.get(toUnixPath(filePath))); 648 await writeObfuscatedSourceCode({ 649 content: genFilesInHar.originalDeclarationContent, 650 buildFilePath: genFilesInHar.originalDeclarationCachePath, 651 relativeSourceFilePath: relativeSourceFilePath, 652 originSourceFilePath: sourcePath 653 }, logger, projectConfig, {}); 654 } 655 } 656} 657 658export async function writeMinimizedSourceCode(content: string, filePath: string, logger: Object, 659 isHar: boolean = false): Promise<void> { 660 let result: MinifyOutput; 661 try { 662 const minifyOptions = { 663 compress: { 664 join_vars: false, 665 sequences: 0, 666 directives: false 667 } 668 }; 669 if (!isHar) { 670 minifyOptions['format'] = { 671 semicolons: false, 672 beautify: true, 673 indent_level: 2 674 }; 675 } 676 result = await minify(content, minifyOptions); 677 } catch { 678 logger.error(red, `ArkTS:INTERNAL ERROR: Failed to obfuscate source code for ${filePath}`, reset); 679 } 680 681 fs.writeFileSync(filePath, result.code); 682} 683 684export function genBuildPath(filePath: string, projectPath: string, buildPath: string, projectConfig: Object): string { 685 filePath = toUnixPath(filePath); 686 if (filePath.endsWith(EXTNAME_MJS)) { 687 filePath = filePath.replace(/\.mjs$/, EXTNAME_JS); 688 } 689 if (filePath.endsWith(EXTNAME_CJS)) { 690 filePath = filePath.replace(/\.cjs$/, EXTNAME_JS); 691 } 692 projectPath = toUnixPath(projectPath); 693 694 if (isPackageModulesFile(filePath, projectConfig)) { 695 const packageDir: string = projectConfig.packageDir; 696 const fakePkgModulesPath: string = toUnixPath(path.join(projectConfig.projectRootPath, packageDir)); 697 let output: string = ''; 698 if (filePath.indexOf(fakePkgModulesPath) === -1) { 699 const hapPath: string = toUnixPath(projectConfig.projectRootPath); 700 const tempFilePath: string = filePath.replace(hapPath, ''); 701 const sufStr: string = tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1); 702 output = path.join(projectConfig.nodeModulesPath, ZERO, sufStr); 703 } else { 704 output = filePath.replace(fakePkgModulesPath, path.join(projectConfig.nodeModulesPath, ONE)); 705 } 706 return output; 707 } 708 709 if (filePath.indexOf(projectPath) !== -1) { 710 const sufStr: string = filePath.replace(projectPath, ''); 711 const output: string = path.join(buildPath, sufStr); 712 return output; 713 } 714 715 return ''; 716} 717 718export function getPackageInfo(configFile: string): Array<string> { 719 if (packageCollection.has(configFile)) { 720 return packageCollection.get(configFile); 721 } 722 const data: Object = JSON.parse(fs.readFileSync(configFile).toString()); 723 const bundleName: string = data.app.bundleName; 724 const moduleName: string = data.module.name; 725 packageCollection.set(configFile, [bundleName, moduleName]); 726 return [bundleName, moduleName]; 727} 728 729/** 730 * This Api only used by webpack compiling process - result_process 731 * @param sourcePath The path in build cache dir 732 * @param sourceContent The intermediate js source code 733 */ 734export function generateSourceFilesToTemporary(sourcePath: string, sourceContent: string, sourceMap: Object, 735 projectConfig: Object, logger: Object): void { 736 let jsFilePath: string = genTemporaryPath(sourcePath, projectConfig.projectPath, process.env.cachePath, 737 projectConfig, undefined); 738 if (jsFilePath.length === 0) { 739 return; 740 } 741 if (jsFilePath.endsWith(EXTNAME_ETS)) { 742 jsFilePath = jsFilePath.replace(/\.ets$/, EXTNAME_JS); 743 } else { 744 jsFilePath = jsFilePath.replace(/\.ts$/, EXTNAME_JS); 745 } 746 let sourceMapFile: string = genSourceMapFileName(jsFilePath); 747 if (sourceMapFile.length > 0 && projectConfig.buildArkMode === 'debug') { 748 let source = toUnixPath(sourcePath).replace(toUnixPath(projectConfig.projectRootPath) + '/', ''); 749 // adjust sourceMap info 750 sourceMap.sources = [source]; 751 sourceMap.file = path.basename(sourceMap.file); 752 delete sourceMap.sourcesContent; 753 newSourceMaps[source] = sourceMap; 754 } 755 sourceContent = transformModuleSpecifier(sourcePath, sourceContent, projectConfig); 756 757 mkdirsSync(path.dirname(jsFilePath)); 758 if (projectConfig.buildArkMode === 'debug') { 759 fs.writeFileSync(jsFilePath, sourceContent); 760 return; 761 } 762 763 writeObfuscatedSourceCode({content: sourceContent, buildFilePath: jsFilePath, relativeSourceFilePath: ''}, 764 logger, projectConfig); 765} 766 767export function genAbcFileName(temporaryFile: string): string { 768 let abcFile: string = temporaryFile; 769 if (temporaryFile.endsWith(EXTNAME_TS)) { 770 abcFile = temporaryFile.replace(/\.ts$/, EXTNAME_ABC); 771 } else { 772 abcFile = temporaryFile.replace(/\.js$/, EXTNAME_ABC); 773 } 774 return abcFile; 775} 776 777export function isOhModules(projectConfig: Object): boolean { 778 return projectConfig.packageDir === OH_MODULES; 779} 780 781export function isEs2Abc(projectConfig: Object): boolean { 782 return projectConfig.pandaMode === ES2ABC || projectConfig.pandaMode === 'undefined' || 783 projectConfig.pandaMode === undefined; 784} 785 786export function isTs2Abc(projectConfig: Object): boolean { 787 return projectConfig.pandaMode === TS2ABC; 788} 789 790export function genProtoFileName(temporaryFile: string): string { 791 return temporaryFile.replace(/\.(?:[tj]s|json)$/, EXTNAME_PROTO_BIN); 792} 793 794export function genMergeProtoFileName(temporaryFile: string): string { 795 let protoTempPathArr: string[] = temporaryFile.split(TEMPORARY); 796 const sufStr: string = protoTempPathArr[protoTempPathArr.length - 1]; 797 let protoBuildPath: string = path.join(process.env.cachePath, 'protos', sufStr); 798 799 return protoBuildPath; 800} 801 802export function removeDuplicateInfo(moduleInfos: Array<any>): Array<any> { 803 const tempModuleInfos: any[] = Array<any>(); 804 moduleInfos.forEach((item) => { 805 let check: boolean = tempModuleInfos.every((newItem) => { 806 return item.tempFilePath !== newItem.tempFilePath; 807 }); 808 if (check) { 809 tempModuleInfos.push(item); 810 } 811 }); 812 moduleInfos = tempModuleInfos; 813 814 return moduleInfos; 815} 816 817export function buildCachePath(tailName: string, projectConfig: Object, logger: Object): string { 818 let pathName: string = process.env.cachePath !== undefined ? 819 path.join(projectConfig.cachePath, tailName) : path.join(projectConfig.aceModuleBuild, tailName); 820 validateFilePathLength(pathName, logger); 821 return pathName; 822} 823 824export function getArkBuildDir(arkDir: string): string { 825 if (isWindows()) { 826 return path.join(arkDir, 'build-win'); 827 } else if (isMac()) { 828 return path.join(arkDir, 'build-mac'); 829 } else { 830 return path.join(arkDir, 'build'); 831 } 832} 833 834export function getBuildBinDir(arkDir: string): string { 835 return path.join(getArkBuildDir(arkDir), 'bin'); 836} 837 838export function cleanUpUtilsObjects(): void { 839 newSourceMaps = {}; 840 nameCacheMap.clear(); 841 packageCollection.clear(); 842} 843 844export function getHookEventFactory(share: Object, pluginName: string, hookName: string): Object { 845 if (typeof share.getHookEventFactory === 'function') { 846 return share.getHookEventFactory(pluginName, hookName); 847 } else { 848 return undefined; 849 } 850} 851 852export function createAndStartEvent(eventOrEventFactory: Object, eventName: string, syncFlag = false): Object { 853 if (eventOrEventFactory === undefined) { 854 return undefined; 855 } 856 let event: Object; 857 if (typeof eventOrEventFactory.createSubEvent === 'function') { 858 event = eventOrEventFactory.createSubEvent(eventName); 859 } else { 860 event = eventOrEventFactory.createEvent(eventName); 861 } 862 if (typeof event.startAsyncEvent === 'function' && syncFlag) { 863 event.startAsyncEvent(); 864 } else { 865 event.start(); 866 } 867 return event; 868} 869 870export function stopEvent(event: Object, syncFlag = false): void { 871 if (event !== undefined) { 872 if (typeof event.stopAsyncEvent === 'function' && syncFlag) { 873 event.stopAsyncEvent(); 874 } else { 875 event.stop(); 876 } 877 } 878} 879 880export function compileToolIsRollUp(): boolean { 881 return process.env.compileTool === 'rollup'; 882} 883 884export function transformOhmurlToRecordName(ohmurl: string): string { 885 // @normalized:N&<moduleName>&<bunldName>&<packageName>/entry/ets/xxx/yyy&<version> 886 // ----> <bunldName>&<packageName>/entry/ets/xxx/yyy&<version> 887 return ohmurl.split(SEPARATOR_BITWISE_AND).slice(2).join(SEPARATOR_BITWISE_AND); 888} 889 890export function transformOhmurlToPkgName(ohmurl: string): string { 891 let normalizedPath: string = ohmurl.split(SEPARATOR_BITWISE_AND)[3]; 892 let paths: Array<string> = normalizedPath.split(SEPARATOR_SLASH); 893 if (normalizedPath.startsWith(SEPARATOR_AT)) { 894 // Spec: If the normalized import path starts with '@', the package name is before the second '/' in the normalized 895 // import path, like: @aaa/bbb/ccc/ddd ---> package name is @aaa/bbb 896 return paths.slice(0, 2).join(SEPARATOR_SLASH); 897 } 898 return paths[0]; 899}