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