本文记录 RowKalendar 接入 OpenHarmony 的完整过程,覆盖 Compose UI 库盘点、kotlinx-datetime 鸿蒙制品线选型、真实日历引擎编译、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。

RowKalendar 是 chouaibMo 开发的 Compose Multiplatform 横向滚动日历库:一个 RowKalendar composable 包住 LazyRow,内部 RowKalendarViewModel 以今天为中心维护日期窗口(初始 [-30,+30]),滚动触边时分页加载(每次 30 天、每侧封顶 maxDays),每个日期格由 DateCell 按 selected / past / future 三态渲染。本次适配与存储类库(KStore 的"真编译整库")有本质区别:RowKalendar 是纯 UI 库,六个源文件里五个锚定 Compose / AndroidX 生命周期,无法整体编进鸿蒙——因此沿用社区已过审的 cmp_qrose 路线,日历引擎(上游 ViewModel 的日期窗口状态机)1:1 去 Compose 化移植进 commonMain 原样编译成 OpenHarmony KLIB,Compose UI 物理隔离到不编译的 composeMain 源集,一个带触边分页、选中态、边界预算的完整日历引擎运行在真机上,ArkUI 页面按 JSON 快照重新渲染横向日历行。

项目地址: AtomGit/oh-tpc/RowKalendar.git

上游仓库: chouaibMo/RowKalendar

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配

RowKalendar 为 Compose Multiplatform 应用提供"横向滚动选日期"这一件事的完整答案:RowKalendar(isBounded, maxDays, content) 一个 composable 开箱即用,内置 DateCell 日期格支持 shape / colors / border / elevation 四维定制(selected / past / future 三态各一套),也可以换任意自定义 composable;ViewModel 负责日期窗口的全部生命周期——冷启动以今天为中心加载前后各 30 天、LazyRow 滚到窗口首尾时自动再加载 30 天、每侧封顶 maxDays(默认 365)、点击切换选中日期。KMP 项目把这些能力放在 commonMain,由 Android、iOS 共享。OpenHarmony 应用同样需要这套日历能力,但上游构建(Compose 1.7.0-alpha03 / Kotlin 2.0.20 / AGP 8.5.2)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS,页面可以显示日期格子,却无法证明公共日历引擎在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证日期引擎、kotlinx-datetime 运行时、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
目标缺失必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。
工具链不一致上游 Compose 1.7.0-alpha03 / Kotlin 2.0.20 / AGP 8.5.2 与鸿蒙工具链(HarmonyOS Kotlin 2.2.21-1.0.0)无法共存于一个 Gradle 构建;上游 wrapper 8.7 也需对齐。
依赖矩阵引擎依赖 kotlinx-datetime 0.6.1(无 ohos 变体)与 lifecycle-viewmodel-compose 2.8.0(无 ohos 变体且属 AndroidX 生命周期);Compose 六件套全部无法进鸿蒙线。
纯 UI 库的拆分难题引擎逻辑(ViewModel 状态机)与 Compose 类型(MutableState)编在同一批文件里,不能像 KStore 那样整库直编,必须做源码手术。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
交互语义跨不了桥LazyRow 触边加载是 Compose 组合期的副作用,桥是同步一来一回的请求-响应。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:日历引擎的去 Compose 化移植与 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。日期序列演化完全由移植后的引擎负责(算法与上游逐位一致),ArkTS 只负责调用 Native、解析 JSON 和渲染结果。

1.2 库提供的能力

Demo 提供一个真实的日历会话,对应桥接的全部入口:

  • 初始窗口(calendar):以今天为中心加载 [-30, +30] 共 61 天,默认选中今天,响应携带每个日期的完整状态;
  • 触边分页(more):请求过去/未来方向再加载 30 天,双侧各自计数、封顶 maxDays——预算耗尽后再触边不增长;
  • 选择(select):切换选中日期,全量快照返回;
  • 配置(configure):运行时调整 maxDays(1…365)与 isBounded,镜像上游 setMaxDaysToLoad / setIsBounded;
  • 重置(reset):回到今天的初始窗口,支持传入 today 覆盖用于确定性测试;
  • 版本握手:rowKalendarVersion() 返回 fork 版本号与 Kotlin 版本,来自真实 Native 调用而非静态文字。

每个日期(days 数组元素)包含:

字段作用
dateISO YYYY-MM-DD。
dayOfMonth / month / year数值拆解,ArkTS 直接摆组件。
isoWeekday / weekDayShort1=Mon…7=Sun 与三字母短标签(Mon/Tue/…,镜像上游 DateCell 的显示逻辑)。
isWeekend / isToday / isPast / isSelected三态渲染所需的全部布尔。isPast 严格小于今天(与上游 isBefore 一致,今天属于 future 分支)。

引擎侧完整能力(初始窗口、触边分页、每侧预算、选中态、isBounded 存储)全部来自上游 RowKalendarViewModel 的逐行移植,与上游语义逐位一致;JVM 侧同一份引擎源码编译出 class 文件作交叉验证目标,真机功能用例覆盖全部五个 op。

1.3 实现适配

