HarmonyOS开发:开发者工具链——CLI工具与自动化脚本
HarmonyOS开发:开发者工具链——CLI工具与自动化脚本
📌 核心要点:掌握命令行工具是脱离IDE依赖、实现自动化构建的第一步,hvigorw和hdc是你最该熟悉的两个命令。
背景与动机
你在DevEco Studio里点按钮构建项目、点按钮运行应用、点按钮看日志——一切都很美好。直到有一天,你要搭CI/CD流水线,发现离开IDE你什么都干不了。
或者更常见的场景:你改了一行代码,IDE要编译两分钟。你受不了了,想只编译改动的部分,但IDE的增量编译有时候不靠谱,全量编译又太慢。
这时候,命令行工具就派上用场了。hvigorw负责构建,hdc负责设备交互,再配合自动化脚本,你可以把整个开发流程搬到命令行上——更快、更可控、更适合自动化。
这篇文章,我把HarmonyOS开发中最常用的CLI工具和自动化脚本技巧讲清楚。
核心原理
工具链全景
HarmonyOS开发者工具链不是一两个命令,而是一整套体系:
graph TB
A[HarmonyOS CLI工具链] --> B[构建工具]
A --> C[设备管理]
A --> D[包管理]
A --> E[签名工具]
A --> F[自定义CLI]
B --> B1[hvigorw - 构建执行器]
B --> B2[hvigor - 构建框架]
C --> C1[hdc - 设备通信]
C --> C2[hdc list targets]
C --> C3[hdc install]
D --> D1[ohpm - 包管理器]
D --> D2[ohpm install]
D --> D3[ohpm publish]
E --> E1[hapsigner - 签名工具]
E --> E2[证书管理]
F --> F1[自定义hvigor任务]
F --> F2[Shell/Python脚本]
classDef root fill:#E91E63,stroke:#880E4F,color:#fff
classDef cat fill:#2196F3,stroke:#1565C0,color:#fff
classDef tool fill:#4CAF50,stroke:#2E7D32,color:#fff
class A root
class B,C,D,E,F cat
class B1,B2,C1,C2,C3,D1,D2,D3,E1,E2,F1,F2 tool
hvigorw vs hvigor
这俩名字容易搞混:
- hvigor:构建框架,类似Gradle。定义任务、管理依赖、配置构建流程。
- hvigorw:构建执行器的包装脚本,类似
gradlew。它会自动下载正确版本的hvigor,然后执行构建任务。
你平时用的就是hvigorw,不用关心hvigor的版本管理。
hdc是什么?
hdc(HarmonyOS Device Connector)是HarmonyOS的设备通信工具,对标Android的adb。通过hdc,你可以在命令行里安装应用、推送文件、查看日志、执行shell命令。
| hdc命令 | 作用 | 对应adb命令 |
|---|---|---|
hdc list targets |
列出已连接设备 | adb devices |
hdc install xxx.hap |
安装应用 | adb install xxx.apk |
hdc hilog |
查看日志 | adb logcat |
hdc file send |
推送文件 | adb push |
hdc shell |
进入设备shell | adb shell |
代码实战
基础用法:hvigorw常用构建命令
// hvigorw常用命令速查
//
// === 构建相关 ===
//
// 构建HAP(默认模块)
// hvigorw assembleHap
//
// 构建HSP动态共享库
// hvigorw assembleHsp
//
// 构建HAR静态共享库
// hvigorw assembleHar
//
// 构建APP包(用于发布)
// hvigorw assembleApp
//
// 清理构建产物
// hvigorw clean
//
// === 模块指定 ===
//
// 只构建指定模块
// hvigorw --module entry assembleHap
//
// 指定产品配置(多目标构建)
// hvigorw --product-name default assembleHap
//
// === 信息查看 ===
//
// 查看所有可用任务
// hvigorw --list
//
// 查看项目依赖树
// hvigorw --dependencies
//
// === 性能相关 ===
//
// 开启并行构建
// hvigorw --parallel assembleHap
//
// 开启构建缓存
// hvigorw --no-build-cache assembleHap // 反向:禁用缓存
//
// 详细日志
// hvigorw --verbose assembleHap
进阶用法:hdc设备管理自动化
hdc的命令很多,但日常用的就那几个。关键是把它们串起来,形成自动化脚本。
// DeviceManager.ets - hdc设备管理自动化脚本
// 用ArkTS写的自动化工具,可以在Node.js环境运行
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
// 设备信息接口
interface DeviceInfo {
id: string;
model: string;
sdkVersion: string;
status: 'online' | 'offline';
}
// 应用信息接口
interface AppInfo {
bundleName: string;
versionCode: number;
versionName: string;
}
export class DeviceManager {
// 列出所有已连接设备
async listDevices(): Promise<DeviceInfo[]> {
try {
const { stdout } = await execAsync('hdc list targets');
const lines = stdout.trim().split('\n').filter(l => l.length > 0);
const devices: DeviceInfo[] = [];
for (const line of lines) {
// 解析设备信息
const id = line.split('\t')[0].trim();
if (id && !id.includes('[Empty]')) {
const model = await this.getDeviceModel(id);
const sdkVersion = await this.getDeviceSdkVersion(id);
devices.push({
id,
model,
sdkVersion,
status: 'online',
});
}
}
return devices;
} catch (err) {
console.error(`获取设备列表失败: ${err}`);
return [];
}
}
// 获取设备型号
private async getDeviceModel(deviceId: string): Promise<string> {
try {
const { stdout } = await execAsync(
`hdc -t ${deviceId} shell param get const.product.model`
);
return stdout.trim();
} catch {
return 'Unknown';
}
}
// 获取设备SDK版本
private async getDeviceSdkVersion(deviceId: string): Promise<string> {
try {
const { stdout } = await execAsync(
`hdc -t ${deviceId} shell param get const.ohos.apiversion`
);
return stdout.trim();
} catch {
return 'Unknown';
}
}
// 安装HAP到指定设备
async installApp(deviceId: string, hapPath: string): Promise<boolean> {
try {
console.info(`正在安装 ${hapPath} 到设备 ${deviceId}...`);
const { stdout } = await execAsync(
`hdc -t ${deviceId} install -r ${hapPath}`,
{ timeout: 60000 } // 安装超时60秒
);
if (stdout.includes('AppMod finish') || stdout.includes('msg:install success')) {
console.info('安装成功!');
return true;
}
console.error(`安装失败: ${stdout}`);
return false;
} catch (err) {
console.error(`安装异常: ${err}`);
return false;
}
}
// 启动应用
async launchApp(deviceId: string, bundleName: string, abilityName: string): Promise<boolean> {
try {
const { stdout } = await execAsync(
`hdc -t ${deviceId} shell aa start -a ${abilityName} -b ${bundleName}`
);
return stdout.includes('start ability successfully');
} catch (err) {
console.error(`启动应用失败: ${err}`);
return false;
}
}
// 获取应用日志(实时)
async streamLogs(deviceId: string, tag?: string): Promise<void> {
const cmd = tag
? `hdc -t ${deviceId} hilog | grep "${tag}"`
: `hdc -t ${deviceId} hilog`;
console.info(`开始监听设备 ${deviceId} 的日志...`);
const child = require('child_process').spawn('hdc', [
'-t', deviceId, 'hilog'
]);
child.stdout.on('data', (data: Buffer) => {
const lines = data.toString().split('\n');
for (const line of lines) {
if (!tag || line.includes(tag)) {
console.log(line);
}
}
});
child.stderr.on('data', (data: Buffer) => {
console.error(`日志错误: ${data}`);
});
}
// 卸载应用
async uninstallApp(deviceId: string, bundleName: string): Promise<boolean> {
try {
const { stdout } = await execAsync(
`hdc -t ${deviceId} uninstall ${bundleName}`
);
return stdout.includes('msg:uninstall success');
} catch (err) {
console.error(`卸载失败: ${err}`);
return false;
}
}
// 推送文件到设备
async pushFile(deviceId: string, localPath: string, remotePath: string): Promise<boolean> {
try {
await execAsync(
`hdc -t ${deviceId} file send "${localPath}" "${remotePath}"`
);
console.info(`文件推送成功: ${localPath} -> ${remotePath}`);
return true;
} catch (err) {
console.error(`文件推送失败: ${err}`);
return false;
}
}
}
完整示例:CI/CD自动化构建脚本
把hvigorw和hdc串起来,写一个完整的CI/CD构建部署脚本。
// cicd-pipeline.ets - CI/CD自动化流水线
import { DeviceManager } from './DeviceManager';
import { exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs';
import * as path from 'path';
const execAsync = promisify(exec);
// 构建配置
interface BuildConfig {
projectDir: string; // 项目目录
moduleName: string; // 模块名
productName: string; // 产品名
outputDir: string; // 输出目录
signConfig?: { // 签名配置
certPath: string;
profilePath: string;
keystorePath: string;
keystorePwd: string;
};
}
// 流水线阶段结果
interface StageResult {
stage: string;
success: boolean;
duration: number; // 毫秒
message: string;
}
export class CICDPipeline {
private config: BuildConfig;
private results: StageResult[] = [];
private deviceManager: DeviceManager;
constructor(config: BuildConfig) {
this.config = config;
this.deviceManager = new DeviceManager();
}
// 执行完整流水线
async run(): Promise<boolean> {
console.info('========================================');
console.info(' HarmonyOS CI/CD Pipeline');
console.info('========================================\n');
// 阶段1:代码检查
const lintResult = await this.runStage('代码检查', async () => {
await execAsync('hvigorw --module entry lint', {
cwd: this.config.projectDir,
});
});
if (!lintResult) return false;
// 阶段2:单元测试
const testResult = await this.runStage('单元测试', async () => {
await execAsync('hvigorw --module entry test@unittest', {
cwd: this.config.projectDir,
timeout: 300000, // 测试超时5分钟
});
});
if (!testResult) return false;
// 阶段3:构建HAP
const buildResult = await this.runStage('构建HAP', async () => {
const cmd = `hvigorw --module ${this.config.moduleName} --product-name ${this.config.productName} assembleHap`;
await execAsync(cmd, {
cwd: this.config.projectDir,
timeout: 600000, // 构建超时10分钟
});
});
if (!buildResult) return false;
// 阶段4:签名
if (this.config.signConfig) {
const signResult = await this.runStage('应用签名', async () => {
await this.signHap();
});
if (!signResult) return false;
}
// 阶段5:部署到设备
const deployResult = await this.runStage('设备部署', async () => {
await this.deployToDevice();
});
if (!deployResult) return false;
// 输出报告
this.printReport();
return true;
}
// 签名HAP
private async signHap(): Promise<void> {
if (!this.config.signConfig) return;
const hapPath = this.findHapFile();
if (!hapPath) throw new Error('找不到HAP文件');
const { certPath, profilePath, keystorePath, keystorePwd } = this.config.signConfig;
await execAsync(
`hapsigner sign-app -keyAlias "debug" -keyPwd "${keystorePwd}" ` +
`-keystoreFile "${keystorePath}" -certFile "${certPath}" ` +
`-profileFile "${profilePath}" -inFile "${hapPath}" -outFile "${hapPath}"`,
{ cwd: this.config.projectDir }
);
}
// 部署到设备
private async deployToDevice(): Promise<void> {
const devices = await this.deviceManager.listDevices();
if (devices.length === 0) {
throw new Error('没有已连接的设备');
}
const hapPath = this.findHapFile();
if (!hapPath) throw new Error('找不到HAP文件');
// 部署到所有已连接设备
for (const device of devices) {
console.info(`部署到设备: ${device.model} (${device.id})`);
const installed = await this.deviceManager.installApp(device.id, hapPath);
if (!installed) {
throw new Error(`设备 ${device.id} 部署失败`);
}
}
}
// 查找构建产物
private findHapFile(): string | null {
const buildDir = path.join(
this.config.projectDir,
this.config.moduleName,
'build',
'default',
'outputs',
'default'
);
if (!fs.existsSync(buildDir)) return null;
const files = fs.readdirSync(buildDir).filter(f => f.endsWith('.hap'));
return files.length > 0 ? path.join(buildDir, files[0]) : null;
}
// 执行单个阶段
private async runStage(stageName: string, fn: () => Promise<void>): Promise<boolean> {
const start = Date.now();
console.info(`\n>>> 阶段: ${stageName}`);
try {
await fn();
const duration = Date.now() - start;
this.results.push({
stage: stageName,
success: true,
duration,
message: '成功',
});
console.info(`✅ ${stageName} 完成 (${(duration / 1000).toFixed(1)}s)`);
return true;
} catch (err) {
const duration = Date.now() - start;
this.results.push({
stage: stageName,
success: false,
duration,
message: (err as Error).message,
});
console.error(`❌ ${stageName} 失败: ${(err as Error).message}`);
return false;
}
}
// 输出构建报告
private printReport(): void {
console.info('\n========================================');
console.info(' 构建报告');
console.info('========================================');
let totalDuration = 0;
let allSuccess = true;
for (const r of this.results) {
const icon = r.success ? '✅' : '❌';
console.info(`${icon} ${r.stage}: ${r.message} (${(r.duration / 1000).toFixed(1)}s)`);
totalDuration += r.duration;
if (!r.success) allSuccess = false;
}
console.info(`\n总耗时: ${(totalDuration / 1000).toFixed(1)}s`);
console.info(`结果: ${allSuccess ? '🎉 全部通过' : '⚠️ 存在失败阶段'}`);
}
}
// 使用示例
async function main() {
const pipeline = new CICDPipeline({
projectDir: '/path/to/your/project',
moduleName: 'entry',
productName: 'default',
outputDir: './dist',
signConfig: {
certPath: './certs/cert.cer',
profilePath: './certs/profile.p7b',
keystorePath: './certs/debug.p12',
keystorePwd: 'your-password',
},
});
const success = await pipeline.run();
process.exit(success ? 0 : 1);
}
踩坑与注意事项
坑1:hdc连接不上设备
你插上设备,hdc list targets返回空列表。大概率是驱动没装或者USB调试没开。
解法:
- 确认设备上开启了开发者模式和USB调试
- Windows上需要安装华为USB驱动
- 如果是无线连接,先用USB连接一次,再配置TCP模式:
# USB连接后切换到TCP模式
hdc tconn 192.168.1.100:5555
坑2:hvigorw构建报内存不足
大型项目编译时,hvigorw可能报OutOfMemoryError。
解法:在hvigor-config.json5里增加JVM内存:
{
"modelVersion": "5.0.0",
"dependencies": {
},
"execution": {
"daemon": true,
"parallel": true,
"incremental": true,
"jvmOptions": "-Xmx4g" // 增加到4GB
}
}
坑3:hdc install报签名错误
你构建的HAP没签名,直接hdc install会报签名校验失败。
解法:要么用签名工具签名,要么用调试签名:
# 使用调试签名安装
hdc install -r -g your-app.hap
但注意,-g参数只在开发者模式下有效,发布版本必须正式签名。
坑4:增量编译不生效
你改了代码,但hvigorw assembleHap还是全量编译。可能是缓存被污染了。
解法:清理缓存后重新构建:
hvigorw clean
hvigorw --no-build-cache assembleHap
坑5:hdc hilog日志刷太快
设备日志每秒几百行,你根本看不过来。
解法:用过滤参数:
# 只看特定tag的日志
hdc hilog -t MyApp
# 只看ERROR级别
hdc hilog -l E
# 只看特定进程
hdc hilog -P <pid>
坑6:自定义hvigor任务不执行
你在hvigorfile.ts里定义了自定义任务,但hvigorw --list里看不到。
解法:确保任务被正确导出和注册:
// hvigorfile.ts
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: hapTasks,
plugins: [{
apply() {
this.registerTask({
name: 'myCustomTask',
run: () => {
console.log('自定义任务执行了!');
}
});
}
}]
};
HarmonyOS 6适配说明
HarmonyOS 6对CLI工具链的更新:
-
hvigor 5.0:构建框架大版本升级,支持增量编译的粒度更细(文件级别→函数级别),构建速度提升约30%。
-
hdc增强:新增
hdc perf命令,可以在命令行直接启动性能分析,不需要IDE。 -
ohpm workspaces:支持monorepo项目管理,类似npm的workspaces特性。一个仓库里有多个模块,一条命令全构建。
-
构建缓存云同步:hvigor 5.0支持将构建缓存推送到远程服务器,团队成员可以共享缓存,首次构建速度大幅提升。
总结
CLI工具不是IDE的替代品,而是IDE的补充。你日常开发用IDE没问题,但遇到CI/CD、批量操作、自动化场景,CLI才是正解。
| 维度 | 评价 |
|---|---|
| 学习难度 | ⭐⭐⭐ 命令本身简单,组合使用需要经验 |
| 使用频率 | ⭐⭐⭐⭐⭐ 每天都在用 |
| 重要程度 | ⭐⭐⭐⭐ 自动化构建的基础 |
几个关键建议:
- 先熟悉hdc:hdc是你和设备之间的桥梁,装应用、看日志、推文件,哪个离得了?
- 构建脚本化:把常用的构建命令封装成脚本,一键执行。别每次都手动敲一长串命令。
- 增量编译优先:开发阶段用增量编译,发布阶段用全量编译。别为了省事每次都全量。
- 日志要过滤:hilog刷屏是常态,学会用
-t、-l、-P过滤,不然你找不到有用的信息。
- 点赞
- 收藏
- 关注作者
评论(0)