HarmonyKit | 鸿蒙开发:hvigor 构建系统命令行与缓存机制详解

HarmonyKit | 鸿蒙开发:hvigor 构建系统命令行与缓存机制详解

引言:构建系统是项目的骨架

HarmonyKit 的第一个 commit 是一行 hvigorw assembleHap 触发的。和大多数鸿蒙开发者一样,我最初对 hvigor 的认知停留在"DevEco Studio 里那个自动跑的构建工具"。直到某天遇到了 .hvigor 目录莫名生成在源代码文件夹里导致编译失败,我才意识到:不理解构建系统,你的项目就永远有一层"黑盒"在掌控一切。

这篇文章是 HarmonyKit 项目在经历多次构建问题后积累的"避坑手册"。涵盖 hvigor 的命令行选项、daemon 机制、增量编译原理、build-profile.json5 的详细配置项,以及最常见的五个构建错误和对应的解决方案。

项目仓库:https://atomgit.com/VON-/harmony-kit

hvigor 是什么:不止是鸿蒙版的 Gradle

官方文档对 hvigor 的定义是"基于 TypeScript 的鸿蒙应用构建系统"。这个定义很容易让人联想到 Gradle------一种声明式构建语言 + 插件系统 + 任务 DAG。确实,hvigor 在架构上借鉴了 Gradle 的核心思想,但做了针对鸿蒙生态的深度定制。

hvigor 的核心结构如下:

复制代码
hvigor/
├── hvigor-config.json5    # hvigor 自身的运行配置
├── hvigorfile.ts           # 项目的构建入口(类似 build.gradle)
└── hvigorw                 # hvigor wrapper 脚本

HarmonyKit 项目中的 hvigorfile.ts 非常简洁:

typescript 复制代码
import { appTasks } from '@ohos/hvigor-ohos-plugin';

export default {
  system: appTasks,
  plugins: []
}

appTasks 是 hvigor 内置的鸿蒙应用构建插件,它定义了一整套标准任务:清理(clean)、资源处理(processResource)、编译(compileArkTS)、打包(packageHap)、签名(packageSignHap)等。plugins: [] 是自定义插件扩展点------HarmonyKit 目前不需要自定义构建逻辑,所以为空。

hvigor/hvigor-config.json5 是构建引擎本身的配置,控制着 daemon 行为、增量编译、日志级别、内存管理等。它是今天的主角。

hvigor-config.json5 完整解析

HarmonyKit 的 hvigor 配置文件保持了默认状态(所有选项注释掉),但注释本身展示了所有可配置项。让我们逐一解读:

json5 复制代码
{
  "modelVersion": "6.1.1",
  "dependencies": {},
  "execution": {
    // "analyze": "normal",
    // "daemon": true,
    // "incremental": true,
    // "parallel": true,
    // "typeCheck": false,
    // "optimizationStrategy": "memory"
  },
  "logging": {
    // "level": "info"
  },
  "debugging": {
    // "stacktrace": false
  },
  "nodeOptions": {
    // "maxOldSpaceSize": 8192,
    // "exposeGC": true
  }
}

execution.analyze:构建分析模式

控制 hvigor 如何分析模块依赖。有三个级别:

  • "normal":标准分析,默认值。适合日常开发。
  • "advanced":高级分析,会检查更深层的依赖关系,适合 CI 环境。
  • "ultrafine":超精细分析,检查所有可能的依赖路径,构建时间显著增加。
  • false:关闭分析,只执行最基础的构建。不建议使用,可能导致增量编译失效。

对于 HarmonyKit 这种单模块项目,"normal" 完全够用。多模块项目(HSP/HAR 共享包)建议在 CI 环境中使用 "advanced" 以确保模块间依赖正确。

execution.daemon:守护进程编译

默认为 true,表示启用 daemon 进程。这是 hvigor 最重要的性能特性之一。

daemon 机制的工作原理是:第一次执行 hvigor 构建时,hvigor 启动一个长驻的 Node.js 进程(daemon),该进程缓存了编译上下文、模块依赖图和 TypeScript 类型信息。后续构建请求不再启动新进程,而是复用这个 daemon 进程------避免了 JIT 预热、模块加载和缓存初始化的开销。