维度要求
代码复用上游日期扩展 LocalDateExtensions.kt 原样编译(零改动);RowKalendarViewModel 的状态机逐行移植为 RowKalendarEngine(结构映射见 3.1,日期算法零改动);Compose UI 六文件物理隔离不编译。
平台目标根构建直接声明 ohosArm64 + ohosX64,保留 jvm() 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离根构建替换为极简 ohos 构建(cmp_qrose 模式);上游构建文件改名 .upstream. 保留;convention-plugins(模板遗留、未被引用)移出 includeBuild。
桥接稳定使用 2 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律引擎依赖收敛为唯一一个:kotlinx-datetime 换 0.7.1-OH-001 社区线(换线升小版本,API 完全兼容);Compose 与 lifecycle 依赖随 UI 隔离移出构建。
UI 完整单页覆盖全部桥接入口:版本握手、横向日历行、触边自动分页 + 显式按钮、三档 maxDays、isBounded 开关、重置、操作日志。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。RowKalendar 的渲染层(RowKalendar composable、DateCell)留在 composeMain 等社区 CMP 鸿蒙线成熟后接回;ArkTS Demo 按 JSON 快照用 ArkUI List 重新渲染同一套日期语义。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点模块与依赖边界、确认 datetime ohos 制品线、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版、Gradle 8.14.1
第 3 阶段:源码手术       ── commonMain→composeMain 隔离、引擎 1:1 复刻、日期扩展抽回
第 4 阶段:原生桥接       ── @CName C ABI × 2、MiniJson、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主     ── 版本握手、横向日期行、触边分页、配置区、操作日志
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
rowKalendar/                          上游唯一库模块(进 ohos 构建:jvm + ohosArm64 + ohosX64)
rowKalendar/src/commonMain/           重建:引擎(RowKalendarEngine,移植自上游 ViewModel)+ 日期扩展
rowKalendar/src/composeMain/          上游 6 个源文件整体迁移(不注册源集,不编译,git mv 历史连续)
convention-plugins/                   上游模板遗留(includeBuild 已移除,留盘不进构建)
example/nativeApp/                    Kotlin/Native 动态库(Bridge + MiniJson)
example/harmonyApp/                   DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md        鸿蒙适配说明(中英双份)
README.upstream.md                    上游原文保存
.github/workflows/publish.yml         上游 Maven Central 发布流水线(留盘,本 fork 不发布)

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :rowKalendar 与 :example:nativeApp,rootProject.name = "ohos_rowkalendar"。上游的 vanniktech 发布插件、Android library 配置、iOS framework 声明、includeBuild("convention-plugins") 全部随根构建替换剥离——本 fork 不发 Maven Central。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)
JDK21(org.gradle.java.home 指向 Homebrew openjdk@21)
OpenHarmony 目标ohosArm64 + ohosX64(jvm() 保留作交叉验证)
Gradle8.14.1(上游 8.7 对齐社区钉法;2.2.21 线不支持 Gradle 9.x)
kotlinxkotlinx-datetime 0.7.1-OH-001(ohos 制品线)
DevEco product6.0.0(20) 兼容(构建机 SDK 为 HarmonyOS 6.0.1 / API 21)
DevEco Studio6.0.1 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.rowkalendar
export JAVA_HOME="/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home"
java -version
1.3 源码依赖度盘点

上游 rowKalendar 模块只有 commonMain 一个源集、6 个 Kotlin 文件、零 expect/actual 声明:

文件依赖形态处置
RowKalendar.ktComposable + LazyRow + viewModel {}隔离→ composeMain
RowKalendarViewModel.ktAndroidX ViewModel / viewModelScope + Compose MutableState + coroutines移植→ 引擎(见 3.1)
RowKalendarUiModel.kt纯数据类 + LocalDate.now() 扩展随 ViewModel 归档→ composeMain(引擎自带快照类型)
DateCell.ktMaterial3 Card + Compose 绘制隔离→ composeMain
DateCellDefaults.ktCompose Color / Shape / Material3 主题隔离→ composeMain
extensions/LocalDateExtensions.kt纯 kotlinx-datetime(isBefore / isAfter / now())原样编译(抽回 commonMain)

expect 声明全库为零——平台差异全部被 kotlinx-datetime 吸收,这是适配面最小的前提。唯一的"手术"发生在 ViewModel:它的三样 Compose 依赖(ViewModel 基类、MutableState 可观察状态、viewModelScope.launch 协程壳)都要去掉,而日期算法一行不能动。

1.4 页面边界
标题区          ROWKALENDAR · OPENHARMONY / 版本握手行(RowKalendar 0.0.3 → 0.0.3-ohos.1 · kotlin 2.2.21-1.0.0)
状态卡          今天 / 选中 / 窗口天数 / 双侧已加载计数 / 上限 / isBounded
日历行          横向 List:weekDayShort + dayOfMonth 日期格,三态配色 + 今天描边 + 周末红字
触边区          触顶/触底自动分页(onReachStart / onReachEnd)+ 显式「加载过去 / 加载未来」按钮
配置区          maxDays 30/90/365 三档 + isBounded Toggle + 重置窗口
日志区          带时间戳的操作日志(最新在前,上限 8 条,失败红字显示 error)

页面启动时先调 rowKalendarVersion() 做握手,再 {"op":"calendar"} 拉初始窗口并滚动定位到今天(镜像上游 LazyRow 的 initialFirstVisibleItemIndex = dates.size / 2 - 1)。

第 2 阶段:目标与依赖打通

2.1 制品线验证(本适配的成败手)

