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 ESM, 24 ESMODULE, 25 EXTNAME_CJS, 26 EXTNAME_ETS, 27 EXTNAME_JS, 28 EXTNAME_JSON, 29 EXTNAME_MJS, 30 EXTNAME_PROTO_BIN, 31 EXTNAME_TS, 32 EXTNAME_TXT, 33 FAIL, 34 FILESINFO, 35 FILESINFO_TXT, 36 MAX_WORKER_NUMBER, 37 MODULES_ABC, 38 MODULES_CACHE, 39 NPM_ENTRIES_PROTO_BIN, 40 NPMENTRIES_TXT, 41 OH_MODULES, 42 PACKAGES, 43 PROTO_FILESINFO_TXT, 44 PROTOS, 45 red, 46 reset, 47 SOURCEMAPS, 48 SOURCEMAPS_JSON, 49 WIDGETS_ABC, 50 TS2ABC, 51 ES2ABC 52} from '../common/ark_define'; 53import { 54 needAotCompiler, 55 isMasterOrPrimary, 56 isAotMode, 57 isDebug 58} from '../utils'; 59import { CommonMode } from '../common/common_mode'; 60import { newSourceMaps } from '../transform'; 61import { 62 changeFileExtension, 63 getEs2abcFileThreadNumber, 64 isCommonJsPluginVirtualFile, 65 isCurrentProjectFiles, 66 shouldETSOrTSFileTransformToJS 67} from '../utils'; 68import { 69 isPackageModulesFile, 70 mkdirsSync, 71 toUnixPath, 72 toHashData, 73 validateFilePathLength 74} from '../../../utils'; 75import { 76 getPackageInfo, 77 getOhmUrlByFilepath, 78 getOhmUrlByHarName, 79 isTs2Abc, 80 isEs2Abc, 81 createAndStartEvent, 82 stopEvent 83} from '../../../ark_utils'; 84import { 85 generateAot, 86 generateBuiltinAbc, 87 FaultHandler 88} from '../../../gen_aot'; 89import { 90 NATIVE_MODULE 91} from '../../../pre_define'; 92 93export class ModuleInfo { 94 filePath: string; 95 cacheFilePath: string; 96 recordName: string; 97 isCommonJs: boolean; 98 sourceFile: string; 99 packageName: string; 100 101 constructor(filePath: string, cacheFilePath: string, isCommonJs: boolean, recordName: string, sourceFile: string, 102 packageName: string 103 ) { 104 this.filePath = filePath; 105 this.cacheFilePath = cacheFilePath; 106 this.recordName = recordName; 107 this.isCommonJs = isCommonJs; 108 this.sourceFile = sourceFile; 109 this.packageName = packageName; 110 } 111} 112 113export class PackageEntryInfo { 114 pkgEntryPath: string; 115 pkgBuildPath: string; 116 constructor(pkgEntryPath: string, pkgBuildPath: string) { 117 this.pkgEntryPath = pkgEntryPath; 118 this.pkgBuildPath = pkgBuildPath; 119 } 120} 121 122export class ModuleMode extends CommonMode { 123 moduleInfos: Map<String, ModuleInfo>; 124 pkgEntryInfos: Map<String, PackageEntryInfo>; 125 hashJsonObject: Object; 126 cacheSourceMapObject: Object; 127 filesInfoPath: string; 128 npmEntriesInfoPath: string; 129 moduleAbcPath: string; 130 sourceMapPath: string; 131 cacheFilePath: string; 132 cacheSourceMapPath: string; 133 workerNumber: number; 134 npmEntriesProtoFilePath: string; 135 protoFilePath: string; 136 filterModuleInfos: Map<String, ModuleInfo>; 137 symlinkMap: Object; 138 139 constructor(rollupObject: Object) { 140 super(rollupObject); 141 this.moduleInfos = new Map<String, ModuleInfo>(); 142 this.pkgEntryInfos = new Map<String, PackageEntryInfo>(); 143 this.hashJsonObject = {}; 144 this.cacheSourceMapObject = {}; 145 this.filesInfoPath = path.join(this.projectConfig.cachePath, FILESINFO_TXT); 146 this.npmEntriesInfoPath = path.join(this.projectConfig.cachePath, NPMENTRIES_TXT); 147 const outPutABC: string = this.projectConfig.widgetCompile ? WIDGETS_ABC : MODULES_ABC; 148 this.moduleAbcPath = path.join(this.projectConfig.aceModuleBuild, outPutABC); 149 this.sourceMapPath = this.arkConfig.isDebug ? path.join(this.projectConfig.aceModuleBuild, SOURCEMAPS) : 150 path.join(this.projectConfig.cachePath, SOURCEMAPS); 151 this.cacheFilePath = path.join(this.projectConfig.cachePath, MODULES_CACHE); 152 this.cacheSourceMapPath = path.join(this.projectConfig.cachePath, SOURCEMAPS_JSON); 153 this.workerNumber = MAX_WORKER_NUMBER; 154 this.npmEntriesProtoFilePath = path.join(this.projectConfig.cachePath, PROTOS, NPM_ENTRIES_PROTO_BIN); 155 this.protoFilePath = path.join(this.projectConfig.cachePath, PROTOS, PROTO_FILESINFO_TXT); 156 this.hashJsonObject = {}; 157 this.filterModuleInfos = new Map<String, ModuleInfo>(); 158 this.symlinkMap = rollupObject.share.symlinkMap; 159 } 160 161 prepareForCompilation(rollupObject: Object, parentEvent: Object): void { 162 const eventPrepareForCompilation = createAndStartEvent(parentEvent, 'preparation for compilation'); 163 this.collectModuleFileList(rollupObject, rollupObject.getModuleIds()); 164 this.removeCacheInfo(rollupObject); 165 stopEvent(eventPrepareForCompilation); 166 } 167 168 collectModuleFileList(module: Object, fileList: IterableIterator<string>): void { 169 let moduleInfos: Map<String, ModuleInfo> = new Map<String, ModuleInfo>(); 170 let pkgEntryInfos: Map<String, PackageEntryInfo> = new Map<String, PackageEntryInfo>(); 171 for (const moduleId of fileList) { 172 if (isCommonJsPluginVirtualFile(moduleId) || !isCurrentProjectFiles(moduleId, this.projectConfig)) { 173 continue; 174 } 175 const moduleInfo: Object = module.getModuleInfo(moduleId); 176 if (moduleInfo['meta']['isNodeEntryFile']) { 177 this.getPackageEntryInfo(moduleId, moduleInfo['meta'], pkgEntryInfos); 178 } 179 180 this.processModuleInfos(moduleId, moduleInfos, moduleInfo['meta']); 181 } 182 this.getDynamicImportEntryInfo(pkgEntryInfos); 183 this.getNativeModuleEntryInfo(pkgEntryInfos); 184 this.moduleInfos = moduleInfos; 185 this.pkgEntryInfos = pkgEntryInfos; 186 } 187 188 private updatePkgEntryInfos(pkgEntryInfos: Map<String, PackageEntryInfo>, key: String, value: PackageEntryInfo): void { 189 if (!pkgEntryInfos.has(key)) { 190 pkgEntryInfos.set(key, new PackageEntryInfo(key, value)); 191 } 192 } 193 194 private getDynamicImportEntryInfo(pkgEntryInfos: Map<String, PackageEntryInfo>): void { 195 if (this.projectConfig.dynamicImportLibInfo) { 196 const REG_LIB_SO: RegExp = /lib(.+)\.so/; 197 for (const [pkgName, pkgInfo] of Object.entries(this.projectConfig.dynamicImportLibInfo)) { 198 if (REG_LIB_SO.test(pkgName)) { 199 let ohmurl: string = pkgName.replace(REG_LIB_SO, (_, libsoKey) => { 200 return `@app.${this.projectConfig.bundleName}/${this.projectConfig.moduleName}/${libsoKey}`; 201 }); 202 this.updatePkgEntryInfos(pkgEntryInfos, pkgName, ohmurl); 203 continue; 204 } 205 let hspOhmurl: string | undefined = getOhmUrlByHarName(pkgName, this.projectConfig); 206 if (hspOhmurl !== undefined) { 207 hspOhmurl = hspOhmurl.replace(/^@(\w+):(.*)/, '@$1.$2'); 208 this.updatePkgEntryInfos(pkgEntryInfos, pkgName, hspOhmurl); 209 continue; 210 } 211 const entryFile: string = pkgInfo.entryFilePath; 212 this.getPackageEntryInfo(entryFile, pkgInfo, pkgEntryInfos); 213 } 214 } 215 } 216 217 private getNativeModuleEntryInfo(pkgEntryInfos: Map<String, PackageEntryInfo>): void { 218 for (const item of NATIVE_MODULE) { 219 let key = '@' + item; 220 this.updatePkgEntryInfos(pkgEntryInfos, key, '@native.' + item); 221 } 222 } 223 224 private getPackageEntryInfo(filePath: string, metaInfo: Object, pkgEntryInfos: Map<String, PackageEntryInfo>): void { 225 if (metaInfo['isLocalDependency']) { 226 const hostModulesInfo: Object = metaInfo.hostModulesInfo; 227 const pkgBuildPath: string = getOhmUrlByFilepath(filePath, this.projectConfig, this.logger, metaInfo['moduleName']); 228 hostModulesInfo.forEach(hostModuleInfo => { 229 const hostDependencyName: string = hostModuleInfo['hostDependencyName']; 230 const hostModuleName: string = hostModuleInfo['hostModuleName']; 231 const pkgEntryPath: string = toUnixPath(path.join(`${PACKAGES}@${hostModuleName}`, hostDependencyName)); 232 if (!pkgEntryInfos.has(pkgEntryPath)) { 233 pkgEntryInfos.set(pkgEntryPath, new PackageEntryInfo(pkgEntryPath, pkgBuildPath)); 234 } 235 this.updatePkgEntryInfos(pkgEntryInfos, hostDependencyName, `@bundle.${pkgBuildPath}`); 236 }); 237 return; 238 } 239 240 if (!metaInfo['pkgPath']) { 241 this.logger.debug("Failed to get 'pkgPath' from metaInfo. File: ", filePath); 242 return; 243 } 244 const pkgPath: string = metaInfo['pkgPath']; 245 let originPkgEntryPath: string = toUnixPath(filePath.replace(pkgPath, '')); 246 if (originPkgEntryPath.startsWith('/')) { 247 originPkgEntryPath = originPkgEntryPath.slice(1, originPkgEntryPath.length); 248 } 249 const pkgEntryPath: string = toUnixPath(this.getPkgModulesFilePkgName(pkgPath)); 250 let pkgBuildPath: string = path.join(pkgEntryPath, originPkgEntryPath); 251 pkgBuildPath = toUnixPath(pkgBuildPath.substring(0, pkgBuildPath.lastIndexOf('.'))); 252 if (!pkgEntryInfos.has(pkgEntryPath)) { 253 pkgEntryInfos.set(pkgEntryPath, new PackageEntryInfo(pkgEntryPath, pkgBuildPath)); 254 } 255 // create symlink path to actual path mapping in ohpm 256 if (this.projectConfig.packageDir == OH_MODULES && this.symlinkMap) { 257 const symlinkEntries: Object = Object.entries(this.symlinkMap); 258 for (const [actualPath, symlinkPaths] of symlinkEntries) { 259 if (actualPath === pkgPath) { 260 (<string[]>symlinkPaths).forEach((symlink: string) => { 261 const symlinkPkgEntryPath: string = toUnixPath(this.getPkgModulesFilePkgName(symlink)); 262 if (!pkgEntryInfos.has(symlinkPkgEntryPath)) { 263 pkgEntryInfos.set(symlinkPkgEntryPath, new PackageEntryInfo(symlinkPkgEntryPath, pkgEntryPath)); 264 } 265 }); 266 break; 267 } 268 } 269 } 270 } 271 272 private processModuleInfos(moduleId: string, moduleInfos: Map<String, ModuleInfo>, metaInfo?: Object): void { 273 switch (path.extname(moduleId)) { 274 case EXTNAME_ETS: { 275 const extName: string = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig) ? EXTNAME_JS : EXTNAME_TS; 276 this.addModuleInfoItem(moduleId, false, extName, metaInfo, moduleInfos); 277 break; 278 } 279 case EXTNAME_TS: { 280 const extName: string = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig) ? EXTNAME_JS : ''; 281 this.addModuleInfoItem(moduleId, false, extName, metaInfo, moduleInfos); 282 break; 283 } 284 case EXTNAME_JS: 285 case EXTNAME_MJS: 286 case EXTNAME_CJS: { 287 const extName: string = (moduleId.endsWith(EXTNAME_MJS) || moduleId.endsWith(EXTNAME_CJS)) ? EXTNAME_JS : ''; 288 const isCommonJS: boolean = metaInfo && metaInfo['commonjs'] && metaInfo['commonjs']['isCommonJS']; 289 this.addModuleInfoItem(moduleId, isCommonJS, extName, metaInfo, moduleInfos); 290 break; 291 } 292 case EXTNAME_JSON: { 293 this.addModuleInfoItem(moduleId, false, '', metaInfo, moduleInfos); 294 break; 295 } 296 default: 297 break; 298 } 299 } 300 301 private addModuleInfoItem(filePath: string, isCommonJs: boolean, extName: string, 302 metaInfo: Object, moduleInfos: Map<String, ModuleInfo>): void { 303 let namespace: string = metaInfo['moduleName']; 304 let recordName: string = getOhmUrlByFilepath(filePath, this.projectConfig, this.logger, namespace); 305 let sourceFile: string = filePath.replace(this.projectConfig.projectRootPath + path.sep, ''); 306 let cacheFilePath: string = 307 this.genFileCachePath(filePath, this.projectConfig.projectRootPath, this.projectConfig.cachePath); 308 let packageName: string = ''; 309 if (isPackageModulesFile(filePath, this.projectConfig)) { 310 packageName = this.getPkgModulesFilePkgName(metaInfo['pkgPath']); 311 } else { 312 packageName = 313 metaInfo['isLocalDependency'] ? namespace : getPackageInfo(this.projectConfig.aceModuleJsonPath)[1]; 314 } 315 316 if (extName.length !== 0) { 317 cacheFilePath = changeFileExtension(cacheFilePath, extName); 318 } 319 320 cacheFilePath = toUnixPath(cacheFilePath); 321 recordName = toUnixPath(recordName); 322 sourceFile = isDebug(this.projectConfig) ? toUnixPath(sourceFile) : 323 cacheFilePath.replace(toUnixPath(this.projectConfig.projectRootPath) + '/', ''); 324 packageName = toUnixPath(packageName); 325 326 moduleInfos.set(filePath, new ModuleInfo(filePath, cacheFilePath, isCommonJs, recordName, sourceFile, packageName)); 327 } 328 329 updateCachedSourceMaps(): void { 330 this.modifySourceMapKeyToCachePath(newSourceMaps); 331 if (!fs.existsSync(this.cacheSourceMapPath)) { 332 this.cacheSourceMapObject = newSourceMaps; 333 return; 334 } 335 336 this.cacheSourceMapObject = JSON.parse(fs.readFileSync(this.cacheSourceMapPath).toString()); 337 338 // remove unused source files's sourceMap 339 let unusedFiles = []; 340 let compileFileList: Set<string> = new Set(); 341 this.moduleInfos.forEach((moduleInfo: ModuleInfo, moduleId: string) => { 342 if (!isDebug(this.projectConfig)) { 343 moduleId = moduleId.replace(this.projectConfig.projectRootPath, this.projectConfig.cachePath); 344 if (moduleId.endsWith(EXTNAME_ETS)) { 345 moduleId = changeFileExtension(moduleId, EXTNAME_TS); 346 } 347 } 348 compileFileList.add(toUnixPath(moduleId)); 349 }) 350 351 Object.keys(this.cacheSourceMapObject).forEach(key => { 352 const sourceFileAbsolutePath: string = toUnixPath(path.join(this.projectConfig.projectRootPath, key)); 353 if (!compileFileList.has(sourceFileAbsolutePath)) { 354 unusedFiles.push(key); 355 } 356 }); 357 unusedFiles.forEach(file => { 358 delete this.cacheSourceMapObject[file]; 359 }) 360 361 // update sourceMap 362 Object.keys(newSourceMaps).forEach(key => { 363 this.cacheSourceMapObject[key] = newSourceMaps[key]; 364 }); 365 } 366 367 buildModuleSourceMapInfo(parentEvent: Object): void { 368 if (this.projectConfig.widgetCompile) { 369 return; 370 } 371 372 const eventUpdateCachedSourceMaps = createAndStartEvent(parentEvent, 'update cached source maps'); 373 this.updateCachedSourceMaps(); 374 stopEvent(eventUpdateCachedSourceMaps); 375 this.triggerAsync(() => { 376 const eventWriteFile = createAndStartEvent(parentEvent, 'write source map (async)', true); 377 fs.writeFile(this.sourceMapPath, JSON.stringify(this.cacheSourceMapObject, null, 2), 'utf-8', (err) => { 378 if (err) { 379 this.throwArkTsCompilerError('ArkTS:ERROR failed to write sourceMaps'); 380 } 381 fs.copyFileSync(this.sourceMapPath, this.cacheSourceMapPath); 382 stopEvent(eventWriteFile, true); 383 this.triggerEndSignal(); 384 }); 385 }); 386 } 387 388 generateEs2AbcCmd() { 389 const fileThreads = getEs2abcFileThreadNumber(); 390 this.cmdArgs.push(`"@${this.filesInfoPath}"`); 391 this.cmdArgs.push('--npm-module-entry-list'); 392 this.cmdArgs.push(`"${this.npmEntriesInfoPath}"`); 393 this.cmdArgs.push('--output'); 394 this.cmdArgs.push(`"${this.moduleAbcPath}"`); 395 this.cmdArgs.push('--file-threads'); 396 this.cmdArgs.push(`"${fileThreads}"`); 397 this.cmdArgs.push('--merge-abc'); 398 this.cmdArgs.push(`"--target-api-version=${this.projectConfig.compatibleSdkVersion}"`); 399 if (isAotMode(this.projectConfig) && isEs2Abc(this.projectConfig)) { 400 this.cmdArgs.push("--type-extractor"); 401 } 402 } 403 404 addCacheFileArgs() { 405 this.cmdArgs.push('--cache-file'); 406 this.cmdArgs.push(`"@${this.cacheFilePath}"`); 407 } 408 409 private generateCompileFilesInfo() { 410 let filesInfo: string = ''; 411 this.moduleInfos.forEach((info) => { 412 const moduleType: string = info.isCommonJs ? COMMONJS : ESM; 413 filesInfo += `${info.cacheFilePath};${info.recordName};${moduleType};${info.sourceFile};${info.packageName}\n`; 414 }); 415 fs.writeFileSync(this.filesInfoPath, filesInfo, 'utf-8'); 416 } 417 418 private generateNpmEntriesInfo() { 419 let entriesInfo: string = ''; 420 for (const value of this.pkgEntryInfos.values()) { 421 entriesInfo += `${value.pkgEntryPath}:${value.pkgBuildPath}\n`; 422 } 423 fs.writeFileSync(this.npmEntriesInfoPath, entriesInfo, 'utf-8'); 424 } 425 426 private generateAbcCacheFilesInfo(): void { 427 let abcCacheFilesInfo: string = ''; 428 429 // generate source file cache 430 this.moduleInfos.forEach((info) => { 431 let abcCacheFilePath: string = changeFileExtension(info.cacheFilePath, EXTNAME_PROTO_BIN); 432 abcCacheFilesInfo += `${info.cacheFilePath};${abcCacheFilePath}\n`; 433 }); 434 435 // generate npm entries cache 436 let npmEntriesCacheFilePath: string = changeFileExtension(this.npmEntriesInfoPath, EXTNAME_PROTO_BIN); 437 abcCacheFilesInfo += `${this.npmEntriesInfoPath};${npmEntriesCacheFilePath}\n`; 438 439 fs.writeFileSync(this.cacheFilePath, abcCacheFilesInfo, 'utf-8'); 440 } 441 442 private genDescriptionsForMergedEs2abc() { 443 this.generateCompileFilesInfo(); 444 this.generateNpmEntriesInfo(); 445 this.generateAbcCacheFilesInfo(); 446 } 447 448 generateMergedAbcOfEs2Abc(parentEvent: Object): void { 449 // collect data error from subprocess 450 let errMsg: string = ''; 451 const eventGenDescriptionsForMergedEs2abc = createAndStartEvent(parentEvent, 'generate descriptions for merged es2abc'); 452 this.genDescriptionsForMergedEs2abc(); 453 stopEvent(eventGenDescriptionsForMergedEs2abc); 454 const genAbcCmd: string = this.cmdArgs.join(' '); 455 try { 456 let eventGenAbc: Object; 457 const child = this.triggerAsync(() => { 458 eventGenAbc = createAndStartEvent(parentEvent, 'generate merged abc by es2abc (async)', true); 459 return childProcess.exec(genAbcCmd, { windowsHide: true }); 460 }); 461 child.on('close', (code: any) => { 462 if (code === FAIL) { 463 this.throwArkTsCompilerError('ArkTS:ERROR failed to execute es2abc'); 464 } 465 stopEvent(eventGenAbc, true); 466 this.triggerEndSignal(); 467 this.processAotIfNeeded(); 468 }); 469 470 child.on('error', (err: any) => { 471 stopEvent(eventGenAbc, true); 472 this.throwArkTsCompilerError(err.toString()); 473 }); 474 475 child.stderr.on('data', (data: any) => { 476 errMsg += data.toString(); 477 }); 478 479 child.stderr.on('end', (data: any) => { 480 if (errMsg !== undefined && errMsg.length > 0) { 481 this.logger.error(red, errMsg, reset); 482 } 483 }); 484 } catch (e) { 485 this.throwArkTsCompilerError('ArkTS:ERROR failed to execute es2abc. Error message: ' + e.toString()); 486 } 487 } 488 489 filterModulesByHashJson() { 490 if (this.hashJsonFilePath.length === 0 || !fs.existsSync(this.hashJsonFilePath)) { 491 for (const key of this.moduleInfos.keys()) { 492 this.filterModuleInfos.set(key, this.moduleInfos.get(key)); 493 } 494 return; 495 } 496 497 let updatedJsonObject: Object = {}; 498 let jsonObject: Object = {}; 499 let jsonFile: string = ''; 500 501 if (fs.existsSync(this.hashJsonFilePath)) { 502 jsonFile = fs.readFileSync(this.hashJsonFilePath).toString(); 503 jsonObject = JSON.parse(jsonFile); 504 this.filterModuleInfos = new Map<string, ModuleInfo>(); 505 for (const [key, value] of this.moduleInfos) { 506 const cacheFilePath: string = value.cacheFilePath; 507 const cacheProtoFilePath: string = changeFileExtension(cacheFilePath, EXTNAME_PROTO_BIN); 508 if (!fs.existsSync(cacheFilePath)) { 509 this.throwArkTsCompilerError(`ArkTS:ERROR ${cacheFilePath} is lost`); 510 } 511 if (fs.existsSync(cacheProtoFilePath)) { 512 const hashCacheFileContentData: string = toHashData(cacheFilePath); 513 const hashProtoFileContentData: string = toHashData(cacheProtoFilePath); 514 if (jsonObject[cacheFilePath] === hashCacheFileContentData && 515 jsonObject[cacheProtoFilePath] === hashProtoFileContentData) { 516 updatedJsonObject[cacheFilePath] = cacheFilePath; 517 updatedJsonObject[cacheProtoFilePath] = cacheProtoFilePath; 518 continue; 519 } 520 } 521 this.filterModuleInfos.set(key, value); 522 } 523 } 524 525 this.hashJsonObject = updatedJsonObject; 526 } 527 528 getSplittedModulesByNumber() { 529 const result: any = []; 530 if (this.filterModuleInfos.size < this.workerNumber) { 531 for (const value of this.filterModuleInfos.values()) { 532 result.push([value]); 533 } 534 return result; 535 } 536 537 for (let i = 0; i < this.workerNumber; ++i) { 538 result.push([]); 539 } 540 541 let pos: number = 0; 542 for (const value of this.filterModuleInfos.values()) { 543 const chunk = pos % this.workerNumber; 544 result[chunk].push(value); 545 pos++; 546 } 547 548 return result; 549 } 550 551 invokeTs2AbcWorkersToGenProto(splittedModules) { 552 let ts2abcCmdArgs: string[] = this.cmdArgs.slice(0); 553 ts2abcCmdArgs.push('--output-proto'); 554 ts2abcCmdArgs.push('--merge-abc'); 555 ts2abcCmdArgs.push('--input-file'); 556 if (isMasterOrPrimary()) { 557 this.setupCluster(cluster); 558 this.workerNumber = splittedModules.length; 559 for (let i = 0; i < this.workerNumber; ++i) { 560 const sn: number = i + 1; 561 const workerFileName: string = `${FILESINFO}_${sn}${EXTNAME_TXT}`; 562 const workerData: Object = { 563 inputs: JSON.stringify(splittedModules[i]), 564 cmd: ts2abcCmdArgs.join(' '), 565 workerFileName: workerFileName, 566 mode: ESMODULE, 567 cachePath: this.projectConfig.cachePath 568 }; 569 this.triggerAsync(() => { 570 const worker: Object = cluster.fork(workerData); 571 worker.on('message', (errorMsg) => { 572 this.logger.error(red, errorMsg.data.toString(), reset); 573 this.throwArkTsCompilerError('ArkTS:ERROR failed to execute ts2abc'); 574 }); 575 }); 576 } 577 } 578 } 579 580 processTs2abcWorkersToGenAbc() { 581 this.generateNpmEntriesInfo(); 582 let workerCount: number = 0; 583 if (isMasterOrPrimary()) { 584 cluster.on('exit', (worker, code, signal) => { 585 if (code === FAIL) { 586 this.throwArkTsCompilerError('ArkTS:ERROR failed to execute ts2abc'); 587 } 588 workerCount++; 589 if (workerCount === this.workerNumber) { 590 this.generateNpmEntryToGenProto(); 591 this.generateProtoFilesInfo(); 592 this.mergeProtoToAbc(); 593 this.processAotIfNeeded(); 594 this.afterCompilationProcess(); 595 } 596 this.triggerEndSignal(); 597 }); 598 if (this.workerNumber == 0) { 599 // process aot for no source file changed. 600 this.processAotIfNeeded(); 601 } 602 } 603 } 604 605 private processAotIfNeeded(): void { 606 if (!needAotCompiler(this.projectConfig)) { 607 return; 608 } 609 let faultHandler: FaultHandler = ((error: string) => { this.throwArkTsCompilerError(error); }) 610 const builtinAbcPath: string = generateBuiltinAbc(this.arkConfig.arkRootPath, this.initCmdEnv(), 611 this.projectConfig.cachePath, this.logger, faultHandler, this.projectConfig.pandaMode); 612 generateAot(this.arkConfig.arkRootPath, builtinAbcPath, this.moduleAbcPath, this.projectConfig, this.logger, faultHandler); 613 } 614 615 private genFileCachePath(filePath: string, projectRootPath: string, cachePath: string): string { 616 const sufStr: string = toUnixPath(filePath).replace(toUnixPath(projectRootPath), ''); 617 const output: string = path.join(cachePath, sufStr); 618 return output; 619 } 620 621 private getPkgModulesFilePkgName(pkgPath: string) { 622 pkgPath = toUnixPath(pkgPath); 623 const packageDir: string = this.projectConfig.packageDir; 624 const projectRootPath = toUnixPath(this.projectConfig.projectRootPath); 625 const projectPkgModulesPath: string = toUnixPath(path.join(projectRootPath, packageDir)); 626 let pkgName: string = ''; 627 if (pkgPath.includes(projectPkgModulesPath)) { 628 pkgName = path.join(PACKAGES, pkgPath.replace(projectPkgModulesPath, '')); 629 } else { 630 for (const key in this.projectConfig.modulePathMap) { 631 const value: string = this.projectConfig.modulePathMap[key]; 632 const fakeModulePkgModulesPath: string = toUnixPath(path.resolve(value, packageDir)); 633 if (pkgPath.indexOf(fakeModulePkgModulesPath) !== -1) { 634 const tempFilePath: string = pkgPath.replace(projectRootPath, ''); 635 pkgName = path.join(`${PACKAGES}@${key}`, 636 tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1)); 637 break; 638 } 639 } 640 } 641 642 return pkgName.replace(new RegExp(packageDir, 'g'), PACKAGES); 643 } 644 645 private generateProtoFilesInfo() { 646 validateFilePathLength(this.protoFilePath, this.logger); 647 mkdirsSync(path.dirname(this.protoFilePath)); 648 let protoFilesInfo: string = ''; 649 const sortModuleInfos: Object = new Map([...this.moduleInfos].sort()); 650 for (const value of sortModuleInfos.values()) { 651 const cacheProtoPath: string = changeFileExtension(value.cacheFilePath, EXTNAME_PROTO_BIN); 652 protoFilesInfo += `${toUnixPath(cacheProtoPath)}\n`; 653 } 654 if (this.pkgEntryInfos.size > 0) { 655 protoFilesInfo += `${toUnixPath(this.npmEntriesProtoFilePath)}\n`; 656 } 657 fs.writeFileSync(this.protoFilePath, protoFilesInfo, 'utf-8'); 658 } 659 660 private mergeProtoToAbc() { 661 mkdirsSync(this.projectConfig.aceModuleBuild); 662 const cmd: string = `"${this.arkConfig.mergeAbcPath}" --input "@${this.protoFilePath}" --outputFilePath "${ 663 this.projectConfig.aceModuleBuild}" --output ${MODULES_ABC} --suffix protoBin`; 664 try { 665 childProcess.execSync(cmd, { windowsHide: true }); 666 } catch (e) { 667 this.throwArkTsCompilerError(`ArkTS:ERROR failed to merge proto file to abc, error message:` + e.toString()); 668 } 669 } 670 671 private afterCompilationProcess() { 672 this.writeHashJson(); 673 } 674 675 private writeHashJson() { 676 if (this.hashJsonFilePath.length === 0) { 677 return; 678 } 679 680 for (const value of this.filterModuleInfos.values()) { 681 const cacheFilePath: string = value.cacheFilePath; 682 const cacheProtoFilePath: string = changeFileExtension(cacheFilePath, EXTNAME_PROTO_BIN); 683 if (!fs.existsSync(cacheFilePath) || !fs.existsSync(cacheProtoFilePath)) { 684 this.throwArkTsCompilerError( 685 `ArkTS:ERROR ${cacheFilePath} or ${cacheProtoFilePath} is lost` 686 ); 687 } 688 const hashCacheFileContentData: string = toHashData(cacheFilePath); 689 const hashCacheProtoContentData: string = toHashData(cacheProtoFilePath); 690 this.hashJsonObject[cacheFilePath] = hashCacheFileContentData; 691 this.hashJsonObject[cacheProtoFilePath] = hashCacheProtoContentData; 692 } 693 694 fs.writeFileSync(this.hashJsonFilePath, JSON.stringify(this.hashJsonObject)); 695 } 696 697 private generateNpmEntryToGenProto() { 698 if (this.pkgEntryInfos.size <= 0) { 699 return; 700 } 701 mkdirsSync(path.dirname(this.npmEntriesProtoFilePath)); 702 const cmd: string = `"${this.arkConfig.js2abcPath}" --compile-npm-entries "${ 703 this.npmEntriesInfoPath}" "${this.npmEntriesProtoFilePath}"`; 704 try { 705 childProcess.execSync(cmd, { windowsHide: true }); 706 } catch (e) { 707 this.throwArkTsCompilerError(`ArkTS:ERROR failed to generate npm proto file to abc. Error message: ` + e.toString()); 708 } 709 } 710 711 private removeCompilationCache(): void { 712 if (isEs2Abc(this.projectConfig)) { 713 this.removeEs2abcCompilationCache(); 714 } else if (isTs2Abc(this.projectConfig)) { 715 this.removeTs2abcCompilationCache(); 716 } else { 717 this.throwArkTsCompilerError(`Invalid projectConfig.pandaMode for module build, should be either 718 "${TS2ABC}" or "${ES2ABC}"`); 719 } 720 } 721 722 private removeEs2abcCompilationCache(): void { 723 if (fs.existsSync(this.cacheFilePath)) { 724 const data: string = fs.readFileSync(this.cacheFilePath, 'utf-8'); 725 const lines: string[] = data.split(/\r?\n/); 726 lines.forEach(line => { 727 const [, abcCacheFilePath]: string[] = line.split(';'); 728 if (fs.existsSync(abcCacheFilePath)) { 729 fs.unlinkSync(abcCacheFilePath); 730 } 731 }); 732 fs.unlinkSync(this.cacheFilePath); 733 } 734 } 735 736 private removeTs2abcCompilationCache(): void { 737 if (fs.existsSync(this.hashJsonFilePath)) { 738 fs.unlinkSync(this.hashJsonFilePath); 739 } 740 if (fs.existsSync(this.protoFilePath)) { 741 const data: string = fs.readFileSync(this.protoFilePath, 'utf-8'); 742 const lines: string[] = data.split(/\r?\n/); 743 lines.forEach(line => { 744 const protoFilePath: string = line; 745 if (fs.existsSync(protoFilePath)) { 746 fs.unlinkSync(protoFilePath); 747 } 748 }); 749 fs.unlinkSync(this.protoFilePath); 750 } 751 } 752 753 private modifySourceMapKeyToCachePath(sourceMap: object): void { 754 const projectConfig: object = this.projectConfig; 755 if (isDebug(projectConfig)) { 756 return; 757 } 758 // modify source map keys to keep IDE tools right 759 const relativeCachePath: string = toUnixPath(projectConfig.cachePath.replace( 760 projectConfig.projectRootPath + path.sep, '')); 761 Object.keys(sourceMap).forEach(key => { 762 let newKey: string = relativeCachePath + '/' + key; 763 if (newKey.endsWith(EXTNAME_ETS)) { 764 newKey = changeFileExtension(newKey, EXTNAME_TS); 765 } 766 sourceMap[newKey] = sourceMap[key]; 767 delete sourceMap[key]; 768 }); 769 } 770} 771