启用 daemon 后,HarmonyKit 的增量编译时间从约 15 秒降到约 4 秒(M1 MacBook Pro)。这 3-4 倍的性能差别来自 daemon 进程中缓存的 TypeScript 类型信息------不需要每次从磁盘重新解析 .ets 文件。

但 daemon 也有代价:

  1. 内存占用 :daemon 进程常驻内存,默认 maxOldSpaceSize 为 8192 MB(8GB)。对于 16GB 内存的机器,daemon 可能占用超过一半的可用内存。
  2. 端口占用:daemon 使用本地端口通信,如果在同一台机器上同时运行多个 DevEco Studio 实例,可能出现端口冲突。
  3. 缓存失效 :修改 hvigor-config.json5 或切换 SDK 版本后,daemon 的缓存可能不再有效,需要重启 daemon。

禁用 daemon 的方式:将 "daemon" 设为 false,或在命令行使用 hvigorw --no-daemon

execution.incremental:增量编译

默认为 true。增量编译是构建速度的核心------hvigor 只重新编译发生了变化的文件及其依赖链。

增量编译的判断依据是文件的时间戳和内容哈希。当 .ets 文件被修改后,hvigor 计算该文件的哈希值,与上次构建记录的哈希值对比。如果哈希值不同,则标记该文件为"脏"(dirty),并触发重新编译。此外,如果一个文件依赖了"脏"文件(通过 import),该文件也会被标记为"脏"并重新编译------这叫"级联重编译"。

在实际开发中,增量编译的粒度很细。举例来说,修改 ColorUtils.ets 中的一个方法实现(不改变导出签名),只会触发 ColorUtils.ets 自身和所有 import 它的文件(ColorConverter.ets)的重编译。Base64Utils.ets 不会被重编译,因为它与 ColorUtils.ets 没有依赖关系。

但也存在"过度重编译"的场景。如果修改了一个被广泛引用的类型定义------比如 ToolItem 接口------那么所有引用了 ToolItem 的文件(Index.ets、ToolCard.ets 等)都会被重编译。这是正确的行为,因为类型定义的改变会级联影响所有使用者。

关闭增量编译(设为 false)将执行全量编译------每次构建都会重新编译所有文件。这仅适用于调试构建系统本身的问题。在 HarmonyKit 开发中,我有两次需要全量编译:一次是切换 API 版本后(从 API 18 到 API 22),一次是 .hvigor 缓存损坏后。

execution.parallel:并行编译

默认为 true。并行编译允许多个文件同时编译,充分利用多核 CPU。在 HarmonyKit 这种 20+ 个 .ets 文件的项目中,并行编译的效果体现在:

  • 资源处理、ArkTS 编译、Native 编译三道工序同时进行
  • 同一工序内的独立文件并行处理
  • 无依赖关系的模块并行构建

对于 HarmonyKit 的单模块结构,并行主要作用于 ArkTS 编译阶段------Index.etsToolCard.ets 和各个工具页面的编译可以同时进行,因为它们之间没有编译顺序依赖。

需要注意的是,并行编译会增加内存使用峰值。如果遇到 JavaScript heap out of memory 错误,可以尝试关闭并行编译或增加 maxOldSpaceSize

execution.typeCheck:类型检查

默认为 false(关闭)。这是一个容易被误解的配置项。

ArkTS 的类型系统是严格模式下的 TypeScript 子集,编译器在编译阶段已经执行了类型检查。所以 typeCheck: false 并不意味着不检查类型------编译阶段已经在做了。

typeCheck: true 会触发一个额外的、更严格的类型检查阶段。该阶段使用的检查规则比编译器内置的规则更全面,可能产生更多类型警告。对于 HarmonyKit 这种代码库,编译阶段足以发现所有类型问题,无需开启此选项。

execution.optimizationStrategy:优化策略

两个选项:"memory"(默认)和 "performance"

"memory" 优先控制内存占用,适合本地开发环境。"performance" 优先构建速度,适合 CI/CD 环境,但会消耗更多内存。

HarmonyKit 选择默认的 "memory",因为我们是在 16GB 内存的 MacBook 上开发,且 DevEco Studio 本身已经消耗了相当多的内存。

logging.level:日志级别

四个级别:"debug""info""warn""error"。默认为 "info"

