* 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 {
Logger,
LogData,
LogDataFactory
} from '../logger';
import { BuildConfig, KPointer } from '../types';
import { ErrorCode, DriverError } from '../util/error';
import { FileManager } from './FileManager';
import { initKoalaPlugins } from '../init/init_koala_modules';
export enum PluginHook {
NEW = 'afterNew',
PARSED = 'parsed',
SCOPE_INITED = 'scopeInited',
CHECKED = 'checked',
LOWERED = 'lowered',
ASM_GENERATED = 'asmGenerated',
BIN_GENERATED = 'binGenerated',
CLEAN = 'clean',
};
type PluginHandlerFunction = () => void;
type PluginHandlerObject = {
order: 'pre' | 'post' | undefined
handler: PluginHandlerFunction
};
type PluginHandler = PluginHandlerFunction | PluginHandlerObject;
interface Plugins {
name: string,
afterNew?: PluginHandler,
parsed?: PluginHandler,
scopeInited?: PluginHandler,
checked?: PluginHandler,
lowered?: PluginHandler,
asmGenerated?: PluginHandler,
binGenerated?: PluginHandler,
clean?: PluginHandler,
}
type PluginExecutor = {
name: string
handler: PluginHandler
};
type PluginInitFunction = () => Plugins;
type RawPlugins = {
name: string,
init: PluginInitFunction | undefined
};
class PluginContext {
private ast: object | undefined;
private program: object | undefined;
private projectConfig: object | undefined;
private fileManager: FileManager | undefined;
private contextPtr: KPointer | undefined;
constructor() {
this.ast = undefined;
this.program = undefined;
this.projectConfig = undefined;
this.fileManager = undefined;
this.contextPtr = undefined;
}
public setArkTSAst(ast: object): void {
this.ast = ast;
}
public getArkTSAst(): object | undefined {
return this.ast;
}
public setArkTSProgram(program: object): void {
this.program = program;
}
public getArkTSProgram(): object | undefined {
return this.program;
}
public setProjectConfig(projectConfig: object): void {
this.projectConfig = projectConfig;
}
public getProjectConfig(): object | undefined {
return this.projectConfig;
}
public setFileManager(projectConfig: BuildConfig): void {
if (!this.fileManager) {
FileManager.init(projectConfig);
this.fileManager = FileManager.getInstance();
}
}
public getFileManager(): FileManager | undefined {
return this.fileManager;
}
public setContextPtr(ptr: KPointer): void {
this.contextPtr = ptr;
}
public getContextPtr(): KPointer | undefined {
return this.contextPtr;
}
}
export class PluginDriver {
private static instance: PluginDriver | undefined;
private sortedPlugins: Map<PluginHook, PluginExecutor[] | undefined>;
private allPlugins: Map<string, Plugins>;
private context: PluginContext | undefined;
private logger: Logger = Logger.getInstance();
constructor() {
this.sortedPlugins = new Map<PluginHook, PluginExecutor[] | undefined>();
this.allPlugins = new Map<string, Plugins>();
this.context = new PluginContext();
}
public static getInstance(): PluginDriver {
if (!this.instance) {
this.instance = new PluginDriver();
}
return this.instance;
}
public static destroyInstance(): void {
PluginDriver.instance = undefined;
}
public setPluginContext(ctxPeer: KPointer): void {
this.context!.setContextPtr(ctxPeer)
}
public initPlugins(projectConfig: BuildConfig): void {
if (!projectConfig || !projectConfig.plugins) {
return;
}
initKoalaPlugins(projectConfig)
const pluginResults: RawPlugins[] = []
Object.entries(projectConfig.plugins).forEach(([key, value]) => {
try {
let pluginObject = require(value as string);
let initFunction = Object.values(pluginObject)[0] as PluginInitFunction;
if (typeof initFunction !== 'function') {
throw ('Failed to load plugin: plugin in wrong format');
}
this.logger.printInfo(`[Plugin Driver] Loaded plugin: ', ${key}, ${value}`);
pluginResults.push({
name: key,
init: initFunction
})
} catch (error) {
if (error instanceof Error) {
throw new DriverError(
LogDataFactory.newInstance(
ErrorCode.BUILDSYSTEM_LOAD_PLUGIN_FAIL,
'Failed to load plugin.',
error.message
)
)
}
}
});
pluginResults.forEach((plugin: RawPlugins) => {
if (plugin.init !== undefined) {
this.allPlugins.set(plugin.name, plugin.init());
}
});
if (this.context !== undefined) {
this.context.setProjectConfig(projectConfig);
this.context.setFileManager(projectConfig);
}
}
private getPlugins(hook: PluginHook): PluginExecutor[] | undefined {
if (!this.sortedPlugins.has(hook)) {
const sortedPlugins: PluginExecutor[] = this.getSortedPlugins(hook);
if (sortedPlugins.length === 0) {
this.sortedPlugins.set(hook, undefined);
} else {
this.sortedPlugins.set(hook, sortedPlugins);
}
}
return this.sortedPlugins.get(hook);
}
private getSortedPlugins(hook: PluginHook): PluginExecutor[] {
let pre: PluginExecutor[] = [];
let normal: PluginExecutor[] = [];
let post: PluginExecutor[] = [];
this.allPlugins.forEach((pluginObject: Plugins, name: string) => {
if (!(pluginObject[hook])) {
return;
}
let pluginName: string = pluginObject.name;
let handler: PluginHandler = pluginObject[hook];
let order: string | undefined = typeof handler === 'object' ? handler.order : undefined;
let rawPluginHook: PluginExecutor = {
name: pluginName,
handler: typeof handler === 'object' ? handler.handler : handler
};
if (order === 'pre') {
pre.push(rawPluginHook);
} else if (order === 'post') {
post.push(rawPluginHook);
} else {
normal.push(rawPluginHook);
}
});
return [...pre, ...normal, ...post];
}
public runPluginHook(hook: PluginHook): void {
let plugins: PluginExecutor[] | undefined = this.getPlugins(hook);
if (!plugins) {
return;
}
plugins.forEach((executor: PluginExecutor) => {
this.logger.printInfo(`[Plugin Driver] executing plugin: ${executor.name}`);
return (executor.handler as Function).apply(this.getPluginContext());
});
}
public getPluginContext(): PluginContext {
if (this.context === undefined) {
throw new Error('Plugin context not initialized, pls call setPluginContext before')
}
return this.context;
}
}