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