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