本文记录 shadcn-multiplatform 接入 OpenHarmony 的完整过程,覆盖纯 Compose UI 库的适配面盘点、鸿蒙工具链选型、源集隔离、设计令牌引擎、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。

shadcn-multiplatform 是构建在 compose-unstyled 原语上的 Compose Multiplatform shadcn/ui 组件库:按钮、弹窗、侧栏、表格……全部组件的颜色、尺寸、阴影、排版都来自一份 Figma 令牌导出(imports/design-tokens.json),再由构建期代码生成器变成 Compose 主题。本次适配与 KStore(真编译整库)是同一条社区路线的两个分支:KStore 的内核是纯 Kotlin 存储引擎,ohos 制品线补齐后可以原样编译;而 shadcn-multiplatform 是纯 Compose UI 库,鸿蒙 Kotlin 工具链(2.2.21-1.0.0)没有能与上游 CMP 1.11.1 / Kotlin 2.4.0 对齐的 Compose 制品,组件代码跨不过工具链鸿沟。但这个库有一个天然的可移植内核——设计令牌引擎:Figma 导出是整个主题体系的单一事实源,语义色、色阶、圆角、阴影、排版全部是数据。因此本次适配把令牌引擎做成纯 Kotlin 运行时(运行时解析嵌入的令牌导出、归一 {tw.colors.neutral.950} 别名),全部 Compose 代码隔离进不参与编译的 composeMain 等工具链对齐后接回;ArkTS 应用通过 C ABI/N-API 桥一次性拉取全量令牌 JSON,直接驱动 ArkUI 写出 shadcn 风格界面,真机四 Tab 页面作为验收宿主。

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

上游仓库: jershell/shadcn-multiplatform

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

shadcn-multiplatform 为 Compose Multiplatform 应用提供整套 shadcn/ui 组件:commonMain 里是组件实现(40+ 个组件目录)与生成主题(GeneratedThemePalettes / GeneratedTypography 等),样式的唯一事实源是 Figma 令牌导出——mode 节按 light/dark 两套给出约 35 项语义色(background / primary / muted-foreground / sidebar* / chart*……),tw 节是 22 组 Tailwind 色阶(50…950),rdx 节是 33 组 Radix 色阶(1…12,light/dark 双份),tokens 节是命名尺寸,effect 节是 shadow-2xs..shadow-2xl 阴影(多层),typography 节是 text-xs..text-9xl 13 档 × 9 字重。KMP 项目把这些能力编译到 Android、iOS、桌面与 Web。OpenHarmony 应用同样需要这套设计体系,但上游构建(Kotlin 2.4.0 / Gradle 9.5.1 / AGP 9.1.0 / CMP 1.11.1)不会生成 OpenHarmony 变体。

UI 库的适配比存储库多一层障碍:引擎库的 commonMain 是纯 Kotlin,制品线补齐就能编译;UI 库的 commonMain 每一个文件都 import androidx.compose.*,而鸿蒙 Kotlin 是 2.2.21-1.0.0 的分支工具链——上游 Compose 1.11 需要 Kotlin 2.4 编译器,社区 CMP ohos 线只有 1.9.2 基线,两套对不齐,降级等于把全部组件重写一遍。适配必须回答"这个库跨过鸿沟的到底是什么"。

障碍具体问题
目标缺失必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。
Compose 制品缺失上游 CMP 1.11.1 / Kotlin 2.4.0 与鸿蒙 Kotlin 2.2.21 无交集;社区 CMP ohos 线 1.9.2 基线不兼容,组件代码无法编译。
工具链不一致上游 Gradle 9.5.1 / AGP 9.1.0 与鸿蒙 Kotlin 插件无法共存于一个 Gradle 构建。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
令牌语义跨不了 UITheme[ColorProps][ColorTokens.xxx]、resolve<N>Colors(variant, state) 是 Compose 侧概念,ArkTS 侧只能消费数据。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:设计令牌引擎的 ohos 编译(Figma 导出运行时解析 + 别名归一)、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。组件渲染语义留在上游 Compose 代码里隔离保管,ArkTS 只负责拉取令牌、换算颜色通道和展示。

1.2 库提供的能力

Demo 提供一次真实的设计令牌会话,对应桥的全部输出段:

  • 语义色两套:light / dark 各约 35 项(background、foreground、primary、ring、sidebar*、chart*……),值格式 Figma 风格 #RRGGBBAA;
  • Tailwind 色阶:22 组 50..950 十一档(neutral / gray / blue / red……,单值的 white / black 不是色阶、语义色里已有);
  • Radix 色阶:33 组 1..12 十二档,light / dark 双 mode;
  • 语义尺寸:radius-xs..radius-4xl、radius-full、border-width、stroke-width({tokens.N} 别名已在引擎内解析为数值);
  • 原始尺寸:tokens 节全部命名数值(Figma 的逗号小数键 "0,5" 归一为 "0.5");
  • 阴影:shadow-2xs..shadow-2xl 等令牌,多层的(如 shadow-lg 两层)拆成有序层列表,每层带 offsetX / offsetY / radius / spread / color;
  • 排版:text-xs..text-9xl 13 档 × 9 字重(thin…black),每档带 fontSize / lineHeight / letterSpacing / fontWeight / fontFamily 等完整参数。

