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