鸿蒙 Hvigor 编译插件实战

347 阅读4分钟

扩展阅读:

如何开发一个Hvigor插件,此处不再详细介绍。详情可查看“扩展阅读”列举的官方参考文档。

本文主要对 Hvigor插件中的重要组件 以及 实战项目“HelloPlugin编译插件”的情况做一个概述。

一、基本概念

在鸿蒙项目的编译过程中,Hvigor是基于 任务(Task) 对项目进行自动化构建的。

  • 任务是Hvigor构建过程中的基本执行单元,它定义了构建项目时需要执行的具体工作。 Task可以完成多种操作,比如源码编译任务,打包任务或签名任务等。
  • HvigorPlugin 定义了插件的基本范式,是多个Task的合集
  • 任务是存在依赖关系的,Hvigor在执行任何任务之前会构建任务依赖图,所有任务会形成一个有向无环图(DAG), 例如 :HAP基础任务流程图

image.png

查看任务树: 会根据工程编译中注册的任务树以下图形式输出

hvigorw taskTree

二、HelloPlugin编译插件分析

2.1 模块

HelloPlugin编译插件包含2个模块,分别为:

  • @hello-plugin/hello-hvigor-common:作为一个公共部分,抽取插件开发的基类(如 BasePluginBaseTask等),定义插件开发的基本结构,维护公共能力以及工具类等。
  • @hello-plugin/hello-hvigor-plugin:面向业务的编译过程,负责将业务编译产物中module.json5文件中关于xxx的信息进行补全或者删除

@hello-plugin/hello-hvigor-pluginpackage.json文件中声明@hello-plugin/hello-hvigor-common模块的依赖项,即可引入。 如:

{
  "name": "@hello-plugin/hello-hvigor-plugin",

  // 略...

  "dependencies": {
    "@hello-plugin/hello-hvigor-common": "file:../HelloCommon",
  },
  "bundledDependencies": [
    "@hello-plugin/hello-hvigor-common"
  ]
}

其中,bundledDependencies字段是一个包含依赖包名的数组对象,在发布时会将这个数组中声明的依赖包打到最终的发布包里。

2.2 产物/交付件

HelloPlugin编译插件包含1个交付件,为:

  • @hello-plugin/hello-hvigor-plugin

其中,@hello-plugin/hello-hvigor-common 模块作为本地依赖项,在发布插件时,会打包到上述插件的交付件中

2.3 发布

编译插件由 npm 进行打包和发布的。

  • npm 安装依赖:npm install
  • npm 发布:npm publish
  • npm 打包: npm pack
  • npm 仓:略...

2.4 调用

每个Task可以声明前置/后置Task,用于说明Task在任务依赖图内的位置,即期望自己的Task运行在这些前置task的后面/后置task的前面。

业务在使用插件时,会在业务工程的module级的hvigorfile.ts文件中调用编译插件的接口(如:genHelloHspCompilePlugin()),然后Hvigor会将上述Task注册到编译过程中。 当编译工程时,会根据Task声明的顺序,在合适的时机执行。

三、HelloPlugin编译插件实战

此处以一个自定义Task为例子,介绍了如何使用 @hello-plugin/hello-hvigor-common 中的基类

3.1 @hello-plugin/hello-hvigor-common中的基类

3.1.1 BaseTask.ts

export abstract class BaseTask {
    
    // 任务名称
    name: string;

    // 任务前置依赖,先执行 preDependency 的任务,再执行 自定义任务
    preDependency: string = '';

    // 任务后置依赖,先执行 自定义任务,再执行 postDependency 的任务
    postDependency: string = '';

    constructor(name: string, preDependency: string, postDependency: string) {
        this.name = name;
        this.preDependency = preDependency;
        this.postDependency = postDependency;
    }

    abstract run(context: ModuleContextType, moduleName: string, modulePath: string, targetName: string, productName: string): void;
}

// 注:export type ModuleContextType = OhosHarContext | OhosHapContext | OhosHspContext;

3.1.2 BaseTaskGroup.ts

/**
 * BaseTaskGroup : 绑定 context 和 Task 的关系,
 * 即只有在编译 App/Hap/Hsp/Har(context) 下才执行这些Task
 */
export abstract class BaseTaskGroup {

    groupId: string = '';

    constructor(groupId: string) {
        this.groupId = groupId;
    }

    /**
     * 获取 Task 工作时的 contextType
     * 如:在har包的module中,不适合执行与hap相关的task
     */
    abstract requireContextType(): Set<string>;

    /**
     * 向 Group 添加待执行的 Task
     */
    abstract requireTask(): Array<BaseTask>;

}

3.1.3 BaseHelloPlugin

const TAG = "BaseHelloPlugin";

export abstract class BaseHelloPlugin implements HvigorPlugin {
    pluginId: string = '';
    protected plugin: PluginDefine | undefined;
    protected taskGroupList: Array<BaseTaskGroup> = [];

    constructor(pluginId: string) {
        this.pluginId = pluginId;
    }

    registerTaskGroup(taskGroup: BaseTaskGroup): this {
        this.taskGroupList.push(taskGroup)
        return this;
    }