日常开发使用 "info" 即可。遇到构建问题需要排查时,可以临时切换到 "debug" 来查看完整的依赖解析日志和编译过程日志。

nodeOptions.maxOldSpaceSize

单位为 MB,默认 8192(8GB)。这个值控制 hvigor daemon 进程(Node.js 进程)的最大堆内存。

对于 HarmonyKit 这种中型项目,8192 是明显过大的。实际使用中,daemon 进程的内存占用约 1-2 GB。保持默认值即可,不需要调低------因为 Node.js 不会主动占用这么多内存,它只是一个上限。

命令行选项:hvigorw vs hvigor

DevEco Studio 安装时会包含两个入口:

  • hvigor:直接调用全局安装的 hvigor 包,依赖于你 PATH 中的 hvigor 版本。
  • hvigorw :wrapper 脚本,和 Gradle Wrapper 的概念一样。它会在项目中查找 hvigor/hvigor-wrapper.js,通过 Wrapper 下载并使用项目指定的 hvigor 版本。

推荐始终使用 hvigorw(Wrapper 方式),这样可以保证团队所有成员和 CI 环境使用相同的 hvigor 版本。hvigorw 会自动处理版本下载和缓存。

常用命令行选项:

bash 复制代码
# 构建 debug HAP
hvigorw assembleHap

# 构建 release HAP
hvigorw assembleHap -p buildMode=release

# 清理构建产物
hvigorw clean

# 不使用 daemon 模式构建(排查问题用)
hvigorw --no-daemon assembleHap

# 显示构建堆栈信息
hvigorw assembleHap --stacktrace

# 指定日志级别
hvigorw assembleHap --info
hvigorw assembleHap --debug

# 构建 + 安装到设备
hvigorw assembleHap -p buildMode=debug
# 然后使用 hdc install

日常开发中,DevEco Studio 的"运行"按钮背后执行的就是 hvigorw assembleHap -p buildMode=debug

构建产物的存放位置

hvigor 构建后的产物按模块存放在 build/ 目录下:

复制代码
entry/build/
├── default/
│   ├── output/
│   │   └── default/
│   │       ├── entry-default-unsigned.hap    # 未签名 HAP
│   │       └── entry-default-signed.hap      # 已签名 HAP(最终产物)
│   └── intermediates/                        # 中间产物
│       ├── loader/
│       ├── res/
│       └── sourceMaps/
└── config/
    └── buildConfig.json

entry-default-signed.hap 是最终产物,可以直接通过 hdc install 安装到设备。未签名的版本在调试和 CI 签名环节有用。

build-profile.json5 的构建相关配置

除了 hvigor-config.json5,项目级别的 build-profile.json5 也包含构建相关配置。HarmonyKit 的配置如下:

json5 复制代码
{
  "app": {
    "signingConfigs": [ /* 略 */ ],
    "products": [
      {
        "name": "default",
        "signingConfig": "default",
        "targetSdkVersion": "6.0.2(22)",
        "compatibleSdkVersion": "6.0.2(22)",
        "runtimeOS": "HarmonyOS",
        "buildOption": {
          "strictMode": {
            "caseSensitiveCheck": true,
            "useNormalizedOHMUrl": true
          }
        }
      }
    ],
    "buildModeSet": [
      { "name": "debug" },
      { "name": "release" }
    ]
  },
  "modules": [
    {
      "name": "entry",
      "srcPath": "./entry",
      "targets": [
        {
          "name": "default",
          "applyToProducts": ["default"]
        }
      ]
    }
  ]
}

buildModeSet

定义了两种构建模式:debugrelease

  • debug 模式:关闭代码混淆(无论 obfuscation 配置如何),开启 SourceMap,不进行代码优化。HAP 体积较大,但支持断点调试。
  • release 模式 :根据 entry/build-profile.json5 中的 obfuscation 配置决定是否混淆,开启代码优化和资源压缩。HAP 体积更小。

HarmonyKit 在 entry/build-profile.json5 中关闭了 release 混淆:

json5 复制代码
{
  "buildOptionSet": [
    {
      "name": "release",
      "arkOptions": {
        "obfuscation": {
          "ruleOptions": {
            "enable": false,
            "files": ["./obfuscation-rules.txt"]
          }
        }
      }
    }
  ]
}