响应字段:

字段作用
ok / mode成功标志与回显模式(请求 `{“mode”:“light”
colorslight/dark 语义色映射,键与上游 ColorTokens 一一对应。
dims语义尺寸(radius-md → 8.0……)。
shadows令牌 → 层列表。
typography / scales / rdx排版矩阵、Tailwind 色阶、当前 mode 的 Radix 色阶。
errorok:false 时的原因原文。

引擎侧全部能力(别名解析、双 mode 语义表、多层阴影、逗号小数归一)都在 :uikit 的 commonMain 里随编译进入 .so,JVM 侧保留 9 项 jvmTest 交叉验证用例(对照 Figma 导出与 Compose 生成主题双基准),证明 ohos .so 里执行的解析逻辑没有漂移。

1.3 实现适配

维度要求
代码纪律上游 Compose 代码零改动,整体 git mv 进 composeMain(不编译,历史保留);新增源码只有令牌引擎一个包。
单一事实源引擎运行时解析 design-tokens.json(TokenJson.kt 嵌入,:uikit:embedTokenJson 可再生),不手工抄值。
平台目标根构建直接声明 ohosArm64 + ohosX64,保留 jvm() 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离根构建替换为极简 ohos 构建(cmp_qrose 模式);demoApp 四端入口与 build-plugin 留盘不进构建。
桥接稳定1 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律fork 零第三方依赖(手写 MiniJson),Kotlin 2.2.21-1.0.0 钉版来自社区已审钉法。
UI 完整单页四 Tab 覆盖全部桥输出:语义色彩 / 色阶 / 圆角与阴影 / 排版与组件(ArkTS 版 shadcn Button/Badge/Card)。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。鸿蒙侧交付的是设计令牌引擎——组件级 UI 等上游 Compose 与鸿蒙工具链对齐后从 composeMain 接回;ArkTS Demo 证明的是"令牌数据原生可用 + 用它能直接搭出 shadcn 风格界面"。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点模块与依赖边界、确认 Compose 制品缺口、确定"令牌引擎"交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版、Gradle 降 8.14.1
第 3 阶段:源集隔离       ── commonMain→composeMain、平台源集→compose* 目录、令牌导出嵌入
第 4 阶段:令牌引擎       ── MiniJson、{alias} 别名解析、ShadcnTokens 七个只读视图
第 5 阶段:原生桥接       ── @CName C ABI、C++ N-API、参数校验和内存释放
第 6 阶段:ArkUI 宿主与验证 ── 四 Tab 页面、HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
uikit/                                上游 UI 库(令牌引擎进 ohos 构建,jvm + ohosArm64 + ohosX64)
uikit/src/commonMain/.../tokens/      新增:令牌引擎(MiniJson + ShadcnTokens + TokenJson 嵌入)
uikit/src/composeMain/                上游全部 Compose 组件与主题代码,git mv 整体迁入,不参与编译
uikit/src/composeAndroid|composeJvm|  上游平台源集同步迁入(jvmMain→composeJvm、androidMain→composeAndroid、
uikit/src/composeIos|composeWeb|       iosMain→composeIos、webMain→composeWeb、commonTest→composeTest、
uikit/src/composeTest|composeJvmTest/  jvmTest→composeJvmTest),与 composeMain 同等隔离
example/nativeApp/                    Kotlin/Native 动态库(Bridge + MiniJson 请求解析)
example/harmonyApp/                   DevEco Stage、CMake N-API 和 ArkTS 四 Tab 页面
demoApp/ + build-plugin/              上游四端 Demo 与 Figma 代码生成插件,留盘不进 settings
README.OpenHarmony.md / _CN.md        鸿蒙适配说明(中英双份)
README.upstream.md                    上游原文保存
doc/                                  本文章与真机效果图(doc/img/)

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :uikit 与 :example:nativeApp,rootProject.name = "ohos_shadcn-multiplatform"。上游的 Compose / AGP / kotlinx-serialization / buildConfig / vanniktech 发布插件、build-plugin 组合构建、四个 demoApp 模块全部随根构建替换剥离——本 fork 不发 Maven Central。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven,qrose 同款已审钉法)
JDK21(Temurin jdk-21.0.12.101-hotspot,org.gradle.java.home 钉死)
OpenHarmony 目标ohosArm64 + ohosX64(jvm() 保留作交叉验证)
Gradle8.14.1(上游 9.5.1 降级;2.2.21 旧线不支持 Gradle 9,同时删除 Gradle 9 专属的 gradle-daemon-jvm.properties)
依赖零第三方依赖(令牌引擎不依赖 kotlinx 库;fork 的 libs.versions.toml 只剩 kotlin 一行)
DevEco product5.1.0(18) 兼容,hvigor modelVersion 5.1.0
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.shadcn
export JAVA_HOME="/c/Program Files/Eclipse Adoptium/jdk-21.0.12.101-hotspot"   # Windows/Git Bash
java -version
1.3 源码依赖度盘点
目录文件数依赖形态处置
uikit/src/commonMain(上游)145每个文件都 import Compose / compose-unstyled整体迁入 composeMain(不编译,git mv 历史保留)
uikit/src/androidMain 等平台源集5平台 actual(ResizableCursor、平台依赖)同步迁入 compose* 目录
uikit/src/commonTest / jvmTest4Compose UI 测试迁入 composeTest / composeJvmTest(不编译)
imports/design-tokens.json1数据(542KB,Figma Tokens 导出)嵌入引擎(TokenJson.kt 分块常量)

expect 声明全库只有 1 处(Modifier.resizableHoverCursor),四个平台 actual 全部随宿主代码隔离;ohos 侧不需要任何 actual——令牌引擎是纯 Kotlin,零 expect。上游的 build-plugin(importThemeFromFigmaTokens 代码生成)留在树里但退出构建:它的产物(Generated*.kt)已经在 composeMain 里静态保管,鸿蒙侧的对应物是运行时解析同一份 JSON。

1.4 页面边界
标题区          ShadcnTokens · OpenHarmony / 副标题(shadcn/ui 设计令牌 · Kotlin/Native ohosArm64/ohosX64)
模式行          深色模式 Toggle(切换即以另一 mode 重新请求桥,全页重渲染)
状态行          令牌统计(如 "dark 模式 · 语义色 35 项 · 色阶 55 组 · 阴影 14 层")
Tab 切换        语义色彩 / 色阶 / 圆角与阴影 / 排版与组件
语义色彩        35 项色块网格(色块 + 键名 + hex 原文),按主题自身的 background/foreground 着色
色阶            tw 22 组(50..950)与 radix 33 组(1..12)选择器 + 色带
圆角与阴影      radius-xs..radius-4xl 圆角方块 + shadow 令牌卡片(ArkTS 单层 .shadow() 取第一层)
排版与组件      text-xs..text-4xl 字号字重样例 + ArkTS 版 shadcn Button(五变体)/ Badge / Card

页面启动时 aboutToAppear 以 light 模式请求桥,统计行数字全部来自真实 Native 响应而非静态文字。

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

2.1 制品缺口验证(本适配的路线分岔点)

先验证生态:eazytec 社区 Maven 上有没有能与上游对齐的 Compose ohos 制品。结论是没有——社区 CMP ohos 线是 1.9.2 基线(配合旧 Kotlin),上游是 CMP 1.11.1 / Kotlin 2.4.0 / compose-unstyled 2.9.0,版本矩阵无交集。对照 KStore 的经验(kotlinx 制品线补齐后真编译整库),UI 库此刻的正确路线不是等制品,而是回到 cmp_qrose 已审模式:隔离 UI、把数据内核带过去。这也决定了工具链选择——没有新制品线的牵引,就用社区最早批准的钉法 Kotlin 2.2.21-1.0.0。

2.2 工具链钉版(为什么是 2.2.21-1.0.0 + Gradle 8.14.1)

settings.gradle.kts 把 eazytec 双仓写在 pluginManagement 与 dependencyResolutionManagement 最前(顺序错了配置期直接 Unresolved reference: ohosArm64):

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_shadcn-multiplatform"

include(":uikit")
include(":example:nativeApp")

Gradle 从上游 9.5.1 降到 8.14.1:2.2.21 旧线的 Kotlin Gradle 插件不支持 Gradle 9。降级伴随两个清理:删除 Gradle 9 专属的 gradle/gradle-daemon-jvm.properties(daemon JVM criteria 会按文件里的 foojay 链接拉 JDK,与钉死的 org.gradle.java.home 冲突),gradle.properties 收敛为 qrose 同款键集(kotlin.native.distribution.downloadFromMaven=true 等)。这与 KStore 的"2.4.20 线沿用 9.x"相反——Gradle 策略跟着工具链线走,两条线的经验不能互相套用。

gradle/libs.versions.toml 精简到一行插件:

[versions]
kotlin = "2.2.21-1.0.0"

[plugins]
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
2.3 编译令牌引擎

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

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

kotlin {
    applyDefaultHierarchyTemplate()
    jvmToolchain(21)

    jvm()            // 交叉验证用
    ohosArm64()
    ohosX64()

    sourceSets {
        commonTest.dependencies {
            implementation(kotlin("test"))
        }
    }
}

上游构建里的 android namespace、Compose resources、buildConfig、vanniktech 发布段全部随重写消失。实测:

./gradlew :uikit:compileKotlinJvm :uikit:compileKotlinOhosArm64 :uikit:compileKotlinOhosX64   # BUILD SUCCESSFUL

commonMain 里现在是令牌引擎一个包,纯 Kotlin 零依赖,三平台同一份代码。

第 3 阶段:源集隔离

3.1 迁移矩阵

上游 commonMain 的 145 个 Kotlin 文件加 composeResources 全部 git mv(历史保留,rename 检出 100%):

uikit/src/commonMain/kotlin/**  →  uikit/src/composeMain/kotlin/**      全部 Compose 组件与主题
uikit/src/commonMain/composeResources/  →  uikit/src/composeMain/composeResources/
uikit/src/androidMain/  →  uikit/src/composeAndroid/
uikit/src/jvmMain/      →  uikit/src/composeJvm/
uikit/src/iosMain/      →  uikit/src/composeIos/
uikit/src/webMain/      →  uikit/src/composeWeb/
uikit/src/commonTest/   →  uikit/src/composeTest/      (Compose UI 测试)
uikit/src/jvmTest/      →  uikit/src/composeJvmTest/

composeMain 不是声明的源集——它只是磁盘目录,任何目标都不编译它。这样上游代码一个字节不改地保留,等 Compose 工具链对齐后把目录改回名字即可接回。demoApp/(四端入口)与 build-plugin/(Figma 代码生成)同样留盘不进 settings.gradle.kts。

3.2 令牌导出嵌入(踩到 JVM 常量池上限)

引擎的数据源是 imports/design-tokens.json(542KB,Figma Tokens 导出)。Kotlin/Native 的资源加载在 sharedLib 形态下不可靠,所以选择编译期嵌入:新增 :uikit:embedTokenJson 任务把 JSON 转成 TokenJson.kt 常量。第一版单常量直接撞 JVM class 文件上限——常量池单条 UTF8 最大 65535 字节,编译器报 UTF8 string too large。修正为分块常量 + 懒拼接:按 15000 字符切块(最坏 3 字节/字符仍小于上限),每块一个 const,运行时 joinToString(""):

// uikit/src/commonMain/.../tokens/TokenJson.kt(生成文件,37 块)
internal val TOKEN_JSON: String by lazy { TOKEN_CHUNKS.joinToString("") }

private val TOKEN_CHUNKS: Array<String> = arrayOf(
    """{
  "font": {
""",
    // …… 共 37 块
)

任务里两个细节:剥离 UTF-8 BOM;$ 替换为 ${'$'}(原始字符串里只有 $ 会触发插值,分块后转义永不跨块)。换令牌导出后重跑一次任务即可再生。

第 4 阶段:令牌引擎(技术难点)

4.1 Figma 导出的真实形状

动手写引擎前先把 542KB 导出的六个节摸清(这步不能省,别名规则全藏在这里):

节内容示例
modelight/dark 两套语义令牌,颜色和尺寸混在同一节primary = {tw.colors.neutral.900}、radius-md = {tokens.8}、semantic-background = #696867ff(裸 hex)
tw.colors22 组 Tailwind 色阶 + 单值 white/blackneutral.900 = #171717ff
rdx.colorslight mode / dark mode 两子节,各 33 组 1…12light mode.blue.8 = #5eb1efff
tokens命名数值,小数键用逗号(Figma 的 locale 怪癖)"8" = 8、"0,5" = 0.5、"-0,8" = -0.8
effect["box shadow"]阴影令牌,单层是对象、多层是 "0" "1" 编号子节点shadow-lg 两层(radius 6/15、offsetY 4/10、spread -4/-3)
typography13 档 × 9 字重,每属性是 {type,value} 包装text-xs.regular.fontSize = {dimension,12}

三条别名规则:{tw.colors.neutral.950} 从根按段下行;{tokens.8} 同法落进 tokens 节;{rdx.colors.black.5} 是无模式 rdx 别名——rdx.colors 下只有 light mode / dark mode 两个子节,没有直接的 black,必须回退进 light mode 子节解析(上游 Figma 插件生成的代码同样用 lightMode 变体,行为一致)。

4.2 MiniJson 与 ShadcnTokens

引擎两个文件。MiniJson.kt 是手写递归下降 JSON 解析器(对象/数组/字符串/数字/布尔/null,处理转义与 Unicode),不引 kotlinx.serialization——fork 零第三方依赖,.so 自包含(与 qrose 同款取舍)。ShadcnTokens.kt 是公共 API,七个只读视图:

public object ShadcnTokens {
    public fun semanticColors(mode: ShadcnTokenMode): Map<String, String>      // 35 项 #RRGGBBAA
    public fun semanticDimensions(mode: ShadcnTokenMode): Map<String, Double>  // radius-md → 8.0
    public fun dimensions(): Map<String, Double>                               // tokens 节,"0,5"→"0.5" 归一
    public fun shadows(): Map<String, List<ShadcnShadowLayer>>                 // 令牌 → 层列表
    public fun typography(): Map<String, Map<String, ShadcnTypographyStyle>>   // 13 档 × 9 字重
    public fun colorScales(): Map<String, Map<String, String>>                 // tw 22 组
    public fun radixScales(mode: ShadcnTokenMode): Map<String, Map<String, String>>  // rdx 33 组
}

别名解析的通用下行器(含 rdx 无模式回退):

private fun resolveAlias(path: String): Any? {
    var node: Any? = root
    for (segment in path.split('.')) {
        val map = node.asMap() ?: return null
        node = map[segment] ?: map["light mode"].asMap()?.get(segment) ?: return null
    }
    return node
}

阴影解析同时兼容单层(type == "custom-shadow" 直接取 value)与多层(编号子节点按数值排序逐层取);语义尺寸把 {tokens.8} 解析成数值;排版把每属性的 {type,value} 包装拆开。

4.3 双基准交叉验证

commonTest 里 9 项用例把引擎钉在两个基准上:Figma 导出真值(light primary = #171717ff = neutral.900、dark background = #0a0a0aff、radius-md = 8、shadow-lg 两层 6/15、text-xs.regular 12/16/400)与 Compose 生成代码(GeneratedThemePalettes 的 dark primary 正是 TwColors.neutralToken200,与 JSON 的 {tw.colors.neutral.200} 一致)。JVM 与 ohos 编的是同一份引擎,jvmTest 全绿即"内核没漂"。

第 5 阶段:原生桥接

5.1 三层桥接
ArkTS
  │ JSON string({"mode":"dark"})
  ▼
C++ N-API entry(libshadcn_napi.so)
  │ const char*
  ▼
Kotlin/Native C ABI(libohosshadcn.so)
  │ ShadcnRender
  ▼
UTF-8 JSON + DisposeString

令牌是静态数据,一次性全量拉取比逐项查询合理——桥只导出一个函数,请求里唯一字段是 mode。

5.2 Kotlin/Native 导出函数
@CName("ShadcnRender")
fun shadcnRender(request: String): String = try {
    val mode = if (MiniJson.parse(request).str("mode").equals("dark", true))
        ShadcnTokenMode.DARK else ShadcnTokenMode.LIGHT
    render(mode)   // colors/dims/shadows/typography/scales/rdx 六段拼装
} catch (t: Throwable) {
    """{"ok":false,"error":${jsonString(t.message ?: t.toString())}}"""
}
C ABI 符号ArkTS 方法覆盖内核
ShadcnRendershadcnRender(req)七个引擎视图的完整 JSON 快照(light/dark 二选一)

请求解析沿用 cmp_qrose 审核模式的扁平 MiniJson(顶层字符串字段),响应六段由手写 JSON writer 拼装(转义、Double/Int 格式与 ArkTS JSON.parse 对齐)。异常整体 try/catch 收进 ok:false 通道,Kotlin 异常永不跨 FFI。

5.3 C++ N-API 与 CMake
napi_property_descriptor desc[] = {
    {"shadcnRender", nullptr, NapiShadcnRender, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};

回调三步:napi_typeof 校验 string、OH_LOG_INFO 打字节数、调 ShadcnRender(const char*) 包装返回。KN 返回字符串必须经 libohosshadcn_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohosshadcn_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与业务符号)。CMake 侧 IMPORTED 引入 KN so(IMPORTED_NO_SONAME TRUE)、$ORIGIN rpath、-Wl,--no-undefined、链接 libace_napi.z.so + libhilog_ndk.z.so,OHOS_ARCH 为空或 KN so 缺失直接 FATAL_ERROR(提示先跑 link 任务),POST_BUILD 把 KN so 与 libc++_shared.so 拷到 NAPI so 旁边。

Gradle 侧 example/nativeApp 出双 ABI sharedLib(baseName = "ohosshadcn"),copyKnSoArm64 / copyKnSoX64 被 linkDebugShared* finalizedBy,把 libohosshadcn.so 连同 ~/.konan/dependencies 里对应 triple 的 libc++_shared.so 一起回填 harmonyApp/entry/libs/{arm64-v8a,x86_64}/。

5.4 颜色通道换算(ArkTS 侧最大的坑)

桥输出的颜色保持 Figma 字节序 #RRGGBBAA(#0000001a 是 10% 透明度的黑),而 ArkUI ResourceColor 要 #AARRGGBB。Index.ets 里一个 toArgb() 统一换算(容错 6 位 #RRGGBB 补 ff),所有颜色——包括阴影层颜色——展示前都必须经过它。

第 6 阶段:ArkUI 宿主与验证

6.1 工程结构
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohosshadcn)+ cxxShared + copyKnSo*
│   └── src/ohosMain/kotlin/example/nativeapp/
│       └── Bridge.kt                         @CName × 1 + 扁平 MiniJson(cmp_qrose 审核模式)
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.shadcn、versionName 1.0.3-dev-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/libohosshadcn_api.h
        ├── src/main/cpp/types/libshadcn_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 四 Tab 验收页面
6.2 构建、签名和安装
./gradlew :uikit:jvmTest                                            # 9 项交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64

cd example/harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon

签名在 DevEco Studio 的 File → Project Structure → Signing Configs 中勾选自动生成,证书不进入仓库(仓库内 build-profile.json5 的 signingConfigs 保持空数组,勾选后由 IDE 回填)。

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.shadcn -m entry

四、完整代码对照

4.1 整体架构

imports/design-tokens.json(Figma 导出,单一事实源)
    │ :uikit:embedTokenJson 分块嵌入(37 块,绕 65535 常量池上限)
uikit/src/commonMain/.../tokens/**(令牌引擎,纯 Kotlin)
    │ MiniJson 解析 + {alias} 归一 + 七个只读视图
Bridge.kt + 扁平 MiniJson
    │ ShadcnRender(@CName × 1)
libshadcn_napi.so
    │ shadcnRender(request) → JSON
Index.ets
    │ JSON.parse + toArgb 通道换算 + @State 更新
    ▼
ArkUI 真机四 Tab 页面(语义色彩 / 色阶 / 圆角与阴影 / 排版与组件)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven 置前、ohos_shadcn-multiplatform、demoApp/build-plugin 移出
gradle/libs.versions.toml只剩 Kotlin 2.2.21-1.0.0 一行插件
build.gradle.kts(根)版本 1.0.3-dev-ohos.1、全局禁用 Ohos*Test* 任务
uikit/build.gradle.kts纯 KMP:jvm + ohosArm64 + ohosX64,embedTokenJson 任务
uikit/src/commonMain/.../tokens/MiniJson.kt手写 JSON 解析器(fork 零依赖的关键)
uikit/src/commonMain/.../tokens/ShadcnTokens.kt七视图 API + 别名解析 + 逗号小数归一
uikit/src/commonMain/.../tokens/TokenJson.kt生成文件:37 块常量 + 懒拼接
uikit/src/composeMain/ 等上游全部 Compose 代码隔离保管(不编译,git mv 历史保留)
example/nativeApp/build.gradle.kts双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../Bridge.kt1 个 C ABI、六段 JSON 拼装、错误通道
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 四 Tab 验收页面 + toArgb
README.OpenHarmony_CN.md鸿蒙适配主文档(23 节)
doc/img/shadcn-tokens-demo-dark.png真机效果图

4.3 关键 API 对照

层次API作用
引擎ShadcnTokens.semanticColors(mode)light/dark 语义色(≈35 项,#RRGGBBAA)
引擎ShadcnTokens.semanticDimensions(mode)radius-* 族、border-width 等(别名已解析)
引擎ShadcnTokens.dimensions()tokens 节原始数值("0,5" → "0.5" 归一)
引擎ShadcnTokens.shadows()阴影令牌 → 有序层列表
引擎ShadcnTokens.typography()text-xs..text-9xl × 9 字重
引擎ShadcnTokens.colorScales() / radixScales(mode)Tailwind 22 组 / Radix 33 组
上游对照Theme[ColorProps][ColorTokens.primary]Compose 侧同义概念(隔离在 composeMain)
NativeShadcnRenderC ABI JSON 导出(唯一符号)
N-APIshadcnRender(req)向 ArkTS 暴露能力
ArkTStoArgb(figmaHex)#RRGGBBAA → #AARRGGBB 通道换算

4.4 ArkTS 与 Kotlin 的边界

const raw = shadcnRender(JSON.stringify({ mode: this.isDark ? 'dark' : 'light' }));
const res = JSON.parse(raw) as TokenResponse;
// res.colors.primary === '#171717ff'(light)—— 展示前经 toArgb() 换算
@CName("ShadcnRender")
fun shadcnRender(request: String): String = try {
    val mode = parseMode(request)
    buildString {
        append("{\"ok\":true,\"mode\":\""); append(mode.json)
        append("\",\"colors\":").append(colorsJson(mode))
        /* dims / shadows / typography / scales / rdx 五段同法拼装 */
        append("}")
    }
} catch (t: Throwable) { errorJson(t) }

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

五、关键决策说明

决策 1:源集隔离而不是降级到社区 CMP 1.9.2

把整库降到 CMP 1.9.2 基线意味着重写全部组件对 compose-unstyled 2.9 / nav3 / 1.11 API 的依赖——那是另一个项目,不是适配。隔离方案让上游代码零改动保管在 composeMain(git mv 历史完整),鸿蒙侧交付当前真正能用的部分(令牌引擎);等上游 Compose 与鸿蒙工具链版本对齐,把目录名改回去即可整体接回。

决策 2:令牌引擎运行时解析 Figma 导出,而不是手工抄值

design-tokens.json 是上游钦定的单一事实源(AGENTS 源头层级第一条)。手工把 542KB 抄成分发数据,上游每次重导出都要人工同步,必然漂移。嵌入 + 运行时解析让"换令牌"退化为"重跑一次 embedTokenJson";别名归一({tw.colors.*} / {rdx.colors.*} / {tokens.*})在引擎内一处实现,桥与 ArkTS 拿到的永远是已解析值。

决策 3:Kotlin 2.2.21-1.0.0 + Gradle 8.14.1(cmp_qrose 已审钉法)

本库没有 kotlinx 制品线牵引(引擎零依赖),没有理由离开社区最早批准的钉法。代价是 Gradle 必须从 9.5.1 降 8.14.1 并删除 gradle-daemon-jvm.properties——与 KStore 的 2.4.20/9.x 组合相反,再次证明 Gradle 策略跟着工具链线走。

决策 4:手写 MiniJson,不引 kotlinx.serialization

fork 的依赖表只剩 Kotlin 一行:引擎零第三方库、.so 自包含、没有制品线可达性问题。解析对象是自家导出的固定形状 JSON,手写递归下降约 200 行,与 qrose 桥的取舍一脉相承。

决策 5:引擎放 :uikit commonMain 而不是塞进 nativeApp

引擎归库、桥归示例::uikit 出的 KLIB 未来可以被任何鸿蒙 Kotlin 消费者复用(不止这一个 Demo);jvm() 目标与 ohos 编同一份引擎,9 项 jvmTest 交叉验证才成立。nativeApp 只做协议换装(Kotlin 视图 → JSON),没有业务逻辑。

六、测试与验证

6.1 测试环境

本次验证使用 Windows 10(x64)、JDK 21(Temurin,org.gradle.java.home 钉死)、DevEco Studio 5.1.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与 HarmonyOS 真机(arm64)。

6.2 JVM 交叉验证

./gradlew :uikit:jvmTest

9 项用例全部通过(对照 Figma 导出与 Compose 生成主题双基准):

用例钉住什么
lightSemanticColorsMatchFigmaExportlight 语义色 7 项真值(含 rdx 无模式别名 background-color = #0000004d、裸 hex 透传)
darkSemanticColorsMatchFigmaExportdark 语义色 6 项真值(primary = neutral.200 = #e5e5e5ff,与生成代码一致)
semanticColorsCoverThePalettelight/dark 键集合一致、数量 ≥ 35
semanticDimensionsMatchFigmaExportradius 族 9 项 + border-width/stroke-width,light/dark 全等
rawDimensionsNormalizeCommaDecimalKeys"0,5" 键归一为 "0.5" 且值为 0.5
shadowsAreLayeredLikeTheFigmaExportshadow-xs 单层(radius 2/offsetY 1)、shadow-lg 两层(6/15、spread -4)
typographyMirrorsTheComposeTextStyleScaletext-xs.regular 12/16/400/Inter、text-base.regular 16/24
tailwindScalesHaveElevenShadesneutral 11 档三真值;单值 white/black 被跳过
radixScalesHaveTwelveStepsgray 12 档(1 = #fcfcfcff)、blue.8、dark gray.1

6.3 HAP 验证

:uikit:compileKotlinJvm / OhosArm64 / OhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: ShadcnRender / libohosshadcn_symbols 均为全局 T 符号
copyKnSoArm64 / copyKnSoX64: 三件套回填 entry/libs/{arm64-v8a,x86_64}
hvigorw assembleHap: BUILD SUCCESSFUL(CAdapter 的 api.cpp:448 警告为已知无害项)
hdc install: install successfully
aa start: start ability successfully

debugShared 未 strip 的 libohosshadcn.so arm64 约 4.8MB、x86_64 约 4.5MB(含 Kotlin/Native 运行时与 542KB 嵌入令牌),每个 ABI 三件套(libshadcn_napi.so / libohosshadcn.so / libc++_shared.so)齐全。

6.4 功能用例

  1. 启动默认 light 模式,状态行显示真实统计(语义色 35 项 · 色阶 55 组 · 阴影 14 层),数字来自 Native 响应而非静态文字;
  2. 打开深色模式开关,页面以 dark 重新请求桥整体重渲染——背景变 #0a0a0aff、前景 #fafafaff、危险色 #f87171ff,与 JVM 用例断言逐字相同;
  3. 语义色彩 Tab:35 项色块(背景/前景/主要/次要/静音/强调/危险/边框/输入/圆环/侧栏 8 项/图表 5 项……),每块附键名与 hex 原文;
  4. 色阶 Tab:Tailwind 22 组(50…950)与 Radix 33 组(1…12)切换,色带按档渲染;
  5. 圆角与阴影 Tab:radius-xs..radius-4xl 圆角方块(取 dims 数值为 vp)与 shadow 令牌卡片(ArkTS 单层 .shadow() 取第一层,多层与 spread 有文字提示);
  6. 排版与组件 Tab:text-xs..text-4xl 字号字重样例 + ArkTS 版 shadcn Button 五变体 / Badge / Card,全部颜色圆角来自桥令牌——证明"拿令牌能直接搭出 shadcn 风格 ArkUI";
  7. 任何一环失败(理论不该发生)会走 ok:false 通道在状态行显示错误原文。

6.5 验证结论

令牌引擎、嵌入数据、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的令牌会话:明暗双模式语义色、双体系色阶、圆角/阴影/排版矩阵和令牌驱动的 shadcn 风格组件(见下节效果图)。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/shadcn-multiplatform.git
cd shadcn-multiplatform
export JAVA_HOME="/c/Program Files/Eclipse Adoptium/jdk-21.0.12.101-hotspot"
./gradlew :uikit:compileKotlinJvm :uikit:compileKotlinOhosArm64 :uikit:compileKotlinOhosX64   # klib
./gradlew :uikit:jvmTest                                                                      # 9 项交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
cd example/harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.shadcn -m entry
hdc shell hilog | findstr Shadcn

八、遗留问题与改进方向

8.1 踩坑复盘

  1. UI 库适配的第一问是"内核是什么":shadcn-multiplatform 的可移植内核不是组件而是设计令牌——先盘点出这一点,路线(隔离 + 引擎)自然成立;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. 2.2.21 旧线的 Kotlin 插件不支持 Gradle 9:除了 wrapper 降到 8.14.1,Gradle 9 专属的 gradle-daemon-jvm.properties 必须删除,否则按 foojay 链接拉 JDK 与钉死的 java.home 打架;
  4. JVM 常量池单条 UTF8 上限 65535 字节:542KB JSON 嵌成单常量,编译器报 UTF8 string too large(KCode 层面合法、ASM 写常量池时才炸);按 15000 字符分块 + 懒拼接解决,且 $ 转义必须在分块之后做,保证转义序列永不跨块;
  5. Figma 导出的 tokens 节小数键是逗号("0,5"、"-0,8",locale 怪癖),引擎必须归一为 "0.5";文件头还可能有 BOM,嵌入前要剥离;
  6. rdx 无模式别名({rdx.colors.black.5})在 rdx.colors 下解析不到——那里只有 light mode / dark mode 两个子节;回退 light mode 子节与上游生成插件行为一致,light/dark 的 background-color 都指向同一值;
  7. 颜色字节序是 ArkTS 侧最大的坑:桥输出 Figma 的 #RRGGBBAA,ArkUI 要 #AARRGGBB,不换算透明度整体错位;一个 toArgb() 门卫统一处理,阴影层颜色同理;
  8. 鸿蒙 CAdapter 不导出 @CName 业务符号给 ArkTS,必须走 CMake NAPI 包装;KN 链接期 api.cpp:448 的 -Wreturn-type 警告是 CAdapter 已知无害项;
  9. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();
  10. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI(模拟器是 x86_64),每个 ABI 三件套缺一不可;KN so 依赖的 libc++_shared.so 要从 ~/.konan/dependencies 一并拷入;
  11. DevEco 打开工程后生成的 example/harmonyApp/.idea/、.clangd、.clang-tidy 不在根锚定 gitignore 覆盖内,要单独追加,否则混进提交;
  12. 签名材料由 DevEco 自动写入 build-profile.json5,仓库内保持空数组不引用,证书绝不进入 AtomGit。

8.2 已知问题

  • 鸿蒙侧交付的是设计令牌引擎,不是 Compose 组件:40+ 组件仍在 composeMain 隔离,等上游 Compose 与鸿蒙 Kotlin 工具链版本对齐后接回;
  • 上游生成代码与 Figma 导出在 chart-1..3 上有历史分歧(生成代码映射 tw blue 800/900/100,导出指向 radix blue 8/9/10,色值不同);引擎按上游钦定的"导出为唯一事实源"取 radix 一侧,分歧已在适配文档记录,等上游修正导出后重跑 embedTokenJson 即归一;
  • ArkTS .shadow() 不支持多层堆叠与 spread,Demo 渲染每令牌第一层并文字提示;
  • Tailwind 单值 white / black 不是色阶,不在 colorScales()(语义色表里已有对应项);
  • android / wasmJs / iOS 目标与四端 demoApp、Maven Central 发布一并剥离(源文件留树),本 fork 不发 Maven Central;
  • HAP 安装必须使用与 bundle name org.terminator.ohos.shadcn 匹配的签名。

8.3 未来优化方向

  • 评估 .har 形态发布令牌引擎,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
  • 基于 toArgb 之上的令牌消费层沉淀一套 ArkTS 版 shadcn 组件主题(Button/Badge/Card 已在 Demo 验证);
  • 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本;
  • 跟踪上游 Compose 的 ohos 支持进展,工具链对齐后把 composeMain 整体接回。

九、总结

9.1 核心难点回顾

上游 shadcn-multiplatform(145 个 Compose 文件 + Figma 令牌导出)
    → 盘点适配面:Compose 制品缺失,内核 = 设计令牌
    → 工具链钉版 Kotlin 2.2.21-1.0.0(Gradle 降 8.14.1,cmp_qrose 已审钉法)
    → 根构建替换 + 源集隔离(commonMain→composeMain,平台源集→compose*,git mv 历史保留)
    → 令牌导出分块嵌入(37 块,绕 JVM 常量池 65535 上限)
    → 令牌引擎(MiniJson + {alias} 归一 + 七视图)
    → C ABI(@CName × 1)+ 全量令牌 JSON
    → C++ N-API(DisposeString 纪律)+ ArkTS toArgb 通道换算
    → ArkUI 真机四 Tab 验收(明暗双模式 / 色阶 / 圆角阴影 / 排版组件)

9.2 封装层次

  • 引擎层:MiniJson 解析、别名归一、双 mode 语义表、多层阴影、逗号小数归一、七个只读视图;
  • 数据层:TokenJson.kt 分块常量(embedTokenJson 可再生,Figma 导出仍是唯一事实源);
  • Native 层:生成双 ABI 动态库并通过 1 个 C ABI 输出 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、模式切换重请求、toArgb 换算、令牌驱动的 shadcn 风格组件;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. UI 库适配的答案不是翻译组件,而是找出"单一事实源"带过工具链鸿沟:shadcn 的样式体系全部溯源于一份 Figma 导出,令牌过河,组件留在原地等工具链——两边的代码都没有重写,这是"零漂移"的唯一保证;
  2. Gradle 策略跟着工具链线走:2.2.21 旧线降 8.14.1(本次)、2.4.20 新线留 9.x(KStore),两条线的经验不能互相套用;降级时还要清理 Gradle 9 专属配置文件;
  3. 交叉验证钉双基准:9 项 jvmTest 同时对照 Figma 导出真值与 Compose 生成代码,dark primary = neutral.200 这种"生成代码与导出一致"的断言,才是"引擎没漂、上游也没漂"的完整证据链。

9.4 适配成果

当前适配已完成:

  • 设计令牌引擎的双 ABI Kotlin/Native 编译(约 35 项语义色 × 2 套、22 组 Tailwind 色阶、33 组 Radix 色阶、语义/原始尺寸、11 个阴影令牌 14 层、13 档 × 9 字重排版,全部运行时解析自嵌入的 Figma 导出);
  • 上游 145 个 Compose 文件零改动隔离保管(git mv 历史 100% 保留,工具链对齐后可整体接回);
  • 1 个 C ABI 符号和 C++ N-API 包装层(llvm-nm 验证全局 T 符号);
  • DevEco Stage ArkUI 四 Tab 验收页面(明暗切换、语义色彩、双体系色阶、圆角阴影、排版与 shadcn 风格组件);
  • 9 项 JVM 交叉验证全绿 + HAP 构建(双 ABI 六件套 so)、签名、设备安装和真机验证;
  • 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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