本文记录 Sekret 接入 OpenHarmony 的完整过程,覆盖编译器插件降版本改造、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、编译器插件进入 Native 编译通道、C ABI / N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。

Sekret 是一套「秘密不泄漏」工具链:编译器插件在 IR 层改写 toString(),把 @Secret 属性的读取替换为掩码常量,日志与崩溃报告里永不出现明文;Gradle 插件把应用秘密用 SHA-256(key) 十六进制串逐字节 XOR 混淆进 intArray,运行时再解出来。本次适配与社区既有路线都有本质区别:qrose / haze 是「零依赖内核提取」,KStore 是「真编译存储引擎」——而 Sekret 的核心资产不是运行时代码而是编译器插件。适配的首要工作不是移植算法,而是让 IR 改写器真正进入 ohosArm64 / ohosX64 的 Kotlin/Native 编译:直觉的 kotlinCompilerPluginClasspath 挂法在 Native 侧静默失效(构建全绿、toString 明文裸奔),正确通道是 KotlinCompilerPluginSupportPlugin + SubpluginArtifact,并且要用 A/B 哈希对照才能证明生效。运行时侧,SHA-256 + XOR 解密核心(SekretHelper)是纯 Kotlin,原样编译成 OpenHarmony KLIB;ArkUI 三 Tab 页面作为验收宿主。

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

上游仓库: DatL4g/Sekret

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

Sekret 上游四个模块:sekret-annotations(@Secret / @Obfuscate 注解,零依赖)、sekret-compiler-plugin(IR 改写器,纯 JVM 但运行在每一台编译器里——IrGenerationExtension 平台无关)、sekret-gradle-plugin(dev.datlag.sekret,注册编译器插件、生成秘密模块、编译 JNI 原生二进制)、sekret-lib(运行时:纯 Kotlin SHA-256 + SekretHelper.getNativeValue XOR 解密 + JNI 互操作层)。KMP 项目把这些能力放在 commonMain 与构建期工具里,由 Android、iOS、桌面共享。OpenHarmony 应用同样需要日志脱敏与应用秘密保护,但上游构建(Kotlin 2.3.0 / Gradle 9.2.1 / AGP 8.13.2)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS,页面可以显示掩码文字,却无法证明 @Secret 掩码在 OpenHarmony ARM64 设备的编译产物里真实存在——而「编译期生效、运行时零反射」恰恰是本库的全部卖点。适配需要同时验证:编译器插件在鸿蒙 Kotlin/Native 编译中加载并改写 IR、注解与运行时库编译成 ohos KLIB、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
目标缺失必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。
工具链不一致上游 Kotlin 2.3.0 / Gradle 9.2.1 / AGP 8.13.2(com.android.kotlin.multiplatform.library)与鸿蒙工具链无法共存于一个 Gradle 构建;编译器插件源码还用了 Kotlin 2.3 独有 API。
依赖矩阵kotlinx-serialization 官方制品没有 ohos 变体;社区鸿蒙线最新变体又是 Kotlin 2.3 元数据,2.2.21 编译器读不了。
插件通道静默失效kotlinCompilerPluginClasspath 只喂 JVM 编译;Native 编译拿不到插件时不报任何错,toString 保持明文——安全功能的静默失败是最坏的失败形态。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:编译器插件的 ohos 编译通道打通与版本兼容、注解与运行时核心的 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI / N-API 桥接层。掩码语义与解密算法完全由上游原版实现负责,ArkTS 只负责调用 Native、解析 JSON 和展示结果。

1.2 库提供的能力

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

  • 掩码(mask):5 个数据类的 toString() 原样返回——User / User(null) / Account / Session 的 password / apiToken / apiKey / secretKey / otp / payload / note 全部显示 ■■■(null 字段同样掩码,secretMaskNull=true),无 @Secret 的对照组 PublicInfo 正常输出;
  • 对照(reveal):同一批模型的真实字段值(password=hunter2、apiKey=AKIAIOSFODNN7EXAMPLE)——证明只有 toString() 被编译期改写,字段本身、直接属性访问、序列化都不受影响;
  • 解密(decrypt):三组 XOR + SHA-256 应用秘密运行时还原,matches=true 表示与上游 Encoder 的编码逐字节一致。

响应包含:

字段作用
ok操作是否成功,失败时 error 带原文。
kind回显本次入口:mask / reveal / decrypt。
results[].label / toString掩码入口:模型名与被改写后的 toString 输出。
results[].fields对照入口:字段名 → 真实值的映射。
results[].key / value / matches解密入口:密钥、还原明文与上游编码一致性校验。

掩码改写、类型匹配(Kotlin 与 Java 的 String / CharSequence / StringBuilder / Appendable)、null 判定全部在上游 ElementTransformer / ToStringTransformer 里原样随编译器插件生效;解密核心 SekretHelper.getNativeValue(intArray, key) 在上游 commonMain 里原样编进 .so,JVM 侧保留 :example:nativeApp:run 交叉验证,证明 ohos .so 里执行的掩码与解密逻辑没有漂移。

1.3 实现适配

