From d3d6a5e117448d99394b393093854101e7513b00 Mon Sep 17 00:00:00 2001 From: tension <1113989231@qq.com> Date: Thu, 11 Sep 2025 20:56:15 +0800 Subject: [PATCH] Pick0702to0728 Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICXLXM?from=project-issue Signed-off-by: zhangli <1113989231@qq.com> Change-id: ac3af9f45d874c15c11a1ce49b318a52aza4e96cd --- ets2panda/driver/build_system/README.md | 10 +- .../build_system/src/build/base_mode.ts | 227 ++++------- .../src/build/build_framework_mode.ts | 2 +- .../src/build/compile_thread_worker.ts | 2 +- .../build_system/src/build/compile_worker.ts | 153 +++---- .../build_system/src/build/declgen_worker.ts | 177 ++++---- .../src/build/generate_arktsconfig.ts | 25 +- .../driver/build_system/src/error_code.ts | 4 +- .../src/init/init_koala_modules.ts | 10 +- .../src/init/process_build_config.ts | 15 +- .../build_system/src/plugins/FileManager.ts | 6 +- .../src/plugins/KitImportTransformer.ts | 2 +- .../src/plugins/plugins_driver.ts | 20 +- .../driver/build_system/src/pre_define.ts | 2 +- ets2panda/driver/build_system/src/types.ts | 16 + .../build_system/src/util/TaskManager.ts | 250 ++++++++++++ .../driver/build_system/src/util/utils.ts | 239 +++++++++++ .../src/util/worker_exit_handler.ts | 79 ++++ .../build_system/src/utils/record_time_mem.ts | 113 ++++++ .../test/ut/base_modeTest/base_mode.test.ts | 308 ++++---------- .../build_framework_mode.test.ts | 2 + .../compile_WorkerTest/compile_worker.test.ts | 152 ++++--- .../compile_thread_worker.test.ts | 13 +- .../declgen_workerTest/declgen_worker.test.ts | 142 ++++--- .../ut/fileManagerTest/filemanager.test.ts | 2 +- .../generate_arktsconfig.test.ts | 378 ++---------------- .../build_system/test/ut/mock/mockData.ts | 15 +- .../process_build_config.test.ts | 48 ++- .../build_system/test/ut/safeRealpath.test.ts | 2 +- .../test/ut/utilsTest/utils.test.ts | 8 +- 30 files changed, 1394 insertions(+), 1028 deletions(-) create mode 100644 ets2panda/driver/build_system/src/util/TaskManager.ts create mode 100644 ets2panda/driver/build_system/src/util/utils.ts create mode 100644 ets2panda/driver/build_system/src/util/worker_exit_handler.ts create mode 100644 ets2panda/driver/build_system/src/utils/record_time_mem.ts diff --git a/ets2panda/driver/build_system/README.md b/ets2panda/driver/build_system/README.md index 667ca25c5d..9d6e98d811 100644 --- a/ets2panda/driver/build_system/README.md +++ b/ets2panda/driver/build_system/README.md @@ -70,4 +70,12 @@ To run tests: ```bash npm run ut_test npm run plugin_test -``` \ No newline at end of file +``` + +## Performance Analysis +The switch of performance analysis is located in the file arkcompiler/ets_frontend/ets2panda/driver/build_system/src/utils/record_time_mem.ts +To open the switch, change recordType to ON_TYPE. +``` +this.recordType = recordType ?? RECORD_TYPE.ON_TYPE +``` +And the report, named bs_record_perf.csv, is located in the project root directory. \ No newline at end of file diff --git a/ets2panda/driver/build_system/src/build/base_mode.ts b/ets2panda/driver/build_system/src/build/base_mode.ts index 0117bdc06b..231eaa7349 100644 --- a/ets2panda/driver/build_system/src/build/base_mode.ts +++ b/ets2panda/driver/build_system/src/build/base_mode.ts @@ -18,17 +18,11 @@ import * as path from 'path'; import * as fs from 'fs'; import * as child_process from 'child_process'; import * as crypto from 'crypto'; - - -import cluster, { - Cluster, - Worker, -} from 'cluster'; import { Worker as ThreadWorker } from 'worker_threads'; + import { ABC_SUFFIX, ARKTSCONFIG_JSON_FILE, - DEFAULT_WOKER_NUMS, DECL_ETS_SUFFIX, DECL_TS_SUFFIX, DEPENDENCY_INPUT_FILE, @@ -48,8 +42,9 @@ import { ensurePathExists, getFileHash, isMac, - isMixCompileProject -} from '../utils'; + isMixCompileProject, + serializeWithIgnore +} from '../util/utils'; import { PluginDriver, PluginHook @@ -72,15 +67,26 @@ import { JobInfo, KPointer, ModuleInfo, - ES2PANDA_MODE + ES2PANDA_MODE, + CompilePayload } from '../types'; import { ArkTSConfig, ArkTSConfigGenerator } from './generate_arktsconfig'; -import { SetupClusterOptions } from '../types'; import { KitImportTransformer } from '../plugins/KitImportTransformer'; - +import { + BS_PERF_FILE_NAME, + CompileSingleData, + RECORDE_COMPILE_NODE, + RECORDE_MODULE_NODE, + RECORDE_RUN_NODE +} from '../utils/record_time_mem'; +import { TaskManager } from '../util/TaskManager'; +import { + handleCompileWorkerExit, + handleDeclgenWorkerExit +} from '../util/worker_exit_handler'; import { initKoalaModules } from '../init/init_koala_modules'; export abstract class BaseMode { @@ -119,6 +125,7 @@ export abstract class BaseMode { public byteCodeHar: boolean; public es2pandaMode: number; public skipDeclCheck: boolean; + public genDeclAnnotations: boolean; constructor(buildConfig: BuildConfig) { this.buildConfig = buildConfig; @@ -160,6 +167,7 @@ export abstract class BaseMode { : ES2PANDA_MODE.RUN ); this.skipDeclCheck = buildConfig?.skipDeclCheck as boolean ?? true; + this.genDeclAnnotations = buildConfig?.genDeclAnnotations as boolean ?? true; } public declgen(fileInfo: CompileFileInfo): void { @@ -187,7 +195,7 @@ export abstract class BaseMode { const declEtsOutputDir = path.dirname(declEtsOutputPath); const staticRecordRelativePath = changeFileExtension( path.relative(declEtsOutputDir, staticRecordPath).replace(/\\/g, '\/'), - '', + "", DECL_TS_SUFFIX ); createFileIfNotExists(staticRecordPath, STATIC_RECORD_FILE_CONTENT); @@ -221,7 +229,8 @@ export abstract class BaseMode { etsOutputPath, false, false, - staticRecordRelativePath + staticRecordRelativePath, + this.genDeclAnnotations ); // Generate 1.0 declaration files & 1.0 glue code this.logger.printInfo('declaration files generated'); } catch (error) { @@ -335,7 +344,10 @@ export abstract class BaseMode { } } - public compileMultiFiles(filePaths: string[], moduleInfo: ModuleInfo): void { + public compileMultiFiles(moduleInfo: ModuleInfo): void { + let compileSingleData = new CompileSingleData(path.join(path.resolve(), BS_PERF_FILE_NAME)); + compileSingleData.record(RECORDE_COMPILE_NODE.PROCEED_PARSE); + const intermediateFilePath = path.resolve(this.cacheDir, MERGED_INTERMEDIATE_FILE); this.abcFiles.clear(); this.abcFiles.add(intermediateFilePath); @@ -368,6 +380,8 @@ export abstract class BaseMode { arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, arktsGlobal.compilerContext.peer); this.logger.printInfo('es2panda proceedToState parsed'); + compileSingleData.record(RECORDE_COMPILE_NODE.PLUGIN_PARSE, RECORDE_COMPILE_NODE.PROCEED_PARSE); + let ast = arkts.EtsScript.fromContext(); if (this.buildConfig.aliasConfig && Object.keys(this.buildConfig.aliasConfig).length > 0) { @@ -387,9 +401,11 @@ export abstract class BaseMode { PluginDriver.getInstance().getPluginContext().setArkTSAst(ast); PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); this.logger.printInfo('plugin parsed finished'); + compileSingleData.record(RECORDE_COMPILE_NODE.PROCEED_CHECK, RECORDE_COMPILE_NODE.PLUGIN_PARSE); arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, arktsGlobal.compilerContext.peer); this.logger.printInfo('es2panda proceedToState checked'); + compileSingleData.record(RECORDE_COMPILE_NODE.PLUGIN_CHECK, RECORDE_COMPILE_NODE.PROCEED_CHECK); if (this.hasMainModule && (this.byteCodeHar || this.moduleType === OHOS_MODULE_TYPE.SHARED)) { for (const sourceFilePath of this.buildConfig.compileFiles) { @@ -409,9 +425,11 @@ export abstract class BaseMode { PluginDriver.getInstance().getPluginContext().setArkTSAst(ast); PluginDriver.getInstance().runPluginHook(PluginHook.CHECKED); this.logger.printInfo('plugin checked finished'); + compileSingleData.record(RECORDE_COMPILE_NODE.BIN_GENERATE, RECORDE_COMPILE_NODE.PLUGIN_CHECK); arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_BIN_GENERATED, arktsGlobal.compilerContext.peer); this.logger.printInfo('es2panda bin generated'); + compileSingleData.record(RECORDE_COMPILE_NODE.CFG_DESTROY, RECORDE_COMPILE_NODE.BIN_GENERATE); } catch (error) { errorStatus = true; throw error; @@ -422,6 +440,8 @@ export abstract class BaseMode { } PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN); arkts.destroyConfig(arktsGlobal.config); + compileSingleData.record(RECORDE_COMPILE_NODE.END, RECORDE_COMPILE_NODE.CFG_DESTROY); + compileSingleData.writeSumSingle(path.resolve()); } } @@ -432,7 +452,7 @@ export abstract class BaseMode { linkerInputContent += abcFile + os.EOL; }); fs.writeFileSync(linkerInputFile, linkerInputContent); - + this.abcLinkerCmd.push('--strip-unused'); this.abcLinkerCmd.push('--output'); this.abcLinkerCmd.push('"' + this.mergedAbcFile + '"'); this.abcLinkerCmd.push('--'); @@ -848,10 +868,17 @@ export abstract class BaseMode { } protected generateModuleInfos(): void { + let compileSingleData = new CompileSingleData(path.join(path.resolve(), BS_PERF_FILE_NAME)); + compileSingleData.record(RECORDE_MODULE_NODE.COLLECT_INFO); this.collectModuleInfos(); + compileSingleData.record(RECORDE_MODULE_NODE.GEN_CONFIG, RECORDE_MODULE_NODE.COLLECT_INFO); this.generateArkTSConfigForModules(); + compileSingleData.record(RECORDE_MODULE_NODE.CLT_FILES, RECORDE_MODULE_NODE.GEN_CONFIG); this.collectCompileFiles(); + compileSingleData.record(RECORDE_MODULE_NODE.SAVE_CACHE, RECORDE_MODULE_NODE.CLT_FILES); this.saveHashCache(); + compileSingleData.record(RECORDE_MODULE_NODE.END, RECORDE_MODULE_NODE.SAVE_CACHE); + compileSingleData.writeSumSingle(path.resolve()); } public async generateDeclaration(): Promise { @@ -868,6 +895,8 @@ export abstract class BaseMode { } public async run(): Promise { + let compileSingleData = new CompileSingleData(path.join(path.resolve(), BS_PERF_FILE_NAME)); + compileSingleData.record(RECORDE_RUN_NODE.GEN_MODULE); this.generateModuleInfos(); const compilePromises: Promise[] = []; @@ -878,9 +907,11 @@ export abstract class BaseMode { } moduleToFile.get(fileInfo.packageName)?.push(fileInfo.filePath); }); + compileSingleData.record(RECORDE_RUN_NODE.COMPILE_FILES, RECORDE_RUN_NODE.GEN_MODULE); try { //@ts-ignore - this.compileMultiFiles([], this.moduleInfos.get(this.packageName)); + this.compileMultiFiles(this.moduleInfos.get(this.packageName)); + compileSingleData.record(RECORDE_RUN_NODE.END, RECORDE_RUN_NODE.COMPILE_FILES); } catch (error) { if (error instanceof Error) { const logData: LogData = LogDataFactory.newInstance( @@ -891,16 +922,12 @@ export abstract class BaseMode { this.logger.printErrorAndExit(logData); } } - + this.mergeAbcFiles(); + compileSingleData.writeSumSingle(path.resolve()); } // -- runParallell code begins -- - private terminateAllWorkers(): void { - Object.values(cluster.workers || {}).forEach(worker => { - worker?.kill(); - }); - }; public generatedependencyFileMap(): void { if (this.enableDeclgenEts2Ts) { @@ -968,18 +995,21 @@ export abstract class BaseMode { public async runParallel(): Promise { this.generateModuleInfos(); - const isPrimary = cluster.isPrimary ?? cluster.isMaster; // Adapt to node-v14 - if (!isPrimary) { - return; - } + const taskManager = new TaskManager(path.resolve(__dirname, 'compile_worker.js'), handleCompileWorkerExit); try { - this.setupCluster(cluster, { - clearExitListeners: true, - execPath: path.resolve(__dirname, 'compile_worker.js'), - }); - await this.dispatchTasks(); - this.logger.printInfo('All tasks complete, merging...'); + taskManager.startWorkers(); + + const taskPromises = Array.from(this.compileFiles.values()).map(task => + taskManager.submitTask({ + fileInfo: task, + buildConfig: this.getSerializableConfig() as BuildConfig, + moduleInfos: Array.from(this.moduleInfos.entries()) + }) + ); + + await Promise.all(taskPromises); + this.mergeAbcFiles(); } catch (error) { this.logger.printError(LogDataFactory.newInstance( @@ -987,137 +1017,46 @@ export abstract class BaseMode { 'Compile abc files failed.' )); } finally { - this.terminateAllWorkers(); + await taskManager.shutdown(); } } public async generateDeclarationParallell(): Promise { this.generateModuleInfos(); - this.generateArkTSConfigForModules(); - const isPrimary = cluster.isPrimary ?? cluster.isMaster; - if (!isPrimary) { - return; - } + const taskManager = new TaskManager( + path.resolve(__dirname, 'declgen_worker.js'), + handleDeclgenWorkerExit + ); try { - this.setupCluster(cluster, { - clearExitListeners: true, - execPath: path.resolve(__dirname, 'declgen_worker.js'), - }); - await this.dispatchTasks(); + taskManager.startWorkers(); + + const taskPromises = Array.from(this.compileFiles.values()).map(task => + taskManager.submitTask({ + fileInfo: task, + buildConfig: this.getSerializableConfig() as BuildConfig, + moduleInfos: Array.from(this.moduleInfos.entries()) + }) + ); + + await Promise.allSettled(taskPromises); + this.logger.printInfo('All declaration generation tasks complete.'); } catch (error) { this.logger.printError(LogDataFactory.newInstance( ErrorCode.BUILDSYSTEM_DECLGEN_FAIL, - 'Generate declaration files failed.' + `Generate declaration files failed.\n${(error as Error)?.message || error}` )); } finally { - this.terminateAllWorkers(); + await taskManager.shutdown(); } } - private async dispatchTasks(): Promise { - const numCPUs = os.cpus().length; - const taskQueue = Array.from(this.compileFiles.values()); - - const configuredWorkers = this.buildConfig?.maxWorkers; - const defaultWorkers = DEFAULT_WOKER_NUMS; - - let effectiveWorkers: number; - - if (configuredWorkers) { - effectiveWorkers = Math.min(configuredWorkers, numCPUs - 1); - } else { - effectiveWorkers = Math.min(defaultWorkers, numCPUs - 1); - } - - const maxWorkers = Math.min(taskQueue.length, effectiveWorkers); - - const chunkSize = Math.ceil(taskQueue.length / maxWorkers); - const serializableConfig = this.getSerializableConfig(); - const workerExitPromises: Promise[] = []; - - const moduleInfosArray = Array.from(this.moduleInfos.entries()); - - for (let i = 0; i < maxWorkers; i++) { - const taskChunk = taskQueue.slice(i * chunkSize, (i + 1) * chunkSize); - const worker = cluster.fork(); - - this.setupWorkerMessageHandler(worker); - worker.send({ taskList: taskChunk, buildConfig: serializableConfig, moduleInfos: moduleInfosArray }); - - const exitPromise = new Promise((resolve, reject) => { - worker.on('exit', (status) => status === 0 ? resolve() : reject()); - }); - - workerExitPromises.push(exitPromise); - } - - await Promise.all(workerExitPromises); - } - - private setupWorkerMessageHandler(worker: Worker): void { - worker.on('message', (message: { - success: boolean; - filePath?: string; - error?: string; - isDeclFile?: boolean; - }) => { - if (message.success) { - return; - } - if (message.isDeclFile) { - this.logger.printError(LogDataFactory.newInstance( - ErrorCode.BUILDSYSTEM_DECLGEN_FAIL, - 'Generate declaration files failed in worker.', - message.error || 'Unknown error', - message.filePath - )); - return; - } - this.logger.printError(LogDataFactory.newInstance( - ErrorCode.BUILDSYSTEM_COMPILE_ABC_FAIL, - 'Compile abc files failed in worker.', - message.error || 'Unknown error', - message.filePath - )); - }); - } - private getSerializableConfig(): Object { - const ignoreList = [ - 'arkts', - ]; - const jsonStr = JSON.stringify(this.buildConfig, (key, value) => { - if (typeof value === 'bigint') { - return undefined; - } - //remove useless data from buildConfig - if (ignoreList.includes(key)) { - return undefined; - } - return value; - }); - return JSON.parse(jsonStr); + return serializeWithIgnore(this.buildConfig, ['arkts']); } - setupCluster(cluster: Cluster, options: SetupClusterOptions): void { - const { - clearExitListeners, - execPath, - execArgs = [], - } = options; - - if (clearExitListeners) { - cluster.removeAllListeners('exit'); - } - const setupFn = cluster.setupPrimary ?? cluster.setupMaster; // Adapt to node-v14 - setupFn({ - exec: execPath, - execArgv: execArgs, - }); - } // -- runParallell code ends -- diff --git a/ets2panda/driver/build_system/src/build/build_framework_mode.ts b/ets2panda/driver/build_system/src/build/build_framework_mode.ts index 5840940c77..ca40ca6642 100644 --- a/ets2panda/driver/build_system/src/build/build_framework_mode.ts +++ b/ets2panda/driver/build_system/src/build/build_framework_mode.ts @@ -16,7 +16,7 @@ import { BaseMode } from './base_mode'; import { BuildConfig, CompileFileInfo, ModuleInfo } from '../types'; import { LogData, LogDataFactory } from '../logger'; -import { changeFileExtension } from '../utils'; +import { changeFileExtension } from '../util/utils'; import { ABC_SUFFIX } from '../pre_define'; import path from 'path'; import { ErrorCode } from '../error_code'; diff --git a/ets2panda/driver/build_system/src/build/compile_thread_worker.ts b/ets2panda/driver/build_system/src/build/compile_thread_worker.ts index b603334549..3d213b7a16 100644 --- a/ets2panda/driver/build_system/src/build/compile_thread_worker.ts +++ b/ets2panda/driver/build_system/src/build/compile_thread_worker.ts @@ -19,7 +19,7 @@ import * as path from 'path'; import { changeFileExtension, ensurePathExists -} from '../utils'; +} from '../util/utils'; import { DECL_ETS_SUFFIX, KOALA_WRAPPER_PATH_FROM_SDK diff --git a/ets2panda/driver/build_system/src/build/compile_worker.ts b/ets2panda/driver/build_system/src/build/compile_worker.ts index a93ea46d22..fbb0b497c0 100644 --- a/ets2panda/driver/build_system/src/build/compile_worker.ts +++ b/ets2panda/driver/build_system/src/build/compile_worker.ts @@ -16,10 +16,12 @@ import { CompileFileInfo, ModuleInfo } from '../types'; import * as fs from 'fs'; import * as path from 'path'; + import { changeFileExtension, - ensurePathExists -} from '../utils'; + ensurePathExists, + serializeWithIgnore +} from '../util/utils'; import { DECL_ETS_SUFFIX, KOALA_WRAPPER_PATH_FROM_SDK @@ -30,93 +32,104 @@ import { BUILD_MODE, OHOS_MODULE_TYPE } from '../types'; -import { Logger } from '../logger'; import { initKoalaModules } from '../init/init_koala_modules'; +import { LogData, LogDataFactory, Logger } from '../logger'; +import { ErrorCode } from '../error_code'; +import { KitImportTransformer } from '../plugins/KitImportTransformer' -process.on('message', (message: { - taskList: CompileFileInfo[]; +process.on('message', async (message: { + id: number; + fileInfo: CompileFileInfo; buildConfig: BuildConfig; - moduleInfos: Array<[string, ModuleInfo]>; }) => { - if (!process.send) { - throw new Error('process.send is undefined. This worker must be run as a forked process.'); - } - const { taskList, buildConfig, moduleInfos } = message; - const isDebug = buildConfig.buildMode === BUILD_MODE.DEBUG; + const id = message.id; + //@ts-ignore + const { fileInfo, buildConfig } = message.payload; Logger.getInstance(buildConfig); PluginDriver.getInstance().initPlugins(buildConfig); let { arkts, arktsGlobal } = initKoalaModules(buildConfig) - const { KitImportTransformer } = require('../plugins/KitImportTransformer'); + let errorStatus = false; - for (const fileInfo of taskList) { - let errorStatus = false; - try { - ensurePathExists(fileInfo.abcFilePath); - const source = fs.readFileSync(fileInfo.filePath).toString(); + try { + Logger.getInstance(buildConfig); + PluginDriver.getInstance().initPlugins(buildConfig); + const isDebug = buildConfig.buildMode === BUILD_MODE.DEBUG; - const ets2pandaCmd = [ - '_', '--extension', 'ets', - '--arktsconfig', fileInfo.arktsConfigFile, - '--output', fileInfo.abcFilePath, - ]; - if (isDebug) { - ets2pandaCmd.push('--debug-info'); - ets2pandaCmd.push('--opt-level=0'); - } - ets2pandaCmd.push(fileInfo.filePath); + ensurePathExists(fileInfo.abcFilePath); + const source = fs.readFileSync(fileInfo.filePath).toString(); - arktsGlobal.filePath = fileInfo.filePath; - arktsGlobal.config = arkts.Config.create(ets2pandaCmd).peer; - arktsGlobal.compilerContext = arkts.Context.createFromString(source); + let ets2pandaCmd = [ + '_', '--extension', 'ets', + '--arktsconfig', fileInfo.arktsConfigFile, + '--output', fileInfo.abcFilePath + ]; + if (isDebug) { + ets2pandaCmd.push('--debug-info'); + ets2pandaCmd.push('--opt-level=0'); + } + ets2pandaCmd.push(fileInfo.filePath); - PluginDriver.getInstance().getPluginContext().setArkTSProgram(arktsGlobal.compilerContext.program); + arktsGlobal.filePath = fileInfo.filePath; + arktsGlobal.config = arkts.Config.create(ets2pandaCmd).peer; + arktsGlobal.compilerContext = arkts.Context.createFromString(source); - arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, arktsGlobal.compilerContext.peer); - if (buildConfig.aliasConfig && Object.keys(buildConfig.aliasConfig).length > 0) { - // if aliasConfig is set, transform aliasName@kit.xxx to default@ohos.xxx through the plugin - let ast = arkts.EtsScript.fromContext(); - let transformAst = new KitImportTransformer( - arkts, - arktsGlobal.compilerContext.program, - buildConfig.buildSdkPath, - buildConfig.aliasConfig - ).transform(ast); - PluginDriver.getInstance().getPluginContext().setArkTSAst(transformAst); - } - PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); + PluginDriver.getInstance().getPluginContext().setArkTSProgram(arktsGlobal.compilerContext.program); - arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, arktsGlobal.compilerContext.peer); - if (buildConfig.hasMainModule && (buildConfig.byteCodeHar || buildConfig.moduleType === OHOS_MODULE_TYPE.SHARED)) { - const filePathFromModuleRoot = path.relative(buildConfig.moduleRootPath, fileInfo.filePath); - const declEtsOutputPath = changeFileExtension( - path.join(buildConfig.declgenV2OutPath!, filePathFromModuleRoot), - DECL_ETS_SUFFIX - ); - ensurePathExists(declEtsOutputPath); - arkts.generateStaticDeclarationsFromContext(declEtsOutputPath); - } - PluginDriver.getInstance().runPluginHook(PluginHook.CHECKED); + arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, arktsGlobal.compilerContext.peer); + if (buildConfig.aliasConfig && Object.keys(buildConfig.aliasConfig).length > 0) { + // if aliasConfig is set, transform aliasName@kit.xxx to default@ohos.xxx through the plugin + let ast = arkts.EtsScript.fromContext(); + let transformAst = new KitImportTransformer( + arkts, + arktsGlobal.compilerContext.program, + buildConfig.buildSdkPath, + buildConfig.aliasConfig + ).transform(ast); + PluginDriver.getInstance().getPluginContext().setArkTSAst(transformAst); + } + PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); - arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_BIN_GENERATED, arktsGlobal.compilerContext.peer); - } catch (error) { - errorStatus = true; - if (error instanceof Error) { + arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, arktsGlobal.compilerContext.peer); + if (buildConfig.hasMainModule && (buildConfig.byteCodeHar || buildConfig.moduleType === OHOS_MODULE_TYPE.SHARED)) { + const filePathFromModuleRoot = path.relative(buildConfig.moduleRootPath, fileInfo.filePath); + const declEtsOutputPath = changeFileExtension( + path.join(buildConfig.declgenV2OutPath!, filePathFromModuleRoot), + DECL_ETS_SUFFIX + ); + ensurePathExists(declEtsOutputPath); + arkts.generateStaticDeclarationsFromContext(declEtsOutputPath); + } + PluginDriver.getInstance().runPluginHook(PluginHook.CHECKED); + + arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_BIN_GENERATED, arktsGlobal.compilerContext.peer); + + if (process.send) { + process.send({ id, success: true, shouldKill: false }); + } + } catch (error) { + errorStatus = true; + if (error instanceof Error) { + const logData: LogData = LogDataFactory.newInstance( + ErrorCode.BUILDSYSTEM_COMPILE_ABC_FAIL, + 'Compile abc files failed.', + error.message, + fileInfo.filePath + ); + if (process.send) { process.send({ + id, success: false, - filePath: fileInfo.filePath, - error: 'Compile abc files failed.\n' + error.message + shouldKill: true, + error: serializeWithIgnore(logData) }); } - } finally { - if (!errorStatus) { - // when error occur,wrapper will destroy context. - arktsGlobal.es2panda._DestroyContext(arktsGlobal.compilerContext.peer); - } - PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN); - arkts.destroyConfig(arktsGlobal.config); } + } finally { + if (!errorStatus) { + arktsGlobal.es2panda._DestroyContext(arktsGlobal.compilerContext.peer); + } + PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN); + arkts.destroyConfig(arktsGlobal.config); } - - process.exit(0); }); diff --git a/ets2panda/driver/build_system/src/build/declgen_worker.ts b/ets2panda/driver/build_system/src/build/declgen_worker.ts index 12d16c66a5..acdaae4687 100644 --- a/ets2panda/driver/build_system/src/build/declgen_worker.ts +++ b/ets2panda/driver/build_system/src/build/declgen_worker.ts @@ -15,15 +15,21 @@ import { CompileFileInfo, ModuleInfo } from '../types'; import { BuildConfig } from '../types'; -import { Logger } from '../logger'; +import { + Logger, + LogData, + LogDataFactory +} from '../logger'; +import { ErrorCode } from '../error_code'; import * as fs from 'fs'; import * as path from 'path'; import { changeDeclgenFileExtension, changeFileExtension, createFileIfNotExists, + serializeWithIgnore, ensurePathExists -} from '../utils'; +} from '../util/utils'; import { DECL_ETS_SUFFIX, DECL_TS_SUFFIX, @@ -34,109 +40,110 @@ import { import { PluginDriver, PluginHook } from '../plugins/plugins_driver'; import { initKoalaModules } from '../init/init_koala_modules'; -process.on('message', (message: { - taskList: CompileFileInfo[]; - buildConfig: BuildConfig; - moduleInfos: Array<[string, ModuleInfo]>; +process.on('message', async (message: { + id: string; + payload: { + fileInfo: CompileFileInfo; + buildConfig: BuildConfig; + moduleInfos: Array<[string, ModuleInfo]>; + }; }) => { if (!process.send) { throw new Error('process.send is undefined. This worker must be run as a forked process.'); } - const { taskList, buildConfig, moduleInfos } = message; - const moduleInfosMap = new Map(moduleInfos); + const { id, payload } = message; + const { fileInfo, buildConfig, moduleInfos } = payload; + const moduleInfosMap = new Map(moduleInfos); const logger = Logger.getInstance(buildConfig); const pluginDriver = PluginDriver.getInstance(); pluginDriver.initPlugins(buildConfig); let { arkts, arktsGlobal } = initKoalaModules(buildConfig) + let errorStatus = false; + let continueOnError = buildConfig.continueOnError ?? true; + try { + const source = fs.readFileSync(fileInfo.filePath, 'utf8'); + const moduleInfo = moduleInfosMap.get(fileInfo.packageName)!; - for (const fileInfo of taskList) { - let errorStatus = false; - try { - const source = fs.readFileSync(fileInfo.filePath, 'utf8'); - let moduleInfo = moduleInfosMap.get(fileInfo.packageName)!; - let filePathFromModuleRoot: string = path.relative(moduleInfo.moduleRootPath, fileInfo.filePath); - let declEtsOutputPath: string = path.join( - moduleInfo.declgenV1OutPath as string, - moduleInfo.packageName, - filePathFromModuleRoot - ); - declEtsOutputPath = changeDeclgenFileExtension(declEtsOutputPath, DECL_ETS_SUFFIX); - let etsOutputPath: string = path.join( - moduleInfo.declgenBridgeCodePath as string, - moduleInfo.packageName, - filePathFromModuleRoot - ); - etsOutputPath = changeDeclgenFileExtension(etsOutputPath, TS_SUFFIX); + let filePathFromModuleRoot = path.relative(moduleInfo.moduleRootPath, fileInfo.filePath); + let declEtsOutputPath = path.join(moduleInfo.declgenV1OutPath!, moduleInfo.packageName, filePathFromModuleRoot); + declEtsOutputPath = changeDeclgenFileExtension(declEtsOutputPath, DECL_ETS_SUFFIX); - ensurePathExists(declEtsOutputPath); - ensurePathExists(etsOutputPath); + let etsOutputPath = path.join(moduleInfo.declgenBridgeCodePath!, moduleInfo.packageName, filePathFromModuleRoot); + etsOutputPath = changeDeclgenFileExtension(etsOutputPath, TS_SUFFIX); - const staticRecordPath = path.join( - moduleInfo.declgenV1OutPath as string, - STATIC_RECORD_FILE - ) - const declEtsOutputDir = path.dirname(declEtsOutputPath); - const staticRecordRelativePath = changeFileExtension( - path.relative(declEtsOutputDir, staticRecordPath).replace(/\\/g, '\/'), - '', - DECL_TS_SUFFIX - ); - createFileIfNotExists(staticRecordPath, STATIC_RECORD_FILE_CONTENT); + ensurePathExists(declEtsOutputPath); + ensurePathExists(etsOutputPath); - arktsGlobal.filePath = fileInfo.filePath; - arktsGlobal.config = arkts.Config.create([ - '_', - '--extension', - 'ets', - '--arktsconfig', - fileInfo.arktsConfigFile, - fileInfo.filePath - ]).peer; - arktsGlobal.compilerContext = arkts.Context.createFromStringWithHistory(source); - pluginDriver.getPluginContext().setArkTSProgram(arktsGlobal.compilerContext.program); - const skipDeclCheck = buildConfig?.skipDeclCheck as boolean ?? true; + const staticRecordPath = path.join(moduleInfo.declgenV1OutPath!, STATIC_RECORD_FILE); + const declEtsOutputDir = path.dirname(declEtsOutputPath); + const staticRecordRelativePath = changeFileExtension( + path.relative(declEtsOutputDir, staticRecordPath).replace(/\\/g, '/'), + '', + DECL_TS_SUFFIX + ); + createFileIfNotExists(staticRecordPath, STATIC_RECORD_FILE_CONTENT); + + arktsGlobal.filePath = fileInfo.filePath; + arktsGlobal.config = arkts.Config.create([ + '_', + '--extension', + 'ets', + '--arktsconfig', + fileInfo.arktsConfigFile, + fileInfo.filePath + ]).peer; - arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, arktsGlobal.compilerContext.peer, skipDeclCheck); + arktsGlobal.compilerContext = arkts.Context.createFromStringWithHistory(source); + pluginDriver.getPluginContext().setArkTSProgram(arktsGlobal.compilerContext.program); + const skipDeclCheck = buildConfig?.skipDeclCheck ?? true; + const genDeclAnnotations = buildConfig?.genDeclAnnotations ?? true; - let ast = arkts.EtsScript.fromContext(); - pluginDriver.getPluginContext().setArkTSAst(ast); - pluginDriver.runPluginHook(PluginHook.PARSED); + arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_PARSED, arktsGlobal.compilerContext.peer, skipDeclCheck); + let ast = arkts.EtsScript.fromContext(); + pluginDriver.getPluginContext().setArkTSAst(ast); + pluginDriver.runPluginHook(PluginHook.PARSED); - arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, arktsGlobal.compilerContext.peer, skipDeclCheck); + arkts.proceedToState(arkts.Es2pandaContextState.ES2PANDA_STATE_CHECKED, arktsGlobal.compilerContext.peer, skipDeclCheck); + ast = arkts.EtsScript.fromContext(); + pluginDriver.getPluginContext().setArkTSAst(ast); + pluginDriver.runPluginHook(PluginHook.CHECKED); - ast = arkts.EtsScript.fromContext(); - pluginDriver.getPluginContext().setArkTSAst(ast); - pluginDriver.runPluginHook(PluginHook.CHECKED); + arkts.generateTsDeclarationsFromContext( + declEtsOutputPath, + etsOutputPath, + false, + false, + staticRecordRelativePath, + genDeclAnnotations + ); - arkts.generateTsDeclarationsFromContext( - declEtsOutputPath, - etsOutputPath, - false, - false, - staticRecordRelativePath - ); // Generate 1.0 declaration files & 1.0 glue code - logger.printInfo('declaration files generated'); + logger.printInfo(`[declgen] ${fileInfo.filePath} processed successfully`); - process.send({ success: true, filePath: fileInfo.filePath }); - } catch (error) { - errorStatus = true; - if (error instanceof Error) { - process.send({ - success: false, - isDeclFile: true, - filePath: fileInfo.filePath, - error: 'Generate declaration files failed.\n' + error.message - }); - } - } finally { - if (!errorStatus) { - // when error occur,wrapper will destroy context. - arktsGlobal.es2panda._DestroyContext(arktsGlobal.compilerContext.peer); - } + process.send({ id, success: true, shouldKill: false }); + } catch (err) { + errorStatus = true; + if (err instanceof Error) { + const logData: LogData = LogDataFactory.newInstance( + ErrorCode.BUILDSYSTEM_DECLGEN_FAIL, + 'Declgen generates declaration files failed.', + err.message, + fileInfo.filePath + ); + process.send({ + id, + success: false, + shouldKill: !continueOnError, + error: serializeWithIgnore(logData) + }); + } + } finally { + if (!errorStatus && arktsGlobal?.compilerContext?.peer) { + arktsGlobal.es2panda._DestroyContext(arktsGlobal.compilerContext.peer); + } + if (arktsGlobal?.config) { arkts.destroyConfig(arktsGlobal.config); } } - process.exit(0); -}); \ No newline at end of file +}); diff --git a/ets2panda/driver/build_system/src/build/generate_arktsconfig.ts b/ets2panda/driver/build_system/src/build/generate_arktsconfig.ts index 2017fa8863..c9b8145109 100644 --- a/ets2panda/driver/build_system/src/build/generate_arktsconfig.ts +++ b/ets2panda/driver/build_system/src/build/generate_arktsconfig.ts @@ -31,9 +31,10 @@ import { getOhmurlByApi, hasEntry, isSubPathOf, + readFirstLineSync, safeRealpath, toUnixPath -} from '../utils'; +} from '../util/utils'; import { AliasConfig, ArkTSConfigObject, @@ -54,13 +55,15 @@ import { export class ArkTSConfig { config: ArkTSConfigObject; - constructor(moduleInfo: ModuleInfo) { + constructor(moduleInfo: ModuleInfo, cacheDir: string) { this.config = { compilerOptions: { package: moduleInfo.packageName, baseUrl: path.resolve(moduleInfo.moduleRootPath, moduleInfo.sourceRoots[0]), paths: {}, - dependencies: {} + dependencies: {}, + rootDir: path.resolve(moduleInfo.moduleRootPath), + cacheDir: path.resolve(cacheDir, moduleInfo.packageName), } }; } @@ -237,11 +240,21 @@ export class ArkTSConfigGenerator { if (!hasEntry(moduleInfo)) { return; } - + if (moduleInfo.language === LANGUAGE_VERSION.ARKTS_1_1) { return; } - + + if (!moduleInfo.entryFile || !fs.existsSync(moduleInfo.entryFile)) { + return; + } + if (moduleInfo.language === LANGUAGE_VERSION.ARKTS_HYBRID) { + const firstLine = readFirstLineSync(moduleInfo.entryFile); + if (!firstLine?.includes('use static')) { + return; + } + } + arktsconfig.addPathMappings({ [moduleInfo.packageName]: [moduleInfo.moduleRootPath] }); @@ -304,7 +317,7 @@ export class ArkTSConfigGenerator { ); this.logger.printErrorAndExit(logData); } - let arktsConfig: ArkTSConfig = new ArkTSConfig(moduleInfo); + let arktsConfig: ArkTSConfig = new ArkTSConfig(moduleInfo, this.buildConfig.cachePath); this.arktsconfigs.set(moduleInfo.packageName, arktsConfig); this.getPathSection(moduleInfo, arktsConfig); diff --git a/ets2panda/driver/build_system/src/error_code.ts b/ets2panda/driver/build_system/src/error_code.ts index fe299f17b8..529679ab83 100644 --- a/ets2panda/driver/build_system/src/error_code.ts +++ b/ets2panda/driver/build_system/src/error_code.ts @@ -43,5 +43,7 @@ export enum ErrorCode { BUILDSYSTEM_PLUGIN_ALIAS_CONFIG_PARSING_FAIL = '11410022', BUILDSYSTEM_ABC_FILE_NOT_EXIST_IN_BCHAR = '11410023', BUILDSYSTEM_ALIAS_MODULE_PATH_NOT_EXIST = '11410024', - BUILDSYSTEM_ENTRY_FILE_NOT_EXIST = '11410025' + BUILDSYSTEM_ENTRY_FILE_NOT_EXIST = "11410025", + BUILDSYSTEM_COMPILE_FAILED_IN_WORKER = "11410026", + BUILDSYSTEM_DECLGEN_FAILED_IN_WORKER = '11410027', } diff --git a/ets2panda/driver/build_system/src/init/init_koala_modules.ts b/ets2panda/driver/build_system/src/init/init_koala_modules.ts index 4c0c070a52..9c3808f3d1 100644 --- a/ets2panda/driver/build_system/src/init/init_koala_modules.ts +++ b/ets2panda/driver/build_system/src/init/init_koala_modules.ts @@ -14,9 +14,9 @@ */ -import { KOALA_WRAPPER_PATH_FROM_SDK, MEMO_PLUGIN_PATH_FROM_SDK, UI_PLUGIN_PATH_FROM_SDK } from '../pre_define'; -import { BuildConfig } from '../types'; -import path from 'path' +import { KOALA_WRAPPER_PATH_FROM_SDK, MEMO_PLUGIN_PATH_FROM_SDK, UI_PLUGIN_PATH_FROM_SDK } from "../pre_define"; +import { BuildConfig } from "../types"; +import path from "path" let koalaModule: any; @@ -46,7 +46,7 @@ export function initKoalaPlugins(projectConfig: BuildConfig): void { // TODO: need change in hvigor if (process.env.USE_KOALA_UI_PLUGIN) { - projectConfig.plugins.ArkUI = uiPluginPath + projectConfig.plugins['ArkUI'] = uiPluginPath } if (process.env.USE_KOALA_MEMO_PLUGIN) { @@ -54,7 +54,7 @@ export function initKoalaPlugins(projectConfig: BuildConfig): void { } } -export function cleanKoalaModule(): void { +export function cleanKoalaModule() { koalaModule = null; } diff --git a/ets2panda/driver/build_system/src/init/process_build_config.ts b/ets2panda/driver/build_system/src/init/process_build_config.ts index 71659ee10a..09c0d539d8 100644 --- a/ets2panda/driver/build_system/src/init/process_build_config.ts +++ b/ets2panda/driver/build_system/src/init/process_build_config.ts @@ -20,7 +20,7 @@ import { isLinux, isMac, isWindows, -} from '../utils'; +} from '../util/utils'; import { PluginDriver } from '../plugins/plugins_driver'; import { API, @@ -223,3 +223,16 @@ function initInteropSDKInfo(buildConfig: BuildConfig): void { } } } + +export function getEs2pandaPath(buildConfig:BuildConfig):string{ + const pandaSdkPath = buildConfig.pandaSdkPath ?? path.resolve(buildConfig.buildSdkPath, PANDA_SDK_PATH_FROM_SDK); + let es2pandaPath = ''; + if (isWindows()) { + es2pandaPath = path.join(pandaSdkPath, 'bin', 'es2panda.exe'); + } + + if (isMac() || isLinux()) { + es2pandaPath = path.join(pandaSdkPath, 'bin', 'es2panda'); + } + return es2pandaPath; +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/src/plugins/FileManager.ts b/ets2panda/driver/build_system/src/plugins/FileManager.ts index 9a2c8b4e18..39b93e8f71 100644 --- a/ets2panda/driver/build_system/src/plugins/FileManager.ts +++ b/ets2panda/driver/build_system/src/plugins/FileManager.ts @@ -15,9 +15,11 @@ import * as path from 'path'; import { BuildConfig, DependentModuleConfig } from '../types'; -import { toUnixPath } from '../utils'; +import { + toUnixPath, + readFirstLineSync +} from '../util/utils'; import { ETS_1_1, ETS_1_1_INTEROP, LANGUAGE_VERSION } from '../pre_define'; -import { readFirstLineSync } from '../utils'; export class FileManager { private static instance: FileManager | undefined = undefined; diff --git a/ets2panda/driver/build_system/src/plugins/KitImportTransformer.ts b/ets2panda/driver/build_system/src/plugins/KitImportTransformer.ts index a7262ac5ba..dfbf9fd788 100644 --- a/ets2panda/driver/build_system/src/plugins/KitImportTransformer.ts +++ b/ets2panda/driver/build_system/src/plugins/KitImportTransformer.ts @@ -15,10 +15,10 @@ import * as fs from 'fs'; import * as path from 'path'; + import { AliasConfig, ArkTS } from '../types'; import { Logger, - LogData, LogDataFactory } from '../logger'; import { ErrorCode } from '../error_code'; diff --git a/ets2panda/driver/build_system/src/plugins/plugins_driver.ts b/ets2panda/driver/build_system/src/plugins/plugins_driver.ts index 48336f68cc..4af2ec6aad 100644 --- a/ets2panda/driver/build_system/src/plugins/plugins_driver.ts +++ b/ets2panda/driver/build_system/src/plugins/plugins_driver.ts @@ -21,8 +21,7 @@ import { import { BuildConfig } from '../types'; import { ErrorCode } from '../error_code'; import { FileManager } from './FileManager'; -import path from 'path' -import { MEMO_PLUGIN_PATH_FROM_SDK, UI_PLUGIN_PATH_FROM_SDK } from '../pre_define'; +import { initKoalaPlugins } from '../init/init_koala_modules'; export enum PluginHook { NEW = 'afterNew', @@ -151,27 +150,12 @@ export class PluginDriver { PluginDriver.instance = undefined; } - private initKoalaPlugins(projectConfig: BuildConfig): void { - - const uiPluginPath = path.resolve(projectConfig.buildSdkPath, UI_PLUGIN_PATH_FROM_SDK); - const memoPluginPath = path.resolve(projectConfig.buildSdkPath, MEMO_PLUGIN_PATH_FROM_SDK); - - // TODO: need change in hvigor - if (process.env.USE_KOALA_UI_PLUGIN) { - projectConfig.plugins.ArkUI = uiPluginPath - } - - if (process.env.USE_KOALA_MEMO_PLUGIN) { - projectConfig.plugins['ArkUI-Memo'] = memoPluginPath - } - } - public initPlugins(projectConfig: BuildConfig): void { if (!projectConfig || !projectConfig.plugins) { return; } - this.initKoalaPlugins(projectConfig) + initKoalaPlugins(projectConfig) const pluginResults: RawPlugins[] = Object.entries(projectConfig.plugins).map(([key, value]) => { try { diff --git a/ets2panda/driver/build_system/src/pre_define.ts b/ets2panda/driver/build_system/src/pre_define.ts index 3abf1323cd..a434824c5a 100644 --- a/ets2panda/driver/build_system/src/pre_define.ts +++ b/ets2panda/driver/build_system/src/pre_define.ts @@ -38,7 +38,7 @@ export const PANDA_SDK_PATH_FROM_SDK: string = './build-tools/ets2panda'; export const SYSTEM_SDK_PATH_FROM_SDK: string = './'; export const KIT_CONFIGS_PATH_FROM_SDK: string = '../ets1.1/build-tools/ets-loader/kit_configs'; -export const DEFAULT_WOKER_NUMS: number = 4; +export const DEFAULT_WOKER_NUMS: number = 6; export const ETS_1_1 = 'ets1.1'; export const ETS_1_1_INTEROP = 'ets1.1interop'; diff --git a/ets2panda/driver/build_system/src/types.ts b/ets2panda/driver/build_system/src/types.ts index ea510e430c..73d44ab46b 100644 --- a/ets2panda/driver/build_system/src/types.ts +++ b/ets2panda/driver/build_system/src/types.ts @@ -13,6 +13,11 @@ * limitations under the License. */ +export enum RECORD_TYPE { + DEFAULT_TYPE = 'OFF', + ON_TYPE = 'ON', +} + export enum BUILD_MODE { DEBUG = 'Debug', RELEASE = 'Release' @@ -48,6 +53,7 @@ export interface BuildBaseConfig { arktsGlobal: ArkTSGlobal; maxWorkers?: number; isBuildConfigModified?: boolean; + recordType?: RECORD_TYPE; } export interface ArkTSGlobal { @@ -166,6 +172,8 @@ export interface DeclgenConfig { declgenV2OutPath?: string; declgenBridgeCodePath?: string; skipDeclCheck?: boolean; + continueOnError?: boolean; + genDeclAnnotations?: boolean; } export interface LoggerConfig { @@ -326,5 +334,13 @@ export interface ArkTSConfigObject { paths: Record; dependencies: Record; useEmptyPackage?: boolean; + rootDir?: string, + cacheDir?: string, } }; + +export interface CompilePayload { + fileInfo: CompileFileInfo; + buildConfig: BuildConfig; + moduleInfos: [string, ModuleInfo][]; +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/src/util/TaskManager.ts b/ets2panda/driver/build_system/src/util/TaskManager.ts new file mode 100644 index 0000000000..7d9eec679a --- /dev/null +++ b/ets2panda/driver/build_system/src/util/TaskManager.ts @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { fork, ChildProcess } from 'child_process'; +import * as os from 'os'; + +import { DEFAULT_WOKER_NUMS } from '../pre_define'; +import { createTaskId } from './utils'; +import { LogData, Logger } from '../logger'; + +export interface Task { + id: string; + payload: T; + resolve: (result: true) => void; + reject: (error: Object) => void; + timeoutTimer?: NodeJS.Timeout; +} + +export interface WorkerInfo { + worker: ChildProcess; + id: number; + currentTaskId?: string; + isKilled: boolean; +} + +type OnWorkerExitCallback = ( + workerInfo: WorkerInfo, + code: number | null, + signal: NodeJS.Signals | null, + runningTasks: Map> +) => void; + +interface WorkerMessage { + id: string; + success: boolean; + shouldKill: boolean; + error?: LogData; +} + +export class TaskManager { + private workers: WorkerInfo[] = []; + private idleWorkers: WorkerInfo[] = []; + private taskQueue: Task[] = []; + private runningTasks = new Map>(); + private maxWorkers = DEFAULT_WOKER_NUMS; + private workerPath: string; + private onWorkerExit: OnWorkerExitCallback; + private taskTimeoutMs: number; + + constructor(workerPath: string, onWorkerExit: OnWorkerExitCallback, + maxWorkers?: number, taskTimeoutMs: number = 180000) { + const cpuCount = Math.max(os.cpus().length - 1, 1); + + this.workerPath = workerPath; + this.onWorkerExit = onWorkerExit; + this.taskTimeoutMs = taskTimeoutMs; + + if (maxWorkers !== undefined) { + this.maxWorkers = Math.min(maxWorkers, cpuCount); + } else { + this.maxWorkers = DEFAULT_WOKER_NUMS; + } + } + + public startWorkers(): void { + for (let i = 0; i < this.maxWorkers; i++) { + const worker = fork(this.workerPath, [], { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'] + }); + + const workerInfo: WorkerInfo = { worker, id: i, isKilled: false }; + + worker.on('message', (message: WorkerMessage) => { + this.handleWorkerMessage(workerInfo, message); + }); + + worker.on('exit', (code, signal) => { + this.handleWorkerExit(workerInfo, code, signal); + }); + + this.workers.push(workerInfo); + this.idleWorkers.push(workerInfo); + } + + this.dispatchNext(); + } + + + private settleTask(taskId: string, success: boolean, error?: string) { + const task = this.runningTasks.get(taskId); + if (!task) { + return; + } + if (task.timeoutTimer) { + clearTimeout(task.timeoutTimer); + task.timeoutTimer = undefined; + } + if (success) { + task.resolve(true); + } + else { + task.reject(error ?? new Error(error)); + } + this.runningTasks.delete(taskId); + } + + private handleSignals(workerInfo: WorkerInfo, signal: NodeJS.Signals | null) { + if (!signal) { + return; + } + switch (signal) { + case "SIGTERM": + break; + case "SIGSEGV": + this.reconfigureWorker(workerInfo); + break; + default: + break; + } + } + + private reconfigureWorker(workerInfo: WorkerInfo) { + const worker = fork(this.workerPath, [], { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'] + }); + workerInfo.currentTaskId = undefined; + workerInfo.worker = worker; + worker.on('message', (message: WorkerMessage) => { + this.handleWorkerMessage(workerInfo, message); + }); + worker.on('exit', (code, signal) => { + this.handleWorkerExit(workerInfo, code, signal); + }); + this.idleWorkers.push(workerInfo); + } + + private handleWorkerExit(workerInfo: WorkerInfo, code: number | null, signal: NodeJS.Signals | null) { + const taskId = workerInfo.currentTaskId; + if (taskId) { + const success = code === 0 && !signal; + const reason = this.getWorkerExitReason(code, signal); + this.settleTask(taskId, success, reason); + } + + this.handleSignals(workerInfo, signal); + + if (this.onWorkerExit) { + this.onWorkerExit(workerInfo, code, signal, this.runningTasks); + } + } + + private logErrorMessage(message: WorkerMessage): void { + const err = message.error; + if (!err) { + return; + } + const logData = new LogData( + err.code, + err.description, + err.cause, + err.position, + err.solutions, + err.moreInfo + ); + if (message.shouldKill) { + this.shutdown(); + Logger.getInstance().printErrorAndExit(logData); + } else { + Logger.getInstance().printError(logData); + } + } + + private handleWorkerMessage(workerInfo: WorkerInfo, message: WorkerMessage) { + const { id, success } = message; + if (!success) { + this.logErrorMessage(message); + } + this.settleTask(id, success); + workerInfo.currentTaskId = undefined; + this.idleWorkers.push(workerInfo); + this.dispatchNext(); + } + + private getWorkerExitReason(code: number | null, signal: NodeJS.Signals | null): + string | undefined { + if (signal && signal !== 'SIGKILL') { + return `Worker killed by signal ${signal}`; + } + return code !== 0 ? `Worker exited with code ${code}` : undefined; + } + + private dispatchNext(): void { + while (this.taskQueue.length > 0 && this.idleWorkers.length > 0) { + const task = this.taskQueue.shift()!; + const workerInfo = this.idleWorkers.shift()!; + + this.runningTasks.set(task.id, task); + workerInfo.currentTaskId = task.id; + + task.timeoutTimer = setTimeout(() => { + this.taskQueue.push(task); + workerInfo.currentTaskId = undefined; + workerInfo.worker.kill(); + this.reconfigureWorker(workerInfo); + this.dispatchNext(); + }, this.taskTimeoutMs); + + workerInfo.worker.send({ id: task.id, payload: task.payload }); + } + } + + public submitTask(payload: T): Promise { + return new Promise((resolve, reject) => { + const task: Task = { + id: createTaskId(), + payload, + resolve, + reject, + }; + this.taskQueue.push(task); + this.dispatchNext(); + }); + } + + public async shutdown(): Promise { + await Promise.all(this.workers.map((workerInfo) => + new Promise((res) => { + workerInfo.isKilled = true; + workerInfo.worker.kill(); + res(); + }) + )); + this.workers = []; + this.idleWorkers = []; + this.runningTasks.clear(); + this.taskQueue = []; + } +} diff --git a/ets2panda/driver/build_system/src/util/utils.ts b/ets2panda/driver/build_system/src/util/utils.ts new file mode 100644 index 0000000000..738d8d7b64 --- /dev/null +++ b/ets2panda/driver/build_system/src/util/utils.ts @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as crypto from 'crypto'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +import { + ARKTS_MODULE_NAME, + DECL_ETS_SUFFIX, + LANGUAGE_VERSION, + NATIVE_MODULE, + sdkConfigPrefix +} from '../pre_define'; +import { + Logger, + LogData, + LogDataFactory +} from '../logger'; +import { ErrorCode } from '../error_code'; +import { + ModuleInfo, + OHOS_MODULE_TYPE, + BuildConfig +} from '../types'; + +const WINDOWS: string = 'Windows_NT'; +const LINUX: string = 'Linux'; +const MAC: string = 'Darwin'; + +export function isWindows(): boolean { + return os.type() === WINDOWS; +} + +export function isLinux(): boolean { + return os.type() === LINUX; +} + +export function isMac(): boolean { + return os.type() === MAC; +} + +export function changeFileExtension(file: string, targetExt: string, originExt = ''): string { + let currentExt = originExt.length === 0 ? getFileExtension(file) : originExt; + let fileWithoutExt = file.substring(0, file.lastIndexOf(currentExt)); + return fileWithoutExt + targetExt; +} + +export function changeDeclgenFileExtension(file: string, targetExt: string): string { + if (file.endsWith(DECL_ETS_SUFFIX)) { + return changeFileExtension(file, targetExt, DECL_ETS_SUFFIX); + } + return changeFileExtension(file, targetExt); +} + +export function ensurePathExists(filePath: string): void { + try { + const dirPath: string = path.dirname(filePath); + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + } catch (error) { + if (error instanceof Error) { + console.error(`Error: ${error.message}`); + } + } +} + +export function getFileHash(filePath: string): string { + const content = fs.readFileSync(filePath, 'utf8'); + return crypto.createHash('sha256').update(content).digest('hex'); +} + +export function toUnixPath(path: string): string { + return path.replace(/\\/g, '/'); +} + +export function readFirstLineSync(filePath: string): string | null { + + const fd = fs.openSync(filePath, 'r'); + const buffer = Buffer.alloc(256); + const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0); + fs.closeSync(fd); + + const content = buffer.toString('utf-8', 0, bytesRead); + const firstLine = content.split(/\r?\n/, 1)[0].trim(); + + return firstLine; +} + +export function safeRealpath(path: string, logger: Logger): string { + try { + return fs.realpathSync(path); + } catch(error) { + const msg = error instanceof Error ? error.message : String(error); + const logData: LogData = LogDataFactory.newInstance( + ErrorCode.BUILDSYSTEM_PATH_RESOLVE_FAIL, + `Error resolving path "${path}".`, + msg + ); + logger.printError(logData); + throw logData; + } +} + +export function getInteropFilePathByApi(apiName: string, interopSDKPath: Set): string { + for (const sdkPath of interopSDKPath) { + const modulePath = path.resolve(sdkPath, apiName + DECL_ETS_SUFFIX); + if (fs.existsSync(modulePath)) { + return modulePath; + } + } + return ''; +} + +/** + * Issue:26513 + * todo read config from external instead of prodcue + */ +export function getOhmurlByApi(api: string): string { + const REG_SYSTEM_MODULE: RegExp = new RegExp(`@(${sdkConfigPrefix})\\.(\\S+)`); + + if (REG_SYSTEM_MODULE.test(api.trim())) { + return api.replace(REG_SYSTEM_MODULE, (_, moduleType, systemKey) => { + const systemModule: string = `${moduleType}.${systemKey}`; + if (NATIVE_MODULE.has(systemModule)) { + return `@native:${systemModule}`; + } else if (moduleType === ARKTS_MODULE_NAME) { + // @arkts.xxx -> @ohos:arkts.xxx + return `@ohos:${systemModule}`; + } else { + return `@ohos:${systemKey}`; + }; + }); + } + return ''; +} + +export function isSubPathOf(targetPath: string, parentDir: string): boolean { + const resolvedParent = toUnixPath(path.resolve(parentDir)); + const resolvedTarget = toUnixPath(path.resolve(targetPath)); + return resolvedTarget === resolvedParent || resolvedTarget.startsWith(resolvedParent + '/'); +} + +/** + * Get the full extension of a file, supporting composite extensions like '.d.ts', '.test.ts', '.d.ets', etc. + * @param filePath - File path or file name. + * @param knownCompositeExts - Optional list of known composite extensions to match against. + * @returns The full extension (e.g., '.d.ts'). Returns an empty string if no extension is found. + */ +export function getFileExtension( + filePath: string, + knownCompositeExts: string[] = ['.d.ts', '.test.ts', '.d.ets'] +): string { + const baseName = path.basename(filePath); + + // Match known composite extensions first + for (const ext of knownCompositeExts) { + if (baseName.endsWith(ext)) { + return ext; + } + } + + // Fallback to default behavior: return the last segment after the final dot + return path.extname(baseName); +} + +export function hasEntry(moduleInfo: ModuleInfo): boolean { + switch (moduleInfo.moduleType) { + case OHOS_MODULE_TYPE.SHARED: + case OHOS_MODULE_TYPE.HAR: + return true; + default: + return false; + } +} + +export function createFileIfNotExists(filePath: string, content: string): boolean { + try { + const normalizedPath = path.normalize(filePath); + if (fs.existsSync(normalizedPath)) { + return false; + } + + const dir = path.dirname(normalizedPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + fs.writeFileSync(normalizedPath, content, { encoding: 'utf-8' }); + return true; + } catch (error) { + return false; + } +} + +export function isMixCompileProject(buildConfig: BuildConfig): boolean { + for (const moduleInfo of buildConfig.dependentModuleList) { + if ( + moduleInfo.language === LANGUAGE_VERSION.ARKTS_1_1 || + moduleInfo.language === LANGUAGE_VERSION.ARKTS_HYBRID + ) { + return true; + } + } + return false; +} + +export function createTaskId(): string { + const timestamp = Date.now().toString(36); + const randomPart = Math.random().toString(36).slice(2, 6); + return `task-${timestamp}-${randomPart}`; +} + +export function serializeWithIgnore(obj: any, ignoreKeys: string[] = []): any { + const jsonStr = JSON.stringify(obj, (key, value) => { + if (typeof value === 'bigint') { + return undefined; + } + if (ignoreKeys.includes(key)) { + return undefined; + } + return value; + }); + return JSON.parse(jsonStr); +} diff --git a/ets2panda/driver/build_system/src/util/worker_exit_handler.ts b/ets2panda/driver/build_system/src/util/worker_exit_handler.ts new file mode 100644 index 0000000000..13d798cf47 --- /dev/null +++ b/ets2panda/driver/build_system/src/util/worker_exit_handler.ts @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ErrorCode } from "../error_code"; +import { getEs2pandaPath } from "../init/process_build_config"; +import { LogData, LogDataFactory, Logger } from "../logger"; +import { CompilePayload } from "../types"; +import { Task, WorkerInfo } from "./TaskManager"; + +export function handleCompileWorkerExit( + workerInfo: WorkerInfo, + code: number | null, + signal: NodeJS.Signals | null, + runningTasks: Map> +): void { + if (!code || code === 0) { + return + } + const taskId = workerInfo.currentTaskId; + const payload = runningTasks.get(taskId!)?.payload; + if (!payload) { + return; + } + const es2pandPath = getEs2pandaPath(payload.buildConfig); + const cmd = [ + es2pandPath, + '--arktsconfig', payload.fileInfo.arktsConfigFile, + '--output', payload.fileInfo.abcFilePath, + payload.fileInfo.filePath + ]; + + const logData: LogData = LogDataFactory.newInstance( + ErrorCode.BUILDSYSTEM_COMPILE_FAILED_IN_WORKER, + `Compile file ${payload.fileInfo.filePath} crashed (exit code ${code})`, + "", + "", + [`Please try to run command locally : ${cmd.join(' ')}`] + ); + + Logger.getInstance().printErrorAndExit(logData); +} + +export function handleDeclgenWorkerExit( + workerInfo: WorkerInfo, + code: number | null, + signal: NodeJS.Signals | null, + runningTasks: Map> +): void { + + if (code && code !== 0) { + let logExitCodeData: LogData = LogDataFactory.newInstance( + ErrorCode.BUILDSYSTEM_DECLGEN_FAILED_IN_WORKER, + `Declgen crashed (exit code ${code})`, + "This error is likely caused internally from compiler.", + ); + Logger.getInstance().printError(logExitCodeData); + return; + } + if (signal && signal !== "SIGTERM") { + let logSignalData: LogData = LogDataFactory.newInstance( + ErrorCode.BUILDSYSTEM_DECLGEN_FAILED_IN_WORKER, + `Declgen crashed (exit signal ${signal})`, + "This error is likely caused internally from compiler.", + ); + Logger.getInstance().printError(logSignalData); + } +} diff --git a/ets2panda/driver/build_system/src/utils/record_time_mem.ts b/ets2panda/driver/build_system/src/utils/record_time_mem.ts new file mode 100644 index 0000000000..3998068a44 --- /dev/null +++ b/ets2panda/driver/build_system/src/utils/record_time_mem.ts @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as fs from 'fs'; +import path from 'path'; +import { RECORD_TYPE } from '../types' + +export const BS_PERF_FILE_NAME = 'bs_record_perf.csv' + +export enum RECORDE_RUN_NODE { + GEN_MODULE = 'run generateModuleInfos', + COMPILE_FILES = 'run compileMultiFiles', + END = 'run end', +} + +export enum RECORDE_COMPILE_NODE { + PROCEED_PARSE = 'compileMultiFiles proceedToState parsed', + PLUGIN_PARSE = 'compileMultiFiles plugin parsed', + PROCEED_CHECK = 'compileMultiFiles proceedToState checked', + PLUGIN_CHECK = 'compileMultiFiles plugin checked', + BIN_GENERATE = 'compileMultiFiles bin generated', + CFG_DESTROY = 'compileMultiFiles config destroyed', + END = 'compileMultiFiles end', +} + +export enum RECORDE_MODULE_NODE { + COLLECT_INFO = 'generateModuleInfos collectModuleInfos', + GEN_CONFIG = 'generateModuleInfos generateArkTSConfigForModules', + CLT_FILES = 'generateModuleInfos collectCompileFiles', + SAVE_CACHE = 'generateModuleInfos saveHashCache', + END = 'generateModuleInfos end', +} + +export class SingleData { + public time: number = 0; + public mem: number = 0; +} + +export class CompileSingleData { + private timeMemMap: Map; + private startTime: number = 0; + private startMem: number = 0; + private file: string = ''; + private recordType: RECORD_TYPE; + + constructor(file: string, recordType?: RECORD_TYPE) { + this.file = file; + this.timeMemMap = new Map(); + // close by default + this.recordType = recordType ?? RECORD_TYPE.DEFAULT_TYPE; + } + + public record(startKey: string, lastEndKey: string = '') { + if (this.recordType == RECORD_TYPE.DEFAULT_TYPE) { + return; + } + let currentTime = new Date().getTime(); + let currentMem = process.memoryUsage.rss(); + let tmp: SingleData | undefined = this.timeMemMap.get(lastEndKey); + if (tmp) { + tmp.time = currentTime - this.startTime; + tmp.mem = (currentMem > this.startMem) ? (currentMem - this.startMem) : 0; + this.timeMemMap.set(lastEndKey, tmp); + } + + if (startKey == '') { + return; + } + + if (this.timeMemMap.get(startKey) !== undefined) { + return; + } + this.startTime = currentTime; + this.startMem = currentMem; + let data: SingleData = new SingleData(); + data.time = 0; + data.mem = 0; + this.timeMemMap.set(startKey, data); + } + + writeSumSingle(cachePath: string, deputyName: string = '') { + if (this.recordType == RECORD_TYPE.DEFAULT_TYPE) { + return; + } + const csvData: string[] = [ + "timeKey, time(ms), mem(M)" + ]; + this.timeMemMap.forEach((v: SingleData, k: string) => { + let element = `${k}` +', ' + `${v.time}` + 'ms' + ', ' + `${Math.round(v.mem / 1024 / 1024)}` + 'M' ; + csvData.push(element); + }); + let name = path.basename(this.file) + let currentExt = path.extname(name) + let fileWithoutExt = name.substring(0, name.lastIndexOf(currentExt)); + let fileName = `${fileWithoutExt}`+ deputyName +'.csv'; + let filePath = path.join(cachePath, fileName); + csvData.forEach(row => { + fs.appendFileSync(filePath, `${row}\n`); + }); + } +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/base_modeTest/base_mode.test.ts b/ets2panda/driver/build_system/test/ut/base_modeTest/base_mode.test.ts index 96440a8124..a8344603bd 100755 --- a/ets2panda/driver/build_system/test/ut/base_modeTest/base_mode.test.ts +++ b/ets2panda/driver/build_system/test/ut/base_modeTest/base_mode.test.ts @@ -13,6 +13,9 @@ * limitations under the License. */ +import fs from 'fs'; +import path from 'path'; + import { BaseMode } from '../../../src/build/base_mode'; import { BuildConfig, BUILD_TYPE, BUILD_MODE, OHOS_MODULE_TYPE, ModuleInfo, ES2PANDA_MODE } from '../../../src/types'; import { BuildMode } from '../../../src/build/build_mode'; @@ -67,6 +70,10 @@ beforeEach(() => { beforeAll(() => { const { execSync } = require('child_process'); execSync('rimraf test/ut/mock/dist', { stdio: 'pipe' }); + const dir = path.resolve('dist/cache'); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } }); // Test the functions of the base_mode.ts file. @@ -82,15 +89,7 @@ describe('test base_mode.ts file api', () => { test('test shouldSkipFile', () => { test_shouldSkipFile(); }); - - test('test setupCluster', () => { - test_setupCluster(); - }); - - test('test terminateAllWorkers', () => { - test_terminateAllWorkers(); - }); - + test('test collectCompileFiles when test declaration files skip branch', () => { test_collectCompileFiles_decl_ets_skip(); }); @@ -175,10 +174,6 @@ describe('test base_mode.ts file api', () => { test_getJobDependencies(); }); - test('test setupWorkerMessageHandler', () => { - test_setupWorkerMessageHandler(); - }); - test('test getSerializableConfig handles bigint values', () => { test_getSerializableConfig(); }); @@ -228,6 +223,7 @@ function test_collectCompileJobs_should_skip_entry_files_not_in_compileFiles() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG }; @@ -306,7 +302,7 @@ function test_collectDependentCompileFiles_isFileChanged_branch() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", - dependentModuleList: [] + dependentModuleList: [], }; class TestBaseMode extends BaseMode { @@ -327,7 +323,7 @@ function test_collectDependentCompileFiles_isFileChanged_branch() { jest.spyOn(fs, 'statSync').mockReturnValue({ mtimeMs: Date.now() }); jest.spyOn(fs, 'readFileSync').mockReturnValue('mocked file content'); - const utils = require('../../../src/utils'); + const utils = require('../../../src/util/utils'); jest.spyOn(utils, 'getFileHash').mockReturnValue("test-hash-123"); const Logger = require('../../../src/logger').Logger; @@ -407,7 +403,7 @@ function test_collectAbcFileFromByteCodeHar_missing_abc_path() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", - dependentModuleList: [] + dependentModuleList: [], }; class TestBaseMode extends BaseMode { @@ -475,7 +471,7 @@ function test_collectCompileFiles_enableDeclgenEts2Ts_false() { loaderOutPath: "./dist", cachePath: "./dist/cache", enableDeclgenEts2Ts: false, - dependentModuleList: [] + dependentModuleList: [], }; class TestBaseMode extends BaseMode { @@ -519,6 +515,8 @@ class TestBaseModeMock extends BaseMode { compileFileInfos: [], dynamicDepModuleInfos: new Map(), staticDepModuleInfos: new Map(), + dependenciesSet: new Set(), + dependentSet: new Set(), moduleType: OHOS_MODULE_TYPE.HAR, entryFile: "index.ets", byteCodeHar: false, @@ -542,7 +540,7 @@ function test_collectModuleInfos1(mockLogger: any, LogDataFactory: any) { loaderOutPath: "./dist", cachePath: "./dist/cache", hasMainModule: true, - dependentModuleList: [] + dependentModuleList: [], }; const baseMode = new TestBaseModeMock(mockConfig as any); (baseMode as any).logger = mockLogger; @@ -773,6 +771,7 @@ function test_updateDependantJobs() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG }; (global as any).finishedJob = []; @@ -797,6 +796,7 @@ function test_loadHashCache() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG }; @@ -861,6 +861,7 @@ function test_isFileChanged() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG }; @@ -918,6 +919,7 @@ function test_collectDependentCompileFiles() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG }; @@ -982,6 +984,7 @@ function test_getSerializableConfig() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG, arkts: { someFunction: () => { } @@ -1012,86 +1015,6 @@ function test_getSerializableConfig() { expect(result).toHaveProperty('sourceRoots'); } -function test_setupWorkerMessageHandler() { - const mockConfig = { - packageName: "test", - moduleRootPath: "/test/path", - sourceRoots: ["./"], - loaderOutPath: "./dist", - cachePath: "./dist/cache", - buildMode: BUILD_MODE.DEBUG - }; - - const mockLogData = { code: "123", message: "Test message" }; - const mockLogger = { - printError: jest.fn(), - printInfo: jest.fn() - }; - - const LogDataFactory = { - newInstance: jest.fn().mockReturnValue(mockLogData) - }; - - const ErrorCode = { - BUILDSYSTEM_COMPILE_ABC_FAIL: '11410099' - }; - - class TestBuildMode extends BaseMode { - public run(): Promise { - return Promise.resolve(); - } - - public testSetupWorkerMessageHandler(worker: any): void { - (this as any).setupWorkerMessageHandler(worker); - } - } - - const baseMode = new TestBuildMode(mockConfig as any); - (baseMode as any).logger = mockLogger; - const errorLogDataSpy = jest.spyOn(LogDataFactory, 'newInstance'); - - const mockWorker = { - on: jest.fn(), - callbacks: {} as Record - }; - - mockWorker.on = jest.fn().mockImplementation((event, callback) => { - mockWorker.callbacks[event] = callback; - return mockWorker; - }); - - (global as any).LogDataFactory = LogDataFactory; - (global as any).ErrorCode = ErrorCode; - - baseMode.testSetupWorkerMessageHandler(mockWorker); - - expect(mockWorker.on).toHaveBeenCalledWith('message', expect.any(Function)); - - if (mockWorker.callbacks['message']) { - mockWorker.callbacks['message']({ success: true }); - } - expect(mockLogger.printError).not.toHaveBeenCalled(); - - if (mockWorker.callbacks['message']) { - mockWorker.callbacks['message']({ - success: false, - filePath: '/test/file.ets', - error: 'Test error' - }); - } - mockLogger.printError.mockClear(); - errorLogDataSpy.mockClear(); - - if (mockWorker.callbacks['message']) { - mockWorker.callbacks['message']({ - success: false, - filePath: '/test/file2.ets' - }); - } - - jest.restoreAllMocks(); -} - function test_getJobDependencies() { const mockConfig = { packageName: "test", @@ -1099,6 +1022,7 @@ function test_getJobDependencies() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG }; @@ -1151,6 +1075,7 @@ function test_getJobDependants() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG }; class TestBuildMode extends BuildMode { @@ -1202,6 +1127,7 @@ function test_collectCompileJobs() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG }; @@ -1303,6 +1229,7 @@ function test_dealWithDependants() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG }; class TestBuildMode extends BuildMode { @@ -1362,6 +1289,7 @@ function test_addJobToQueues() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG }; @@ -1447,6 +1375,7 @@ function test_initCompileQueues() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG }; @@ -1530,6 +1459,7 @@ function test_checkAllTasksDone() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG }; @@ -1569,6 +1499,7 @@ function test_processAfterCompile() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG, arkts: { destroyConfig: jest.fn() @@ -1612,9 +1543,13 @@ function test_processAfterCompile() { function test_runConcurrent() { const mockConfig = { - packageName: "test", compileFiles: ["/test/path/file1.ets"], - moduleRootPath: "/test/path", sourceRoots: ["./"], - loaderOutPath: "./dist", cachePath: "./dist/cache", + packageName: "test", + compileFiles: ["/test/path/file1.ets"], + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG } as any; @@ -1678,8 +1613,13 @@ function test_collectDependencyModules_language_branches() { } const baseMode = new TestBaseMode({ - packageName: "test", moduleRootPath: "/test/path", sourceRoots: ["./"], - loaderOutPath: "./dist", cachePath: "./dist/cache", buildMode: BUILD_MODE.DEBUG + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + dependentModuleList: [], + buildMode: BUILD_MODE.DEBUG } as any); { @@ -1739,6 +1679,7 @@ function test_getDependentModules_missing_module() { sourceRoots: ["./"], loaderOutPath: "./dist", cachePath: "./dist/cache", + dependentModuleList: [], buildMode: BUILD_MODE.DEBUG }; const Logger = require('../../../src/logger').Logger; @@ -1774,11 +1715,17 @@ function test_declgen_method() { const fs = require('fs'); jest.spyOn(fs, 'readFileSync').mockReturnValue('test source code'); const mockConfig = { - packageName: "test", moduleRootPath: "/test/path", - loaderOutPath: "./dist", cachePath: "./dist/cache", + packageName: "test", + moduleRootPath: "/test/path", + loaderOutPath: "./dist", + cachePath: "./dist/cache", + dependentModuleList: [], arkts: { Config: { create: jest.fn().mockReturnValue({ peer: 'mockConfigPeer' }) }, - Context: { createFromString: jest.fn().mockReturnValue({ peer: 'mockContextPeer', program: 'mockProgram' }) }, + Context: { + createFromString: jest.fn().mockReturnValue({ peer: 'mockContextPeer', program: 'mockProgram' }), + createFromStringWithHistory: jest.fn().mockReturnValue({ peer: 'mockContextPeer', program: 'mockProgram' }) + }, proceedToState: jest.fn(), EtsScript: { fromContext: jest.fn().mockReturnValue('mockAst') }, Es2pandaContextState: { ES2PANDA_STATE_PARSED: 'parsed', ES2PANDA_STATE_CHECKED: 'checked' }, generateTsDeclarationsFromContext: jest.fn(), destroyConfig: jest.fn() @@ -1787,7 +1734,7 @@ function test_declgen_method() { }; const Logger = require('../../../src/logger').Logger; const PluginDriver = require('../../../src/plugins/plugins_driver').PluginDriver; - const utils = require('../../../src/utils'); + const utils = require('../../../src/util/utils'); const path = require('path'); Logger.getInstance = jest.fn().mockReturnValue({ printInfo: jest.fn(), printError: jest.fn() }); PluginDriver.getInstance = jest.fn().mockReturnValue({ @@ -1813,7 +1760,7 @@ function test_declgen_method() { }]]); baseMode.testDeclgen({ filePath: '/test/path/file1.ets', packageName: 'test', arktsConfigFile: '/test/path/arktsconfig.json' }); expect(fs.readFileSync).toHaveBeenCalledWith('/test/path/file1.ets', 'utf8'); - expect(mockConfig.arkts.Context.createFromString).toHaveBeenCalled(); + expect(mockConfig.arkts.Context.createFromStringWithHistory).toHaveBeenCalled(); expect(mockConfig.arkts.proceedToState).toHaveBeenCalledWith('parsed', 'mockContextPeer', true); expect(mockConfig.arkts.proceedToState).toHaveBeenCalledWith('checked', 'mockContextPeer', true); expect(mockConfig.arkts.generateTsDeclarationsFromContext).toHaveBeenCalled(); @@ -1980,7 +1927,8 @@ function test_assignTaskToIdleWorker_empty_queues() { moduleRootPath: "/test/path", sourceRoots: ["./"], loaderOutPath: "./dist", - cachePath: "./dist/cache" + cachePath: "./dist/cache", + dependentModuleList: [], }; const Logger = require('../../../src/logger').Logger; @@ -2065,7 +2013,8 @@ function test_assignTaskToIdleWorker_abcQueue_no_job() { moduleRootPath: "/test/path", sourceRoots: ["./"], loaderOutPath: "./dist", - cachePath: "./dist/cache" + cachePath: "./dist/cache", + dependentModuleList: [], }; const Logger = require('../../../src/logger').Logger; @@ -2153,8 +2102,13 @@ function test_assignTaskToIdleWorker_abcQueue_no_job() { function test_findStronglyConnectedComponents_branches() { const mockConfig = { - packageName: "test", moduleRootPath: "/test/path", sourceRoots: ["./"], - loaderOutPath: "./dist", cachePath: "./dist/cache", buildMode: "Debug" + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: "Debug", + dependentModuleList: [], }; class TestBaseMode extends BaseMode { @@ -2206,7 +2160,8 @@ function test_createExternalProgramJob_branches() { loaderOutPath: "./dist", cachePath: "./dist/cache", buildMode: "Debug", - moduleType: "har" + moduleType: "har", + dependentModuleList: [], }; class TestBaseMode extends BaseMode { @@ -2297,7 +2252,7 @@ function test_collectCompileFiles_bytecode_har() { loaderOutPath: "./dist", cachePath: "./dist/cache", enableDeclgenEts2Ts: true, - dependentModuleList: [] + dependentModuleList: [], }; class TestBaseMode extends BaseMode { @@ -2340,7 +2295,7 @@ function test_collectCompileFiles_bytecode_har() { }); (global as any).getFileHash = jest.fn().mockReturnValue("hash123"); - const utils = require('../../../src/utils'); + const utils = require('../../../src/util/utils'); jest.spyOn(baseMode, 'testCollectAbcFileFromByteCodeHar').mockImplementation(() => { }); @@ -2362,7 +2317,7 @@ function test_collectCompileFiles_file_not_in_module() { loaderOutPath: "./dist", cachePath: "./dist/cache", enableDeclgenEts2Ts: true, - dependentModuleList: [] + dependentModuleList: [], }; class TestBaseMode extends BaseMode { @@ -2427,7 +2382,7 @@ function test_collectCompileFiles_decl_ets_skip() { loaderOutPath: "./dist", cachePath: "./dist/cache", enableDeclgenEts2Ts: true, - dependentModuleList: [] + dependentModuleList: [], }; class TestBaseMode extends BaseMode { @@ -2465,7 +2420,7 @@ function test_collectCompileFiles_decl_ets_skip() { }); (global as any).getFileHash = jest.fn().mockReturnValue("hash123"); - const utils = require('../../../src/utils'); + const utils = require('../../../src/util/utils'); baseMode.testCollectCompileFiles(); } @@ -2498,7 +2453,7 @@ function test_collectModuleInfos() { dependentModuleList: [ { - "packageName": "", + "packageName": "harA", "moduleName": "harA", "moduleType": "har", "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/harA", @@ -2509,7 +2464,7 @@ function test_collectModuleInfos() { "byteCodeHar": false }, { - "packageName": "", + "packageName": "hspA", "moduleName": "hspA", "moduleType": "shared", "modulePath": "hspA", @@ -2657,6 +2612,8 @@ function test_shouldSkipFile() { declgenBridgeCodePath: "/dist/bridge", dynamicDepModuleInfos: new Map(), staticDepModuleInfos: new Map(), + dependenciesSet: new Set(), + dependentSet: new Set(), moduleType: OHOS_MODULE_TYPE.HAR, entryFile: "index.ets", declgenV2OutPath: "/dist/declgen/v2", @@ -2673,114 +2630,3 @@ function test_shouldSkipFile() { expect(result4).toBe(false); } -function test_setupCluster() { - const mockConfig: BuildConfig = { - buildMode: BUILD_MODE.DEBUG, - compileFiles: ["test.ets"], - packageName: "test", - moduleRootPath: "/test/path", - sourceRoots: ["./"], - loaderOutPath: "./dist", - cachePath: "./dist/cache", - plugins: {}, - dependentModuleList: [], - maxWorkers: 1, - buildType: BUILD_TYPE.BUILD, - hasMainModule: true, - arkts: {} as any, - arktsGlobal: {} as any, - enableDeclgenEts2Ts: false, - moduleType: OHOS_MODULE_TYPE.HAR, - declgenV1OutPath: "./dist/declgen", - declgenV2OutPath: "./dist/declgen/v2", - buildSdkPath: "./sdk", - byteCodeHar: false, - externalApiPaths: [] - } as any; - const Logger = require('../../../src/logger').Logger; - Logger.instance = null; - Logger.getInstance(mockConfig); - let baseModule: BaseMode = new BuildMode(mockConfig); - - const originalRemoveAllListeners = cluster.removeAllListeners; - const originalSetupPrimary = cluster.setupPrimary; - - const removeAllListenersSpy = jest.fn(); - cluster.removeAllListeners = removeAllListenersSpy; - - const setupPrimarySpy = jest.fn(); - cluster.setupPrimary = setupPrimarySpy; - - try { - (baseModule as any).setupCluster(cluster, { - clearExitListeners: false, - execPath: '/path/to/worker', - execArgs: [] - }); - - expect(removeAllListenersSpy).not.toHaveBeenCalled(); - expect(setupPrimarySpy).toHaveBeenCalledWith({ - exec: '/path/to/worker', - execArgv: [] - }); - } finally { - cluster.removeAllListeners = originalRemoveAllListeners; - cluster.setupPrimary = originalSetupPrimary; - } -} - -function test_terminateAllWorkers() { - const mockConfig: BuildConfig = { - buildMode: BUILD_MODE.DEBUG, - compileFiles: ["test.ets"], - packageName: "test", - moduleRootPath: "/test/path", - sourceRoots: ["./"], - loaderOutPath: "./dist", - cachePath: "./dist/cache", - plugins: {}, - dependentModuleList: [], - buildType: BUILD_TYPE.BUILD, - hasMainModule: true, - arkts: {} as any, - arktsGlobal: {} as any, - enableDeclgenEts2Ts: false, - moduleType: OHOS_MODULE_TYPE.HAR, - declgenV1OutPath: "./dist/declgen", - declgenV2OutPath: "./dist/declgen/v2", - buildSdkPath: "./sdk", - byteCodeHar: false, - externalApiPaths: [] - } as any; - const Logger = require('../../../src/logger').Logger; - Logger.instance = null; - Logger.getInstance(mockConfig); - let baseModule: BaseMode = new BuildMode(mockConfig); - - const originalWorkers = cluster.workers; - - try { - Object.defineProperty(cluster, 'workers', { - value: {}, - configurable: true - }); - - expect(() => { - (baseModule as any).terminateAllWorkers(); - }).not.toThrow(); - - Object.defineProperty(cluster, 'workers', { - value: undefined, - configurable: true - }); - - expect(() => { - (baseModule as any).terminateAllWorkers(); - }).not.toThrow(); - } finally { - Object.defineProperty(cluster, 'workers', { - value: originalWorkers, - configurable: true - }); - } -} diff --git a/ets2panda/driver/build_system/test/ut/build_framework_modeTest/build_framework_mode.test.ts b/ets2panda/driver/build_system/test/ut/build_framework_modeTest/build_framework_mode.test.ts index d5f1c84eb0..2a902b0e6c 100755 --- a/ets2panda/driver/build_system/test/ut/build_framework_modeTest/build_framework_mode.test.ts +++ b/ets2panda/driver/build_system/test/ut/build_framework_modeTest/build_framework_mode.test.ts @@ -161,6 +161,8 @@ describe('test build_framework_mode.ts file api', () => { compileFileInfos: [], dynamicDepModuleInfos: new Map(), staticDepModuleInfos: new Map(), + dependenciesSet: new Set(), + dependentSet: new Set(), moduleType: OHOS_MODULE_TYPE.HAR, entryFile: 'index.ets', byteCodeHar: false, diff --git a/ets2panda/driver/build_system/test/ut/compile_WorkerTest/compile_worker.test.ts b/ets2panda/driver/build_system/test/ut/compile_WorkerTest/compile_worker.test.ts index da01ed9b34..56f1ae6626 100755 --- a/ets2panda/driver/build_system/test/ut/compile_WorkerTest/compile_worker.test.ts +++ b/ets2panda/driver/build_system/test/ut/compile_WorkerTest/compile_worker.test.ts @@ -17,7 +17,7 @@ jest.mock('fs'); jest.mock('path'); -jest.mock('../../../src/utils', () => ({ +jest.mock('../../../src/util/utils', () => ({ changeFileExtension: jest.fn((p, ext) => p.replace(/\.[^/.]+$/, ext)), ensurePathExists: jest.fn() })); @@ -52,15 +52,43 @@ jest.mock('/sdk/koala', () => ({ const fakeArkts = { Config: { create: jest.fn(() => ({ peer: 'peer' })) }, - Context: { createFromString: jest.fn(() => ({ program: {}, peer: 'peer' })) }, + Context: { + createFromString: jest.fn(() => ({ program: {}, peer: 'peer' })), + createFromStringWithHistory: jest.fn(() => ({ program: {}, peer: 'peer' })) + }, proceedToState: jest.fn(), - Es2pandaContextState: { ES2PANDA_STATE_PARSED: 1, ES2PANDA_STATE_CHECKED: 2, ES2PANDA_STATE_BIN_GENERATED: 3 }, + Es2pandaContextState: { ES2PANDA_STATE_PARSED: 1, ES2PANDA_STATE_CHECKED: 2 }, + generateTsDeclarationsFromContext: jest.fn(), generateStaticDeclarationsFromContext: jest.fn(), - destroyConfig: jest.fn() + destroyConfig: jest.fn(), + EtsScript: { fromContext: jest.fn(() => ({})) } }; const fakeArktsGlobal = { - es2panda: { _DestroyContext: jest.fn() } + es2panda: { + _SetUpSoPath: jest.fn(), + _DestroyContext: jest.fn((pandaSDKPath: string) => { + return; + }), + }, + filePath: '', + config: '', + compilerContext: { program: {}, peer: 'peer' } }; + +jest.mock('../../../src/init/init_koala_modules', () => ({ + initKoalaModules: jest.fn((buildConfig) => { + const fakeKoala = { + arkts: fakeArkts, + arktsGlobal: fakeArktsGlobal + }; + fakeKoala.arktsGlobal.es2panda._SetUpSoPath(buildConfig.pandaSdkPath); + + buildConfig.arkts = fakeKoala.arkts; + buildConfig.arktsGlobal = fakeKoala.arktsGlobal; + return fakeKoala; + }) +})); + jest.mock('path', () => ({ ...jest.requireActual('path'), resolve: jest.fn((...args) => args.join('/')), @@ -83,96 +111,104 @@ afterEach(() => { }); // Test the functions of the compile_worker.ts file -import { changeFileExtension } from '../../../src/utils'; +import { changeFileExtension } from '../../../src/util/utils'; import { DECL_ETS_SUFFIX } from '../../../src/pre_define'; +import { + CompileFileInfo, + BuildConfig, + ES2PANDA_MODE, + BUILD_MODE, + BUILD_TYPE, +} from '../../../src/types'; describe('compile_worker', () => { - const fileInfo = { + + const compileFileInfo: CompileFileInfo ={ filePath: '/src/foo.ets', - abcFilePath: '/out/foo.abc', - arktsConfigFile: '/src/arktsconfig.json' + dependentFiles: [], + abcFilePath: 'foo.abc', + arktsConfigFile: '/src/arktsconfig.json', + packageName: 'pkg', }; + const buildConfig = { - buildMode: 0, hasMainModule: true, byteCodeHar: true, - moduleType: 0, - declgenV2OutPath: '/decl', - packageName: 'pkg', + moduleType: 9999, + declgenV2OutPath: 'declgenV2Out', moduleRootPath: '/src', - buildSdkPath: '/sdk' + plugins: { pkg: 'plugin' }, + paths: { pkg: ['plugin'] }, + compileFiles: ['/src/foo.ets'], + entryFiles: ['/src/foo.ets'], + dependentModuleList: [], + aliasConfig: {}, }; - const moduleInfos: any[] = []; + + beforeEach(() => { + jest.resetModules(); + (process as any).send = jest.fn(); + jest.spyOn(process, 'exit').mockImplementation((code?: string | number | null | undefined) => { + throw new Error(`exit: ${code}`); + }); + }); + test('compile all files && exit(0)', () => { require('fs').readFileSync.mockReturnValue(Buffer.from('source code')); require('path').relative.mockImplementation((from: string, to: string) => to.replace(from, '').replace(/^\//, '')); require('path').join.mockImplementation((...args: string[]) => args.join('/')); require('path').resolve.mockImplementation((...args: string[]) => args.join('/')); require('path').basename.mockImplementation((p: string) => p.split('/').pop()); + const processId: number = 1; + const payload = { + fileInfo: compileFileInfo, + buildConfig: buildConfig, + }; require('../../../src/build/compile_worker'); - expect(() => { - (process as any).emit('message', { taskList: [fileInfo], buildConfig, moduleInfos }); - }).toThrow('exit'); - expect(require('../../../src/utils').ensurePathExists).toHaveBeenCalled(); - expect(require('fs').readFileSync).toHaveBeenCalledWith(fileInfo.filePath); + (process as any).emit('message', { processId, payload }); + + expect(require('../../../src/util/utils').ensurePathExists).toHaveBeenCalled(); + expect(require('fs').readFileSync).toHaveBeenCalledWith(compileFileInfo.filePath); expect(fakeArkts.Config.create).toHaveBeenCalled(); expect(fakeArkts.Context.createFromString).toHaveBeenCalled(); expect(fakeArkts.proceedToState).toHaveBeenCalledTimes(3); expect(fakeArkts.generateStaticDeclarationsFromContext).toHaveBeenCalled(); expect(fakeArkts.destroyConfig).toHaveBeenCalled(); expect(fakeArktsGlobal.es2panda._DestroyContext).toHaveBeenCalled(); - expect(process.exit).toHaveBeenCalledWith(0); }); - - test('handle error && send fail message', () => { - jest.spyOn(process, 'exit').mockImplementation(() => undefined as never); - - require('fs').readFileSync.mockImplementation(() => { throw new Error('fail'); }); - require('../../../src/build/compile_worker'); - expect(() => { - (process as any).emit('message', { taskList: [fileInfo], buildConfig, moduleInfos }); - }).not.toThrow(); - - expect(process.send).toHaveBeenCalledWith(expect.objectContaining({ - success: false, - filePath: fileInfo.filePath, - error: expect.any(String) - })); - }); - + test('generate decl file', () => { require('fs').readFileSync.mockReturnValue(Buffer.from('source code')); - let config = { ...buildConfig, hasMainModule: false }; + let config = buildConfig; + config.hasMainModule = false; + let processId: number = 3; + let payload = { + fileInfo: compileFileInfo, + buildConfig: config, + }; require('../../../src/build/compile_worker'); - expect(() => { - (process as any).emit('message', { taskList: [fileInfo], buildConfig: config, moduleInfos }); - }).toThrow('exit'); + (process as any).emit('message', { processId, payload }); expect(fakeArkts.generateStaticDeclarationsFromContext).not.toHaveBeenCalled(); + require('fs').readFileSync.mockReturnValue(Buffer.from('source code')); - config = { ...buildConfig, byteCodeHar: false, moduleType: 9999 }; + config.hasMainModule = true; + config.byteCodeHar = false; + config.moduleType = 999999; require('../../../src/build/compile_worker'); - expect(() => { - (process as any).emit('message', { taskList: [fileInfo], buildConfig: config, moduleInfos }); - }).toThrow('exit'); + processId = 4; + (process as any).emit('message', { processId, payload }); expect(fakeArkts.generateStaticDeclarationsFromContext).not.toHaveBeenCalled(); + require('path').relative.mockImplementation((from: string, to: string) => to.replace(from, '').replace(/^\//, '')); require('fs').readFileSync.mockReturnValue(Buffer.from('source code')); - config = { ...buildConfig, hasMainModule: true, byteCodeHar: true }; + config.byteCodeHar = true; require('../../../src/build/compile_worker'); - expect(() => { - (process as any).emit('message', { taskList: [fileInfo], buildConfig: config, moduleInfos }); - }).toThrow('exit'); - let filePathFromModuleRoot = require('path').relative(buildConfig.moduleRootPath, fileInfo.filePath); + processId = 5; + (process as any).emit('message', { processId, payload }); + let filePathFromModuleRoot = require('path').relative(buildConfig.moduleRootPath, compileFileInfo.filePath); let declarationPath = require('path').join(buildConfig.declgenV2OutPath, filePathFromModuleRoot); let declarationFilePath = changeFileExtension(declarationPath, DECL_ETS_SUFFIX); expect(fakeArkts.generateStaticDeclarationsFromContext).toHaveBeenCalledWith(declarationFilePath); }); - test('throw while process.send is undefined', () => { - delete (process as any).send; - require('../../../src/build/compile_worker'); - expect(() => { - (process as any).emit('message', { taskList: [fileInfo], buildConfig, moduleInfos }); - }).toThrow('process.send is undefined. This worker must be run as a forked process.'); - }); }); diff --git a/ets2panda/driver/build_system/test/ut/compile_thread_workerTest/compile_thread_worker.test.ts b/ets2panda/driver/build_system/test/ut/compile_thread_workerTest/compile_thread_worker.test.ts index 7b2783a1cb..87d39b2da4 100755 --- a/ets2panda/driver/build_system/test/ut/compile_thread_workerTest/compile_thread_worker.test.ts +++ b/ets2panda/driver/build_system/test/ut/compile_thread_workerTest/compile_thread_worker.test.ts @@ -19,7 +19,7 @@ import { EventEmitter } from 'events'; jest.mock('fs'); jest.mock('path'); -jest.mock('../../../src/utils', () => ({ +jest.mock('../../../src/util/utils', () => ({ changeFileExtension: jest.fn((p: string, ext: string) => p.replace(/\.[^/.]+$/, ext)), ensurePathExists: jest.fn() })); @@ -88,7 +88,14 @@ beforeEach(() => { require('path').basename.mockImplementation((p: string) => p.split('/').pop()); }); -// Test the functions of the compile_thread_worker.ts file +// create a test to avoid throw error +describe('mockSDK', () => { + it('should load correctly', () => { + + }); +}); + +/* compile_thread_worker is'not used in the project now, so we comment out the test cases. describe('compile_thread_worker', () => { let parentPort: EventEmitter & { postMessage?: jest.Mock }; const workerData = { workerId: 1 }; @@ -207,4 +214,6 @@ describe('compile_thread_worker', () => { }).toThrow('exit'); spy.mockRestore(); }); + }); + */ \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/declgen_workerTest/declgen_worker.test.ts b/ets2panda/driver/build_system/test/ut/declgen_workerTest/declgen_worker.test.ts index 156a15f123..7e57575fa7 100755 --- a/ets2panda/driver/build_system/test/ut/declgen_workerTest/declgen_worker.test.ts +++ b/ets2panda/driver/build_system/test/ut/declgen_workerTest/declgen_worker.test.ts @@ -17,8 +17,22 @@ jest.mock('fs'); jest.mock('path'); -jest.mock('../../../src/utils', () => ({ - changeFileExtension: jest.fn((p: string, ext: string) => p.replace(/\.[^/.]+$/, ext)), +jest.mock('../../../src/util/utils', () => ({ + // simplified functions for testing + changeFileExtension: jest.fn((file: string, targetExt: string, originExt = '') => { + const currentExt = originExt.length === 0 ? file.substring(file.lastIndexOf('.')) + : originExt; + const fileWithoutExt = file.substring(0, file.lastIndexOf(currentExt)); + return fileWithoutExt + targetExt; + }), + changeDeclgenFileExtension: jest.fn((file: string, targetExt: string) => { + const DECL_ETS_SUFFIX = '.d.ets'; + if (file.endsWith(DECL_ETS_SUFFIX)) { + return file.replace(DECL_ETS_SUFFIX, targetExt); + } + return file.substring(0, file.lastIndexOf('.')) + targetExt; + }), + createFileIfNotExists: jest.fn(), ensurePathExists: jest.fn() })); jest.mock('../../../src/plugins/plugins_driver', () => { @@ -41,8 +55,11 @@ jest.mock('../../../src/logger', () => { } as any; return { Logger: mLogger, - LogDataFactory: { newInstance: jest.fn(() => ({ - code: '001', description: '', cause: '', position: '', solutions: [], moreInfo: {} })) } + LogDataFactory: { + newInstance: jest.fn(() => ({ + code: '001', description: '', cause: '', position: '', solutions: [], moreInfo: {} + })) + } }; }); jest.mock('../../../src/pre_define', () => ({ @@ -51,9 +68,26 @@ jest.mock('../../../src/pre_define', () => ({ KOALA_WRAPPER_PATH_FROM_SDK: 'koala' })); +jest.mock('../../../src/init/init_koala_modules', () => ({ + initKoalaModules: jest.fn((buildConfig) => { + const fakeKoala = { + arkts: fakeArkts, + arktsGlobal: fakeArktsGlobal + }; + fakeKoala.arktsGlobal.es2panda._SetUpSoPath(buildConfig.pandaSdkPath); + + buildConfig.arkts = fakeKoala.arkts; + buildConfig.arktsGlobal = fakeKoala.arktsGlobal; + return fakeKoala; + }) +})); + const fakeArkts = { Config: { create: jest.fn(() => ({ peer: 'peer' })) }, - Context: { createFromString: jest.fn(() => ({ program: {}, peer: 'peer' })) }, + Context: { + createFromString: jest.fn(() => ({ program: {}, peer: 'peer' })), + createFromStringWithHistory: jest.fn(() => ({ program: {}, peer: 'peer' })) + }, proceedToState: jest.fn(), Es2pandaContextState: { ES2PANDA_STATE_PARSED: 1, ES2PANDA_STATE_CHECKED: 2 }, generateTsDeclarationsFromContext: jest.fn(), @@ -72,7 +106,7 @@ const fakeArktsGlobal = { jest.mock('/sdk/koala', () => ({ arkts: fakeArkts, - arktsGlobal: fakeArktsGlobal + arktsGlobal: fakeArktsGlobal, }), { virtual: true }); beforeEach(() => { @@ -93,76 +127,88 @@ afterEach(() => { // Test the functions of the declgen_worker.ts file describe('declgen_worker', () => { - const fileInfo = { + const compileFileInfo = { filePath: '/src/foo.ets', + dependentFiles: [], + abcFilePath: 'foo.abc', arktsConfigFile: '/src/arktsconfig.json', - packageName: 'pkg' + packageName: 'pkg', }; + const buildConfig = { - buildSdkPath: '/sdk', - pandaSdkPath: '/panda' + hasMainModule: true, + byteCodeHar: true, + moduleType: 9999, + declgenV2OutPath: 'declgenV2Out', + moduleRootPath: '/src', + plugins: { pkg: 'plugin' }, + paths: { pkg: ['plugin'] }, + compileFiles: ['/src/foo.ets'], + entryFiles: ['/src/foo.ets'], + dependentModuleList: [], + aliasConfig: {}, }; + const moduleInfo = { + isMainModule: true, + packageName: 'pkg', moduleRootPath: '/src', - declgenV1OutPath: '/decl', - declgenBridgeCodePath: '/bridge', - packageName: 'pkg' + moduleType: 'type', + sourceRoots: [], + entryFile: 'foo.ets', + arktsConfigFile: 'arktsconfig.json', + compileFileInfos: [], + declgenV1OutPath: undefined, + declgenV2OutPath: undefined, + declgenBridgeCodePath: undefined, + byteCodeHar: false, + staticDepModuleInfos: new Map(), + dynamicDepModuleInfos: new Map(), + dependenciesSet: new Set(), + dependentSet: new Set(), }; + const moduleInfos = [['pkg', moduleInfo]]; test('generate declaration && glue files && exit', () => { require('fs').readFileSync.mockReturnValue('source code'); + const id = 'processId1'; + const payload = { + fileInfo: compileFileInfo, + buildConfig: buildConfig, + moduleInfos: moduleInfos, + }; require('../../../src/build/declgen_worker'); - expect(() => { - (process as any).emit('message', { taskList: [fileInfo], buildConfig, moduleInfos }); - }).toThrow('exit'); + (process as any).emit('message', { id, payload }); - expect(require('../../../src/utils').ensurePathExists).toHaveBeenCalledTimes(2); + expect(require('../../../src/util/utils').ensurePathExists).toHaveBeenCalledTimes(2); expect(fakeArkts.Config.create).toHaveBeenCalled(); - expect(fakeArkts.Context.createFromString).toHaveBeenCalled(); + expect(fakeArkts.Context.createFromStringWithHistory).toHaveBeenCalled(); expect(fakeArkts.proceedToState).toHaveBeenCalledWith(1, 'peer', true); expect(fakeArkts.proceedToState).toHaveBeenCalledWith(2, 'peer', true); expect(fakeArkts.EtsScript.fromContext).toHaveBeenCalled(); expect(fakeArkts.generateTsDeclarationsFromContext).toHaveBeenCalled(); expect(fakeArkts.destroyConfig).toHaveBeenCalled(); expect(fakeArktsGlobal.es2panda._DestroyContext).toHaveBeenCalled(); - expect(process.exit).toHaveBeenCalledWith(0); - expect(process.send).toHaveBeenCalledWith(expect.objectContaining({ + expect(process.send).toHaveBeenCalledWith({ + id: 'processId1', success: true, - filePath: fileInfo.filePath - })); - }); - - test('handle error && send fail message', () => { - jest.spyOn(process, 'exit').mockImplementation(() => undefined as never); - require('fs').readFileSync.mockImplementation(() => { throw new Error('fail'); }); - require('../../../src/build/declgen_worker'); - expect(() => { - (process as any).emit('message', { taskList: [fileInfo], buildConfig, moduleInfos }); - }).not.toThrow(); - - expect(process.send).toHaveBeenCalledWith(expect.objectContaining({ - success: false, - filePath: fileInfo.filePath, - error: expect.any(String) - })); - }); - - test('throw if process.send is undefined', () => { - delete (process as any).send; - require('../../../src/build/declgen_worker'); - expect(() => { - (process as any).emit('message', { taskList: [fileInfo], buildConfig, moduleInfos }); - }).toThrow('process.send is undefined. This worker must be run as a forked process.'); + shouldKill: false + }); }); test('destroy context && config', () => { require('fs').readFileSync.mockReturnValue('source code'); require('../../../src/build/declgen_worker'); - expect(() => { - (process as any).emit('message', { taskList: [fileInfo], buildConfig, moduleInfos }); - }).toThrow('exit'); + const processId = 'processId3'; + const payload = { + fileInfo: compileFileInfo, + buildConfig: buildConfig, + moduleInfos: moduleInfos, + }; + (process as any).emit('message', { processId, payload }); expect(fakeArkts.destroyConfig).toHaveBeenCalled(); expect(fakeArktsGlobal.es2panda._DestroyContext).toHaveBeenCalled(); }); + }); diff --git a/ets2panda/driver/build_system/test/ut/fileManagerTest/filemanager.test.ts b/ets2panda/driver/build_system/test/ut/fileManagerTest/filemanager.test.ts index 809fb3d50f..8f760d966a 100755 --- a/ets2panda/driver/build_system/test/ut/fileManagerTest/filemanager.test.ts +++ b/ets2panda/driver/build_system/test/ut/fileManagerTest/filemanager.test.ts @@ -15,7 +15,7 @@ import { FileManager } from '../../../src/plugins/FileManager'; import { LANGUAGE_VERSION } from '../../../src/pre_define'; -import * as utils from '../../../src/utils'; +import * as utils from '../../../src/util/utils'; // This test suite is for the FileManager class, which manages file paths and language versions in the build system. describe('class FileManager', () => { diff --git a/ets2panda/driver/build_system/test/ut/generate_arktsconfigTest/generate_arktsconfig.test.ts b/ets2panda/driver/build_system/test/ut/generate_arktsconfigTest/generate_arktsconfig.test.ts index 67bc81d332..daca36aec1 100755 --- a/ets2panda/driver/build_system/test/ut/generate_arktsconfigTest/generate_arktsconfig.test.ts +++ b/ets2panda/driver/build_system/test/ut/generate_arktsconfigTest/generate_arktsconfig.test.ts @@ -13,7 +13,7 @@ * limitations under the License. */ -import { ArkTSConfigGenerator } from '../../../src/build/generate_arktsconfig'; +import { ArkTSConfigGenerator, ArkTSConfig } from '../../../src/build/generate_arktsconfig'; import { BuildConfig, BUILD_MODE, BUILD_TYPE, ModuleInfo, OHOS_MODULE_TYPE } from '../../../src/types'; import { mockLogger, moduleInfoWithNullSourceRoots, moduleInfoWithFalseEts2Ts } from '../mock/mockData'; import * as fs from 'fs'; @@ -77,6 +77,7 @@ describe('test writeArkTSConfigFile in normal and abnormal scenarios', () => { (generator as any).getDynamicPathSection = jest.fn(); (generator as any).logger = mockLogger; (generator as any).dynamicSDKPaths = ['/sdk/apis/interop']; + (generator as any).arktsconfigs = new Map(); }); afterEach(() => { @@ -85,311 +86,16 @@ describe('test writeArkTSConfigFile in normal and abnormal scenarios', () => { test('should throw error if sourceRoots is empty', () => { expect(() => { - generator.writeArkTSConfigFile(moduleInfoWithNullSourceRoots, false, mockConfig); + generator.generateArkTSConfigFile(moduleInfoWithNullSourceRoots, false); }).toThrow('Exit with error.'); }); test('should generate correct arktsConfig when enableDeclgenEts2Ts is false', () => { - generator.writeArkTSConfigFile(moduleInfoWithFalseEts2Ts, false, mockConfig); + generator.generateArkTSConfigFile(moduleInfoWithFalseEts2Ts, false); expect((generator as any).getDependenciesSection).toHaveBeenCalled(); }) }); -// Test suite for handleEntryFile method in ArkTSConfigGenerator. -describe('handleEntryFile', () => { - test('should add path to pathSection for ARKTS_1_2 language module', () => { - const fs = require('fs'); - const path = require('path'); - - jest.spyOn(fs, 'statSync').mockReturnValue({ - isFile: () => true - } as fs.Stats); - - jest.spyOn(fs, 'readFileSync').mockReturnValue('any content'); - - jest.spyOn(path, 'resolve').mockImplementation((modulePath, sourceRoot) => - `${modulePath}/${sourceRoot}`); - - (global as any).LogDataFactory = { - newInstance: jest.fn().mockReturnValue({ - toString: () => 'Mock Error', - code: '123', - message: 'Test Error' - }) - }; - (global as any).ErrorCode = { - BUILDSYSTEM_HANDLE_ENTRY_FILE: '11410200' - }; - - const generator = Object.create(ArkTSConfigGenerator.prototype); - - generator.pathSection = {}; - generator.logger = { - printError: jest.fn(), - printInfo: jest.fn() - }; - - const LANGUAGE_VERSION = { - ARKTS_1_2: "11.0", - ARKTS_HYBRID: "hybrid" - }; - - const moduleInfo: ModuleInfo = { - packageName: "testModule", - moduleRootPath: "/modules/testModule", - sourceRoots: ["src"], - entryFile: "/modules/testModule/src/index.ets", - language: LANGUAGE_VERSION.ARKTS_1_2, - arktsConfigFile: "/config/arktsconfig.json", - compileFileInfos: [], - dynamicDepModuleInfos: new Map(), - staticDepModuleInfos: new Map(), - moduleType: OHOS_MODULE_TYPE.HAR, - isMainModule: true, - byteCodeHar: false - } as any; - - (ArkTSConfigGenerator.prototype as any).handleEntryFile.call(generator, moduleInfo); - - delete (global as any).LogDataFactory; - delete (global as any).ErrorCode; - }); - - test('should add path to pathSection for ARKTS_HYBRID language module with "use static"', () => { - const fs = require('fs'); - const path = require('path'); - - jest.spyOn(fs, 'statSync').mockReturnValue({ - isFile: () => true - } as fs.Stats); - - jest.spyOn(fs, 'readFileSync').mockReturnValue('use static\nother content'); - - jest.spyOn(path, 'resolve').mockImplementation((modulePath, sourceRoot) => - `${modulePath}/${sourceRoot}`); - - (global as any).LogDataFactory = { - newInstance: jest.fn().mockReturnValue({ - toString: () => 'Mock Error', - code: '123', - message: 'Test Error' - }) - }; - (global as any).ErrorCode = { - BUILDSYSTEM_HANDLE_ENTRY_FILE: '11410200' - }; - - const generator = Object.create(ArkTSConfigGenerator.prototype); - - generator.pathSection = {}; - generator.logger = { - printError: jest.fn(), - printInfo: jest.fn() - }; - - const LANGUAGE_VERSION = { - ARKTS_1_2: "11.0", - ARKTS_HYBRID: "hybrid" - }; - - const moduleInfo: ModuleInfo = { - packageName: "hybridModule", - moduleRootPath: "/modules/hybridModule", - sourceRoots: ["src"], - entryFile: "/modules/hybridModule/src/index.ets", - language: LANGUAGE_VERSION.ARKTS_HYBRID, - arktsConfigFile: "/config/arktsconfig.json", - compileFileInfos: [], - dynamicDepModuleInfos: new Map(), - staticDepModuleInfos: new Map(), - moduleType: OHOS_MODULE_TYPE.HAR, - isMainModule: true, - byteCodeHar: false - } as any; - - (ArkTSConfigGenerator.prototype as any).handleEntryFile.call(generator, moduleInfo); - - expect(generator.pathSection).toEqual({ - "hybridModule": ["/modules/hybridModule/src"] - }); - - expect(fs.statSync).toHaveBeenCalledWith("/modules/hybridModule/src/index.ets"); - expect(fs.readFileSync).toHaveBeenCalledWith("/modules/hybridModule/src/index.ets", "utf-8"); - expect(path.resolve).toHaveBeenCalledWith("/modules/hybridModule", "src"); - - delete (global as any).LogDataFactory; - delete (global as any).ErrorCode; - }); - - test('should NOT add path to pathSection for ARKTS_HYBRID language module without "use static"', () => { - const fs = require('fs'); - const path = require('path'); - - jest.spyOn(fs, 'statSync').mockReturnValue({ - isFile: () => true - } as fs.Stats); - - jest.spyOn(fs, 'readFileSync').mockReturnValue('import something\nother content'); - - jest.spyOn(path, 'resolve').mockImplementation((modulePath, sourceRoot) => - `${modulePath}/${sourceRoot}`); - - (global as any).LogDataFactory = { - newInstance: jest.fn().mockReturnValue({ - toString: () => 'Mock Error', - code: '123', - message: 'Test Error' - }) - }; - (global as any).ErrorCode = { - BUILDSYSTEM_HANDLE_ENTRY_FILE: '11410200' - }; - - const generator = Object.create(ArkTSConfigGenerator.prototype); - - generator.pathSection = {}; - generator.logger = { - printError: jest.fn(), - printInfo: jest.fn() - }; - - const LANGUAGE_VERSION = { - ARKTS_1_2: "11.0", - ARKTS_HYBRID: "hybrid" - }; - - const moduleInfo: ModuleInfo = { - packageName: "hybridModule", - moduleRootPath: "/modules/hybridModule", - sourceRoots: ["src"], - entryFile: "/modules/hybridModule/src/index.ets", - language: LANGUAGE_VERSION.ARKTS_HYBRID, - arktsConfigFile: "/config/arktsconfig.json", - compileFileInfos: [], - dynamicDepModuleInfos: new Map(), - staticDepModuleInfos: new Map(), - moduleType: OHOS_MODULE_TYPE.HAR, - isMainModule: true, - byteCodeHar: false - } as any; - - (ArkTSConfigGenerator.prototype as any).handleEntryFile.call(generator, moduleInfo); - - expect(generator.pathSection).toEqual({}); - - expect(fs.statSync).toHaveBeenCalledWith("/modules/hybridModule/src/index.ets"); - expect(fs.readFileSync).toHaveBeenCalledWith("/modules/hybridModule/src/index.ets", "utf-8"); - delete (global as any).LogDataFactory; - delete (global as any).ErrorCode; - }); - - test('should handle error when entry file does not exist', () => { - const fs = require('fs'); - const path = require('path'); - - jest.spyOn(fs, 'statSync').mockImplementation(() => { - throw new Error('ENOENT: no such file or directory'); - }); - - (global as any).LogDataFactory = { - newInstance: jest.fn().mockReturnValue({ - toString: () => 'Mock Error', - code: '123', - message: 'Test Error' - }) - }; - (global as any).ErrorCode = { - BUILDSYSTEM_HANDLE_ENTRY_FILE: '11410200' - }; - - const generator = Object.create(ArkTSConfigGenerator.prototype); - - generator.pathSection = {}; - generator.logger = { - printError: jest.fn(), - printInfo: jest.fn() - }; - - const LANGUAGE_VERSION = { - ARKTS_1_2: "11.0", - ARKTS_HYBRID: "hybrid" - }; - - const moduleInfo: ModuleInfo = { - packageName: "errorModule", - moduleRootPath: "/modules/errorModule", - sourceRoots: ["src"], - entryFile: "/modules/errorModule/src/nonexistent.ets", - language: LANGUAGE_VERSION.ARKTS_1_2, - arktsConfigFile: "/config/arktsconfig.json", - compileFileInfos: [], - dynamicDepModuleInfos: new Map(), - staticDepModuleInfos: new Map(), - moduleType: OHOS_MODULE_TYPE.HAR, - isMainModule: true, - byteCodeHar: false - } as any; - - (ArkTSConfigGenerator.prototype as any).handleEntryFile.call(generator, moduleInfo); - - expect(generator.pathSection).toEqual({}); - - delete (global as any).LogDataFactory; - delete (global as any).ErrorCode; - }); - - test('should return early when entry file is not a regular file', () => { - const fs = require('fs'); - - jest.spyOn(fs, 'statSync').mockReturnValue({ - isFile: () => false, - isDirectory: () => true - } as fs.Stats); - - (global as any).LogDataFactory = { - newInstance: jest.fn() - }; - (global as any).ErrorCode = { - BUILDSYSTEM_HANDLE_ENTRY_FILE: '11410200' - }; - - const generator = Object.create(ArkTSConfigGenerator.prototype); - - generator.pathSection = {}; - generator.logger = { - printError: jest.fn(), - printInfo: jest.fn() - }; - - const LANGUAGE_VERSION = { - ARKTS_1_2: "11.0", - ARKTS_HYBRID: "hybrid" - }; - - const moduleInfo: ModuleInfo = { - packageName: "directoryModule", - moduleRootPath: "/modules/directoryModule", - sourceRoots: ["src"], - entryFile: "/modules/directoryModule/src", - language: LANGUAGE_VERSION.ARKTS_1_2, - arktsConfigFile: "/config/arktsconfig.json", - compileFileInfos: [], - dynamicDepModuleInfos: new Map(), - staticDepModuleInfos: new Map(), - moduleType: OHOS_MODULE_TYPE.HAR, - isMainModule: true, - byteCodeHar: false - } as any; - - (ArkTSConfigGenerator.prototype as any).handleEntryFile.call(generator, moduleInfo); - - expect(generator.pathSection).toEqual({}); - - delete (global as any).LogDataFactory; - delete (global as any).ErrorCode; - }); -}); - describe('test if the generateSystemSdkPathSection is working correctly', () => { test('should traverse directories and add correct paths to pathSection', () => { const fs = require('fs'); @@ -520,7 +226,9 @@ describe('test if the generateSystemSdkPathSection is working correctly', () => (ArkTSConfigGenerator.prototype as any).generateSystemSdkPathSection.call(generator, pathSection); expect(pathSection).toEqual({ + 'escompat': [undefined], 'external': ['/external/api/external'], + 'std': [undefined], 'widgets.widget': ['/external/api/widgets/widget'] }); @@ -617,6 +325,7 @@ describe('test if the getDependenciesSection is working correctly', () => { }); const generator = Object.create(ArkTSConfigGenerator.prototype); + generator.systemDependenciesSection = {}; generator.getOhmurl = jest.fn((file, depModuleInfo) => { return `${depModuleInfo.packageName}/${file.replace(/^src\//, '').replace(/\.ets$/, '')}`; @@ -655,14 +364,12 @@ describe('test if the getDependenciesSection is working correctly', () => { byteCodeHar: false } as any; - const dynamicPathSection: Record = {}; - (ArkTSConfigGenerator.prototype as any).getDependenciesSection.call(generator, moduleInfo, dynamicPathSection); + const arktsconfig: ArkTSConfig = new ArkTSConfig(moduleInfo); + + (ArkTSConfigGenerator.prototype as any).getDependenciesSection.call(generator, moduleInfo, arktsconfig); expect(fs.existsSync).toHaveBeenCalledWith("/modules/dep1/dist/decls.json"); expect(fs.readFileSync).toHaveBeenCalledWith("/modules/dep1/dist/decls.json", "utf-8"); - expect(generator.getOhmurl).toHaveBeenCalledWith("src/file1.ets", depModuleInfo); - expect(generator.getOhmurl).toHaveBeenCalledWith("src/index.ets", depModuleInfo); - expect(generator.getOhmurl).toHaveBeenCalledWith("src/file2.ets", depModuleInfo); expect(path.resolve).toHaveBeenCalledWith("/modules/dep1", "src/file1.ets"); expect(path.resolve).toHaveBeenCalledWith("/modules/dep1", "src/index.ets"); expect(path.resolve).toHaveBeenCalledWith("/modules/dep1", "src/file2.ets"); @@ -679,6 +386,7 @@ describe('test if the getDependenciesSection is working correctly', () => { jest.spyOn(fs, 'existsSync').mockReturnValue(false); const generator = Object.create(ArkTSConfigGenerator.prototype); + generator.systemDependenciesSection = {}; const depModuleInfo: ModuleInfo = { packageName: "dep1", @@ -713,10 +421,9 @@ describe('test if the getDependenciesSection is working correctly', () => { byteCodeHar: false } as any; - const dynamicPathSection: Record = {}; - (ArkTSConfigGenerator.prototype as any).getDependenciesSection.call(generator, moduleInfo, dynamicPathSection); + const arktsconfig: ArkTSConfig = new ArkTSConfig(moduleInfo); - expect(dynamicPathSection).toEqual({}); + (ArkTSConfigGenerator.prototype as any).getDependenciesSection.call(generator, moduleInfo, arktsconfig); expect(consoleErrorMock).toHaveBeenCalled(); expect(consoleErrorMock.mock.calls[0][0]).toContain("mainModule depends on dynamic module dep1"); @@ -732,6 +439,7 @@ describe('test if the getDependenciesSection is working correctly', () => { const consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => { }); const generator = Object.create(ArkTSConfigGenerator.prototype); + generator.systemDependenciesSection = {}; const depModuleInfo: ModuleInfo = { packageName: "dep1", @@ -765,10 +473,9 @@ describe('test if the getDependenciesSection is working correctly', () => { byteCodeHar: false } as any; - const dynamicPathSection: Record = {}; - (ArkTSConfigGenerator.prototype as any).getDependenciesSection.call(generator, moduleInfo, dynamicPathSection); + const arktsconfig: ArkTSConfig = new ArkTSConfig(moduleInfo); - expect(dynamicPathSection).toEqual({}); + (ArkTSConfigGenerator.prototype as any).getDependenciesSection.call(generator, moduleInfo, arktsconfig); expect(consoleErrorMock).toHaveBeenCalled(); expect(consoleErrorMock.mock.calls[0][0]).toContain("mainModule depends on dynamic module dep1"); @@ -790,25 +497,22 @@ describe('test if the processAlias is working correctly', () => { printInfo: jest.fn() }; - const aliasConfig = new Map>(); + const aliasConfig: Record> = {}; - const moduleAliasConfig = new Map(); - moduleAliasConfig.set("static1", { + const moduleAliasConfig: Record = {}; + moduleAliasConfig["static1"] = { isStatic: true, originalAPIName: "@ohos.test1" - } as any); - - moduleAliasConfig.set("kit1", { + } as AliasConfig; + moduleAliasConfig["kit1"] = { isStatic: false, originalAPIName: "@kit.test2" - } as any); - - moduleAliasConfig.set("dynamic1", { + } as AliasConfig; + moduleAliasConfig["dynamic1"] = { isStatic: false, originalAPIName: "@ohos.test3" - } as any); - - aliasConfig.set("testModule", moduleAliasConfig); + } as AliasConfig; + aliasConfig["testModule"] = moduleAliasConfig; generator.aliasConfig = aliasConfig; const moduleInfo: ModuleInfo = { @@ -819,32 +523,29 @@ describe('test if the processAlias is working correctly', () => { compileFileInfos: [] } as any; - const dynamicPathSection: Record = {}; + const arktsconfig: ArkTSConfig = new ArkTSConfig(moduleInfo); (ArkTSConfigGenerator.prototype as any).processAlias.call( generator, - moduleInfo, - dynamicPathSection + arktsconfig ); - expect(generator.processStaticAlias).toHaveBeenCalledWith( - "static1", - { isStatic: true, originalAPIName: "@ohos.test1" } - ); + expect(generator.processStaticAlias).toHaveBeenCalledTimes(1); + expect(generator.processDynamicAlias).toHaveBeenCalledTimes(1); expect(generator.processStaticAlias).toHaveBeenCalledWith( "kit1", - { isStatic: false, originalAPIName: "@kit.test2" } + { isStatic: false, originalAPIName: "@kit.test2" }, + arktsconfig ); expect(generator.processDynamicAlias).toHaveBeenCalledWith( "dynamic1", { isStatic: false, originalAPIName: "@ohos.test3" }, - dynamicPathSection + arktsconfig ); - expect(generator.processStaticAlias).toHaveBeenCalledTimes(2); - expect(generator.processDynamicAlias).toHaveBeenCalledTimes(1); + }); test('should handle undefined aliasConfig gracefully', () => { @@ -859,7 +560,8 @@ describe('test if the processAlias is working correctly', () => { printInfo: jest.fn() }; - generator.aliasConfig = new Map>(); + const aliasConfig: Record> = {}; + generator.aliasConfig = aliasConfig; const moduleInfo: ModuleInfo = { packageName: "testModule", @@ -869,12 +571,11 @@ describe('test if the processAlias is working correctly', () => { compileFileInfos: [] } as any; - const dynamicPathSection: Record = {}; + const arktsconfig: ArkTSConfig = new ArkTSConfig(moduleInfo); (ArkTSConfigGenerator.prototype as any).processAlias.call( generator, - moduleInfo, - dynamicPathSection + arktsconfig ); expect(generator.processStaticAlias).not.toHaveBeenCalled(); @@ -903,12 +604,11 @@ describe('test if the processAlias is working correctly', () => { compileFileInfos: [] } as any; - const dynamicPathSection: Record = {}; + const arktsconfig: ArkTSConfig = new ArkTSConfig(moduleInfo); (ArkTSConfigGenerator.prototype as any).processAlias.call( generator, - moduleInfo, - dynamicPathSection + arktsconfig ); expect(generator.processStaticAlias).not.toHaveBeenCalled(); diff --git a/ets2panda/driver/build_system/test/ut/mock/mockData.ts b/ets2panda/driver/build_system/test/ut/mock/mockData.ts index 11587529cf..4b26f7de06 100755 --- a/ets2panda/driver/build_system/test/ut/mock/mockData.ts +++ b/ets2panda/driver/build_system/test/ut/mock/mockData.ts @@ -29,7 +29,9 @@ export const moduleInfoWithNullSourceRoots: ModuleInfo = { declgenBridgeCodePath: undefined, byteCodeHar: false, staticDepModuleInfos: new Map(), - dynamicDepModuleInfos: new Map() + dynamicDepModuleInfos: new Map(), + dependenciesSet: new Set(), + dependentSet: new Set(), }; export const moduleInfoWithFalseEts2Ts: ModuleInfo = { @@ -47,6 +49,8 @@ export const moduleInfoWithFalseEts2Ts: ModuleInfo = { byteCodeHar: false, staticDepModuleInfos: new Map(), dynamicDepModuleInfos: new Map(), + dependenciesSet: new Set(), + dependentSet: new Set(), }; export const moduleInfo: ModuleInfo = { @@ -60,6 +64,8 @@ export const moduleInfo: ModuleInfo = { compileFileInfos: [], dynamicDepModuleInfos: new Map(), staticDepModuleInfos: new Map(), + dependenciesSet: new Set(), + dependentSet: new Set(), declgenV1OutPath: '/path/to/moduleA/declgen/v1', declgenV2OutPath: '/path/to/moduleA/declgen/v2', declgenBridgeCodePath: '/path/to/moduleA/bridge/code', @@ -87,3 +93,10 @@ interface mockLogger { printErrorAndExit: jest.Mock; printWarn: jest.Mock; } + +describe('mockData', () => { + it('should load correctly', () => { + const mock = require('./mockData'); + expect(mock).toBeDefined(); + }); +}); \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/process_build_configTest/process_build_config.test.ts b/ets2panda/driver/build_system/test/ut/process_build_configTest/process_build_config.test.ts index 4f1848d04b..fd9db4e067 100755 --- a/ets2panda/driver/build_system/test/ut/process_build_configTest/process_build_config.test.ts +++ b/ets2panda/driver/build_system/test/ut/process_build_configTest/process_build_config.test.ts @@ -39,19 +39,50 @@ jest.mock('../../../src/pre_define', () => ({ PANDA_SDK_PATH_FROM_SDK: 'panda', PROJECT_BUILD_CONFIG_FILE: 'projectionConfig.json' })); -jest.mock('../../../src/utils', () => ({ +jest.mock('../../../src/util/utils', () => ({ isLinux: jest.fn(() => false), isMac: jest.fn(() => false), isWindows: jest.fn(() => false) })); -const fakeArkts = {}; +const fakeArkts = { + Config: { create: jest.fn(() => ({ peer: 'peer' })) }, + Context: { + createFromString: jest.fn(() => ({ program: {}, peer: 'peer' })), + createFromStringWithHistory: jest.fn(() => ({ program: {}, peer: 'peer' })) + }, + proceedToState: jest.fn(), + Es2pandaContextState: { ES2PANDA_STATE_PARSED: 1, ES2PANDA_STATE_CHECKED: 2 }, + generateTsDeclarationsFromContext: jest.fn(), + destroyConfig: jest.fn(), + EtsScript: { fromContext: jest.fn(() => ({})) } +}; const fakeArktsGlobal = { es2panda: { - _SetUpSoPath: jest.fn() - } + _SetUpSoPath: jest.fn(), + _DestroyContext: jest.fn((pandaSDKPath: string) => { + return; + }), + }, + filePath: '', + config: '', + compilerContext: { program: {}, peer: 'peer' } }; +jest.mock('../../../src/init/init_koala_modules', () => ({ + initKoalaModules: jest.fn((buildConfig) => { + const fakeKoala = { + arkts: fakeArkts, + arktsGlobal: fakeArktsGlobal + }; + fakeKoala.arktsGlobal.es2panda._SetUpSoPath(buildConfig.pandaSdkPath); + + buildConfig.arkts = fakeKoala.arkts; + buildConfig.arktsGlobal = fakeKoala.arktsGlobal; + return fakeKoala; + }) +})); + beforeEach(() => { jest.resetModules(); jest.clearAllMocks(); @@ -163,7 +194,7 @@ describe('test processBuildConfig in different scenarios', () => { test('set DYLD_LIBRARY_PATH on Mac', () => { jest.resetModules(); - require('../../../src/utils').isMac.mockReturnValue(true); + require('../../../src/util/utils').isMac.mockReturnValue(true); const { processBuildConfig, initBuildEnv } = require('../../../src/init/process_build_config'); const config = { ...buildConfigBase, pandaSdkPath: '/sdk/panda' }; process.env.PATH = '/usr/bin'; @@ -187,10 +218,15 @@ describe('test processBuildConfig in different scenarios', () => { }); test('throw if koala wrapper require fails', () => { + jest.unmock('../../../src/init/init_koala_modules'); + jest.resetModules(); + process.env.KOALA_WRAPPER_PATH = '/bad/koala'; jest.doMock('/bad/koala', () => { throw new Error('fail'); }, { virtual: true }); const { processBuildConfig } = require('../../../src/init/process_build_config'); expect(() => processBuildConfig({ ...buildConfigBase })).toThrow(); delete process.env.KOALA_WRAPPER_PATH; - }); +}); + + }); diff --git a/ets2panda/driver/build_system/test/ut/safeRealpath.test.ts b/ets2panda/driver/build_system/test/ut/safeRealpath.test.ts index 788945ee41..cd1e822d8f 100644 --- a/ets2panda/driver/build_system/test/ut/safeRealpath.test.ts +++ b/ets2panda/driver/build_system/test/ut/safeRealpath.test.ts @@ -15,7 +15,7 @@ import * as path from 'path'; import * as fs from 'fs'; -import { safeRealpath } from '../../src/utils'; +import { safeRealpath } from '../../src/util/utils'; import { ErrorCode } from '../../src/error_code'; import { Logger } from '../../src/logger'; diff --git a/ets2panda/driver/build_system/test/ut/utilsTest/utils.test.ts b/ets2panda/driver/build_system/test/ut/utilsTest/utils.test.ts index 4733ec42d6..403fcbcbbb 100755 --- a/ets2panda/driver/build_system/test/ut/utilsTest/utils.test.ts +++ b/ets2panda/driver/build_system/test/ut/utilsTest/utils.test.ts @@ -13,7 +13,7 @@ * limitations under the License. */ -import { ensurePathExists } from '../../../src/utils'; +import { ensurePathExists } from '../../../src/util/utils'; import * as fs from 'fs'; import * as path from 'path'; import { @@ -23,7 +23,7 @@ import { isWindows, isLinux, isMac, changeFileExtension, changeDeclgenFileExtension, toUnixPath, safeRealpath -} from '../../../src/utils'; +} from '../../../src/util/utils'; import { DECL_ETS_SUFFIX } from '../../../src/pre_define'; describe('Check if the path exists. If not, create it and ensure it exists', () => { @@ -123,7 +123,7 @@ describe('test if get interop files\' path by Api', () => { }; jest.mock('fs', () => mockFs); jest.resetModules(); - const utils = require('../../../src/utils'); + const utils = require('../../../src/util/utils'); (global as any).getInteropFilePathByApi = utils.getInteropFilePathByApi; }); @@ -146,7 +146,7 @@ describe('test if get interop files\' path by Api', () => { describe('test if get OhmurlByApi works correctly', () => { beforeEach(() => { jest.resetModules(); - const utils = require('../../../src/utils'); + const utils = require('../../../src/util/utils'); (global as any).getOhmurlByApi = utils.getOhmurlByApi; (global as any).NATIVE_MODULE = require('../../../src/pre_define').NATIVE_MODULE; (global as any).ARKTS_MODULE_NAME = require('../../../src/pre_define').ARKTS_MODULE_NAME; -- Gitee