关闭混淆的考虑是:HarmonyKit 是一个开源项目,混淆会降低代码可读性,影响社区贡献者理解源码。而且作为开发者工具,HAP 体积约 3MB------远低于鸿蒙应用的 100MB 上限,混淆带来的体积优化意义不大。

strictMode

HarmonyKit 开启了两项严格检查:

  1. caseSensitiveCheck: true:启用大小写敏感检查。HarmonyOS 文件系统在某些场景下可能大小写不敏感,但 SDK 分发和上架流程要求严格大小写一致。开启此检查可以提前发现潜在问题。

  2. useNormalizedOHMUrl: true:强制使用标准化的 OHM URL 格式。OHM(OpenHarmony Module)URL 是鸿蒙应用引用资源的标准路径格式。开启此检查确保所有 import 路径符合规范。

resOptions.copyCodeResource

entry/build-profile.json5 中,HarmonyKit 明确关闭了代码资源复制:

json5 复制代码
{
  "buildOption": {
    "resOptions": {
      "copyCodeResource": {
        "enable": false
      }
    }
  }
}

这个选项控制是否将 rawfile/ 目录中的代码文件(.ets.ts.js)复制到 rawfile 资源包中。HarmonyKit 不需要从 rawfile 动态加载代码,所以关闭它以减小 HAP 体积。

五个最常见的构建错误及修复

错误一:.hvigor 目录生成在源码目录中

现象 :在 entry/src/main/ets/pages/tools/ 下出现了一个 .hvigor 子目录,或者 hvigor 目录递归嵌套,导致编译报错 Too many open files 或文件找不到。

原因 :hvigor daemon 在扫描模块源码时,如果当前工作目录或某个中间状态导致它把 .hvigor 缓存目录生成在了源码路径下。这通常是 daemon 进程异常退出后重启导致的。

修复

bash 复制代码
# 1. 清理错误的 .hvigor 目录
rm -rf entry/src/main/ets/**/.hvigor
rm -rf entry/src/main/**/.hvigor

# 2. 停止所有 hvigor daemon 进程
# macOS/Linux:
killall node
# 或者更精确地:
kill $(lsof -t -i :5038)

# 3. 清理构建缓存
rm -rf .hvigor entry/build

# 4. 重新构建(使用 --no-daemon 确保新 daemon)
hvigorw --no-daemon clean
hvigorw assembleHap

错误二:SDK 版本不匹配

现象

复制代码
ERROR: The compatibleSdkVersion 6.0.2(22) is not supported by the current SDK.

原因build-profile.json5 中配置的 SDK 版本与 DevEco Studio 安装的 SDK 版本不一致。例如你在另一台电脑上配置了 API 22,但当前电脑的 SDK Manager 中只有 API 18。

修复

  1. 打开 DevEco Studio > Settings > SDK > HarmonyOS
  2. 确认已安装与 targetSdkVersioncompatibleSdkVersion 匹配的 SDK 版本
  3. 或者修改 build-profile.json5 中的 SDK 版本为当前安装的版本
  4. 清理缓存重建:rm -rf .hvigor entry/build && hvigorw assembleHap

错误三:hvigor daemon 连接失败

现象

复制代码
ERROR: Failed to connect to hvigor daemon.
ERROR: Port 5038 is already in use.

原因:上一次 hvigor 构建进程异常退出后,端口 5038 被残留的 daemon 进程占用。或者两个 DevEco Studio 实例同时运行,端口冲突。

修复

bash 复制代码
# 查找占用 5038 端口的进程
lsof -i :5038
# 杀掉该进程
kill -9 <PID>
# 或使用 --no-daemon 跳过 daemon
hvigorw --no-daemon assembleHap

错误四:oh_modules 依赖解析失败

现象

复制代码
ERROR: ArkTS Compiler Error
ERROR: Cannot find module '@kit.UIDesignKit' or its corresponding type declarations.

原因oh_modules 目录中的依赖包未正确安装或版本不匹配。这通常发生在 clone 新项目后没有执行依赖安装,或者 ohpm 缓存损坏。

修复

bash 复制代码
# 清理并重新安装依赖
rm -rf oh_modules entry/oh_modules
ohpm install

