• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import {
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}