    apply(node: HvigorNode): void | Promise<void> {
        Logger.info(TAG, `begin to apply pluginId=${this.pluginId}`);
        this.plugin = new PluginDefine(node);
        const targetContext = this.plugin.getCurrentModuleCxt();

        if (targetContext === undefined) {
            Logger.info(TAG, `Context is undefined. exit current task`);
            return;
        }

        // 注册任务
        this.filterTask(this.plugin, this.taskGroupList).forEach((task) => {
            targetContext.targets((target: Target) => {
                try {
                    this.registerTask(node, task, targetContext, target);
                } catch (e) {
                    Logger.error(TAG, `register Task exception: ${JSON.stringify(e)}, ${e}`)
                }
            });
        })
    }

    /**
     * 制定过滤规则,在 taskGroupList 中选择部分task 进行执行
     * 默认规则:按照 task 支持的 context 进行过滤,即只有task 的 context 与当前插件集成方工程构建的需求一致时,才执行。
     * 子类可以按需 override 这个方法,设置自定义的规则
     */
    protected filterTask(plugin: PluginDefine, taskGroupList: Array<BaseTaskGroup>): Array<BaseTask> {
        let ret: Array<BaseTask> = []
        let cxtType: string = plugin.getCurrentCxtType();
        if (cxtType === undefined) {
            return ret;
        }
        Logger.info(TAG, `filter current Context: ${cxtType}`)

        // 对 taskGroupList 进行过滤,将符合当前场景的Group
        for (let group of taskGroupList) {
            let requiredCxtSet = group.requireContextType();
            if (requiredCxtSet.has(cxtType)) {
                Logger.info(TAG, `filter taskGroup : ${group.groupId}`)
                ret = ret.concat(group.requireTask())
            }
        }
        return ret;
    }

    /**
     * 向当前节点注册 task
     */
    private registerTask(node: HvigorNode, task: BaseTask, targetContext: ModuleContextType, target: Target): void {
        const targetName = target.getTargetName();
        const productName = target.getCurrentProduct().getProductName()
        // 校验 依赖的 前后任务 是否存在
        let preDep = `${targetName}@${task.preDependency}`;
        let postDep = `${targetName}@${task.postDependency}`;
        Logger.info(TAG, `begin to register Task: taskName=${task.name}, targetName:${targetName}, 
                        productName:${productName}, preDep=${preDep}, postDep=${postDep}`);
        if (node.getTaskByName(preDep) === undefined ||
            node.getTaskByName(postDep) === undefined) {
            Logger.error(TAG, `task on depending not exist. preDep:${preDep}, postDep:${postDep}`)
            return;
        }
        // 注册自己的任务task
        node.registerTask(
            {
                // 任务名称
                name: task.name,
                // 任务执行体
                run: (taskContext: HvigorTaskContext) => {
                    try {
                        Logger.info(TAG, "begin to execute Task. taskName=" + task.name + " moduleName=" + taskContext.moduleName);
                        task.run(targetContext, taskContext.moduleName, taskContext.modulePath, targetName, productName);
                        Logger.info(TAG, "end to execute Task. taskName=" + task.name);
                    } catch (e) {
                        Logger.error(TAG, `execute Task exception: ${JSON.stringify(e)}, ${e}`);
                    }
                },
                dependencies: [`${targetName}@${task.preDependency}`],
                postDependencies: [`${targetName}@${task.postDependency}`],
            }
        );
    }

}

3.2 @hello-plugin/hello-hvigor-plugin中的实现类

3.2.1 自定义Task:GenCodeTask

const TAG = 'GenCodeTask'
const TASK_NAME = 'GenCodeTask'
const TASK_NAME_HAR_PRE_BUILD = "PreBuild"
const TASK_NAME_HAR_BUILD_NATIVE_WITH_CMAKE = "BuildNativeWithCmake"

export class GenCodeTask extends BaseTask {

    constructor() {
        // 设置 自定义Task名称、前置Task 和 后置Task
        super(TASK_NAME, TASK_NAME_HAR_PRE_BUILD, TASK_NAME_HAR_BUILD_NATIVE_WITH_CMAKE);
    }

    run(context: ModuleContextType, moduleName: string, modulePath: string, targetName: string, productName: string): void {
        // do something
    }
}

3.2.2 自定义Group:GenCodeTaskGroup

const GROUP_ID = "GenCodeTaskGroup"

export class GenCodeTaskGroup extends BaseTaskGroup {

    constructor() {
        super(GROUP_ID);
    }

    requireContextType(): Set<string> {
        return new Set<string>([OhosPluginId.OHOS_HAR_PLUGIN]);
    }

    requireTask(): Array<BaseTask> {
        return [
            new GenCodeTask(),
        ];
    }
}

3.2.3 自定义Plugin:HelloGenCodePlugin

const PLUGIN_ID = "HelloGenCodePlugin"

export class HelloGenCodePlugin extends BaseHelloPlugin {
    constructor() {
        super(PLUGIN_ID);
    }
}

/**
 * 对外接口
 * 在 Index.ts 文件中将接口导出即可
 */
export function genHelloGenCodePlugin(): HvigorPlugin {
    return new HelloGenCodePlugin()
        .registerTaskGroup(new GenCodeTaskGroup())
}

3.3 工程项目实战

详见GitHub(待补充)