引擎的日期算力全部站在 kotlinx-datetime 上(LocalDate / DatePeriod / Clock.System.now() / TimeZone.currentSystemDefault()),先查社区 Maven(https://maven.eazytec-cloud.com)上有没有 ohos 变体。用 .module 元数据验证:

org.jetbrains.kotlinx:kotlinx-datetime:0.7.1-OH-001
  variants: ohosArm64Api-published / ohosX64Api-published
            / jvmApi-published / jvmRuntime-published / iosArm64… / androidNative…

三个关键结论:其一,普通 0.6.1 与 0.7.1 在 Maven Central 上都没有 ohos 变体,必须钉 0.7.1-OH-001 后缀才解析得到;其二,OH 制品同时发布 jvm 变体,jvm() 交叉验证可以用同一个版本号,不需要双线隔离;其三,0.6→0.7 对本库用到的 API 零破坏,唯一变化是 kotlinx.datetime.Clock 被标为 deprecated(让位于 kotlin.time.Clock)——日期扩展源码保持与上游一致,文件级 @Suppress("DEPRECATION") 吸收告警。

另一个依赖就没有这么幸运:org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0 没有任何 ohos 制品,它是 ViewModel 文件的根依赖——这正是引擎必须去 Compose 化移植、而不能整库直编的根本原因。

2.2 工具链钉版(为什么是 2.2.21-1.0.0,而不是 KStore 的 2.4.20 线)

社区两条工具链线的选择完全由依赖反推:KStore 的 coroutines 1.11.0-0.1.0-07 基于 stdlib 2.4.20,必须钉 Kotlin 2.4.20-0.1.0-07;RowKalendar 的 kotlinx-datetime 0.7.1-OH-001 元数据在 2.2 线内,沿用社区已过审的 HarmonyOS Kotlin 2.2.21-1.0.0(cmp_qrose 同款)即可。代价是 Gradle 必须从上游 8.7 升到 8.14.1 对齐社区钉法——2.2.21 线不支持 Gradle 9.x,而上游源码(Kotlin 2.0.20 语法)对 2.2.21 完全向后兼容,源码零改动。

settings.gradle.kts 把 eazytec 仓库写在最前:

pluginManagement {
    repositories {
        maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
        maven("https://maven.eazytec-cloud.com/nexus/content/groups/harmonyos")
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}

rootProject.name = "ohos_rowkalendar"

include(":rowKalendar")
include(":example:nativeApp")

dependencyResolutionManagement 同款双仓置顶 + RepositoriesMode.PREFER_PROJECT。

2.3 编译真实引擎

rowKalendar/build.gradle.kts 收敛为纯 KMP 模块:

plugins {
    alias(libs.plugins.kotlin.multiplatform)
}

kotlin {
    applyDefaultHierarchyTemplate()
    jvmToolchain(21)

    jvm()            // 交叉验证线:同一份引擎源码产出 JVM class
    ohosArm64()
    ohosX64()

    sourceSets {
        commonMain.dependencies {
            api(libs.kotlinx.datetime)   // 0.7.1-OH-001,引擎唯一依赖
        }
    }
}

实测:

./gradlew :rowKalendar:compileKotlinJvm :rowKalendar:compileKotlinOhosArm64 :rowKalendar:compileKotlinOhosX64
# BUILD SUCCESSFUL(三 target 全绿,仅 datetime deprecation 告警,已抑制)

库模块不开 binaries.sharedLib——否则每个库模块各吐一份带 Kotlin/Native 运行时的 so;动态库只在 example/nativeApp 出一次,这是 cmp_qrose 确立的纪律。

2.4 根构建替换与模板遗留处置

上游 convention-plugins 是 Kotlin 官方 multiplatform-library-template 的遗留物(两个 precompiled script plugin,全仓没有任何地方 apply,实际发布走的是模块内 vanniktech 插件)。处置是把它从 includeBuild 移除、目录留盘——它的存在方式只有这一种。上游 .github/workflows/publish.yml(Maven Central 发布)同样留盘不删:本 fork 不发布,保留仅为与上游对照。所有上游构建文件改名保留(settings.upstream.gradle.kts、build.upstream.gradle.kts、gradle/libs.upstream.versions.toml、rowKalendar/build.upstream.gradle.kts),diff 一眼可辨。

第 3 阶段:源码手术(技术难点)

3.1 引擎复刻:ViewModel → Engine 的结构映射

RowKalendarViewModel 的三样 Compose 依赖去掉后,状态机逐行搬进 RowKalendarEngine.kt(commonMain,object 单例——镜像上游"每个 composable 一个 ViewModel 实例"的 ArkTS 主线程单线程调用约定):

上游(ViewModel)引擎(Engine)说明
uiState: MutableState<RowKalendarUiModel>dates / selectedDate 私有字段 + state(): RowKalendarState 快照可观察性服务 Compose 重组,桥只要不可变快照
loadInitialDates()reset(today)((-30)..30).map { today.plus(DatePeriod(days = it)) } 一字不差;today 参数化便于确定性测试
loadDates(isPast)loadPreviousDates() / loadUpcomingDates()minOf(DAYS_TO_LOAD, maxDaysToLoad - daysLoaded) 预算逻辑原样,双侧独立计数
selectDate(date)selectDate(date)原样
setMaxDaysToLoad / setIsBoundedconfigure(maxDays, bounded)原样:只存储,下次加载才生效
viewModelScope.launch { … } + isLoading 门闩同步执行上游协程壳为 Compose 重组服务;桥单线程调用无重入,日期序列演化一致。快照保留 isLoading(恒 false)对齐上游 UI 模型

两处忠实保留的"上游考古发现",是本次适配最值得记录的语义细节:

  1. isBounded 在上游实现中只写不读。setIsBounded 赋值后,loadDates 里的 maxDaysToLoad 封顶检查是无条件的——isBounded 从未参与判断。上游 README 声称"If bounded, the calendar will not load more dates when reaching the maxDays limit",但 0.0.3 的代码并未实现该分支。本 fork 原样保留(引擎行为与上游逐位一致是验收底线),Demo 把 isBounded 作为可观察字段透出。修上游逻辑不在适配边界内。
  2. "今天"落在 future 分支。DateCell 的三态判断是 isSelected → date.isBefore(today) → else,isBefore 严格小于,今天不属于 past。桥的 isPast 字段同样严格 <,ArkTS 侧另用 isToday 做高亮描边。
3.2 日期扩展抽回

LocalDateExtensions.kt(isBefore / isAfter / LocalDate.Companion.now())零 Compose 依赖,从 composeMain 抽回 commonMain 原样编译。唯一的产物差异是文件头 8 行注释 + @file:Suppress("DEPRECATION"):0.7.1-OH-001 线里 kotlinx.datetime.Clock 让位于 kotlin.time.Clock,源码保持与上游字节级一致,只压掉告警。

3.3 Compose UI 隔离

上游 6 个文件中 5 个(含已归档的 UiModel)git mv 到 src/composeMain。composeMain 不是注册的 source set,Kotlin 编译器视而不见——留它有三个用处:上游 API 对照、git blame 连续(rename 追踪 100%)、社区 CMP 鸿蒙线工具链成熟后原样接回。这与 cmp_qrose 对 *Painter.kt 的处理完全一致。

第 4 阶段:桥协议建模与原生桥接

4.1 JSON 契约
io.github.chouaibmo.rowkalendar/**(引擎 + 日期扩展,移植后原样编译)
        ↓ RowKalendarEngine 状态机 / kotlinx-datetime 日期算力
Bridge.kt(@CName × 2,扁平 JSON 进出)
        ↓ UTF-8 JSON 全量快照
N-API rowKalendarVersion() / rowKalendarDispatch()
        ↓
ArkUI Index.ets(单页)

请求与响应:

请求 : {"op":"calendar"}
       {"op":"more","direction":"past"|"future"}
       {"op":"select","date":"2026-10-20"}
       {"op":"configure","maxDays":90,"isBounded":true}
       {"op":"reset"[,"today":"2026-01-01"]}
响应 : {"ok":true,"op":"...","today":"...","selectedDate":"...",
        "isBounded":true,"maxDaysToLoad":365,
        "daysLoadedPast":30,"daysLoadedFuture":30,
        "count":61,"selectedIndex":30,
        "days":[{...日期格状态...}]}
       {"ok":false,"error":"..."}

一个核心设计:每个成功响应都是全量快照。上游的 MutableState 让 Compose 自动订阅增量;桥没有订阅通道,干脆每次把窗口、选中、双侧预算、全部日期格一次给齐,ArkTS 无需本地拼状态,@State 一刷到底。触边分页的交互语义(上游 LazyRow 首尾 item 渲染时触发加载)映射为 ArkTS List 的 onReachStart / onReachEnd 事件 + 两个显式按钮兜底。

4.2 Kotlin/Native 导出函数
@CName("RowKalendarVersion")
fun rowKalendarVersion(): String   // 版本握手:name / upstreamVersion / version / kotlin / repo

@CName("RowKalendarDispatch")
fun rowKalendarDispatch(request: String): String   // 五个 op 一口进,整体 try/catch
C ABI 符号ArkTS 方法覆盖引擎
RowKalendarDispatchrowKalendarDispatch(req)reset / load* / selectDate / configure 全部入口
RowKalendarVersionrowKalendarVersion()fork 版本握手(真机版本行的数据来源)

JSON 解析用桥内手写 MiniJson(扁平 key→string,约 60 行,cmp_qrose 审核模式同款),序列化 buildString 手拼——五个扁平字段不值得把 kotlinx-serialization 运行时与编译器插件链拖进 ohos 线。日期入参 LocalDate.parse(ISO)直接用 datetime 的解析;Kotlin 异常统一转 {"ok":false,"error":…},永不跨 FFI。

4.3 C++ N-API 与 CMake
napi_property_descriptor desc[] = {
    {"rowKalendarVersion", nullptr, NapiRowKalendarVersion, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
    {"rowKalendarDispatch", nullptr, NapiRowKalendarDispatch, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};

每个回调统一三步:napi_typeof 校验必须是 string(Version 无参跳过)、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohosrowkalendar_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohosrowkalendar_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与两个业务符号),不拖整份生成头。

CMake 侧 KN so 作 IMPORTED 库(IMPORTED_NO_SONAME TRUE),-Wl,--no-undefined 链接期暴露缺失符号,$ORIGIN rpath + POST_BUILD 把 KN so 与 libc++_shared.so 拷到 NAPI so 旁。KN so 与 libc++ 由 Gradle 侧 copyKnSoArm64 / copyKnSoX64(finalizedBy 挂在 linkDebugSharedOhos* 后)拷进 entry/libs/<abi>/——先跑 Gradle 再跑 hvigor,顺序错了 CMake 直接 FATAL_ERROR 并提示该跑哪个任务。

第 5 阶段:ArkUI 宿主

5.1 握手与初始加载
aboutToAppear(): void {
  this.handshake();                        // rowKalendarVersion() → 状态行
  this.call('{"op":"calendar"}');          // 初始窗口 61 天
  this.scroller.scrollToIndex(this.resp.selectedIndex);   // 定位今天
}

private call(request: string): void {
  const r = JSON.parse(rowKalendarDispatch(request)) as CalendarResp;
  if (!r.ok) { /* 失败红字进日志 */ return; }
  this.resp = r;                           // 全量快照一刷到底
  this.log(`✓ ${r.op}: ${r.count} days · selected ${r.selectedDate} …`);
}
5.2 日期格渲染(上游 DateCell 的 ArkTS 孪生)
  • 布局:横向 List(space: 6,镜像上游 Arrangement.spacedBy(6.dp))+ ListItem 日期格(圆角 8、内边距 h12/v8,镜像 DateCellDefaults.shape 与 padding);
  • 字号:weekDayShort 13fp Bold、dayOfMonth 24fp Bold(镜像上游 24.sp + FontWeight.Black——ArkTS 无 Black 档,取 Bold);
  • 三态配色:selected 用 #EADDFF/#21005D(Material3 primaryContainer/onPrimaryContainer),past 与 future 共用 #E8DEF8/#49454F(secondaryContainer/onSecondaryContainer,与上游默认配色同构);周末文字红(Demo 增强)、今天加 #6750A4 描边(Demo 增强,数据仍来自引擎的 isToday);
  • 交互:点击日期格调 select;onReachStart / onReachEnd 调 more(镜像上游 LazyRow 触边加载),另配「← 加载过去」「加载未来 →」两个显式按钮兜底验证。
