写点什么

一键多环境构建——用 Hvigor 玩转 HarmonyOS Next

作者:鸿蒙魔法师
  • 2025-05-16
    湖南
  • 本文字数:3968 字

    阅读完需:约 13 分钟

一键多环境构建——用 Hvigor 玩转 HarmonyOS Next

引言

在 HarmonyOS Next 的应用开发中,常常需要针对不同环境(测试、预发、线上)或不同签名(调试、正式)输出多个 APP/HAP 包。虽然 HarmonyOS 提供了多目标构建(Multi-Target Build)能力,可以在同一项目里配置多个 product 并生成不同包名的多个产物,但某些深度定制(如动态修改 module.json5 中的 client_idapp_id,或基于构建时间改写输出包名)仍需借助自定义 Hvigor 插件来完成。


结合示例项目,演示如何通过 Hvigor 插件配合多目标构建,实现:


  1. 统一管理各环境的 API 地址、埋点地址等配置;

  2. 动态注入 clientIdappId 等应用信息;

  3. 基于构建时间和 product 名称,定制化输出包名,便于后续上架和线上排查。



一、在项目中使用 Hvigor 自定义插件

1. Hvigor 插件的入口

在 HarmonyOS 项目根目录存在入口文件 hvigorfile.ts,将自定义 task 注册到 plugins 中:


// 文件:hvigorfile.tsimport { buildSchemaProcessing } from './scripts/build-schema-processing';
export default { system: appTasks, // Hvigor 内置任务,不可修改 plugins: [ buildSchemaProcessing() // 自定义插件:动态修改 module.json5、build-profile.json5 等 ]};
复制代码


点击 DevEco Studio 上方的 Sync Now 或执行 hvigor sync,即可将插件加载到构建流程中。



2. Hvigor 常用 API 概览

在自定义插件代码中,我们通常会用到以下核心 API:


  • hvigor.getRootNode():获取根节点,进而拿到各插件上下文

  • rootNode.getContext(pluginId):获取指定插件的上下文对象,例如 OHOS_APP_PLUGINOHOS_HAP_PLUGIN

  • context.getAppJsonOpt() / getModuleJsonOpt():读取 app.json5module.json5 的 AST 对象

  • context.getBuildProfileOpt():读取根目录 build-profile.json5 的 AST 对象

  • context.setAppJsonOpt() / setBuildProfileOpt():将修改后的 AST 写回,生效于后续构建


// 示例:读取上下文const root = hvigor.getRootNode();const appCtx = root.getContext(OhosPluginId.OHOS_APP_PLUGIN) as OhosAppContext;const hapCtx = root.getContext(OhosPluginId.OHOS_HAP_PLUGIN) as OhosAppContext;const appJson = appCtx.getAppJsonOpt();const modJson = hapCtx.getModuleJsonOpt();const buildProfile = appCtx.getBuildProfileOpt();
// 修改后需要重新 set 回去appCtx.setAppJsonOpt(appJson);appCtx.setBuildProfileOpt(buildProfile);
复制代码




二、结合多目标构建与自定义插件 实现环境切换

1. 规范 Product 命名

为了脚本中便于解析,我们约定 productname 采用下划线分隔、固定格式:


<应用标识>_<签名标识>_<环境标识>例如:demo1_debug_test、demo1_release_preRelease、demo2_debug_official
复制代码


build-profile.json5 中配置多个 product:


"products": [  {    "name": "demo1_debug_test",    "signingConfig": "debug",    "buildOption": {      "arkOptions": {        "buildProfileFields": {          "productsName": "demo1",          "buildTime": "",          "apiUrl": "",          "trackApiUrl": ""        }      }    }  },  {    "name": "demo1_release_officiallyReleased",    "signingConfig": "release",    "buildOption": { /* 同上 */ }  },  /* 更多 product 配置… */]
复制代码


TipbuildProfileFields 下的字段会被注入到应用运行时,便于在代码中读取环境信息。



2. 本地配置文件:config.json

将各环境的 API 地址,以及各应用的 clientId/appId 信息集中管理:


{  "environmentInfo": {    "test": {      "apiUrl": "https://api.test.com",      "trackApiUrl": "https://stat.test.com"    },    "preRelease": {      "apiUrl": "https://api-pre.example.com",      "trackApiUrl": "https://stat-pre.example.com"    },    "officiallyReleased": {      "apiUrl": "https://api.prod.com",      "trackApiUrl": "https://stat.prod.com"    }  },  "appConfigInfo": {    "demo1": {      "clientId": "111898773",      "appId": "5765880207855284373"    },    "demo2": {      "clientId": "6917571239128090930",      "appId": "6917571239128090930"    }  }}
复制代码


并提供两个辅助函数,在插件中使用:


// scripts/config.tsimport cfgRaw from '../config.json';import { format } from 'date-fns';
export function formatBuildTime(date = new Date()): string { const pad = (n: number) => String(n).padStart(2, '0'); return `${date.getFullYear()}-${pad(date.getMonth()+1)}-${pad(date.getDate())}` + `_${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`;}
export function getLocalConfig() { const cfg = JSON.parse(JSON.stringify(cfgRaw)); cfg.buildTime = formatBuildTime(); return cfg;}
复制代码



3. 插件实现核心逻辑

build-schema-processing.ts 中,利用 Hvigor 生命周期钩子完成配置注入与产物重命名。


// scripts/build-schema-processing.tsimport { getLocalConfig } from './config';
export function buildSchemaProcessing() { const localCfg = getLocalConfig(); let currentProduct = '', versionName = '', bundleName = '', appConfig: any;
return { pluginId: 'custom-build-processor', apply(hvigor) { hvigor.getRootNode().afterNodeEvaluate(root => { // 获取上下文 const appCtx = root.getContext(OhosPluginId.OHOS_APP_PLUGIN) as OhosAppContext; const hapCtx = root.getContext(OhosPluginId.OHOS_HAP_PLUGIN) as OhosAppContext; const buildProfile = appCtx.getBuildProfileOpt(); const appJson = appCtx.getAppJsonOpt(); const modJson = hapCtx.getModuleJsonOpt();
// 当前 product 信息 currentProduct = appCtx.getCurrentProduct().getProductName() || ''; versionName = appJson.app.versionName; const productKeys = currentProduct.split('_'); const appKey = productKeys[0]; const envKey = productKeys[2]; appConfig = localCfg.appConfigInfo[appKey]; const envConfig = localCfg.environmentInfo[envKey];
// 注入 app.json5 中的 clientId、appId if (modJson) { modJson['module']['appId'] = appConfig.appId; modJson['module']['clientId'] = appConfig.clientId; hapCtx.setModuleJsonOpt(modJson); }
// 遍历各 product,注入环境 & 时间 & 重命名 artifact (buildProfile.app.products || []).forEach((prd: any) => { prd.buildOption.arkOptions.buildProfileFields.buildTime = localCfg.buildTime; prd.buildOption.arkOptions.buildProfileFields.apiUrl = envConfig.apiUrl; prd.buildOption.arkOptions.buildProfileFields.trackApiUrl= envConfig.trackApiUrl;
const suffix = `${appKey}_${versionName}_${localCfg.buildTime}`; if (prd.name === currentProduct) { bundleName = prd.bundleName || appJson.app.bundleName; } prd.output.artifactName = `AtomicPlatform-${suffix}`; });
appCtx.setBuildProfileOpt(buildProfile); });
hvigor.buildFinished(() => { console.log(`📅 构建时间: ${localCfg.buildTime}`); console.log(`📦 当前产物: ${currentProduct}`); console.log(`🔖 包名(bundleName): ${bundleName}`); console.log(`🆔 ClientID: ${appConfig.clientId}`); console.log(`🆔 AppID: ${appConfig.appId}`); }); } };}
复制代码




