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