5.3 配置区与错误边界

maxDays 三档按钮(30/90/365,当前档反色高亮)与 isBounded Toggle 都走 configure op;「重置窗口」走 reset。ok:false 时错误原文进日志区红字,Native 调用本身抛错(理论不该发生)同样被 try/catch 收进日志。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohosrowkalendar)+ cxxShared + copyKnSo*
│   └── src/ohosMain/kotlin/example/nativeapp/
│       └── Bridge.kt                         @CName × 2 + MiniJson(继承 cmp_qrose 审核模式)
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.rowkalendar、versionName 0.0.3-ohos.1
    └── entry/
        ├── build-profile.json5               externalNativeOptions + abiFilters [arm64-v8a, x86_64]
        ├── libs/{arm64-v8a,x86_64}/          linkDebugShared* 经 copyKnSo* 回填的三件套(gitignore)
        ├── src/main/cpp/CMakeLists.txt       IMPORTED 引入 KN so、$ORIGIN rpath
        ├── src/main/cpp/napi_init.cpp        N-API 导出、参数检查、DisposeString
        ├── src/main/cpp/include/libohosrowkalendar_api.h
        ├── src/main/cpp/types/librowkalendar_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 验收页面
6.2 构建、签名和安装
./gradlew :rowKalendar:compileKotlinJvm :rowKalendar:compileKotlinOhosArm64 :rowKalendar:compileKotlinOhosX64
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64

