• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2025 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 * as os from 'os';
17import * as path from 'path';
18import * as fs from 'fs';
19import * as child_process from 'child_process';
20import cluster, {
21  Cluster,
22  Worker
23} from 'cluster';
24import {
25  ABC_SUFFIX,
26  ARKTSCONFIG_JSON_FILE,
27  BUILD_MODE,
28  DEFAULT_WOKER_NUMS,
29  DECL_ETS_SUFFIX,
30  DECL_TS_SUFFIX,
31  DEPENDENCY_INPUT_FILE,
32  DEPENDENCY_JSON_FILE,
33  LANGUAGE_VERSION,
34  LINKER_INPUT_FILE,
35  MERGED_ABC_FILE,
36  STATIC_RECORD_FILE,
37  STATIC_RECORD_FILE_CONTENT,
38  TS_SUFFIX
39} from '../pre_define';
40import {
41  changeDeclgenFileExtension,
42  changeFileExtension,
43  createFileIfNotExists,
44  ensurePathExists,
45  getFileHash,
46  isHybrid,
47  isMac
48} from '../utils';
49import {
50  PluginDriver,
51  PluginHook
52} from '../plugins/plugins_driver';
53import {
54  Logger,
55  LogData,
56  LogDataFactory
57} from '../logger';
58import { ErrorCode } from '../error_code';
59import {
60  ArkTS,
61  ArkTSGlobal,
62  BuildConfig,
63  CompileFileInfo,
64  DependencyFileConfig,
65  DependentModuleConfig,
66  ModuleInfo
67} from '../types';
68import { ArkTSConfigGenerator } from './generate_arktsconfig';
69import { SetupClusterOptions } from '../types';
70
71export abstract class BaseMode {
72  public buildConfig: BuildConfig;
73  public entryFiles: Set<string>;
74  public compileFiles: Map<string, CompileFileInfo>;
75  public outputDir: string;
76  public cacheDir: string;
77  public pandaSdkPath: string;
78  public buildSdkPath: string;
79  public packageName: string;
80  public sourceRoots: string[];
81  public moduleRootPath: string;
82  public moduleType: string;
83  public dependentModuleList: DependentModuleConfig[];
84  public moduleInfos: Map<string, ModuleInfo>;
85  public mergedAbcFile: string;
86  public dependencyJsonFile: string;
87  public abcLinkerCmd: string[];
88  public dependencyAnalyzerCmd: string[];
89  public logger: Logger;
90  public isDebug: boolean;
91  public enableDeclgenEts2Ts: boolean;
92  public declgenV1OutPath: string | undefined;
93  public declgenV2OutPath: string | undefined;
94  public declgenBridgeCodePath: string | undefined;
95  public hasMainModule: boolean;
96  public abcFiles: Set<string>;
97  public hashCacheFile: string;
98  public hashCache: Record<string, string>;
99  public isCacheFileExists: boolean;
100  public dependencyFileMap: DependencyFileConfig | null;
101  public isBuildConfigModified: boolean | undefined;
102  public byteCodeHar: boolean;
103  public isHybrid: boolean;
104
105  constructor(buildConfig: BuildConfig) {
106    this.buildConfig = buildConfig;
107    this.entryFiles = new Set<string>(buildConfig.compileFiles as string[]);
108    this.compileFiles = new Map<string, CompileFileInfo>();
109    this.outputDir = buildConfig.loaderOutPath as string;
110    this.cacheDir = buildConfig.cachePath as string;
111    this.pandaSdkPath = buildConfig.pandaSdkPath as string;
112    this.buildSdkPath = buildConfig.buildSdkPath as string;
113    this.packageName = buildConfig.packageName as string;
114    this.sourceRoots = buildConfig.sourceRoots as string[];
115    this.moduleRootPath = buildConfig.moduleRootPath as string;
116    this.moduleType = buildConfig.moduleType as string;
117    this.dependentModuleList = buildConfig.dependentModuleList;
118    this.moduleInfos = new Map<string, ModuleInfo>();
119    this.mergedAbcFile = path.resolve(this.outputDir, MERGED_ABC_FILE);
120    this.dependencyJsonFile = path.resolve(this.cacheDir, DEPENDENCY_JSON_FILE);
121    this.abcLinkerCmd = ['"' + this.buildConfig.abcLinkerPath + '"'];
122    this.dependencyAnalyzerCmd = ['"' + this.buildConfig.dependencyAnalyzerPath + '"'];
123    this.logger = Logger.getInstance();
124    this.isDebug = buildConfig.buildMode as string === BUILD_MODE.DEBUG;
125    this.enableDeclgenEts2Ts = buildConfig.enableDeclgenEts2Ts as boolean;
126    this.declgenV1OutPath = buildConfig.declgenV1OutPath as string | undefined;
127    this.declgenV2OutPath = buildConfig.declgenV2OutPath as string | undefined;
128    this.declgenBridgeCodePath = buildConfig.declgenBridgeCodePath as string | undefined;
129    this.hasMainModule = buildConfig.hasMainModule;
130    this.abcFiles = new Set<string>();
131    this.hashCacheFile = path.join(this.cacheDir, 'hash_cache.json');
132    this.hashCache = this.loadHashCache();
133    this.isCacheFileExists = fs.existsSync(this.hashCacheFile);
134    this.dependencyFileMap = null;
135    this.isBuildConfigModified = buildConfig.isBuildConfigModified as boolean | undefined;
136    this.byteCodeHar = buildConfig.byteCodeHar as boolean;
137    this.isHybrid = isHybrid(buildConfig);
138  }
139
140  public declgen(fileInfo: CompileFileInfo): void {
141    const source = fs.readFileSync(fileInfo.filePath, 'utf8');
142    const moduleInfo: ModuleInfo = this.moduleInfos.get(fileInfo.packageName)!;
143    const filePathFromModuleRoot: string = path.relative(moduleInfo.moduleRootPath, fileInfo.filePath);
144    const declEtsOutputPath: string = changeDeclgenFileExtension(
145      path.join(moduleInfo.declgenV1OutPath as string, moduleInfo.packageName, filePathFromModuleRoot),
146      DECL_ETS_SUFFIX
147    );
148    const etsOutputPath: string = changeDeclgenFileExtension(
149      path.join(moduleInfo.declgenBridgeCodePath as string, moduleInfo.packageName, filePathFromModuleRoot),
150      TS_SUFFIX
151    );
152    ensurePathExists(declEtsOutputPath);
153    ensurePathExists(etsOutputPath);
154    const arktsGlobal: ArkTSGlobal = this.buildConfig.arktsGlobal;
155    const arkts: ArkTS = this.buildConfig.arkts;
156    let errorStatus = false;
157    try {
158      const staticRecordPath = path.join(
159        moduleInfo.declgenV1OutPath as string,
160        STATIC_RECORD_FILE
161      )
162      const declEtsOutputDir = path.dirname(declEtsOutputPath);
163      const staticRecordRelativePath = changeFileExtension(
164        path.relative(declEtsOutputDir, staticRecordPath).replaceAll(/\\/g, '\/'),
165        '',
166        DECL_TS_SUFFIX
167      );
168      createFileIfNotExists(staticRecordPath, STATIC_RECORD_FILE_CONTENT);
169
170      arktsGlobal.filePath = fileInfo.filePath;
171      arktsGlobal.config = arkts.Config.create([
172        '_',
173        '--extension',
174        'ets',
175        '--arktsconfig',
176        fileInfo.arktsConfigFile,
177        fileInfo.filePath
178      ]).peer;
179      arktsGlobal.compilerContext = arkts.Context.createFromString(source);
180      PluginDriver.getInstance().getPluginContext().setArkTSProgram(arktsGlobal.compilerContext.program);
181
182      arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, true);
183
184      let ast = arkts.EtsScript.fromContext();
185      PluginDriver.getInstance().getPluginContext().setArkTSAst(ast);
186      PluginDriver.getInstance().runPluginHook(PluginHook.PARSED);
187
188      arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, true);
189
190      ast = arkts.EtsScript.fromContext();
191      PluginDriver.getInstance().getPluginContext().setArkTSAst(ast);
192      PluginDriver.getInstance().runPluginHook(PluginHook.CHECKED);
193
194      arkts.generateTsDeclarationsFromContext(
195        declEtsOutputPath,
196        etsOutputPath,
197        false,
198        staticRecordRelativePath
199      ); // Generate 1.0 declaration files & 1.0 glue code
200      this.logger.printInfo('declaration files generated');
201    } catch (error) {
202      errorStatus = true;
203      if (error instanceof Error) {
204        const logData: LogData = LogDataFactory.newInstance(
205          ErrorCode.BUILDSYSTEM_DECLGEN_FAIL,
206          'Generate declaration files failed.',
207          error.message,
208          fileInfo.filePath
209        );
210        this.logger.printError(logData);
211      }
212    } finally {
213      if (!errorStatus) {
214        // when error occur,wrapper will destroy context.
215        arktsGlobal.es2panda._DestroyContext(arktsGlobal.compilerContext.peer);
216      }
217      arkts.destroyConfig(arktsGlobal.config);
218    }
219  }
220
221  public compile(fileInfo: CompileFileInfo): void {
222    ensurePathExists(fileInfo.abcFilePath);
223
224    const ets2pandaCmd: string[] = [
225      '_',
226      '--extension',
227      'ets',
228      '--arktsconfig',
229      fileInfo.arktsConfigFile,
230      '--output',
231      fileInfo.abcFilePath,
232    ];
233
234    if (this.isDebug) {
235      ets2pandaCmd.push('--debug-info');
236    }
237    ets2pandaCmd.push(fileInfo.filePath);
238    this.logger.printInfo('ets2pandaCmd: ' + ets2pandaCmd.join(' '));
239
240    const arktsGlobal = this.buildConfig.arktsGlobal;
241    const arkts = this.buildConfig.arkts;
242    let errorStatus = false;
243    try {
244      arktsGlobal.filePath = fileInfo.filePath;
245      arktsGlobal.config = arkts.Config.create(ets2pandaCmd).peer;
246      const source = fs.readFileSync(fileInfo.filePath).toString();
247      arktsGlobal.compilerContext = arkts.Context.createFromString(source);
248      PluginDriver.getInstance().getPluginContext().setArkTSProgram(arktsGlobal.compilerContext.program);
249
250      arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED);
251      this.logger.printInfo('es2panda proceedToState parsed');
252      let ast = arkts.EtsScript.fromContext();
253      PluginDriver.getInstance().getPluginContext().setArkTSAst(ast);
254      PluginDriver.getInstance().runPluginHook(PluginHook.PARSED);
255      this.logger.printInfo('plugin parsed finished');
256
257      arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED);
258      this.logger.printInfo('es2panda proceedToState checked');
259      ast = arkts.EtsScript.fromContext();
260      PluginDriver.getInstance().getPluginContext().setArkTSAst(ast);
261      PluginDriver.getInstance().runPluginHook(PluginHook.CHECKED);
262      this.logger.printInfo('plugin checked finished');
263
264      arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_BIN_GENERATED);
265      this.logger.printInfo('es2panda bin generated');
266    } catch (error) {
267      errorStatus = true;
268      if (error instanceof Error) {
269        const logData: LogData = LogDataFactory.newInstance(
270          ErrorCode.BUILDSYSTEM_COMPILE_ABC_FAIL,
271          'Compile abc files failed.',
272          error.message,
273          fileInfo.filePath
274        );
275        this.logger.printError(logData);
276      }
277    } finally {
278      if (!errorStatus) {
279        // when error occur,wrapper will destroy context.
280        arktsGlobal.es2panda._DestroyContext(arktsGlobal.compilerContext.peer);
281      }
282      PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN);
283      arkts.destroyConfig(arktsGlobal.config);
284    }
285  }
286
287  public compileMultiFiles(filePaths: string[], moduleInfo: ModuleInfo): void {
288    let ets2pandaCmd: string[] = [
289      '_',
290      '--extension',
291      'ets',
292      '--arktsconfig',
293      moduleInfo.arktsConfigFile,
294      '--output',
295      path.resolve(this.outputDir, MERGED_ABC_FILE),
296      '--simultaneous'
297    ];
298    ensurePathExists(path.resolve(this.outputDir, MERGED_ABC_FILE));
299    if (this.isDebug) {
300      ets2pandaCmd.push('--debug-info');
301    }
302    ets2pandaCmd.push(this.buildConfig.compileFiles[0]);
303    this.logger.printInfo('ets2pandaCmd: ' + ets2pandaCmd.join(' '));
304
305    let arktsGlobal = this.buildConfig.arktsGlobal;
306    let arkts = this.buildConfig.arkts;
307    let errorStatus = false;
308    try {
309      arktsGlobal.config = arkts.Config.create(ets2pandaCmd).peer;
310      //@ts-ignore
311      arktsGlobal.compilerContext = arkts.Context.createContextGenerateAbcForExternalSourceFiles(this.buildConfig.compileFiles);;
312      PluginDriver.getInstance().getPluginContext().setArkTSProgram(arktsGlobal.compilerContext.program);
313
314      arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, arktsGlobal.compilerContext.peer);
315      this.logger.printInfo('es2panda proceedToState parsed');
316      let ast = arkts.EtsScript.fromContext();
317      PluginDriver.getInstance().getPluginContext().setArkTSAst(ast);
318      PluginDriver.getInstance().runPluginHook(PluginHook.PARSED);
319      this.logger.printInfo('plugin parsed finished');
320
321      arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, arktsGlobal.compilerContext.peer);
322      this.logger.printInfo('es2panda proceedToState checked');
323
324      ast = arkts.EtsScript.fromContext();
325      PluginDriver.getInstance().getPluginContext().setArkTSAst(ast);
326      PluginDriver.getInstance().runPluginHook(PluginHook.CHECKED);
327      this.logger.printInfo('plugin checked finished');
328
329      arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_BIN_GENERATED, arktsGlobal.compilerContext.peer);
330      this.logger.printInfo('es2panda bin generated');
331    } catch (error) {
332      errorStatus = true;
333      throw error;
334    } finally {
335      PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN);
336      arkts.destroyConfig(arktsGlobal.config);
337    }
338  }
339
340  public mergeAbcFiles(): void {
341    let linkerInputFile: string = path.join(this.cacheDir, LINKER_INPUT_FILE);
342    let linkerInputContent: string = '';
343    this.abcFiles.forEach((abcFile: string) => {
344      linkerInputContent += abcFile + os.EOL;
345    });
346    fs.writeFileSync(linkerInputFile, linkerInputContent);
347
348    this.abcLinkerCmd.push('--output');
349    this.abcLinkerCmd.push('"' + this.mergedAbcFile + '"');
350    this.abcLinkerCmd.push('--');
351    this.abcLinkerCmd.push('@' + '"' + linkerInputFile + '"');
352
353    let abcLinkerCmdStr: string = this.abcLinkerCmd.join(' ');
354    if (isMac()) {
355      const loadLibrary = 'DYLD_LIBRARY_PATH=' + '"' + process.env.DYLD_LIBRARY_PATH + '"';
356      abcLinkerCmdStr = loadLibrary + ' ' + abcLinkerCmdStr;
357    }
358    this.logger.printInfo(abcLinkerCmdStr);
359
360    ensurePathExists(this.mergedAbcFile);
361    try {
362      child_process.execSync(abcLinkerCmdStr).toString();
363    } catch (error) {
364      if (error instanceof Error) {
365        const logData: LogData = LogDataFactory.newInstance(
366          ErrorCode.BUILDSYSTEM_LINK_ABC_FAIL,
367          'Link abc files failed.',
368          error.message
369        );
370        this.logger.printError(logData);
371      }
372    }
373  }
374
375  private getDependentModules(moduleInfo: ModuleInfo): Map<string, ModuleInfo>[] {
376    let dynamicDepModules: Map<string, ModuleInfo> = new Map<string, ModuleInfo>();
377    let staticDepModules: Map<string, ModuleInfo> = new Map<string, ModuleInfo>();
378    this.collectDependencyModules(moduleInfo.packageName, moduleInfo, dynamicDepModules, staticDepModules);
379
380    if (moduleInfo.isMainModule) {
381      this.moduleInfos.forEach((module: ModuleInfo, packageName: string) => {
382        if (module.isMainModule) {
383          return;
384        }
385        this.collectDependencyModules(packageName, module, dynamicDepModules, staticDepModules);
386      });
387      if (moduleInfo.language === LANGUAGE_VERSION.ARKTS_HYBRID) {
388        dynamicDepModules.set(moduleInfo.packageName, moduleInfo);
389      }
390      return [dynamicDepModules, staticDepModules];
391    }
392
393    if (moduleInfo.dependencies) {
394      moduleInfo.dependencies.forEach((packageName: string) => {
395        let depModuleInfo: ModuleInfo | undefined = this.moduleInfos.get(packageName);
396        if (!depModuleInfo) {
397          const logData: LogData = LogDataFactory.newInstance(
398            ErrorCode.BUILDSYSTEM_DEPENDENT_MODULE_INFO_NOT_FOUND,
399            `Module ${packageName} not found in moduleInfos`
400          );
401          this.logger.printErrorAndExit(logData);
402        } else {
403          this.collectDependencyModules(packageName, depModuleInfo, dynamicDepModules, staticDepModules);
404        }
405      });
406    }
407    return [dynamicDepModules, staticDepModules];
408  }
409
410  private collectDependencyModules(
411    packageName: string,
412    module: ModuleInfo,
413    dynamicDepModules: Map<string, ModuleInfo>,
414    staticDepModules: Map<string, ModuleInfo>
415  ): void {
416    if (module.language === LANGUAGE_VERSION.ARKTS_1_2) {
417      staticDepModules.set(packageName, module);
418    } else if (module.language === LANGUAGE_VERSION.ARKTS_1_1) {
419      dynamicDepModules.set(packageName, module);
420    } else if (module.language === LANGUAGE_VERSION.ARKTS_HYBRID) {
421      staticDepModules.set(packageName, module);
422      dynamicDepModules.set(packageName, module);
423    }
424  }
425
426  protected generateArkTSConfigForModules(): void {
427    this.moduleInfos.forEach((moduleInfo: ModuleInfo, moduleRootPath: string) => {
428      ArkTSConfigGenerator.getInstance(this.buildConfig, this.moduleInfos)
429        .writeArkTSConfigFile(moduleInfo, this.enableDeclgenEts2Ts, this.buildConfig);
430    });
431  }
432
433  private collectDepModuleInfos(): void {
434    this.moduleInfos.forEach((moduleInfo: ModuleInfo) => {
435      let [dynamicDepModules, staticDepModules] = this.getDependentModules(moduleInfo);
436      moduleInfo.dynamicDepModuleInfos = dynamicDepModules;
437      moduleInfo.staticDepModuleInfos = staticDepModules;
438    });
439  }
440
441  protected collectModuleInfos(): void {
442    if (this.hasMainModule && (!this.packageName || !this.moduleRootPath || !this.sourceRoots)) {
443      const logData: LogData = LogDataFactory.newInstance(
444        ErrorCode.BUILDSYSTEM_MODULE_INFO_NOT_CORRECT_FAIL,
445        'Main module info from hvigor is not correct.'
446      );
447      this.logger.printError(logData);
448    }
449    const mainModuleInfo: ModuleInfo = this.getMainModuleInfo();
450    this.moduleInfos.set(this.packageName, mainModuleInfo);
451    this.dependentModuleList.forEach((module: DependentModuleConfig) => {
452      if (!module.packageName || !module.modulePath || !module.sourceRoots || !module.entryFile) {
453        const logData: LogData = LogDataFactory.newInstance(
454          ErrorCode.BUILDSYSTEM_DEPENDENT_MODULE_INFO_NOT_CORRECT_FAIL,
455          'Dependent module info from hvigor is not correct.'
456        );
457        this.logger.printError(logData);
458      }
459      if (this.moduleInfos.has(module.packageName)) {
460        return;
461      }
462      let moduleInfo: ModuleInfo = {
463        isMainModule: false,
464        packageName: module.packageName,
465        moduleRootPath: module.modulePath,
466        moduleType: module.moduleType,
467        sourceRoots: module.sourceRoots,
468        entryFile: module.entryFile,
469        arktsConfigFile: path.resolve(this.cacheDir, module.packageName, ARKTSCONFIG_JSON_FILE),
470        compileFileInfos: [],
471        dynamicDepModuleInfos: new Map<string, ModuleInfo>(),
472        staticDepModuleInfos: new Map<string, ModuleInfo>(),
473        declgenV1OutPath: module.declgenV1OutPath,
474        declgenV2OutPath: module.declgenV2OutPath,
475        declgenBridgeCodePath: module.declgenBridgeCodePath,
476        language: module.language,
477        declFilesPath: module.declFilesPath,
478        byteCodeHar: module.byteCodeHar,
479        dependencies: module.dependencies
480      };
481      this.moduleInfos.set(module.packageName, moduleInfo);
482    });
483    this.collectDepModuleInfos();
484  }
485
486  protected getMainModuleInfo(): ModuleInfo {
487    const mainModuleInfo = this.dependentModuleList.find((module: DependentModuleConfig) => module.packageName === this.packageName);
488    return {
489      isMainModule: this.hasMainModule,
490      packageName: mainModuleInfo?.packageName ?? this.packageName,
491      moduleRootPath: mainModuleInfo?.modulePath ?? this.moduleRootPath,
492      moduleType: mainModuleInfo?.moduleType ?? this.moduleType,
493      sourceRoots: this.sourceRoots,
494      entryFile: '',
495      arktsConfigFile: path.resolve(this.cacheDir, this.packageName, ARKTSCONFIG_JSON_FILE),
496      dynamicDepModuleInfos: new Map<string, ModuleInfo>(),
497      staticDepModuleInfos: new Map<string, ModuleInfo>(),
498      compileFileInfos: [],
499      declgenV1OutPath: mainModuleInfo?.declgenV1OutPath ?? this.declgenV1OutPath,
500      declgenV2OutPath: mainModuleInfo?.declgenV2OutPath ?? this.declgenV2OutPath,
501      declgenBridgeCodePath: mainModuleInfo?.declgenBridgeCodePath ?? this.declgenBridgeCodePath,
502      byteCodeHar: this.byteCodeHar,
503      language: mainModuleInfo?.language ?? LANGUAGE_VERSION.ARKTS_1_2,
504      declFilesPath: mainModuleInfo?.declFilesPath,
505    };
506  }
507
508  private loadHashCache(): Record<string, string> {
509    try {
510      if (!fs.existsSync(this.hashCacheFile)) {
511        return {};
512      }
513
514      const cacheContent: string = fs.readFileSync(this.hashCacheFile, 'utf-8');
515      const cacheData: Record<string, string> = JSON.parse(cacheContent);
516      const filteredCache: Record<string, string> = Object.fromEntries(
517        Object.entries(cacheData).filter(([file]) => this.entryFiles.has(file))
518      );
519      return filteredCache;
520    } catch (error) {
521      if (error instanceof Error) {
522        const logData: LogData = LogDataFactory.newInstance(
523          ErrorCode.BUILDSYSTEM_LOAD_HASH_CACHE_FAIL,
524          'Failed to load hash cache.',
525          error.message
526        );
527        this.logger.printError(logData);
528      }
529      return {};
530    }
531  }
532
533  private saveHashCache(): void {
534    ensurePathExists(this.hashCacheFile);
535    fs.writeFileSync(this.hashCacheFile, JSON.stringify(this.hashCache, null, 2));
536  }
537
538  private isFileChanged(etsFilePath: string, abcFilePath: string): boolean {
539    if (fs.existsSync(abcFilePath)) {
540      const etsFileLastModified: number = fs.statSync(etsFilePath).mtimeMs;
541      const abcFileLastModified: number = fs.statSync(abcFilePath).mtimeMs;
542      if (etsFileLastModified < abcFileLastModified) {
543        const currentHash = getFileHash(etsFilePath);
544        const cachedHash = this.hashCache[etsFilePath];
545        if (cachedHash && cachedHash === currentHash) {
546          return false;
547        }
548      }
549    }
550    return true;
551  }
552
553  private collectDependentCompileFiles(): void {
554    if (!this.dependencyFileMap) {
555      const logData: LogData = LogDataFactory.newInstance(
556        ErrorCode.BUILDSYSTEM_Dependency_Analyze_FAIL,
557        'Analyze files dependency failed.',
558        'Dependency map not initialized.'
559      );
560      this.logger.printError(logData);
561      return;
562    }
563
564    const compileFiles = new Set<string>();
565    const processed = new Set<string>();
566    const queue: string[] = [];
567
568    this.entryFiles.forEach((file: string) => {
569      let hasModule = false;
570      for (const [_, moduleInfo] of this.moduleInfos) {
571        if (!file.startsWith(moduleInfo.moduleRootPath)) {
572          continue;
573        }
574
575        hasModule = true;
576        const filePathFromModuleRoot = path.relative(moduleInfo.moduleRootPath, file);
577        const filePathInCache = path.join(this.cacheDir, moduleInfo.packageName, filePathFromModuleRoot);
578        const abcFilePath = path.resolve(changeFileExtension(filePathInCache, ABC_SUFFIX));
579
580        this.abcFiles.add(abcFilePath);
581        if (this.isBuildConfigModified || this.isFileChanged(file, abcFilePath)) {
582          compileFiles.add(file);
583          queue.push(file);
584        }
585        this.hashCache[file] = getFileHash(file);
586        break;
587      }
588      if (!hasModule) {
589        const logData: LogData = LogDataFactory.newInstance(
590          ErrorCode.BUILDSYSTEM_FILE_NOT_BELONG_TO_ANY_MODULE_FAIL,
591          'File does not belong to any module in moduleInfos.',
592          '',
593          file
594        );
595        this.logger.printError(logData);
596        return;
597      }
598    });
599
600    while (queue.length > 0) {
601      const currentFile = queue.shift()!;
602      processed.add(currentFile);
603
604      (this.dependencyFileMap?.dependants[currentFile] || []).forEach(dependant => {
605        // For the 1.1 declaration file referenced in dynamicPaths, if a path is detected as non-existent, it will be skipped.
606        const isFileExist = fs.existsSync(dependant);
607        if (!isFileExist) {
608          return;
609        }
610        if (!compileFiles.has(dependant) && !processed.has(dependant)) {
611          queue.push(dependant);
612        }
613        compileFiles.add(dependant);
614      });
615    }
616
617    compileFiles.forEach((file: string) => {
618      let hasModule = false;
619      for (const [_, moduleInfo] of this.moduleInfos) {
620        if (!file.startsWith(moduleInfo.moduleRootPath)) {
621          continue;
622        }
623        hasModule = true;
624        const filePathFromModuleRoot = path.relative(moduleInfo.moduleRootPath, file);
625        const filePathInCache = path.join(this.cacheDir, moduleInfo.packageName, filePathFromModuleRoot);
626        const abcFilePath = path.resolve(changeFileExtension(filePathInCache, ABC_SUFFIX));
627
628        const fileInfo: CompileFileInfo = {
629          filePath: file,
630          dependentFiles: this.dependencyFileMap?.dependants[file] || [],
631          abcFilePath,
632          arktsConfigFile: moduleInfo.arktsConfigFile,
633          packageName: moduleInfo.packageName
634        };
635
636        moduleInfo.compileFileInfos.push(fileInfo);
637        this.compileFiles.set(file, fileInfo);
638        break;
639      }
640      if (!hasModule) {
641        const logData: LogData = LogDataFactory.newInstance(
642          ErrorCode.BUILDSYSTEM_FILE_NOT_BELONG_TO_ANY_MODULE_FAIL,
643          'File does not belong to any module in moduleInfos.',
644          '',
645          file
646        );
647        this.logger.printError(logData);
648      }
649    });
650  }
651
652  private shouldSkipFile(file: string, moduleInfo: ModuleInfo, filePathFromModuleRoot: string, abcFilePath: string): boolean {
653    const targetPath = this.enableDeclgenEts2Ts
654      ? changeFileExtension(path.join(moduleInfo.declgenBridgeCodePath as string, moduleInfo.packageName, filePathFromModuleRoot), TS_SUFFIX)
655      : abcFilePath;
656    return !this.isFileChanged(file, targetPath);
657  }
658
659  protected collectCompileFiles(): void {
660    if (!this.isBuildConfigModified && this.isCacheFileExists && !this.enableDeclgenEts2Ts && !this.isHybrid) {
661      this.collectDependentCompileFiles();
662      return;
663    }
664    this.entryFiles.forEach((file: string) => {
665      for (const [_, moduleInfo] of this.moduleInfos) {
666        const relativePath = path.relative(moduleInfo.moduleRootPath, file);
667        if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
668          continue;
669        }
670        const filePathFromModuleRoot: string = path.relative(moduleInfo.moduleRootPath, file);
671        const filePathInCache: string = path.join(this.cacheDir, moduleInfo.packageName, filePathFromModuleRoot);
672        const abcFilePath: string = path.resolve(changeFileExtension(filePathInCache, ABC_SUFFIX));
673        this.abcFiles.add(abcFilePath);
674        if (!this.isBuildConfigModified && this.shouldSkipFile(file, moduleInfo, filePathFromModuleRoot, abcFilePath)) {
675          return;
676        }
677        this.hashCache[file] = getFileHash(file);
678        const fileInfo: CompileFileInfo = {
679          filePath: file,
680          dependentFiles: [],
681          abcFilePath: abcFilePath,
682          arktsConfigFile: moduleInfo.arktsConfigFile,
683          packageName: moduleInfo.packageName
684        };
685        moduleInfo.compileFileInfos.push(fileInfo);
686        this.compileFiles.set(file, fileInfo);
687        return;
688      }
689      const logData: LogData = LogDataFactory.newInstance(
690        ErrorCode.BUILDSYSTEM_FILE_NOT_BELONG_TO_ANY_MODULE_FAIL,
691        'File does not belong to any module in moduleInfos.',
692        '',
693        file
694      );
695      this.logger.printError(logData);
696    });
697  }
698
699  protected generateModuleInfos(): void {
700    this.collectModuleInfos();
701    this.generateArkTSConfigForModules();
702    this.generatedependencyFileMap();
703    this.collectCompileFiles();
704    this.saveHashCache();
705  }
706
707  public async generateDeclaration(): Promise<void> {
708    this.generateModuleInfos();
709
710    const compilePromises: Promise<void>[] = [];
711    this.compileFiles.forEach((fileInfo: CompileFileInfo, _: string) => {
712      compilePromises.push(new Promise<void>((resolve) => {
713        this.declgen(fileInfo);
714        resolve();
715      }));
716    });
717    await Promise.all(compilePromises);
718  }
719
720  public async run(): Promise<void> {
721    this.generateModuleInfos();
722
723    let moduleToFile = new Map<string, string[]>();
724    this.compileFiles.forEach((fileInfo: CompileFileInfo, _: string) => {
725      if (!moduleToFile.has(fileInfo.packageName)) {
726        moduleToFile.set(fileInfo.packageName, []);
727      }
728      moduleToFile.get(fileInfo.packageName)?.push(fileInfo.filePath);
729    });
730    try {
731      //@ts-ignore
732      this.compileMultiFiles([], this.moduleInfos.get(this.packageName));
733    } catch (error) {
734      if (error instanceof Error) {
735        const logData: LogData = LogDataFactory.newInstance(
736          ErrorCode.BUILDSYSTEM_COMPILE_ABC_FAIL,
737          'Compile abc files failed.',
738          error.message
739        );
740        this.logger.printErrorAndExit(logData);
741      }
742    }
743  }
744
745  private terminateAllWorkers(): void {
746    Object.values(cluster.workers || {}).forEach(worker => {
747      worker?.kill();
748    });
749  };
750
751  public generatedependencyFileMap(): void {
752    if (this.isBuildConfigModified || !this.isCacheFileExists || this.enableDeclgenEts2Ts || this.isHybrid) {
753      return;
754    }
755    const dependencyInputFile: string = path.join(this.cacheDir, DEPENDENCY_INPUT_FILE);
756    let dependencyInputContent: string = '';
757    this.entryFiles.forEach((entryFile: string) => {
758      dependencyInputContent += entryFile + os.EOL;
759    });
760    fs.writeFileSync(dependencyInputFile, dependencyInputContent);
761
762    this.dependencyAnalyzerCmd.push('@' + '"' + dependencyInputFile + '"');
763    for (const [_, module] of this.moduleInfos) {
764      if (module.isMainModule) {
765        this.dependencyAnalyzerCmd.push('--arktsconfig=' + '"' + module.arktsConfigFile + '"');
766        break;
767      }
768    }
769    this.dependencyAnalyzerCmd.push('--output=' + '"' + this.dependencyJsonFile + '"');
770    let dependencyAnalyzerCmdStr: string = this.dependencyAnalyzerCmd.join(' ');
771    if (isMac()) {
772      const loadLibrary = 'DYLD_LIBRARY_PATH=' + '"' + process.env.DYLD_LIBRARY_PATH + '"';
773      dependencyAnalyzerCmdStr = loadLibrary + ' ' + dependencyAnalyzerCmdStr;
774    }
775    this.logger.printInfo(dependencyAnalyzerCmdStr);
776
777    ensurePathExists(this.dependencyJsonFile);
778    try {
779      const output = child_process.execSync(dependencyAnalyzerCmdStr, {
780        stdio: 'pipe',
781        encoding: 'utf-8'
782      });
783      if (output.trim() !== '') {
784        const logData: LogData = LogDataFactory.newInstance(
785          ErrorCode.BUILDSYSTEM_Dependency_Analyze_FAIL,
786          'Analyze files dependency failed.',
787          output
788        );
789        this.logger.printError(logData);
790        return;
791      }
792      const dependencyJsonContent = fs.readFileSync(this.dependencyJsonFile, 'utf-8');
793      this.dependencyFileMap = JSON.parse(dependencyJsonContent);
794    } catch (error) {
795      if (error instanceof Error) {
796        const execError = error as child_process.ExecException;
797        let fullErrorMessage = execError.message;
798        if (execError.stderr) {
799          fullErrorMessage += `\nError output: ${execError.stderr}`;
800        }
801        if (execError.stdout) {
802          fullErrorMessage += `\nOutput: ${execError.stdout}`;
803        }
804        const logData: LogData = LogDataFactory.newInstance(
805          ErrorCode.BUILDSYSTEM_Dependency_Analyze_FAIL,
806          'Analyze files dependency failed.',
807          fullErrorMessage
808        );
809        this.logger.printError(logData);
810      }
811    }
812  }
813
814  public async runParallel(): Promise<void> {
815    this.generateModuleInfos();
816
817    if (!cluster.isPrimary) {
818      return;
819    }
820
821    try {
822      this.setupCluster(cluster, {
823        clearExitListeners: true,
824        execPath: path.resolve(__dirname, 'compile_worker.js'),
825      });
826      await this.dispatchTasks();
827      this.logger.printInfo('All tasks complete, merging...');
828      this.mergeAbcFiles();
829    } catch (error) {
830      this.logger.printError(LogDataFactory.newInstance(
831        ErrorCode.BUILDSYSTEM_COMPILE_ABC_FAIL,
832        'Compile abc files failed.'
833      ));
834    } finally {
835      this.terminateAllWorkers();
836    }
837  }
838
839  public async generateDeclarationParallell(): Promise<void> {
840    this.generateModuleInfos();
841    this.generateArkTSConfigForModules();
842
843    if (!cluster.isPrimary) {
844      return;
845    }
846
847    try {
848      this.setupCluster(cluster, {
849        clearExitListeners: true,
850        execPath: path.resolve(__dirname, 'declgen_worker.js'),
851      });
852      await this.dispatchTasks();
853      this.logger.printInfo('All declaration generation tasks complete.');
854    } catch (error) {
855      this.logger.printError(LogDataFactory.newInstance(
856        ErrorCode.BUILDSYSTEM_DECLGEN_FAIL,
857        'Generate declaration files failed.'
858      ));
859    } finally {
860      this.terminateAllWorkers();
861    }
862  }
863
864  private async dispatchTasks(): Promise<void> {
865    const numCPUs = os.cpus().length;
866    const taskQueue = Array.from(this.compileFiles.values());
867
868    const configuredWorkers = this.buildConfig?.maxWorkers;
869    const defaultWorkers = DEFAULT_WOKER_NUMS;
870
871    let effectiveWorkers: number;
872
873    if (configuredWorkers) {
874      effectiveWorkers = Math.min(configuredWorkers, numCPUs - 1);
875    } else {
876      effectiveWorkers = Math.min(defaultWorkers, numCPUs - 1);
877    }
878
879    const maxWorkers = Math.min(taskQueue.length, effectiveWorkers);
880
881    const chunkSize = Math.ceil(taskQueue.length / maxWorkers);
882    const serializableConfig = this.getSerializableConfig();
883    const workerExitPromises: Promise<void>[] = [];
884
885    const moduleInfosArray = Array.from(this.moduleInfos.entries());
886
887    for (let i = 0; i < maxWorkers; i++) {
888      const taskChunk = taskQueue.slice(i * chunkSize, (i + 1) * chunkSize);
889      const worker = cluster.fork();
890
891      this.setupWorkerMessageHandler(worker);
892      worker.send({ taskList: taskChunk, buildConfig: serializableConfig, moduleInfos: moduleInfosArray });
893
894      const exitPromise = new Promise<void>((resolve, reject) => {
895        worker.on('exit', (status) => status === 0 ? resolve() : reject());
896      });
897
898      workerExitPromises.push(exitPromise);
899    }
900
901    await Promise.all(workerExitPromises);
902  }
903
904  private setupWorkerMessageHandler(worker: Worker): void {
905    worker.on('message', (message: {
906      success: boolean;
907      filePath?: string;
908      error?: string;
909      isDeclFile?: boolean;
910    }) => {
911      if (message.success) {
912        return;
913      }
914      if (message.isDeclFile) {
915        this.logger.printError(LogDataFactory.newInstance(
916          ErrorCode.BUILDSYSTEM_DECLGEN_FAIL,
917          'Generate declaration files failed in worker.',
918          message.error || 'Unknown error',
919          message.filePath
920        ));
921        return;
922      }
923      this.logger.printError(LogDataFactory.newInstance(
924        ErrorCode.BUILDSYSTEM_COMPILE_ABC_FAIL,
925        'Compile abc files failed in worker.',
926        message.error || 'Unknown error',
927        message.filePath
928      ));
929    });
930  }
931
932  private getSerializableConfig(): Object {
933    const ignoreList = [
934      'arkts',
935    ];
936    const jsonStr = JSON.stringify(this.buildConfig, (key, value) => {
937      if (typeof value === 'bigint') {
938        return undefined;
939      }
940      //remove useless data from buildConfig
941      if (ignoreList.includes(key)) {
942        return undefined;
943      }
944      return value;
945    });
946    return JSON.parse(jsonStr);
947  }
948
949  setupCluster(cluster: Cluster, options: SetupClusterOptions): void {
950    const {
951      clearExitListeners,
952      execPath,
953      execArgs = [],
954    } = options;
955
956    if (clearExitListeners) {
957      cluster.removeAllListeners('exit');
958    }
959
960    cluster.setupPrimary({
961      exec: execPath,
962      execArgv: execArgs,
963    });
964  }
965}
966