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 childProcess from 'child_process'; 17import fs from 'fs'; 18import path from 'path'; 19import cluster from 'cluster'; 20 21import { 22 COMMONJS, 23 COMPILE_CONTEXT_INFO_JSON, 24 ESM, 25 ESMODULE, 26 EXTNAME_CJS, 27 EXTNAME_ETS, 28 EXTNAME_JS, 29 EXTNAME_JSON, 30 EXTNAME_MJS, 31 EXTNAME_PROTO_BIN, 32 EXTNAME_TS, 33 EXTNAME_TXT, 34 FAIL, 35 SUCCESS, 36 FILESINFO, 37 FILESINFO_TXT, 38 MAX_WORKER_NUMBER, 39 MODULES_ABC, 40 MODULES_CACHE, 41 NPM_ENTRIES_PROTO_BIN, 42 NPMENTRIES_TXT, 43 OH_MODULES, 44 PACKAGES, 45 PROTO_FILESINFO_TXT, 46 PROTOS, 47 red, 48 reset, 49 SOURCEMAPS, 50 SOURCEMAPS_JSON, 51 WIDGETS_ABC, 52 TS2ABC, 53 ES2ABC, 54 ETS, 55 TS, 56 JS, 57 PERFREPORT_JSON, 58} from '../common/ark_define'; 59import { 60 needAotCompiler, 61 isMasterOrPrimary, 62 isAotMode, 63 isDebug 64} from '../utils'; 65import { CommonMode } from '../common/common_mode'; 66import { 67 handleObfuscatedFilePath, 68 enableObfuscateFileName, 69 enableObfuscatedFilePathConfig 70} from '../common/ob_config_resolver'; 71import { 72 changeFileExtension, 73 getEs2abcFileThreadNumber, 74 isCommonJsPluginVirtualFile, 75 isCurrentProjectFiles, 76 shouldETSOrTSFileTransformToJS 77} from '../utils'; 78import { 79 isPackageModulesFile, 80 mkdirsSync, 81 toUnixPath, 82 toHashData, 83 validateFilePathLength 84} from '../../../utils'; 85import { 86 getPackageInfo, 87 getNormalizedOhmUrlByFilepath, 88 getOhmUrlByFilepath, 89 getOhmUrlByExternalPackage, 90 isTs2Abc, 91 isEs2Abc, 92 transformOhmurlToPkgName, 93 transformOhmurlToRecordName, 94} from '../../../ark_utils'; 95import { 96 generateAot, 97 FaultHandler 98} from '../../../gen_aot'; 99import { 100 NATIVE_MODULE 101} from '../../../pre_define'; 102import { 103 sharedModuleSet 104} from '../check_shared_module'; 105import { SourceMapGenerator } from '../generate_sourcemap'; 106import { MemoryMonitor } from '../../meomry_monitor/rollup-plugin-memory-monitor'; 107import { MemoryDefine } from '../../meomry_monitor/memory_define'; 108import { 109 ArkTSInternalErrorDescription, 110 ArkTSErrorDescription, 111 ErrorCode 112} from '../error_code'; 113import { 114 LogData, 115 LogDataFactory 116} from '../logger'; 117import { 118 CompileEvent, 119 createAndStartEvent, 120 ExternalEventType, 121 isNeedPerformanceDotting, 122 processExternalEvents, 123 stopEvent 124 } from '../../../performance'; 125import { BytecodeObfuscator } from '../bytecode_obfuscator'; 126 127export class ModuleInfo { 128 filePath: string; 129 cacheFilePath: string; 130 recordName: string; 131 isCommonJs: boolean; 132 sourceFile: string; 133 packageName: string; 134 originSourceLang: string; 135 136 constructor(filePath: string, cacheFilePath: string, isCommonJs: boolean, recordName: string, sourceFile: string, 137 packageName: string, originSourceLang: string 138 ) { 139 this.filePath = filePath; 140 this.cacheFilePath = cacheFilePath; 141 this.recordName = recordName; 142 this.isCommonJs = isCommonJs; 143 this.sourceFile = sourceFile; 144 this.packageName = packageName; 145 this.originSourceLang = originSourceLang; 146 } 147} 148 149export class PackageEntryInfo { 150 pkgEntryPath: string; 151 pkgBuildPath: string; 152 constructor(pkgEntryPath: string, pkgBuildPath: string) { 153 this.pkgEntryPath = pkgEntryPath; 154 this.pkgBuildPath = pkgBuildPath; 155 } 156} 157 158export class ModuleMode extends CommonMode { 159 moduleInfos: Map<String, ModuleInfo>; 160 pkgEntryInfos: Map<String, PackageEntryInfo>; 161 hashJsonObject: Object; 162 filesInfoPath: string; 163 npmEntriesInfoPath: string; 164 moduleAbcPath: string; 165 sourceMapPath: string; 166 cacheFilePath: string; 167 cacheSourceMapPath: string; 168 workerNumber: number; 169 npmEntriesProtoFilePath: string; 170 protoFilePath: string; 171 filterModuleInfos: Map<String, ModuleInfo>; 172 symlinkMap: Object; 173 useNormalizedOHMUrl: boolean; 174 compileContextInfoPath: string; 175 abcPaths: string[] = []; 176 byteCodeHar: boolean; 177 perfReportPath: string; 178 179 constructor(rollupObject: Object) { 180 super(rollupObject); 181 this.moduleInfos = new Map<String, ModuleInfo>(); 182 this.pkgEntryInfos = new Map<String, PackageEntryInfo>(); 183 this.hashJsonObject = {}; 184 this.filesInfoPath = path.join(this.projectConfig.cachePath, FILESINFO_TXT); 185 this.perfReportPath = path.join(this.projectConfig.cachePath, PERFREPORT_JSON); 186 this.npmEntriesInfoPath = path.join(this.projectConfig.cachePath, NPMENTRIES_TXT); 187 const outPutABC: string = this.projectConfig.widgetCompile ? WIDGETS_ABC : MODULES_ABC; 188 this.moduleAbcPath = path.join(this.projectConfig.aceModuleBuild, outPutABC); 189 this.sourceMapPath = this.arkConfig.isDebug ? path.join(this.projectConfig.aceModuleBuild, SOURCEMAPS) : 190 path.join(this.projectConfig.cachePath, SOURCEMAPS); 191 this.cacheFilePath = path.join(this.projectConfig.cachePath, MODULES_CACHE); 192 this.cacheSourceMapPath = path.join(this.projectConfig.cachePath, SOURCEMAPS_JSON); 193 this.workerNumber = MAX_WORKER_NUMBER; 194 this.npmEntriesProtoFilePath = path.join(this.projectConfig.cachePath, PROTOS, NPM_ENTRIES_PROTO_BIN); 195 this.protoFilePath = path.join(this.projectConfig.cachePath, PROTOS, PROTO_FILESINFO_TXT); 196 this.hashJsonObject = {}; 197 this.filterModuleInfos = new Map<String, ModuleInfo>(); 198 this.symlinkMap = rollupObject.share.symlinkMap; 199 this.useNormalizedOHMUrl = this.isUsingNormalizedOHMUrl(); 200 if (Object.prototype.hasOwnProperty.call(this.projectConfig, 'byteCodeHarInfo')) { 201 let byteCodeHarInfo = this.projectConfig.byteCodeHarInfo; 202 for (const packageName in byteCodeHarInfo) { 203 const abcPath = toUnixPath(byteCodeHarInfo[packageName].abcPath); 204 this.abcPaths.push(abcPath); 205 } 206 } 207 this.byteCodeHar = !!this.projectConfig.byteCodeHar; 208 if (this.useNormalizedOHMUrl) { 209 this.compileContextInfoPath = this.generateCompileContextInfo(rollupObject); 210 } 211 } 212 213 private generateCompileContextInfo(rollupObject: Object): string { 214 let compileContextInfoPath: string = path.join(this.projectConfig.cachePath, COMPILE_CONTEXT_INFO_JSON); 215 let compileContextInfo: Object = {}; 216 let hspPkgNames: Array<string> = []; 217 for (const hspAliasName in this.projectConfig.hspNameOhmMap) { 218 let hspPkgName: string = hspAliasName; 219 if (this.projectConfig.dependencyAliasMap.has(hspAliasName)) { 220 hspPkgName = this.projectConfig.dependencyAliasMap.get(hspAliasName); 221 } 222 hspPkgNames.push(toUnixPath(hspPkgName)); 223 } 224 compileContextInfo.hspPkgNames = hspPkgNames; 225 let compileEntries: Set<string> = new Set(); 226 let entryObj: Object = this.projectConfig.entryObj; 227 if (!!this.projectConfig.widgetCompile) { 228 entryObj = this.projectConfig.cardEntryObj; 229 } 230 for (const key in entryObj) { 231 let moduleId: string = entryObj[key]; 232 let moduleInfo: Object = rollupObject.getModuleInfo(moduleId); 233 if (!moduleInfo) { 234 const errInfo: LogData = LogDataFactory.newInstance( 235 ErrorCode.ETS2BUNDLE_INTERNAL_MODULE_INFO_NOT_FOUND, 236 ArkTSInternalErrorDescription, 237 'Failed to find module info. ' + 238 `Failed to find module info with '${moduleId}' from the context information.` 239 ); 240 this.logger.printErrorAndExit(errInfo); 241 } 242 let metaInfo: Object = moduleInfo.meta; 243 if (!metaInfo) { 244 const errInfo: LogData = LogDataFactory.newInstance( 245 ErrorCode.ETS2BUNDLE_INTERNAL_META_INFO_NOT_FOUND, 246 ArkTSInternalErrorDescription, 247 'Failed to find meta info. ' + 248 `Failed to find meta info with '${moduleId}' from the module info.` 249 ); 250 this.logger.printErrorAndExit(errInfo); 251 } 252 253 const pkgParams = { 254 pkgName: metaInfo.pkgName, 255 pkgPath: metaInfo.pkgPath, 256 isRecordName: true, 257 }; 258 let recordName: string = getNormalizedOhmUrlByFilepath(moduleId, this.projectConfig, this.logger, pkgParams, 259 undefined); 260 compileEntries.add(recordName); 261 } 262 this.collectDeclarationFilesEntry(compileEntries, hspPkgNames); 263 compileContextInfo.compileEntries = Array.from(compileEntries); 264 if (this.projectConfig.updateVersionInfo) { 265 compileContextInfo.updateVersionInfo = this.projectConfig.updateVersionInfo; 266 } else if (this.projectConfig.pkgContextInfo) { 267 compileContextInfo.pkgContextInfo = this.projectConfig.pkgContextInfo; 268 } 269 // The bundleType is 'shared' in cross-app hsp. 270 if (this.projectConfig.bundleType === 'shared') { 271 compileContextInfo.needModifyRecord = true; 272 compileContextInfo.bundleName = this.projectConfig.bundleName; 273 } 274 fs.writeFileSync(compileContextInfoPath, JSON.stringify(compileContextInfo), 'utf-8'); 275 return compileContextInfoPath; 276 } 277 278 private collectDeclarationFilesEntry(compileEntries: Set<string>, hspPkgNames: Array<string>): void { 279 if (this.projectConfig.arkRouterMap) { 280 // Collect bytecode har's declaration files entries in router map, use 281 // by es2abc for dependency resolution. 282 this.collectRouterMapEntries(compileEntries, hspPkgNames); 283 } 284 if (this.projectConfig.declarationEntry) { 285 // Collect bytecode har's declaration files entries include dynamic import and workers, use 286 // by es2abc for dependency resolution. 287 this.projectConfig.declarationEntry.forEach((ohmurl) => { 288 let pkgName: string = transformOhmurlToPkgName(ohmurl); 289 if (!hspPkgNames.includes(pkgName)) { 290 let recordName: string = transformOhmurlToRecordName(ohmurl); 291 compileEntries.add(recordName); 292 } 293 }); 294 } 295 } 296 297 private collectRouterMapEntries(compileEntries: Set<string>, hspPkgNames: Array<string>): void { 298 this.projectConfig.arkRouterMap.forEach((router) => { 299 if (router.ohmurl) { 300 let pkgName: string = transformOhmurlToPkgName(router.ohmurl); 301 if (!hspPkgNames.includes(pkgName)) { 302 let recordName: string = transformOhmurlToRecordName(router.ohmurl); 303 compileEntries.add(recordName); 304 } 305 } 306 }); 307 } 308 309 prepareForCompilation(rollupObject: Object, parentEvent: CompileEvent): void { 310 const eventPrepareForCompilation = createAndStartEvent(parentEvent, 'preparation for compilation'); 311 this.collectModuleFileList(rollupObject, rollupObject.getModuleIds()); 312 this.removeCacheInfo(rollupObject); 313 stopEvent(eventPrepareForCompilation); 314 } 315 316 collectModuleFileList(module: Object, fileList: IterableIterator<string>): void { 317 const recordInfo = MemoryMonitor.recordStage(MemoryDefine.PKG_ENTRY_INFOS_MODULE_INFOS); 318 let moduleInfos: Map<String, ModuleInfo> = new Map<String, ModuleInfo>(); 319 let pkgEntryInfos: Map<String, PackageEntryInfo> = new Map<String, PackageEntryInfo>(); 320 for (const moduleId of fileList) { 321 if (isCommonJsPluginVirtualFile(moduleId) || !isCurrentProjectFiles(moduleId, this.projectConfig)) { 322 continue; 323 } 324 const moduleInfo: Object = module.getModuleInfo(moduleId); 325 if (moduleInfo.meta.isNodeEntryFile && !this.useNormalizedOHMUrl) { 326 this.getPackageEntryInfo(moduleId, moduleInfo.meta, pkgEntryInfos); 327 } 328 329 this.processModuleInfos(moduleId, moduleInfos, moduleInfo.meta); 330 } 331 if (!this.useNormalizedOHMUrl) { 332 this.getDynamicImportEntryInfo(pkgEntryInfos); 333 } 334 this.getNativeModuleEntryInfo(pkgEntryInfos); 335 this.moduleInfos = moduleInfos; 336 this.pkgEntryInfos = pkgEntryInfos; 337 MemoryMonitor.stopRecordStage(recordInfo); 338 } 339 340 private isUsingNormalizedOHMUrl(): boolean { 341 return !!this.projectConfig.useNormalizedOHMUrl; 342 } 343 344 private updatePkgEntryInfos(pkgEntryInfos: Map<String, PackageEntryInfo>, key: String, value: PackageEntryInfo): void { 345 if (!pkgEntryInfos.has(key)) { 346 pkgEntryInfos.set(key, new PackageEntryInfo(key, value)); 347 } 348 } 349 350 private getDynamicImportEntryInfo(pkgEntryInfos: Map<String, PackageEntryInfo>): void { 351 if (this.projectConfig.dynamicImportLibInfo) { 352 const REG_LIB_SO: RegExp = /lib(.+)\.so/; 353 for (const [pkgName, pkgInfo] of Object.entries(this.projectConfig.dynamicImportLibInfo)) { 354 if (REG_LIB_SO.test(pkgName)) { 355 let ohmurl: string = pkgName.replace(REG_LIB_SO, (_, libsoKey) => { 356 return `@app.${this.projectConfig.bundleName}/${this.projectConfig.moduleName}/${libsoKey}`; 357 }); 358 this.updatePkgEntryInfos(pkgEntryInfos, pkgName, ohmurl); 359 continue; 360 } 361 let hspOhmurl: string | undefined = getOhmUrlByExternalPackage(pkgName, this.projectConfig, undefined, this.logger, 362 this.useNormalizedOHMUrl); 363 if (hspOhmurl !== undefined) { 364 hspOhmurl = hspOhmurl.replace(/^@(\w+):(.*)/, '@$1.$2'); 365 this.updatePkgEntryInfos(pkgEntryInfos, pkgName, hspOhmurl); 366 continue; 367 } 368 const entryFile: string = pkgInfo.entryFilePath; 369 this.getPackageEntryInfo(entryFile, pkgInfo, pkgEntryInfos); 370 } 371 } 372 } 373 374 private getNativeModuleEntryInfo(pkgEntryInfos: Map<String, PackageEntryInfo>): void { 375 for (const item of NATIVE_MODULE) { 376 let key = '@' + item; 377 this.updatePkgEntryInfos(pkgEntryInfos, key, '@native.' + item); 378 } 379 } 380 381 private getPackageEntryInfo(filePath: string, metaInfo: Object, pkgEntryInfos: Map<String, PackageEntryInfo>): void { 382 if (metaInfo.isLocalDependency) { 383 const hostModulesInfo: Object = metaInfo.hostModulesInfo; 384 const pkgBuildPath: string = getOhmUrlByFilepath(filePath, this.projectConfig, this.logger, metaInfo.moduleName); 385 hostModulesInfo.forEach(hostModuleInfo => { 386 const hostDependencyName: string = hostModuleInfo.hostDependencyName; 387 const hostModuleName: string = hostModuleInfo.hostModuleName; 388 const pkgEntryPath: string = toUnixPath(path.join(`${PACKAGES}@${hostModuleName}`, hostDependencyName)); 389 if (!pkgEntryInfos.has(pkgEntryPath)) { 390 pkgEntryInfos.set(pkgEntryPath, new PackageEntryInfo(pkgEntryPath, pkgBuildPath)); 391 } 392 this.updatePkgEntryInfos(pkgEntryInfos, hostDependencyName, `@bundle.${pkgBuildPath}`); 393 }); 394 return; 395 } 396 397 if (!metaInfo.pkgPath) { 398 const errInfo: LogData = LogDataFactory.newInstance( 399 ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_MODULE_INFO_META_PKG_PATH, 400 ArkTSInternalErrorDescription, 401 `Failed to get ModuleInfo properties 'meta.pkgPath', moduleId: ${filePath}` 402 ); 403 this.logger.printErrorAndExit(errInfo); 404 } 405 const pkgPath: string = metaInfo.pkgPath; 406 let originPkgEntryPath: string = toUnixPath(filePath.replace(pkgPath, '')); 407 if (originPkgEntryPath.startsWith('/')) { 408 originPkgEntryPath = originPkgEntryPath.slice(1, originPkgEntryPath.length); 409 } 410 const pkgEntryPath: string = toUnixPath(this.getPkgModulesFilePkgName(pkgPath)); 411 let pkgBuildPath: string = path.join(pkgEntryPath, originPkgEntryPath); 412 pkgBuildPath = toUnixPath(pkgBuildPath.substring(0, pkgBuildPath.lastIndexOf('.'))); 413 if (!pkgEntryInfos.has(pkgEntryPath)) { 414 pkgEntryInfos.set(pkgEntryPath, new PackageEntryInfo(pkgEntryPath, pkgBuildPath)); 415 } 416 // create symlink path to actual path mapping in ohpm 417 if (this.projectConfig.packageDir == OH_MODULES && this.symlinkMap) { 418 const symlinkEntries: Object = Object.entries(this.symlinkMap); 419 for (const [actualPath, symlinkPaths] of symlinkEntries) { 420 if (actualPath === pkgPath) { 421 (<string[]>symlinkPaths).forEach((symlink: string) => { 422 const symlinkPkgEntryPath: string = toUnixPath(this.getPkgModulesFilePkgName(symlink)); 423 if (!pkgEntryInfos.has(symlinkPkgEntryPath)) { 424 pkgEntryInfos.set(symlinkPkgEntryPath, new PackageEntryInfo(symlinkPkgEntryPath, pkgEntryPath)); 425 } 426 }); 427 break; 428 } 429 } 430 } 431 } 432 433 private processModuleInfos(moduleId: string, moduleInfos: Map<String, ModuleInfo>, metaInfo?: Object): void { 434 switch (path.extname(moduleId)) { 435 case EXTNAME_ETS: { 436 const extName: string = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig, metaInfo) ? EXTNAME_JS : EXTNAME_TS; 437 this.addModuleInfoItem(moduleId, false, extName, metaInfo, moduleInfos, ETS); 438 break; 439 } 440 case EXTNAME_TS: { 441 const extName: string = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig, metaInfo) ? EXTNAME_JS : ''; 442 this.addModuleInfoItem(moduleId, false, extName, metaInfo, moduleInfos, TS); 443 break; 444 } 445 case EXTNAME_JS: 446 case EXTNAME_MJS: 447 case EXTNAME_CJS: { 448 const extName: string = (moduleId.endsWith(EXTNAME_MJS) || moduleId.endsWith(EXTNAME_CJS)) ? EXTNAME_JS : ''; 449 const isCommonJS: boolean = metaInfo && metaInfo.commonjs && metaInfo.commonjs.isCommonJS; 450 this.addModuleInfoItem(moduleId, isCommonJS, extName, metaInfo, moduleInfos, JS); 451 break; 452 } 453 case EXTNAME_JSON: { 454 this.addModuleInfoItem(moduleId, false, '', metaInfo, moduleInfos); 455 break; 456 } 457 default: 458 break; 459 } 460 } 461 462 private handleFileNameObfuscationInModuleInfo(sourceMapGenerator: SourceMapGenerator, isPackageModules: boolean, originalFilePath: string, filePath: string, 463 sourceFile: string) { 464 if (!enableObfuscateFileName(isPackageModules, this.projectConfig)) { 465 if (sourceMapGenerator.isNewSourceMaps()) { 466 sourceFile = sourceMapGenerator.genKey(originalFilePath); 467 } 468 return {filePath: filePath, sourceFile: sourceFile}; 469 } 470 471 // if release mode, enable obfuscation, enable filename obfuscation -> call mangleFilePath() 472 filePath = handleObfuscatedFilePath(originalFilePath, isPackageModules, this.projectConfig); 473 sourceFile = filePath.replace(toUnixPath(this.projectConfig.projectRootPath) + '/', ''); 474 475 if (sourceMapGenerator.isNewSourceMaps()) { 476 sourceFile = sourceMapGenerator.genKey(originalFilePath); // If the file name is obfuscated, meta info cannot be found. 477 if (!sourceMapGenerator.sourceMapKeyMappingForObf.get(sourceFile)) { 478 sourceMapGenerator.saveKeyMappingForObfFileName(originalFilePath); 479 } 480 // If the file name is obfuscated, the sourceFile needs to be updated. 481 sourceFile = sourceMapGenerator.sourceMapKeyMappingForObf.get(sourceFile); 482 } 483 return {filePath: filePath, sourceFile: sourceFile}; 484 } 485 486 private addModuleInfoItem(originalFilePath: string, isCommonJs: boolean, extName: string, 487 metaInfo: Object, moduleInfos: Map<String, ModuleInfo>, originSourceLang: string = ''): void { 488 const sourceMapGenerator: SourceMapGenerator = SourceMapGenerator.getInstance(); 489 const isPackageModules = isPackageModulesFile(originalFilePath, this.projectConfig); 490 let filePath: string = originalFilePath; 491 let sourceFile: string = filePath.replace(this.projectConfig.projectRootPath + path.sep, ''); 492 const isObfuscateEnabled: boolean = enableObfuscatedFilePathConfig(isPackageModules, this.projectConfig); 493 if (isObfuscateEnabled) { 494 const filePathAndSourceFile = this.handleFileNameObfuscationInModuleInfo(sourceMapGenerator, isPackageModules, originalFilePath, filePath, sourceFile); 495 filePath = filePathAndSourceFile.filePath; 496 sourceFile = filePathAndSourceFile.sourceFile; 497 } else { 498 if (sourceMapGenerator.isNewSourceMaps()) { 499 sourceFile = sourceMapGenerator.genKey(originalFilePath); 500 } 501 } 502 503 let moduleName: string = metaInfo.moduleName; 504 let recordName: string = ''; 505 let cacheFilePath: string = 506 this.genFileCachePath(filePath, this.projectConfig.projectRootPath, this.projectConfig.cachePath, metaInfo); 507 let packageName: string = ''; 508 509 if (this.useNormalizedOHMUrl) { 510 packageName = metaInfo.pkgName; 511 const pkgParams = { 512 pkgName: packageName, 513 pkgPath: metaInfo.pkgPath, 514 isRecordName: true, 515 }; 516 recordName = getNormalizedOhmUrlByFilepath(filePath, this.projectConfig, this.logger, pkgParams, undefined); 517 } else { 518 recordName = getOhmUrlByFilepath(filePath, this.projectConfig, this.logger, moduleName); 519 if (isPackageModules) { 520 packageName = this.getPkgModulesFilePkgName(metaInfo.pkgPath); 521 } else { 522 packageName = 523 metaInfo.isLocalDependency ? moduleName : getPackageInfo(this.projectConfig.aceModuleJsonPath)[1]; 524 } 525 } 526 527 if (extName.length !== 0) { 528 cacheFilePath = changeFileExtension(cacheFilePath, extName); 529 } 530 531 cacheFilePath = toUnixPath(cacheFilePath); 532 recordName = toUnixPath(recordName); 533 packageName = toUnixPath(packageName); 534 if (!sourceMapGenerator.isNewSourceMaps()) { 535 sourceFile = cacheFilePath.replace(toUnixPath(this.projectConfig.projectRootPath) + '/', ''); 536 } 537 filePath = toUnixPath(filePath); 538 539 moduleInfos.set(filePath, new ModuleInfo(filePath, cacheFilePath, isCommonJs, recordName, sourceFile, packageName, 540 originSourceLang)); 541 } 542 543 generateEs2AbcCmd() { 544 const fileThreads = getEs2abcFileThreadNumber(); 545 this.cmdArgs.push(`"@${this.filesInfoPath}"`); 546 if (!this.byteCodeHar) { 547 this.cmdArgs.push('--npm-module-entry-list'); 548 this.cmdArgs.push(`"${this.npmEntriesInfoPath}"`); 549 } 550 this.cmdArgs.push('--output'); 551 this.cmdArgs.push(`"${this.moduleAbcPath}"`); 552 this.cmdArgs.push('--file-threads'); 553 this.cmdArgs.push(`"${fileThreads}"`); 554 this.cmdArgs.push('--merge-abc'); 555 this.cmdArgs.push(`"--target-api-version=${this.projectConfig.compatibleSdkVersion}"`); 556 if (this.projectConfig.compatibleSdkVersionStage) { 557 this.cmdArgs.push(`"--target-api-sub-version=${this.projectConfig.compatibleSdkVersionStage}"`); 558 } 559 // when enable branch elimination and bytecode obfuscation will crash 560 if (this.arkConfig.isBranchElimination && !BytecodeObfuscator.enable) { 561 this.cmdArgs.push('--branch-elimination'); 562 } 563 if (this.projectConfig.transformLib) { 564 this.cmdArgs.push(`--transform-lib`); 565 this.cmdArgs.push(`"${this.projectConfig.transformLib}"`); 566 } 567 if (this.compileContextInfoPath !== undefined) { 568 this.cmdArgs.push(`--compile-context-info`); 569 this.cmdArgs.push(`"${this.compileContextInfoPath}"`); 570 } 571 if (this.abcPaths.length > 0 && !this.byteCodeHar) { 572 this.cmdArgs.push('--enable-abc-input'); 573 this.cmdArgs.push('--remove-redundant-file'); 574 } 575 if (!this.arkConfig.optTryCatchFunc) { 576 this.cmdArgs.push('--opt-try-catch-func=false'); 577 } 578 if (this.projectConfig.allowEtsAnnotations) { 579 this.cmdArgs.push('--enable-annotations'); 580 } 581 // Add column numbers for bytecode instructions in release mode 582 if (!Object.prototype.hasOwnProperty.call(this.projectConfig, 'enableColumnNum') || 583 this.projectConfig.enableColumnNum) { 584 this.cmdArgs.push('--enable-release-column'); 585 } 586 if (isNeedPerformanceDotting(this.projectConfig)) { 587 this.cmdArgs.push(`--perf-file=${this.perfReportPath}`); 588 } 589 } 590 591 addCacheFileArgs() { 592 this.cmdArgs.push('--cache-file'); 593 this.cmdArgs.push(`"@${this.cacheFilePath}"`); 594 } 595 596 private generateCompileFilesInfo(includeByteCodeHarInfo: boolean): void { 597 let filesInfo: string = ''; 598 const cacheFilePathSet: Set<string> = new Set(); 599 this.moduleInfos.forEach((info) => { 600 const moduleType: string = info.isCommonJs ? COMMONJS : ESM; 601 const isSharedModule: boolean = sharedModuleSet.has(info.filePath); 602 if (cacheFilePathSet.has(info.cacheFilePath)) { 603 const errInfo: LogData = LogDataFactory.newInstance( 604 ErrorCode.ETS2BUNDLE_EXTERNAL_DUPLICATE_FILE_NAMES_ERROR, 605 ArkTSErrorDescription, 606 `Failed to generate filesInfo.\n 607 File ${info.filePath} with the same name in module ${info.packageName}.\n 608 Please modify the names of these files with the same name in the source code.` 609 ); 610 this.logger.printError(errInfo); 611 } 612 cacheFilePathSet.add(info.cacheFilePath); 613 filesInfo += `${info.cacheFilePath};${info.recordName};${moduleType};${info.sourceFile};${info.packageName};` + 614 `${isSharedModule};${info.originSourceLang}\n`; 615 }); 616 if (includeByteCodeHarInfo) { 617 Object.entries(this.projectConfig.byteCodeHarInfo).forEach(([pkgName, abcInfo]) => { 618 // es2abc parses file path and pkgName according to the file extension .abc. 619 // Accurate version replacement requires 'pkgName' to es2abc. 620 const abcPath: string = toUnixPath(abcInfo.abcPath); 621 filesInfo += `${abcPath};;;;${pkgName};\n`; 622 }); 623 } 624 625 fs.writeFileSync(this.filesInfoPath, filesInfo, 'utf-8'); 626 } 627 628 private generateNpmEntriesInfo() { 629 let entriesInfo: string = ''; 630 for (const value of this.pkgEntryInfos.values()) { 631 entriesInfo += `${value.pkgEntryPath}:${value.pkgBuildPath}\n`; 632 } 633 fs.writeFileSync(this.npmEntriesInfoPath, entriesInfo, 'utf-8'); 634 } 635 636 private generateAbcCacheFilesInfo(): void { 637 let abcCacheFilesInfo: string = ''; 638 639 // generate source file cache 640 this.moduleInfos.forEach((info) => { 641 let abcCacheFilePath: string = changeFileExtension(info.cacheFilePath, EXTNAME_PROTO_BIN); 642 abcCacheFilesInfo += `${info.cacheFilePath};${abcCacheFilePath}\n`; 643 }); 644 645 // generate npm entries cache 646 let npmEntriesCacheFilePath: string = changeFileExtension(this.npmEntriesInfoPath, EXTNAME_PROTO_BIN); 647 abcCacheFilesInfo += `${this.npmEntriesInfoPath};${npmEntriesCacheFilePath}\n`; 648 if (this.projectConfig.cacheBytecodeHar) { 649 this.abcPaths.forEach((abcPath) => { 650 let abcCacheFilePath: string = this.genAbcCacheFilePath(abcPath); 651 mkdirsSync(path.dirname(abcCacheFilePath)); 652 abcCacheFilesInfo += `${abcPath};${abcCacheFilePath}\n`; 653 }); 654 } 655 fs.writeFileSync(this.cacheFilePath, abcCacheFilesInfo, 'utf-8'); 656 } 657 658 // Generate cache file path for bytecode har 659 private genAbcCacheFilePath(abcPath: string): string { 660 /** 661 * The projectTopDir is the path of the main project, the projectRootPath is the project path to which it belongs, 662 * and the path of bytecode har is within the main project. Therefore, the projectTopDir is used to intercept the 663 * relative path of bytecodehar. 664 */ 665 let relativeAbcPath: string = abcPath.replace(toUnixPath(this.projectConfig.projectTopDir), ''); 666 let tempPath: string = path.join(this.projectConfig.cachePath, relativeAbcPath); 667 return changeFileExtension(tempPath, EXTNAME_PROTO_BIN); 668 } 669 670 genDescriptionsForMergedEs2abc(includeByteCodeHarInfo: boolean, parentEvent: CompileEvent): void { 671 const eventEenDescriptionsForMergedEs2abc = createAndStartEvent(parentEvent, 'generate descriptions for merged es2abc'); 672 this.generateCompileFilesInfo(includeByteCodeHarInfo); 673 if (!this.byteCodeHar) { 674 this.generateNpmEntriesInfo(); 675 } 676 this.generateAbcCacheFilesInfo(); 677 stopEvent(eventEenDescriptionsForMergedEs2abc); 678 } 679 680 generateMergedAbcOfEs2Abc(parentEvent: CompileEvent): void { 681 // collect data error from subprocess 682 let logDataList: Object[] = []; 683 let errMsg: string = ''; 684 685 const genAbcCmd: string = this.cmdArgs.join(' '); 686 let eventGenAbc: CompileEvent; 687 try { 688 const child = this.triggerAsync(() => { 689 eventGenAbc = createAndStartEvent(parentEvent, 'generate merged abc by es2abc (async)', true); 690 return childProcess.exec(genAbcCmd, { windowsHide: true }); 691 }); 692 child.on('close', (code: any) => { 693 if (code === SUCCESS) { 694 stopEvent(eventGenAbc, true); 695 this.processAotIfNeeded(); 696 processExternalEvents(this.projectConfig, ExternalEventType.ES2ABC, { parentEvent: eventGenAbc, filePath: this.perfReportPath }); 697 BytecodeObfuscator.enable && BytecodeObfuscator.getInstance().execute(this.moduleInfos); 698 this.triggerEndSignal(); 699 return; 700 } 701 for (const logData of logDataList) { 702 this.logger.printError(logData); 703 } 704 if (errMsg !== '') { 705 this.logger.error(`Error Message: ${errMsg}`); 706 } 707 const errInfo: LogData = LogDataFactory.newInstance( 708 ErrorCode.ETS2BUNDLE_EXTERNAL_ES2ABC_EXECUTION_FAILED, 709 ArkTSErrorDescription, 710 'Failed to execute es2abc.', 711 '', 712 ["Please refer to es2abc's error codes."] 713 ); 714 this.logger.printErrorAndExit(errInfo); 715 }); 716 717 child.on('error', (err: any) => { 718 stopEvent(eventGenAbc, true); 719 const errInfo: LogData = LogDataFactory.newInstance( 720 ErrorCode.ETS2BUNDLE_INTERNAL_ES2ABC_SUBPROCESS_START_FAILED, 721 ArkTSInternalErrorDescription, 722 `Failed to initialize or launch the es2abc process. ${err.toString()}` 723 ); 724 this.logger.printErrorAndExit(errInfo); 725 }); 726 727 child.stderr.on('data', (data: any) => { 728 const logData = LogDataFactory.newInstanceFromEs2AbcError(data.toString()); 729 if (logData) { 730 logDataList.push(logData); 731 } else { 732 errMsg += data.toString(); 733 } 734 }); 735 736 } catch (e) { 737 const errInfo: LogData = LogDataFactory.newInstance( 738 ErrorCode.ETS2BUNDLE_INTERNAL_EXECUTE_ES2ABC_WITH_ASYNC_HANDLER_FAILED, 739 ArkTSInternalErrorDescription, 740 `Failed to execute es2abc with async handler. ${e.toString()}` 741 ); 742 this.logger.printErrorAndExit(errInfo); 743 } 744 } 745 746 filterModulesByHashJson() { 747 if (this.hashJsonFilePath.length === 0 || !fs.existsSync(this.hashJsonFilePath)) { 748 for (const key of this.moduleInfos.keys()) { 749 this.filterModuleInfos.set(key, this.moduleInfos.get(key)); 750 } 751 return; 752 } 753 754 let updatedJsonObject: Object = {}; 755 let jsonObject: Object = {}; 756 let jsonFile: string = ''; 757 758 if (fs.existsSync(this.hashJsonFilePath)) { 759 jsonFile = fs.readFileSync(this.hashJsonFilePath).toString(); 760 jsonObject = JSON.parse(jsonFile); 761 this.filterModuleInfos = new Map<string, ModuleInfo>(); 762 for (const [key, value] of this.moduleInfos) { 763 const cacheFilePath: string = value.cacheFilePath; 764 const cacheProtoFilePath: string = changeFileExtension(cacheFilePath, EXTNAME_PROTO_BIN); 765 if (!fs.existsSync(cacheFilePath)) { 766 this.logger.throwArkTsCompilerError( 767 `ArkTS:INTERNAL ERROR: Failed to get module cache abc from ${cacheFilePath} in incremental build.` + 768 'Please try to rebuild the project.'); 769 } 770 if (fs.existsSync(cacheProtoFilePath)) { 771 const hashCacheFileContentData: string = toHashData(cacheFilePath); 772 const hashProtoFileContentData: string = toHashData(cacheProtoFilePath); 773 if (jsonObject[cacheFilePath] === hashCacheFileContentData && 774 jsonObject[cacheProtoFilePath] === hashProtoFileContentData) { 775 updatedJsonObject[cacheFilePath] = cacheFilePath; 776 updatedJsonObject[cacheProtoFilePath] = cacheProtoFilePath; 777 continue; 778 } 779 } 780 this.filterModuleInfos.set(key, value); 781 } 782 } 783 784 this.hashJsonObject = updatedJsonObject; 785 } 786 787 getSplittedModulesByNumber() { 788 const result: any = []; 789 if (this.filterModuleInfos.size < this.workerNumber) { 790 for (const value of this.filterModuleInfos.values()) { 791 result.push([value]); 792 } 793 return result; 794 } 795 796 for (let i = 0; i < this.workerNumber; ++i) { 797 result.push([]); 798 } 799 800 let pos: number = 0; 801 for (const value of this.filterModuleInfos.values()) { 802 const chunk = pos % this.workerNumber; 803 result[chunk].push(value); 804 pos++; 805 } 806 807 return result; 808 } 809 810 invokeTs2AbcWorkersToGenProto(splittedModules) { 811 let ts2abcCmdArgs: string[] = this.cmdArgs.slice(0); 812 ts2abcCmdArgs.push('--output-proto'); 813 ts2abcCmdArgs.push('--merge-abc'); 814 ts2abcCmdArgs.push('--input-file'); 815 if (isMasterOrPrimary()) { 816 this.setupCluster(cluster); 817 this.workerNumber = splittedModules.length; 818 for (let i = 0; i < this.workerNumber; ++i) { 819 const sn: number = i + 1; 820 const workerFileName: string = `${FILESINFO}_${sn}${EXTNAME_TXT}`; 821 const workerData: Object = { 822 inputs: JSON.stringify(splittedModules[i]), 823 cmd: ts2abcCmdArgs.join(' '), 824 workerFileName: workerFileName, 825 mode: ESMODULE, 826 cachePath: this.projectConfig.cachePath 827 }; 828 this.triggerAsync(() => { 829 const worker: Object = cluster.fork(workerData); 830 worker.on('message', (errorMsg) => { 831 this.logger.error(red, errorMsg.data.toString(), reset); 832 this.logger.throwArkTsCompilerError('ArkTS:ERROR Failed to execute ts2abc.'); 833 }); 834 }); 835 } 836 } 837 } 838 839 processTs2abcWorkersToGenAbc() { 840 this.generateNpmEntriesInfo(); 841 let workerCount: number = 0; 842 if (isMasterOrPrimary()) { 843 cluster.on('exit', (worker, code, signal) => { 844 if (code === FAIL) { 845 this.logger.throwArkTsCompilerError('ArkTS:ERROR Failed to execute ts2abc'); 846 } 847 workerCount++; 848 if (workerCount === this.workerNumber) { 849 this.generateNpmEntryToGenProto(); 850 this.generateProtoFilesInfo(); 851 this.mergeProtoToAbc(); 852 this.processAotIfNeeded(); 853 this.afterCompilationProcess(); 854 } 855 this.triggerEndSignal(); 856 }); 857 if (this.workerNumber === 0) { 858 // process aot for no source file changed. 859 this.processAotIfNeeded(); 860 } 861 } 862 } 863 864 private processAotIfNeeded(): void { 865 if (!needAotCompiler(this.projectConfig)) { 866 return; 867 } 868 let faultHandler: FaultHandler = ((error: string) => { this.logger.throwArkTsCompilerError(error); }); 869 generateAot(this.arkConfig.arkRootPath, this.moduleAbcPath, this.projectConfig, this.logger, faultHandler); 870 } 871 872 private genFileCachePath(filePath: string, projectRootPath: string, cachePath: string, metaInfo: Object): string { 873 filePath = toUnixPath(filePath); 874 let sufStr: string = ''; 875 if (metaInfo) { 876 if (metaInfo.isLocalDependency) { 877 sufStr = filePath.replace(toUnixPath(metaInfo.belongModulePath), metaInfo.moduleName); 878 } else { 879 sufStr = filePath.replace(toUnixPath(metaInfo.belongProjectPath), ''); 880 } 881 } else { 882 sufStr = filePath.replace(toUnixPath(projectRootPath), ''); 883 } 884 const output: string = path.join(cachePath, sufStr); 885 return output; 886 } 887 888 private getPkgModulesFilePkgName(pkgPath: string) { 889 pkgPath = toUnixPath(pkgPath); 890 const packageDir: string = this.projectConfig.packageDir; 891 const projectRootPath = toUnixPath(this.projectConfig.projectRootPath); 892 const projectPkgModulesPath: string = toUnixPath(path.join(projectRootPath, packageDir)); 893 let pkgName: string = ''; 894 if (pkgPath.includes(projectPkgModulesPath)) { 895 pkgName = path.join(PACKAGES, pkgPath.replace(projectPkgModulesPath, '')); 896 } else { 897 for (const key in this.projectConfig.modulePathMap) { 898 const value: string = this.projectConfig.modulePathMap[key]; 899 const fakeModulePkgModulesPath: string = toUnixPath(path.resolve(value, packageDir)); 900 if (pkgPath.indexOf(fakeModulePkgModulesPath) !== -1) { 901 const tempFilePath: string = pkgPath.replace(projectRootPath, ''); 902 pkgName = path.join(`${PACKAGES}@${key}`, 903 tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1)); 904 break; 905 } 906 } 907 } 908 909 return pkgName.replace(new RegExp(packageDir, 'g'), PACKAGES); 910 } 911 912 private generateProtoFilesInfo() { 913 validateFilePathLength(this.protoFilePath, this.logger); 914 mkdirsSync(path.dirname(this.protoFilePath)); 915 let protoFilesInfo: string = ''; 916 const sortModuleInfos: Object = new Map([...this.moduleInfos].sort()); 917 for (const value of sortModuleInfos.values()) { 918 const cacheProtoPath: string = changeFileExtension(value.cacheFilePath, EXTNAME_PROTO_BIN); 919 protoFilesInfo += `${toUnixPath(cacheProtoPath)}\n`; 920 } 921 if (this.pkgEntryInfos.size > 0) { 922 protoFilesInfo += `${toUnixPath(this.npmEntriesProtoFilePath)}\n`; 923 } 924 fs.writeFileSync(this.protoFilePath, protoFilesInfo, 'utf-8'); 925 } 926 927 private mergeProtoToAbc() { 928 mkdirsSync(this.projectConfig.aceModuleBuild); 929 const cmd: string = `"${this.arkConfig.mergeAbcPath}" --input "@${this.protoFilePath}" --outputFilePath "${ 930 this.projectConfig.aceModuleBuild}" --output ${MODULES_ABC} --suffix protoBin`; 931 try { 932 childProcess.execSync(cmd, { windowsHide: true }); 933 } catch (e) { 934 this.logger.throwArkTsCompilerError('ArkTS:INTERNAL ERROR: Failed to merge proto file to abc.\n' + 935 'Error message:' + e.toString()); 936 } 937 } 938 939 private afterCompilationProcess() { 940 this.writeHashJson(); 941 } 942 943 private writeHashJson() { 944 if (this.hashJsonFilePath.length === 0) { 945 return; 946 } 947 948 for (const value of this.filterModuleInfos.values()) { 949 const cacheFilePath: string = value.cacheFilePath; 950 const cacheProtoFilePath: string = changeFileExtension(cacheFilePath, EXTNAME_PROTO_BIN); 951 if (!fs.existsSync(cacheFilePath) || !fs.existsSync(cacheProtoFilePath)) { 952 this.logger.throwArkTsCompilerError( 953 `ArkTS:ERROR ${cacheFilePath} or ${cacheProtoFilePath} is lost` 954 ); 955 } 956 const hashCacheFileContentData: string = toHashData(cacheFilePath); 957 const hashCacheProtoContentData: string = toHashData(cacheProtoFilePath); 958 this.hashJsonObject[cacheFilePath] = hashCacheFileContentData; 959 this.hashJsonObject[cacheProtoFilePath] = hashCacheProtoContentData; 960 } 961 962 fs.writeFileSync(this.hashJsonFilePath, JSON.stringify(this.hashJsonObject)); 963 } 964 965 private generateNpmEntryToGenProto() { 966 if (this.pkgEntryInfos.size <= 0) { 967 return; 968 } 969 mkdirsSync(path.dirname(this.npmEntriesProtoFilePath)); 970 const cmd: string = `"${this.arkConfig.js2abcPath}" --compile-npm-entries "${ 971 this.npmEntriesInfoPath}" "${this.npmEntriesProtoFilePath}"`; 972 try { 973 childProcess.execSync(cmd, { windowsHide: true }); 974 } catch (e) { 975 this.logger.throwArkTsCompilerError('ArkTS:ERROR failed to generate npm proto file to abc. Error message: ' + e.toString()); 976 } 977 } 978 979 private removeCompilationCache(): void { 980 if (isEs2Abc(this.projectConfig)) { 981 this.removeEs2abcCompilationCache(); 982 } else if (isTs2Abc(this.projectConfig)) { 983 this.removeTs2abcCompilationCache(); 984 } else { 985 const errInfo: LogData = LogDataFactory.newInstance( 986 ErrorCode.ETS2BUNDLE_INTERNAL_INVALID_COMPILE_MODE, 987 ArkTSInternalErrorDescription, 988 'Invalid compilation mode. ' + 989 `ProjectConfig.pandaMode should be either ${TS2ABC} or ${ES2ABC}.` 990 ); 991 this.logger.printErrorAndExit(errInfo); 992 } 993 } 994 995 private removeEs2abcCompilationCache(): void { 996 if (fs.existsSync(this.cacheFilePath)) { 997 const data: string = fs.readFileSync(this.cacheFilePath, 'utf-8'); 998 const lines: string[] = data.split(/\r?\n/); 999 lines.forEach(line => { 1000 const [, abcCacheFilePath]: string[] = line.split(';'); 1001 if (fs.existsSync(abcCacheFilePath)) { 1002 fs.unlinkSync(abcCacheFilePath); 1003 } 1004 }); 1005 fs.unlinkSync(this.cacheFilePath); 1006 } 1007 } 1008 1009 private removeTs2abcCompilationCache(): void { 1010 if (fs.existsSync(this.hashJsonFilePath)) { 1011 fs.unlinkSync(this.hashJsonFilePath); 1012 } 1013 if (fs.existsSync(this.protoFilePath)) { 1014 const data: string = fs.readFileSync(this.protoFilePath, 'utf-8'); 1015 const lines: string[] = data.split(/\r?\n/); 1016 lines.forEach(line => { 1017 const protoFilePath: string = line; 1018 if (fs.existsSync(protoFilePath)) { 1019 fs.unlinkSync(protoFilePath); 1020 } 1021 }); 1022 fs.unlinkSync(this.protoFilePath); 1023 } 1024 } 1025} 1026