维度要求
代码复用IR 改写器(ElementTransformer / ToStringTransformer)与解密核心(SHA256 / SekretHelper)零改动;注解库零改动;仅删编译器插件里 2 行 Kotlin 2.3 独有 API。
平台目标注解与运行时库声明 ohosArm64 + ohosX64,保留 jvm() 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离根构建替换为极简 ohos 构建(cmp_qrose 模式);JNI 源集树(jniNativeCommonMain / androidNativeMain / cinterop jni.h)留盘不进构建。
桥接稳定使用 1 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律kotlinx-serialization 换到 ohos 制品线 1.9.1-1.0.0(版本主体贴近上游、metadata 可读),其余零外部依赖。
UI 完整三 Tab 覆盖全部桥接入口:掩码、对照、解密与状态行。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。Sekret 本身不碰 UI,适配面天然最小;ArkTS Demo 只是把桥暴露的「编译期掩码 vs 运行时数据完好」画出来。

二、实现路线图

第 1 阶段:项目初始化       ── 盘点模块与编译期/运行时边界、确认依赖制品线、确定交付物
第 2 阶段:目标与依赖打通   ── 根构建替换、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 + Gradle 8.14.1 钉版
第 3 阶段:编译器插件接入   ── 2.2.21 API 兼容、SPI 手写注册、SubpluginArtifact 通道、A/B 哈希对照
第 4 阶段:桥协议与原生桥接 ── mask/reveal/decrypt JSON 契约、@CName C ABI、C++ N-API 和内存释放
第 5 阶段:ArkUI 宿主       ── 三 Tab、掩码高亮、状态行
第 6 阶段:示例与验证       ── JVM 交叉验证、双 ABI so、HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
sekret-annotations/                   上游注解(进 ohos 构建,jvm + ohosArm64 + ohosX64,零源码改动)
sekret-compiler-plugin/               上游 IR 改写器(纯 JVM 模块,删 2 行 2.3 API + 手写 SPI)
sekret-gradle-plugin/                 上游 dev.datlag.sekret(纯 JVM 模块,Gradle 8 兼容 + native 插件通道补丁)
sekret-lib/                           上游运行时核心(commonMain 进 ohos 构建)
sekret-lib/src/{jniNative*,androidNative,android}Main/   JNI 源集树,留盘不进构建
example/nativeApp/                    Kotlin/Native 动态库(Bridge + DemoCore + MiniJson + JVM 交叉验证)
example/harmonyApp/                   DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md        鸿蒙适配说明(中英双份)
README.upstream.md                    上游原文保存
sample/                               上游 Compose Desktop 遗留,留盘不进构建

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include 四个生产模块与 :example:nativeApp,rootProject.name = "ohos_sekret"。上游的 vanniktech 发布插件、signing、AGP classpath、kover、ben-manes versions、buildSrc/ 全部随根构建替换剥离——本 fork 不发 Maven Central。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)
JDK21(org.gradle.java.home 指向 Eclipse Temurin 21)
OpenHarmony 目标ohosArm64 + ohosX64(jvm() 保留作交叉验证)
Gradle8.14.1(wrapper 从上游 9.2.1 降级,与 2.2.21 线配套)
kotlinxkotlinx-serialization-core 1.9.1-1.0.0(eazytec ohos 制品线)
DevEco product5.1.0(18) 兼容,DevEco Studio 26.0.0.851 / hvigor 6.26.8
DevEco Studio26.0.0.851 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.sekret
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
模块形态依赖处置
sekret-annotations纯 commonMain,2 个注解文件零依赖整体编译(jvm + 双 ohos)
sekret-compiler-plugin纯 JVM,8 个文件compileOnly kotlin-compiler-embeddable整体编译(平台无关,产物进编译器)
sekret-gradle-plugin纯 JVM,32 个文件KGP + kotlinpoet + serialization + kaml整体编译(构建期工具)
sekret-libKMP,commonMain 纯 Kotlin + JNI 源集树annotations + serialization-corecommonMain 整体编译;JNI 树留盘

两个关键判断:其一,编译器插件唯一的目标平台是「编译器」,IrGenerationExtension 在 JVM 与 Native 编译里是同一套后端扩展点,插件本身不需要 ohos 目标——需要的是分发通道(第 3 阶段);其二,sekret-lib 的 SHA256 / SekretHelper 在 commonMain 里零平台 API,ohos 直接可编,而 JNI 树依赖 jni.h 与 JNINativeInterface_,鸿蒙没有 JNI,处置为留盘不进构建——与 KStore 适配里 kstore-storage(浏览器 localStorage 专属)的处置同构。

1.4 页面边界
标题区          SEKRET · OPENHARMONY / 副标题(toString() @Secret 字段掩码 · Kotlin/Native ohosArm64/ohosX64)
Tab 切换        掩码 / 对照 / 解密(底部三 Tab)
掩码区          5 张数据类卡片(toString 输出,■■■ 以强调色高亮)
对照区          同批模型字段名值对 + 说明文案(直接属性访问不受影响,只有 toString() 被编译期改写)
解密区          三组 key/value 卡片 + matches 徽章
状态行          最近一次调用结果(如"生成成功 mask 5 项" / "失败: ...")