cd example/harmonyApp
hvigorw assembleHap --no-daemon

签名在 DevEco Studio 的 File → Project Structure → Signing Configs 中勾选自动生成,证书写入本机 ~/.ohos/config,不进入仓库(仓库内 build-profile.json5 的 signingConfigs 保持空数组)。

hdc list targets -v
hdc -t <设备序列号> install -r \
  entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
  -a EntryAbility -b org.terminator.ohos.rowkalendar -m entry

四、完整代码对照

4.1 整体架构

io.github.chouaibmo.rowkalendar/**(引擎 + 日期扩展,移植后原样编译)
    │ RowKalendarEngine 状态机 / kotlinx-datetime 日期算力
example/nativeapp/Bridge.kt + MiniJson
    │ RowKalendarDispatch / RowKalendarVersion
librowkalendar_napi.so
    │ rowKalendarDispatch / rowKalendarVersion
Index.ets
    │ JSON parse + @State 快照刷新
    ▼
ArkUI 真机页面(单页滚动)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven 置前、ohos_rowkalendar、convention-plugins 移出
gradle/libs.versions.tomlKotlin 2.2.21-1.0.0 + kotlinx-datetime 0.7.1-OH-001 钉版
build.gradle.kts(根)版本 0.0.3-ohos.1、group 沿用上游 io.github.chouaibmo、全局禁用 Ohos*Test* 任务
rowKalendar/build.gradle.kts纯 KMP:jvm + ohosArm64 + ohosX64,api(kotlinx-datetime)
rowKalendar/src/commonMain/.../RowKalendarEngine.kt引擎:上游 ViewModel 逐行移植(唯一新增库源文件)
rowKalendar/src/commonMain/.../extensions/LocalDateExtensions.kt上游原样(唯一 diff:deprecation 抑制注释)
rowKalendar/src/composeMain/**上游 6 文件物理隔离,不编译
example/nativeApp/build.gradle.kts双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../Bridge.kt2 个 C ABI、五 op 分发、MiniJson、错误通道
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 验收页面
README.OpenHarmony_CN.md鸿蒙适配主文档(18 节)

4.3 关键 API 对照

层次API作用
引擎reset(today)初始窗口:[-30,+30] 天,选中今天(镜像 loadInitialDates)
引擎loadPreviousDates() / loadUpcomingDates()触边分页:每次 30 天,双侧封顶 maxDaysToLoad
引擎selectDate(date)切换选中(镜像上游同名)
引擎configure(maxDays, bounded)边界配置,下次加载生效(镜像 setMaxDaysToLoad / setIsBounded)
引擎state()不可变全量快照(RowKalendarState)
上游 UIRowKalendar(isBounded, maxDays, content)Composable,隔离在 composeMain 待接回
上游 UIDateCell 三态 colors/border/elevationJSON isToday/isPast/isSelected/isWeekend + ArkTS 样式承担
NativeRowKalendarDispatch / RowKalendarVersionC ABI JSON 导出
N-APIrowKalendarDispatch(req) / rowKalendarVersion()向 ArkTS 暴露能力
ArkTScall(request)触发操作并快照刷新 UI

4.4 ArkTS 与 Kotlin 的边界

const resp = JSON.parse(rowKalendarDispatch(
  JSON.stringify({ op: 'select', date: '2026-10-20' }))) as CalendarResp;
// resp.ok === true;resp.selectedDate === '2026-10-20'
// resp.days[35].isSelected === true,其余格 isSelected === false
"select" -> {
    val date = parseDate(json.str("date")) ?: return err("date must be YYYY-MM-DD")
    engine.selectDate(date)
    stateJson("select", engine.state())      // 全量快照:61 天 × 每格 11 字段
}

两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。

五、关键决策说明

决策 1:source set 级隔离而不是模块级

kalendar 适配能做模块级隔离(kalendar 视图模块留树不编、kalendar-foundation 引擎模块整编),是因为上游本来就分了两个模块。RowKalendar 是单模块、UI 与引擎编在同一批文件里,模块级隔离无从下手——因此取 cmp_qrose 的 source set 级隔离:Compose 文件整体 git mv 到 composeMain,引擎在 commonMain 重建。git rename 追踪 100%,上游 API 对照与未来接回的路径都保留。

决策 2:引擎移植而不是引擎重写

ViewModel 的日期算法(初始窗口、分页预算、双侧计数)是这条库的全部价值,必须与上游逐位一致。做法是结构映射(MutableState→快照、协程壳→同步体),算法行原样搬运,包括两处"上游考古发现"(isBounded 只写不读、今天落在 future 分支)也忠实保留——适配记录不夹带上游 bug 修复,否则交叉验证失去基准。

决策 3:工具链钉 2.2.21 线、Gradle 升 8.14.1,与 KStore 相反

工具链版本跟着依赖走:KStore 的 coroutines 制品线逼它升到 Kotlin 2.4.20 + Gradle 9.6.1;RowKalendar 的 datetime 0.7.1-OH-001 在 2.2 线内,沿用 cmp_qrose 的 HarmonyOS Kotlin 2.2.21-1.0.0 最稳,代价是 Gradle 从 8.7 升到 8.14.1(2.2 线不支持 9.x)。两条线的经验不能互相套用,唯一通用的规则是"先查制品元数据再钉版本"。

决策 4:全量快照响应而不是增量协议

上游靠 Compose 状态订阅做增量;桥没有订阅通道,五个 op 全部返回完整快照(窗口 + 选中 + 双侧预算 + 全部日期格)。代价是单次响应稍大(61 天 × 11 字段 ≈ 数 KB),换来的是 ArkTS 侧零状态拼接逻辑、零同步 bug 面——对一个 Demo 桥而言这是正确的复杂度取舍。

决策 5:datetime 换线不换 API,交叉验证保留 jvm

0.7.1-OH-001 同时带 jvm 与 ohosArm64/ohosX64 变体(.module 实测),所以 jvm() 交叉验证线得以保留——同一份引擎源码同时产出 JVM class 与 ohos KLIB,加上引擎与上游的映射表(3.1),构成"内核没漂"的结构性证明。kalendar 因 Compose OH 制品无 jvm 变体只能删掉 jvm 靠源码零 diff 论证;RowKalendar 引擎零 Compose 依赖,没有这个约束。

六、测试与验证

6.1 测试环境

本次验证使用 macOS(arm64)、JDK 21(Homebrew openjdk@21)、DevEco Studio 6.0.1 Release(自带 SDK HarmonyOS 6.0.1 / API 21 与 hvigor 6.26.4)、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64)。

6.2 交叉验证

上游没有 commonTest(6 个源文件全在 main),没有现成测试可用——这与 KStore(51 项上游测试)不同。处置分两层:

  • 结构层:引擎与日期扩展在 commonMain,compileKotlinJvm 与 compileKotlinOhosArm64/X64 从同一份源码出双线产物(三 target 全绿),引擎相对上游的唯一 diff 是第 3.1 节的结构映射表——git diff 即是"日期算法零改动"的证明;
  • 行为层:真机功能用例(6.4)覆盖五个 op 的全部语义分支,包括预算封顶与双侧独立计数。

6.3 HAP 验证

:rowKalendar:compileKotlinJvm / compileKotlinOhosArm64 / compileKotlinOhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
nm -D: RowKalendarVersion / RowKalendarDispatch / libohosrowkalendar_symbols 均为全局 T 符号(双 ABI)
BuildNativeWithCmake / BuildNativeWithNinja: finished(双 ABI 编出 librowkalendar_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

entry-default-unsigned.hap 约 11.5 MB,内含双 ABI 各三件套(librowkalendar_napi.so / libohosrowkalendar.so / libc++_shared.so);HAP 内 strip 后 KN so arm64 约 4.7 MB、x86_64 约 3.9 MB(debugShared 未 strip 时分别约 13.6 MB / 12.4 MB,含 Kotlin/Native 运行时与 kotlinx-datetime)。

6.4 功能用例(真机实测通过)

  1. 冷启动握手行显示 RowKalendar 0.0.3 → 0.0.3-ohos.1 · kotlin 2.2.21-1.0.0(真实 Native 返回,非静态文字);
  2. 初始窗口 61 天(count=61),今天自动选中(selectedIndex=30),日历行滚动定位到今天;
  3. 日期格三态正确:今天紫底深紫字选中态、过去格灰绿、未来格正常,周末星期缩写红字;
  4. 触底自动追加 30 天(日志 ✓ more: 91 days,daysLoadedFuture 30→60),触顶同理且 daysLoadedPast 独立计数;
  5. maxDays=30 档位下触边不再增长(预算封顶,daysLoaded* 停在 30);
  6. 点击任意日期:选中态移动、状态卡同步、日志回执 ✓ select: 61 days · selected 2026-10-xx;
  7. isBounded Toggle 与 reset 正常回显,reset 后回到今天窗口并重新定位;
  8. 每步操作日志按时间戳滚动留痕。

6.5 验证结论

日历引擎、kotlinx-datetime 运行时、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的日历会话:版本握手、初始窗口、触边分页、预算封顶、选择与重置闭环(见下节效果图)。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/RowKalendar.git
cd RowKalendar
export JAVA_HOME="/path/to/jdk-21"
./gradlew :rowKalendar:compileKotlinJvm :rowKalendar:compileKotlinOhosArm64 :rowKalendar:compileKotlinOhosX64   # klib + 交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
cd example/harmonyApp
hvigorw assembleHap --no-daemon
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.rowkalendar -m entry
hdc shell hilog | grep RowKalendar

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让 :rowKalendar 真正编译成 ohosArm64 / ohosX64 动态库;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. kotlinx-datetime 在 Maven Central 上的 0.6.1 / 0.7.1 都没有 ohos 变体,必须钉 0.7.1-OH-001——resolve 失败时没有更多提示,动工前先 curl .module 元数据看变体清单;
  4. lifecycle-viewmodel-compose 无 ohos 制品是纯 UI 库整库直编的硬边界——引擎必须先去 Compose 化移植,这一刀的验收基准是"日期算法与上游逐位一致";
  5. command-line-tools 的 hvigor 6.21.2 有 fullVersion 解析缺陷:其内置 hos-sdkmanager-common 的 transferVersionIntoHosVersion() 不识别 6.0.0(20) 这类带括号的形态(实测返回 undefined → 报 SDK component missing);改用 DevEco Studio 自带 hvigor 6.26.4(/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw)后正常;
  6. DEVECO_SDK_HOME 必须指向含 default 的 SDK 根(.../Contents/sdk),多拼一层 /default 或指向 shim 目录都会组件扫描失败;
  7. compatibleSdkVersion 按构建机 SDK 实情选:qrose 模板的 5.1.0(18) 在 6.0.1 SDK 上触发组件缺失,本机实测 6.0.0(20) 可建;
  8. ArkTS 的 FontWeight 无 Black 档(上游 Compose FontWeight.Black 映射为 Bold);List 的子组件必须是 ListItem,ForEach 不能直接挂裸容器——两条都是编译期报错,好修;
  9. 上游 .gitignore 忽略了 gradle.properties(CI 动态生成),ohos 构建必须把它变成提交文件(JDK 路径、版本号、konan 开关都在里面);
  10. kotlin.native.distribution.downloadFromMaven=true 必须写进 gradle.properties,受限网络下 konan 预构建与 ohos sysroot 全靠它;
  11. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套(NAPI so / KN so / libc++)缺一不可;
  12. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free()。

8.2 已知问题

  • composeMain 里的 Compose UI 未编译(上游 RowKalendar composable、DateCell 定制体系),等社区 CMP 鸿蒙线工具链成熟后接回;接回前 ArkTS 侧的三态配色是 Material3 baseline 的手工映射;
  • isBounded 忠实保留上游"只存不判"的现状(见 3.1);若上游后续修复,引擎同步一行即可;
  • 星期短标签为上游英文三字母(Mon/Tue/…),中文化可在 ArkTS 侧按 isoWeekday 映射,协议无需变更;
  • 选中日期与窗口为进程内状态,杀进程重置(与上游 ViewModel 行为一致),无持久化语义;
  • android / ios 目标剥离(源文件留在 composeMain 与 build.upstream 里),发布体系一并剥离,本 fork 不发 Maven Central。

8.3 未来优化方向

  • ohosTest + uitest 自动化截图,把冷启动握手行与 61 天窗口纳入 CI 断言;
  • 把 weekDayShort 协议扩展 locale 字段(引擎侧出数字、ArkTS 侧查表),替代英文硬标签;
  • 评估 .har 形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
  • 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵。

九、总结

9.1 核心难点回顾

上游 rowKalendar(6 文件,5 个锚定 Compose)
    → 验证 datetime ohos 制品线(0.7.1-OH-001,jvm+ohos 双线齐全)
    → 工具链对齐 Kotlin 2.2.21-1.0.0 / Gradle 8.14.1(依赖反推,与 KStore 的 2.4.20 线相反)
    → 根构建替换(cmp_qrose 模式,上游构建文件改名保留)
    → 源码手术:composeMain 隔离 + 引擎 1:1 复刻 + 日期扩展抽回
    → ohosArm64 / ohosX64 KLIB + jvm 交叉验证线
    → C ABI(@CName × 2)+ 全量快照 JSON
    → C++ N-API(DisposeString 纪律)
    → ArkTS JSON 解析 + ArkUI List 三态渲染
    → ArkUI 真机单页验收(初始窗口 / 触边分页 / 预算封顶 / 选择 / 重置)

9.2 封装层次

  • 引擎层:移植后的 RowKalendarEngine——初始窗口、触边分页、双侧预算、选中态、isBounded 存储,算法与上游逐位一致;
  • 日期层:上游原版 LocalDateExtensions + kotlinx-datetime 0.7.1-OH-001 的 LocalDate / DatePeriod / Clock 算力;
  • Native 层:生成双 ABI 动态库并通过 2 个 C ABI 输出全量快照 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、JSON 解析、操作分发、日志与错误文本;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 纯 UI 库的适配答案是"引擎在哪一刀切开":kalendar 有现成的模块缝、KStore 根本没有 UI,RowKalendar 两者都不是——引擎与 Compose 类型编在同一批文件里,必须自己做 source set 级手术。判据只有一个:引擎与上游逐位一致(结构映射表 + git diff 可证明),UI 隔离件等工具链接回;
  2. 工具链版本跟着依赖走,两条线的经验不能互相套用:KStore 被 coroutines 逼升 Kotlin 2.4.20 + Gradle 9.x,RowKalendar 的 datetime 在 2.2 线内就钉 2.2.21 + Gradle 8.14.1——共同规则是动工前先查制品的 .module 元数据(变体清单 + stdlib 依赖),而不是沿用上一个库的钉法;
  3. 交互语义的桥上翻译要找对等物:上游 LazyRow 触边加载、MutableState 增量订阅、initialFirstVisibleItemIndex 初始定位,分别映射为 ArkTS List 的 onReachStart/onReachEnd、全量快照响应、scrollToIndex(selectedIndex)——每一个上游交互行为都有可验证的 ArkTS 对等物,Demo 验收清单才算完整。

9.4 适配成果

当前适配已完成:

  • RowKalendar 0.0.3 日历引擎的双 ABI Kotlin/Native 编译(上游日期算法零改动,引擎移植有结构映射表背书);
  • kotlinx-datetime 0.7.1-OH-001 社区制品线在日历类库上的完整落地(jvm + ohosArm64 + ohosX64 三变体解析,保留 JVM 交叉验证线);
  • 2 个 C ABI 符号和 C++ N-API 包装层(nm -D 验证全局 T 符号,双 ABI);
  • DevEco Stage ArkUI 单页验收页面(版本握手、横向日历行、触边自动分页、maxDays/isBounded 配置、选择、重置、操作日志);
  • HAP 构建(双 ABI 六件套 so,约 11.5MB)、签名、设备安装和真机验证(六类功能用例全部通过);
  • AtomGit 项目文档、OpenHarmony README(中英双份)和本地效果图。

适配过程 FAQ

以下问题来自 ohosArm64 目标接入、NAPI 桥接、HAP 构建与真机验收的实际流程,按出现频率排序。

Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?

  • 确认 DevEco Studio 版本与工程 build-profile.json5 的 compatibleSdkVersion 一致;
  • 在 File > Settings > Build, Execution, Deployment > Ohpm 配置可用的 ohpm 仓库镜像或代理;
  • 删除 oh_modules、oh-package-lock.json5 与 .hvigor 缓存后重新 Sync;
  • 公司网络需要代理时,同时给 ohpm 与 hvigor 配置 http.proxy。

Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?

  • 先在 example 目录执行 ./gradlew :nativeApp:linkDebugSharedOhosArm64(模拟器用 linkDebugSharedOhosX64);
  • 确认 libxxx.so 与 libc++_shared.so 已复制到 ohos/entry/libs/<abi>/;
  • 检查 entry/build-profile.json5 的 abiFilters 与设备 ABI 是否一致(真机 arm64-v8a,模拟器 x86_64);
  • 清理 entry/.cxx 与 entry/build 后重试。

Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?

  • 确认 CMake 的 add_library(<target> SHARED ...) 名称与 import ... from 'libxxx_napi.so' 完全一致;
  • 解包 HAP,确认 libs/<abi>/libxxx_napi.so 已打包;
  • 删除 entry/build、entry/.cxx 与 oh_modules 后重新构建;
  • 检查 module.json5 的 srcEntry 与页面路径是否正确。

Q4:真机安装报 signature verification failed 或 install failed?

  • 在 DevEco Studio 的 Signing Configs 配置证书、Profile 与密码;
  • bundleName 必须与签名 profile 中的 bundle name 一致;
  • 设备需加入 profile 的 deviceIds,调试证书有效期需覆盖当前时间;
  • 用 hdc list targets 确认设备在线,再执行 hdc install -r xxx-signed.hap。

Q5:应用启动白屏、闪退或 NAPI 调用崩溃?

  • 用 hdc hilog | grep -i "napi\|kotlin" 查看原生日志;
  • 确认 libc++_shared.so 与 Kotlin/Native 工具链版本匹配,且 ABI 与设备一致;
  • 检查 C ABI 返回的字符串是否由 Native 侧分配、ArkTS 侧复制后是否调用 XxxFree 释放;
  • 在 DevEco 的 Native 调试配置中附加 lldb,定位空指针与越界访问。

Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?

  • 使用鸿蒙发行版 Kotlin(如 2.2.21-1.0.0),并在 settings.gradle.kts 配置 eazytec 仓库;
  • Gradle 建议 8.x、JDK 21,版本不匹配时先统一工具链;
  • 确认 kotlin { ohosArm64() } 已应用,且 local.properties 的 SDK 路径有效;
  • 清理 ~/.gradle/caches 中对应版本的 Kotlin/Native 缓存后重试。

Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?

  • 对齐 DevEco Studio、HarmonyOS SDK 与 build-profile.json5 中的 compatibleSdkVersion;
  • 升级 DevEco Studio 后同步升级 hvigor 与 SDK,避免 modelVersion 与 IDE 不匹配;
  • 命令行构建时设置 DEVECO_SDK_HOME 指向 DevEco 的 sdk 目录。

Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?

  • 使用 DevEco 自带的 node 与 hvigor:
    "$DEVECO/Contents/tools/node/bin/node" "$DEVECO/Contents/tools/hvigor/bin/hvigorw.js" ...;
  • 设置 JAVA_HOME、NODE_HOME、DEVECO_SDK_HOME;
  • 加上 --no-daemon 排除守护进程缓存影响,并清理 entry/build 与 .hvigor。

参考文档

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