# 如果 ohpm 缓存有问题
ohpm cache clean
ohpm install

错误五:增量编译状态不一致

现象:修改了文件但构建结果没有变化;或者删除了文件但构建仍然引用它。

原因 :增量编译的缓存状态与实际文件系统不一致。.hvigor/cache/ 目录中的缓存信息过期或损坏。

修复

bash 复制代码
# 全量清理并重建
rm -rf .hvigor
rm -rf entry/build
hvigorw --no-daemon assembleHap

构建性能优化建议

基于 HarmonyKit 的开发经验,以下是构建性能的优化建议:

  1. 保持 daemon 运行:不要在每次构建后杀掉 daemon 进程。daemon 的缓存预热需要时间------第一次构建慢是正常的,后续增量编译会快得多。

  2. 合理使用增量编译:日常开发中始终开启增量编译。仅在切换 SDK 版本或遇到"幽灵 bug"时才执行全量清理重建。

  3. 注意 import 的影响范围 :一个文件被越多的文件 import,修改它导致的级联重编译范围越大。ToolItem 接口和 CopyButton 组件是 HarmonyKit 中被引用最多的模块------修改它们会触发几乎全量重编译。在架构设计上,尽量保持核心类型的稳定性。

  4. 将不常变化的代码抽离为独立模块:如果某部分代码很少修改,可以把它放到独立的 HAR 包中,这样修改业务代码时不需要重编译它。

  5. CI 环境使用发布构建hvigorw assembleHap -p buildMode=release 会触发更完整的优化流程,虽然单次构建更慢,但产物体积更小。

构建系统的选择:hvigor 的设计哲学

最后谈谈我理解的 hvigor 设计哲学。与 Gradle(基于 Groovy/Kotlin DSL)或 Webpack(配置驱动)相比,hvigor 有几个鲜明的特征:

基于 TypeScript 的任务编排:hvigorfile.ts 使用 TypeScript 编写,和 ArkTS 共享同一套类型系统。这意味着构建脚本中的类型和源码中的类型是统一的------你可以在构建脚本中 import 项目的类型定义,实现构建时类型检查。

插件化架构@ohos/hvigor-ohos-plugin 是一个标准的 hvigor 插件,提供了鸿蒙应用的完整构建任务。HarmonyKit 的构建配置极简------只因为 hvigor-ohos-plugin 已经内置了所有常见场景的构建逻辑。

Wrapper 机制 :和 Gradle Wrapper 一样,hvigorw 保证了构建工具版本的一致性。这在多人协作中极其重要------"在我电脑上能构建"的常见根源就是构建工具版本差异。

理解构建系统不是选做题,而是必做题。当你遇到一个奇怪的编译错误而搜索引擎里没有答案时,对构建系统的理解是你排查问题的最后一道防线。

项目仓库:https://atomgit.com/VON-/harmony-kit

相关推荐
星释1 小时前
鸿蒙智能体开发实战:35.鸿蒙壁纸大师 - 调用火山引擎模型生成壁纸
算法·华为·ai·harmonyos·鸿蒙·火山引擎
数据知道1 小时前
DNS 安全攻防:缓存投毒、DNS 隧道与检测实战
安全·网络安全·缓存·缓存投毒
ZZZMMM.zip1 小时前
思维安检门-逻辑漏洞分析的HarmonyOS开发实践
人工智能·华为·harmonyos·鸿蒙·鸿蒙系统
爸爸6191 小时前
鸿蒙实战:手写板 WritePage 触摸绘图实现
华为·harmonyos·鸿蒙系统
在书中成长2 小时前
HarmonyOS 小游戏《对战五子棋》开发第35篇 - 列表渲染性能优化
harmonyos
xianjixiance_2 小时前
鸿蒙实战:SharePage 报告分享——雷达图预览与 pasteboard 剪贴板
华为·harmonyos
全堆鸿蒙12 小时前
27 RdbPredicates 条件查询详解:EqualTo、OrderBy、组合条件
华为·harmonyos·鸿蒙系统
心中有国也有家12 小时前
AtomGit Flutter 鸿蒙客户端: ChangeNotifier 模式
学习·flutter·华为·harmonyos
吴声子夜歌13 小时前
Redis 5.x——布隆过滤器
数据库·redis·缓存