页面启动时 aboutToAppear 立即调一次 kind='mask',状态栏出现生成计数即证明 Native 链路活着。

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

2.1 制品线验证(ohos serialization 的三岔口)

先用 .module 元数据在社区 Maven(https://maven.eazytec-cloud.com)上探针,三个候选三种结局:

kotlinx-serialization-core 1.9.0(Maven Central 官方)
  → ohosArm64 klib 404:官方线根本不发布 ohos 变体
kotlinx-serialization-core 1.11.0-0.1.0-07(eazytec 鸿蒙线)
  → ohosArm64ApiElements 变体存在,但 stdlib 元数据是 2.4/2.3 线
  → Kotlin 2.2.21 编译期直接报 Incompatible classes were found in dependencies
kotlinx-serialization-core 1.9.1-1.0.0(eazytec 鸿蒙线)
  → ohosArm64ApiElements / ohosX64ApiElements 变体齐全,元数据 2.2 线可读 ✓

结论:钉 1.9.1-1.0.0。版本主体与上游声明(1.9.0)只差一个补丁位,commonMain 里的依赖坐标只改一个版本号;鸿蒙线同时发布 jvm 变体,交叉验证用同一个版本号。

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

社区钉法有两条线:KStore 用的 2.4.20-0.1.0-07 新线(Gradle 留 9.x),qrose / haze 用的 2.2.21-1.0.0 旧线(Gradle 必须降 8.14.1)。Sekret 选旧线,理由有三:其一,上游源码是 Kotlin 2.3.0 编的,2.4.20 线虽然同代但 Gradle 9.2.1 的 wrapper 与 AGP classpath 仍然要动,降级 wrapper 的成本与升线持平;其二,编译器插件源码里有 2.3 独有 API(CompilerPluginRegistrar.pluginId override),无论哪条线都要删——删掉之后 2.2.21 直接编过;其三,serialization 鸿蒙线里可用的 1.9.1-1.0.0 元数据正好是 2.2 线可读。两条工具链线的经验不能互相套用,路线选择必须基于当天的制品可达性与源码兼容面。

settings.gradle.kts 把 eazytec 仓库写在最前,并为本地插件坐标解析补 mavenLocal():

pluginManagement {
    repositories {
        mavenLocal()
        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_sekret"

include(":sekret-annotations")
include(":sekret-compiler-plugin")
include(":sekret-gradle-plugin")
include(":sekret-lib")
include(":example:nativeApp")

gradle.properties 写死 JDK 21 与 fork 版本号:

org.gradle.java.home=C:\\Program Files\\Eclipse Adoptium\\jdk-21.0.12.101-hotspot
version=2.3.0-ohos.1
kotlin.native.ignoreDisabledTargets=true
kotlin.native.distribution.downloadFromMaven=true
2.3 编译注解与运行时核心

库模块收敛为纯 KMP 五句话:

kotlin {
    applyDefaultHierarchyTemplate()
    jvmToolchain(21)
    jvm()          // 交叉验证用
    ohosArm64()
    ohosX64()

    sourceSets {
        commonMain.dependencies {
            api(project(":sekret-annotations"))
            implementation(libs.serialization)   // 1.9.1-1.0.0 ohos 线
        }
    }
}

实测:

./gradlew :sekret-annotations:compileKotlinOhosArm64 :sekret-annotations:compileKotlinOhosX64  # BUILD SUCCESSFUL
./gradlew :sekret-lib:compileKotlinOhosArm64 :sekret-lib:compileKotlinOhosX64                  # BUILD SUCCESSFUL

注解与解密核心的全部 commonMain 文件一个没动。库模块绝不开 binaries.sharedLib——so 只由 example/nativeApp 出一份,否则每个模块各带一份 Kotlin/Native 运行时,体积和符号全乱。

第 3 阶段:编译器插件接入(本适配的成败手)

3.1 编译器插件的 2.2.21 兼容改造

三处动作,一处验证:

  1. 删 Kotlin 2.3 独有 API:SekretComponentRegistrar 里的 override val pluginId: String 在 2.2.21 的 CompilerPluginRegistrar 上不存在(overrides nothing 编译错误),删除即可;
  2. auto-service / kapt 换手写 SPI:kapt 在降级后的工具链里是不必要的摩擦面,直接手写两个注册文件——META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar 与 ...CommandLineProcessor,各一行实现类全名,注册关系显式可见、少两个依赖;
  3. gradle-plugin 的 Gradle 8 兼容:maybeCreate(name, X::class)(KClass 重载是 Gradle 9 API)改为 ::class.java;
  4. 验证:org.jetbrains.kotlin.backend.jvm.ir.constantValue 这类 backend.jvm 命名空间 API 在 2.2.21 的 embeddable 里仍然存在,compileKotlin 直接过——上游 transformer 逻辑零改动。
./gradlew :sekret-compiler-plugin:jar :sekret-gradle-plugin:jar   # BUILD SUCCESSFUL
3.2 kotlinCompilerPluginClasspath 在 Native 侧静默失效

直觉做法(上游 sample 的做法):

dependencies {
    add("kotlinCompilerPluginClasspath", project(":sekret-compiler-plugin"))
}

在 JVM 单平台项目里这是对的;实测 JVM 侧掩码立即生效。但在 KMP 工程里它只喂 JVM 编译——compileKotlinOhosArm64 / compileKotlinOhosX64 拿不到插件类路径。更糟的是没有任何报错:

  • 构建成功;
  • -P plugin:sekretPlugin:secretMask=… 选项没人认领,kotlinc-native 对未认领的 -P 静默忽略(实测传一个不存在的插件 id 选项照样 BUILD SUCCESSFUL);
  • toString 保持明文,产物看起来一切正常。

安全类功能的静默失败是最坏的失败形态:你以为掩码在,其实不在。

3.3 正确通道:KotlinCompilerPluginSupportPlugin + SubpluginArtifact

KGP 分发编译器插件给 Native 编译的受支持通道是 KotlinCompilerPluginSupportPlugin(kotlinx-serialization、Compose 都靠它触达 Native 目标):

class SekretLocalCompilerPlugin : KotlinCompilerPluginSupportPlugin {
    private val version = "2.3.0-ohos.1"

    override fun getCompilerPluginId() = "sekretPlugin"

    override fun getPluginArtifact() =
        SubpluginArtifact("dev.datlag.sekret", "compiler-plugin", version)

    override fun getPluginArtifactForNative() = getPluginArtifact()   // ← native 通道

    override fun applyToCompilation(c: KotlinCompilation<*>) =
        c.target.project.provider {
            listOf(
                SubpluginOption("secretMask", "■■■"),
                SubpluginOption("secretMaskNull", "true")
            )
        }

    override fun isApplicable(c: KotlinCompilation<*>) = true
}

project.pluginManager.apply(SekretLocalCompilerPlugin::class.java)

三个不可省略的细节:其一,SubpluginArtifact 走 Maven 坐标解析,本地开发流程是 :sekret-compiler-plugin:publishToMavenLocal + settings 里 mavenLocal() 前置;其二,插件的 metadata 版本必须与宿主编译器一致——必须用本仓库在 HarmonyOS Kotlin 2.2.21 下编出的插件,不能从 Maven Central 拉官方 2.3.0(Kotlin 2.3 元数据,2.2.21 编译器读不了,插件加载直接失败);其三,getPluginArtifactForNative() 是独立的 native 通道开关——上游 SekretCompilerSubPlugin 恰恰缺这个 override,意味着即便在 iOS 上游生态里 Native 目标同样没被掩码,本 fork 顺手修正了它。

3.4 A/B 哈希对照:证明插件改写了 ohos 产物

kotlinc-native 对未认领选项静默,「传坏选项看报错」的负对照无效;so 里的字符串常量是压缩编码存储的(连明文 hunter2 都 grep 不到),「搜掩码字符串」也无效。唯一诚实的方法是字节级对照:

组唯一变量产物哈希(x86_64 debugShared)复现性
无插件移除插件接线ada8131f…三次构建全等
有插件完整接线07e4fdd1…两次构建全等

构建是确定性的,两组哈希稳定互异——IR 改写确实进入了 ohos 产物。配合 JVM 行为验证(第 6.2 节,同一插件同一选项输出 password=■■■),证据链闭合。

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

4.1 JSON 契约
dev.datlag.sekret/**(上游注解 + 解密核心,原样编译)
        ↓ @Secret 标注的数据类 + SekretHelper.getNativeValue
DemoCore.kt(commonMain,mask / reveal / decrypt 三入口)
        ↓ 同一份代码
Bridge.kt(@CName × 1,扁平 JSON 进出)
        ↓ UTF-8 JSON
N-API sekretRender()
        ↓
ArkUI Index.ets(三 Tab)

请求与响应:

请求 : {"kind":"mask"}
       {"kind":"reveal"}
       {"kind":"decrypt"}                    // 亦接受自定义 {"kind":"decrypt","key":"…","secret":"0x…, 0x…"}
响应 : {"ok":true,"kind":"mask","results":[{"label":"User","toString":"User(name=Ada Lovelace, password=■■■, apiToken=■■■)"},…]}
       {"ok":true,"kind":"reveal","results":[{"label":"User","fields":{"password":"hunter2",…}},…]}
       {"ok":true,"kind":"decrypt","results":[{"key":"password12345","value":"TEST_VALUE","matches":true},…]}
       {"ok":false,"error":"..."}

业务逻辑全部放 commonMain 的 DemoCore,ohosMain 的 Bridge 只留 @CName 一行委托,jvmMain 的 main() 跑同一份 DemoCore——JVM 与 ohos 跑的是同一份代码,交叉验证因此成立。

4.2 Kotlin/Native 导出函数
@CName("SekretRender")
fun sekretRender(request: String): String = DemoCore.render(request)
C ABI 符号ArkTS 方法覆盖内核
SekretRendersekretRender(req)掩码 / 对照 / 解密三入口一口进

llvm-nm -D 实测 SekretRender 与 libohossekret_symbols 均为全局 T 符号。

4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
    {"sekretRender", nullptr, NapiSekretRender, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};

回调统一三步:napi_typeof 校验必须是 string(API 26 的 NAPI 头对缓冲区有非 const 约束,先量长度再开 std::vector<char>)、OH_LOG_INFO 打日志、调 KN 函数并包装返回。KN 返回的字符串必须经 libohossekret_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohossekret_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与业务符号),不拖整份生成头。

4.4 CMake 和类型声明
add_library(ohossekret SHARED IMPORTED)
set_target_properties(ohossekret PROPERTIES
  IMPORTED_LOCATION ${KN_SO}
  IMPORTED_NO_SONAME TRUE)

add_library(sekret_napi SHARED napi_init.cpp)
target_link_libraries(sekret_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohossekret)

OHOS_ARCH 为空或 libohossekret.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。

export const sekretRender: (request: string) => string;

第 5 阶段:ArkUI 宿主

5.1 触发调用
aboutToAppear(): void {
  this.request('mask');   // 启动即验证 Native 链路
}

private request(kind: string): void {
  const raw: string = sekretRender(JSON.stringify({ kind: kind }));
  // 解析 -> @State 刷新卡片 + 状态行("生成成功 mask 5 项" / "失败: …")
}

sekretRender 是同步 C 调用,微秒级返回;错误统一走 ok:false 通道进状态行,不静默吞。

5.2 页面交互

三个 Tab 自上而下:掩码 Tab 展示 5 张数据类卡片,toString 输出里的 ■■■ 片段以强调色高亮(Text + Span 拼装),肉眼可断定掩码样式是编译期配置的 ■■■ 而非默认 ***;对照 Tab 展示同批模型的字段名值对(password=hunter2),附说明「直接属性访问不受影响,只有 toString() 被编译期改写」;解密 Tab 展示三组 key/value 卡片与 matches=true 徽章。底部状态行滚动更新最近一次调用结果。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohossekret)+ cxxShared + copyKnSo*
│   │                                         + SekretLocalCompilerPlugin(SubpluginArtifact 通道)
│   └── src/
│       ├── commonMain/kotlin/example/nativeapp/
│       │   ├── Model.kt                      @Secret 数据类(String/String?/CharSequence/StringBuilder/Appendable)
│       │   ├── DemoCore.kt                   mask / reveal / decrypt 三入口 + 演示秘密
│       │   └── MiniJson.kt                   扁平 JSON 解析(继承自 cmp_qrose 审核模式)
│       ├── ohosMain/kotlin/example/nativeapp/
│       │   └── Bridge.kt                     @CName("SekretRender") 一行委托
│       └── jvmMain/kotlin/example/nativeapp/
│           └── Main.kt                       JVM 交叉验证入口
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.sekret、versionName 2.3.0-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/libohossekret_api.h
        ├── src/main/cpp/types/libsekret_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 三 Tab 验收页面
6.2 构建、签名和安装
./gradlew :sekret-compiler-plugin:publishToMavenLocal            # SubpluginArtifact 坐标供给
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
./gradlew :example:nativeApp:run                                  # JVM 交叉验证

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

签名在 DevEco Studio 的 File → Project Structure → Signing Configs 中勾选自动生成,证书写入本机,不进入仓库(仓库内 build-profile.json5 的 signingConfigs 保持空数组,product 侧预留 "signingConfig": "default" 引用)。

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

四、完整代码对照

4.1 整体架构

【编译期】业务数据类 ──@Secret──▶ sekret-compiler-plugin(IR transformer,零改动)
    │                                  ↑ SubpluginArtifact + getPluginArtifactForNative(本 fork 打通的通道)
    ▼ 编译期烙进 so,运行时零反射
【运行时】ohosArm64 / ohosX64 KLIB / libohossekret.so
    │ SekretHelper.getNativeValue(SHA-256 + XOR,上游原件)
    ▼
Bridge.kt(DemoCore 三入口)
    │ SekretRender
    ▼
libsekret_napi.so
    │ sekretRender
    ▼
Index.ets(JSON parse + @State 更新)
    ▼
ArkUI 真机页面(三 Tab)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven + mavenLocal 置前、ohos_sekret、JNI 树随 lib 模块收敛
gradle/libs.versions.tomlKotlin 2.2.21-1.0.0 + serialization 1.9.1-1.0.0 ohos 线钉版
build.gradle.kts(根)版本 2.3.0-ohos.1、全局禁用 Ohos*Test* 任务
sekret-annotations / sekret-lib 的 build.gradle.kts纯 KMP:jvm + ohosArm64 + ohosX64 五句话
sekret-compiler-plugin/.../SekretComponentRegistrar.kt删 2.3 独有 pluginId override
sekret-compiler-plugin/.../META-INF/services/*手写 SPI 注册(替代 auto-service/kapt)
sekret-gradle-plugin/.../SekretCompilerSubPlugin.ktgetPluginArtifactForNative() 补丁(上游缺失)
sekret-gradle-plugin/.../SekretPlugin.ktmaybeCreate Gradle 8 兼容
example/nativeApp/build.gradle.kts双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填、插件通道
nativeApp/.../Model.kt / DemoCore.kt / MiniJson.kt演示模型、三入口逻辑、手写 JSON
nativeApp/.../Bridge.kt@CName("SekretRender") 一行委托
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 三 Tab 验收页面
README.OpenHarmony_CN.md鸿蒙适配主文档(17 节)

4.3 关键 API 对照

层次API作用
注解@Secret标注需要掩码的属性(AnnotationTarget.PROPERTY)
编译器插件ElementTransformer / ToStringTransformer收集 @Secret 属性,改写 toString 函数体里的字段读取
编译器插件secretMask / secretMaskNull SubpluginOption掩码样式与 null 处理(编译期选项,Demo 用 ■■■)
编译器插件getPluginArtifactForNative()KGP 对 Native 编译的插件分发开关(本 fork 补上)
运行时SekretHelper.getNativeValue(secret, key)SHA-256(key) hex 逐字节 XOR 还原明文(纯 Kotlin)
运行时EncryptedSecret.decrypt(key)intArray 秘密的包装入口
NativeSekretRenderC ABI JSON 导出(mask / reveal / decrypt)
N-APIsekretRender(req)向 ArkTS 暴露能力
ArkTSrequest(kind)触发调用并刷新三 Tab

4.4 ArkTS 与 Kotlin 的边界

const raw: string = sekretRender(JSON.stringify({ kind: 'mask' }));
const res: SekretResponse = JSON.parse(raw) as SekretResponse;
// res.results[0].toString === 'User(name=Ada Lovelace, password=■■■, apiToken=■■■)'
data class User(
    val name: String,
    @Secret val password: String,
    @Secret val apiToken: String? = null
)

@CName("SekretRender")
fun sekretRender(request: String): String = DemoCore.render(request)

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

五、关键决策说明

决策 1:真编译编译器插件,而不是在运行时做掩码

在 ArkTS 或 Kotlin 运行时写一个 toString 脱敏函数,等于重新发明一个日志过滤库——错过本库的全部卖点。sekret 的价值在编译期把明文从 toString 的代码路径里抹掉,运行时零反射零配置;适配的关键是把这条编译期机制完整搬进鸿蒙工具链,并用对照实验证明它生效。

决策 2:工具链钉 2.2.21-1.0.0,Gradle 降 8.14.1

与 KStore 升到 2.4.20 线、沿用 Gradle 9.6.1 的选择相反,两条路线都对:KStore 的依赖(coroutines 1.11.0-0.1.0-07)元数据逼着它升线;Sekret 的源码(2.3 独有 API)与可用 serialization 线(1.9.1-1.0.0)都指向旧线。工具链版本跟着依赖与源码兼容面走,不能拿别的仓库的钉法直接抄。

决策 3:JNI 源集树留盘,鸿蒙走纯 Kotlin XOR 路径

鸿蒙没有 JNI,等价移植 NAPI 体系等于新写一个 NativeJNIGenerator + cinterop。而纯 Kotlin 路径的安全语义等价:混淆 intArray 进包、运行时求值、明文不落 class / 二进制。JNI 树留在仓库里等上游或工具链演进——与 qrose 把 Compose Painter 挪进 composeMain 不编译是同一哲学:不删上游资产。

决策 4:插件分发走 SubpluginArtifact + mavenLocal,并给上游补 native 通道

kotlinCompilerPluginClasspath 的静默失效是本次适配最贵的发现。修复分两层:Demo 侧用本地 KotlinCompilerPluginSupportPlugin 实现声明坐标;产品侧给上游 SekretCompilerSubPlugin 补 getPluginArtifactForNative()——真实业务工程 apply dev.datlag.sekret 后,ohos 目标的掩码会自动生效(把 compiler-plugin 发布到可达仓库即可)。

决策 5:JVM 交叉验证 + A/B 哈希对照双闸门

DemoCore 在 commonMain,JVM 与 ohos 跑同一份代码,:example:nativeApp:run 是「行为没漂」的闸门;kotlinc-native 静默忽略未认领选项,A/B 哈希对照是「插件真的进了 ohos 编译」的唯一可信证明。两层缺一不可:只有 JVM 验证证明不了 native 通道,只有哈希对照证明不了行为正确。

六、测试与验证

6.1 测试环境

本次验证使用 Windows 10(x64)、JDK 21(Eclipse Temurin)、DevEco Studio 26.0.0.851(hvigor 6.26.8、JBR 25 打包)、OpenHarmony 命令行工具(hdc)与鸿蒙真机(arm64)。

6.2 JVM 交叉验证

./gradlew :example:nativeApp:run

三段输出(节选):

=== mask ===
User(name=Ada Lovelace, password=■■■, apiToken=■■■)
User(null) … password=■■■, apiToken=■■■        ← null 字段同样掩码
Account(id=10086, apiKey=■■■, secretKey=■■■)
Session(device=OHOS-Phone, otp=■■■, payload=■■■, note=■■■)
PublicInfo(app=sekret-ohos-demo, version=2.3.0-ohos.1)   ← 无 @Secret 的对照组,原样输出
=== reveal ===
… password=hunter2 … apiKey=AKIAIOSFODNN7EXAMPLE …        ← 直接属性访问不受影响
=== decrypt ===
{"key":"password12345","value":"TEST_VALUE","matches":true}
{"key":"password12345","value":"atomgit.com/oh-tpc/Sekret","matches":true}
{"key":"sekret-ohos-key","value":"AKIAIOSFODNN7EXAMPLE","matches":true}

decrypt 三组的期望值由 Java 独立实现的同算法(MessageDigest SHA-256 → hex → XOR)预生成,matches=true 说明 Kotlin/Native 侧 SekretHelper 的字节序、hex 展开与上游完全一致。

6.3 HAP 验证

:sekret-annotations:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
:sekret-lib:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: SekretRender / libohossekret_symbols 均为全局 T 符号
A/B 对照: 有插件 07e4fdd1… vs 无插件 ada8131f…(各自可复现,稳定互异)
BuildNativeWithCmake: finished(双 ABI 编出 libsekret_napi.so,约 64KB)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

entry-default-unsigned.hap 约 5.4 MB,内含双 ABI 各三件套(libsekret_napi.so / libohossekret.so / libc++_shared.so);HAP 内 strip 后 KN so arm64 约 1.39MB、x86_64 约 1.19MB(debugShared 未 strip 时分别约 2.5MB / 2.1MB,含 Kotlin/Native 运行时)。

6.4 功能用例

  1. 启动即调 kind='mask',状态行出现「生成成功 mask 5 项」(来自真实 Native 调用,非静态文字);
  2. 掩码 Tab 五张卡片:User 的 password / apiToken 为 ■■■,name 明文;User(null) 的空 token 同样 ■■■(secretMaskNull=true);
  3. Account 的 apiKey / secretKey、Session 的 otp / payload / note 全部 ■■■,id / device 明文;
  4. PublicInfo 无 @Secret,app=sekret-ohos-demo, version=2.3.0-ohos.1 原样输出——对照组证明掩码只作用于标注字段;
  5. 对照 Tab 显示 password=hunter2 / apiKey=AKIAIOSFODNN7EXAMPLE 等真实值,证明数据本身完好、只有 toString 被改写;
  6. 解密 Tab 三组全部 matches=true:TEST_VALUE / atomgit.com/oh-tpc/Sekret / AKIAIOSFODNN7EXAMPLE 逐字核对;
  7. 掩码样式为 ■■■(Demo 配置的编译期选项),不是默认 ***——证明 SubpluginOption 真的传进了编译器;
  8. hdc shell hilog | grep Sekret 可见 N-API 层日志,无未捕获异常。

6.5 验证结论

编译器插件(ohos 编译通道)、注解与解密核心(ohos KLIB)、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的掩码会话:编译期掩码、数据完好对照与应用秘密解密三段闭环(见下节效果图)。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/Sekret.git
cd Sekret
export JAVA_HOME="/path/to/jdk-21"
./gradlew :sekret-annotations:compileKotlinOhosArm64 :sekret-lib:compileKotlinOhosArm64   # klib
./gradlew :sekret-compiler-plugin:publishToMavenLocal                                     # 插件坐标供给
./gradlew :example:nativeApp:run                                                          # JVM 交叉验证
./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.sekret -m entry
hdc shell hilog | grep Sekret

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让 @Secret 掩码真正在 ohosArm64 / ohosX64 编译产物里生效;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. kotlinx-serialization 官方线没有 ohos 变体,鸿蒙线 1.11.0-0.1.0-07 又是 Kotlin 2.3+ 元数据——版本必须落在 1.9.1-1.0.0 这个「有 ohos klib 且 2.2 线可读」的交点上;
  4. 上游编译器插件的 CompilerPluginRegistrar.pluginId override 是 Kotlin 2.3 独有 API,降线后 'pluginId' overrides nothing,删掉即可;
  5. kotlinCompilerPluginClasspath 不达 Native 编译且静默失效——JVM 验证通过会给出虚假的安全感,Native 侧必须走 KotlinCompilerPluginSupportPlugin + SubpluginArtifact;
  6. kotlinc-native 对未认领的 -P 选项静默忽略,「传坏选项看报错」的负对照无效,只能用 A/B 哈希对照;
  7. SubpluginArtifact 走 Maven 坐标,本地开发要 publishToMavenLocal + settings mavenLocal() 前置;且不能从 Maven Central 拉官方 2.3.0 插件(元数据失配,加载即崩);
  8. 上游 SekretCompilerSubPlugin 缺 getPluginArtifactForNative()——KGP 对 Native 编译默认不喂插件,这个缺口在 iOS 上游生态同样存在,本 fork 顺手修正;
  9. gradle-plugin 源码用了 Gradle 9 的 maybeCreate(name, KClass) 重载,Gradle 8.14 下要改 ::class.java;
  10. PackageHap 报 spawn java ENOENT 是 hvigor 找不到 java——把 DevEco 自带 JBR 的 bin 加进 PATH;hvigor 必须在 example/harmonyApp 目录下执行;
  11. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套(NAPI so / KN so / libc++)缺一不可;
  12. Kotlin/Native 没有 String.format("%04x".format(…) 编不过),用 toString(16).padStart(4, '0');buildSrc 的 kotlin-dsl 内嵌 Kotlin 与外部 HarmonyOS Kotlin 有冲突面,直接删除、配置内联。

8.2 已知问题

  • generateSekret(从 sekret.properties 生成 KMP 模块)的 Target.kt 没有 OHOS 分支,ohos-only 工程不会生成 ohos actual;鸿蒙侧请直接用 sekret-lib 的 SekretHelper 纯 Kotlin 路径,或按 nativeMain actual 模式手写生成模块;
  • JNI 原生二进制藏秘密不适用鸿蒙(无 JNI);JNI 源集树、NativeLoader、ReLinker 留盘不进构建;
  • @Obfuscate 类级字符串混淆上游本身未实现编译期逻辑(FqName 已声明未使用),本 fork 不改变其状态;
  • StringBuffer / java.lang.String 等 Java 类型掩码仅在 JVM 目标可用,Kotlin/Native 无对应物;
  • 掩码样式编译期固化:secretMask 是编译器选项,一份 so 一个掩码,运行时切换需重编(上游设计如此);
  • android / js / wasmJs / apple / linux / windows 目标与发布链、dokka、kover 一并剥离,本 fork 不发 Maven Central;
  • 上游 CI(iOS/wasm 矩阵 + MavenCentral 发布)为遗留状态,未覆盖 ohos 链接任务;
  • HAP 安装必须使用与 bundle name org.terminator.ohos.sekret 匹配的签名。

8.3 未来优化方向

  • Target.kt 补 OHOS 分支,让 generateSekret 在鸿蒙工程生成 nativeMain actual;
  • 把 dev.datlag.sekret:compiler-plugin:2.3.0-ohos.1 发布到社区可达 Maven(如 eazytec),业务工程一行插件接入;
  • 评估 .har 形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • 在 CI 中加入 ohos 编译、llvm-nm 符号检查、A/B 哈希对照和 HAP 构建;
  • 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本。

九、总结

9.1 核心难点回顾

上游 4 模块(注解 / 编译器插件 / Gradle 插件 / 运行时库)
    → 验证 ohos serialization 制品线(1.9.1-1.0.0 交点)
    → 工具链对齐 Kotlin 2.2.21-1.0.0 + Gradle 8.14.1(降线)
    → 编译器插件 2.2.21 兼容(删 2.3 API + 手写 SPI)
    → ohosArm64 / ohosX64 KLIB(注解 + 解密核心零改动)
    → 插件进入 Native 编译通道(SubpluginArtifact + getPluginArtifactForNative)
    → A/B 哈希对照证明 IR 改写生效
    → C ABI(@CName × 1)+ JSON 契约
    → C++ N-API(DisposeString 纪律)
    → ArkTS JSON 解析
    → ArkUI 真机三 Tab 验收(掩码 / 对照 / 解密)

9.2 封装层次

  • 编译期层:上游原版 IR transformer 在鸿蒙 Kotlin/Native 编译中改写 toString,掩码常量烙进 so;
  • 运行时层:上游原版 SHA-256 + XOR 解密核心编成 ohos KLIB;
  • Native 层:生成双 ABI 动态库并通过 1 个 C ABI 输出 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、JSON 解析、三 Tab 展示与状态行;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 编译器插件类库的适配关键是分发通道而不是代码移植:IR transformer 平台无关、一行不用改,全部工作量在「让插件进 Native 编译」——kotlinCompilerPluginClasspath 只喂 JVM、kotlinc-native 静默忽略未认领选项,这两个静默行为叠在一起足以让掩码裸奔而无人察觉;后来任何「编译器插件类」库(日志格式化器、DeepLink 处理器、Compose 编译扩展…)走鸿蒙,这套结论直接复用;
  2. 静默失效必须用对照实验钉死:负对照(传坏选项等报错)在 kotlinc-native 上无效,字符串搜索在 so 压缩存储下无效,确定性构建 + A/B 哈希对照是唯一诚实的方法——安全类功能的验证设计要比功能类多想一层「它可能假装在工作」;
  3. 工具链线选择跟依赖生态与源码兼容面走,不抄邻居作业:KStore 因 coroutines 元数据升到 2.4.20 线留 Gradle 9,Sekret 因源码 2.3 API 与可用 serialization 线钉 2.2.21 降 Gradle 8.14——两条路线都对,抄错线就多走一天弯路。

9.4 适配成果

当前适配已完成:

  • Sekret 2.3.0(本 fork 2.3.0-ohos.1)注解与解密核心的双 ABI Kotlin/Native 编译(上游 commonMain 文件零改动);
  • 编译器插件在鸿蒙 Kotlin/Native 编译中真实生效:SubpluginArtifact 通道 + 上游 getPluginArtifactForNative() 缺口修正 + A/B 哈希对照证据;
  • 1 个 C ABI 符号和 C++ N-API 包装层(llvm-nm 验证全局 T 符号);
  • DevEco Stage ArkUI 三 Tab 验收页面(掩码、对照、解密、状态行);
  • JVM 交叉验证全绿 + HAP 构建(双 ABI 六件套 so,约 5.4MB)、签名、设备安装和真机验证;
  • 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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