1/* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { 17 Logger, 18 LogData, 19 LogDataFactory 20} from '../logger'; 21import { BuildConfig } from '../types'; 22import { ErrorCode } from '../error_code'; 23import { FileManager } from './FileManager'; 24 25export enum PluginHook { 26 NEW = 'afterNew', 27 PARSED = 'parsed', 28 SCOPE_INITED = 'scopeInited', 29 CHECKED = 'checked', 30 LOWERED = 'lowered', 31 ASM_GENERATED = 'asmGenerated', 32 BIN_GENERATED = 'binGenerated', 33 CLEAN = 'clean', 34}; 35 36type PluginHandlerFunction = () => void; 37 38type PluginHandlerObject = { 39 order: 'pre' | 'post' | undefined 40 handler: PluginHandlerFunction 41}; 42 43type PluginHandler = PluginHandlerFunction | PluginHandlerObject; 44 45interface Plugins { 46 name: string, 47 afterNew?: PluginHandler, 48 parsed?: PluginHandler, 49 scopeInited?: PluginHandler, 50 checked?: PluginHandler, 51 lowered?: PluginHandler, 52 asmGenerated?: PluginHandler, 53 binGenerated?: PluginHandler, 54 clean?: PluginHandler, 55} 56 57type PluginExecutor = { 58 name: string 59 handler: PluginHandler 60}; 61 62type PluginInitFunction = () => Plugins; 63 64type RawPlugins = { 65 name: string, 66 init: PluginInitFunction | undefined 67}; 68 69class PluginContext { 70 private ast: object | undefined; 71 private program: object | undefined; 72 private projectConfig: object | undefined; 73 private fileManager: FileManager | undefined; 74 75 constructor() { 76 this.ast = undefined; 77 this.program = undefined; 78 this.projectConfig = undefined; 79 this.fileManager = undefined; 80 } 81 82 public setArkTSAst(ast: object): void { 83 this.ast = ast; 84 } 85 86 public getArkTSAst(): object | undefined { 87 return this.ast; 88 } 89 90 public setArkTSProgram(program: object): void { 91 this.program = program; 92 } 93 94 public getArkTSProgram(): object | undefined { 95 return this.program; 96 } 97 98 public setProjectConfig(projectConfig: object): void { 99 if (this.projectConfig) { 100 const logData: LogData = LogDataFactory.newInstance( 101 ErrorCode.BUILDSYSTEM_PLUGIN_CONTEXT_RESET_PROJECT_CONFIG, 102 'Trying to reset projectConfig in PluginContext, abort compiling.', 103 'projectConfig in PluginContext can only be set once.' 104 ); 105 Logger.getInstance().printErrorAndExit(logData); 106 return; 107 } 108 this.projectConfig = projectConfig; 109 } 110 111 public getProjectConfig(): object | undefined { 112 return this.projectConfig; 113 } 114 115 public setFileManager(projectConfig: BuildConfig):void{ 116 if(!this.fileManager){ 117 FileManager.init(projectConfig); 118 this.fileManager = FileManager.getInstance(); 119 } 120 } 121 122 public getFileManager():FileManager| undefined{ 123 return this.fileManager; 124 } 125} 126 127export class PluginDriver { 128 private static instance: PluginDriver | undefined; 129 private sortedPlugins: Map<PluginHook, PluginExecutor[] | undefined>; 130 private allPlugins: Map<string, Plugins>; 131 private context: PluginContext; 132 private logger: Logger = Logger.getInstance(); 133 134 constructor() { 135 this.sortedPlugins = new Map<PluginHook, PluginExecutor[] | undefined>(); 136 this.allPlugins = new Map<string, Plugins>(); 137 this.context = new PluginContext(); 138 } 139 140 public static getInstance(): PluginDriver { 141 if (!this.instance) { 142 this.instance = new PluginDriver(); 143 } 144 return this.instance; 145 } 146 147 public static destroyInstance(): void { 148 PluginDriver.instance = undefined; 149 } 150 151 public initPlugins(projectConfig: BuildConfig): void { 152 if (!projectConfig || !projectConfig.plugins) { 153 return; 154 } 155 156 const pluginResults: RawPlugins[] = Object.entries(projectConfig.plugins).map(([key, value]) => { 157 try { 158 let pluginObject = require(value as string); 159 let initFunction = Object.values(pluginObject)[0] as PluginInitFunction; 160 if (typeof initFunction !== 'function') { 161 throw ('Failed to load plugin: plugin in wrong format'); 162 } 163 this.logger.printInfo(`Loaded plugin: ', ${key}, ${pluginObject}`); 164 165 return { 166 name: key, 167 init: initFunction 168 }; 169 } catch (error) { 170 const logData: LogData = LogDataFactory.newInstance( 171 ErrorCode.BUILDSYSTEM_LOAD_PLUGIN_FAIL, 172 'Failed to load plugin.', 173 error as string 174 ); 175 this.logger.printError(logData); 176 return { 177 name: key, 178 init: undefined 179 }; 180 } 181 }); 182 183 pluginResults.forEach((plugin: RawPlugins) => { 184 if (plugin.init !== undefined) { 185 this.allPlugins.set(plugin.name, plugin.init()); 186 } 187 }); 188 189 this.context.setProjectConfig(projectConfig); 190 this.context.setFileManager(projectConfig); 191 } 192 193 private getPlugins(hook: PluginHook) : PluginExecutor[] | undefined { 194 if (!this.sortedPlugins.has(hook)) { 195 const sortedPlugins: PluginExecutor[] = this.getSortedPlugins(hook); 196 if (sortedPlugins.length === 0) { 197 this.sortedPlugins.set(hook, undefined); 198 } else { 199 this.sortedPlugins.set(hook, sortedPlugins); 200 } 201 } 202 203 return this.sortedPlugins.get(hook); 204 } 205 206 private getSortedPlugins(hook: PluginHook): PluginExecutor[] { 207 let pre: PluginExecutor[] = []; 208 let normal: PluginExecutor[] = []; 209 let post: PluginExecutor[] = []; 210 211 this.allPlugins.forEach((pluginObject: Plugins, name: string) => { 212 if (!(pluginObject[hook])) { 213 return; 214 } 215 216 let pluginName: string = pluginObject.name; 217 let handler: PluginHandler = pluginObject[hook]; 218 let order: string | undefined = typeof handler === 'object' ? handler.order : undefined; 219 220 let rawPluginHook: PluginExecutor = { 221 name: pluginName, 222 handler: typeof handler === 'object' ? handler.handler : handler 223 }; 224 225 if (order === 'pre') { 226 pre.push(rawPluginHook); 227 } else if (order === 'post') { 228 post.push(rawPluginHook); 229 } else { 230 normal.push(rawPluginHook); 231 } 232 }); 233 234 return [...pre, ...normal, ...post]; 235 } 236 237 public runPluginHook(hook: PluginHook): void { 238 let plugins: PluginExecutor[] | undefined = this.getPlugins(hook); 239 if (!plugins) { 240 return; 241 } 242 plugins.forEach((executor: PluginExecutor) => { 243 this.logger.printInfo(`executing plugin: ${executor.name}`); 244 return (executor.handler as Function).apply(this.context); 245 }); 246 } 247 248 public getPluginContext(): PluginContext { 249 return this.context; 250 } 251}