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