三、运行效果与日志验证

执行 hvigor build,在日志中即可看到注入与重命名结果:


====================== 编译包信息 ======================📅 构建时间:  2025-04-24_09-32-05📦 当前产物: demo2_debug_test🔖 包名: com.atomicservice.6917571239128090930🆔 ClientID: 6917571239128090930🆔 AppID: 6917571239128090930====================================================
复制代码


同时,输出的 HAP 包会命名为:


AtomicPlatform-demo2_1.0.0_2025-04-24_09-32-05.hap
复制代码


通过上述方式,每次在不同 product 与环境下编译,都能自动完成配置注入与产物重命名,极大提升了多环境多签名的开发与发布效率。



四、总结

  • 统一管理:将环境信息、应用信息集中在 config.json,便于维护与扩展;

  • 自动注入:借助 Hvigor 插件,在构建阶段动态修改 module.json5build-profile.json5,免去手动切换;

  • 定制产物:基于构建时间与 product 名称,自定义输出包名,方便版本追踪与线上排查。


未来可在插件中进一步添加:


  1. 构建报告自动上传到 CI/CD 平台;

  2. 自动生成构建差异对比的 HTML 报告;

  3. 与 Git 提交、发布流程集成,构建完成自动触发审核或推送。

五、源码仓库

仓库分支参考:feature/hvigorfileBuild

仓库地址:MultiBuildDemo: 构建多目标产物 - Gitee.com

六、参考文档

扩展构建-编译构建-DevEco Studio - 华为HarmonyOS开发者


HarmonyOS Next 编译之如何构建不同包名应用在日常的开发中涉及到多签名和多产物构建输出时手动切换签名文件和包 - 掘金


HarmonyOS多环境+多渠道+自定义路径输出+自定义名称一键打app和hap包前言 做移动端开发时,不可避免的会遇到 - 掘金


发布于: 刚刚阅读数: 5
用户头像

鸿蒙开发工程师,个人开发应用:兔习惯 2025-04-29 加入

鸿蒙元服务踩坑选手

评论

发布
暂无评论
一键多环境构建——用 Hvigor 玩转 HarmonyOS Next_鸿蒙_鸿蒙魔法师_InfoQ写作社区