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