开源鸿蒙平台 CMP 三方库「Jewel」适配全流程

本文记录 Jewel 接入 OpenHarmony 的完整过程,覆盖主题模型盘点、Compose 源码集隔离、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。

Jewel 是 JetBrains 孵化的 Compose for Desktop 主题库:用 Compose 复刻 IntelliJ Platform 的 New UI Swing Look and Feel,对外提供 ThemeDefinition 主题模型与一整套 jewel 风格 Composable(Button / Checkbox / Tab / TextField......)。本次适配与 KStore 一类"纯引擎库"(社区 Maven 已有 kotlinx ohos 制品线,整库真编译)有本质区别:Jewel 上游没有 可整库编译的无 Compose 依赖引擎------它的全部价值落在主题模型上(8 组索引色阶、全局色、度量、字体样式、组件样式索引表)。因此走的是社区早期已审核路线(qrose / haze / PreCompose / voyager 的"模型内核提取"):主题模型以纯 Kotlin 内核提取进 commonMain 原样编译,Compose UI 整体隔离到 src/composeMain 不参与鸿蒙编译,真机上的 IntUI Light / Dark 色值与上游 0.27.0 构建期生成的主题类逐位一致,ArkUI 页面作为验收宿主。

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

上游仓库: JetBrains/Jewel

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

Jewel 为 Compose for Desktop 应用提供"IntelliJ New UI 外观"这一件事的完整答案:ThemeDefinition 把调色板(ThemeColorPalette 的 Gray1...14 / Blue1...13 等 8 组索引色阶)、全局色(边框三色、outline 五色、文本五色、面板底色)、全局度量(2dp 焦点描边、24dp 行高)、三种字体样式(Inter 13sp×1.3、JetBrains Mono 13sp×1.2)和图标数据组装成单一主题对象;int-ui-standalone 再按组件给出样式工厂------默认按钮四态用 blue(4) / blue(3) / blue(2) / gray(12),Tab 选中下划线明色 blue(4) 暗色 blue(6),输入框聚焦描边 blue(4) / blue(6)......这些颜色语义索引表才是 jewel 真正的数据资产,与渲染框架无关。

OpenHarmony 应用同样需要这套 New UI 设计语言,但上游构建(纯 Kotlin/JVM 单平台、Compose Desktop 1.7.1、Kotlin 2.0.21-RC、无 AGP)不会生成 OpenHarmony 变体。如果只把示例页面改成 ArkTS 并手抄一份色表,页面可以显示色块,却无法证明 jewel 的主题模型在 OpenHarmony ARM64 设备上完成编译、链接和执行------手抄色表必然与上游漂移。适配需要同时验证主题模型内核、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍 具体问题
没有纯引擎 上游全部模块依赖 androidx.compose.* / AWT / JBR 字体,不存在"整库真编译"的选项,必须先做模型内核提取。
目标缺失 必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。
工具链不一致 上游 Kotlin 2.0.21-RC / Gradle 8.10 / JDK 17 与鸿蒙工具链无法共存于一个 Gradle 构建。
构建逻辑耦合 上游构建逻辑在 buildSrc convention plugin 里(detekt / ktfmt / IntelliJ Platform 代码生成),Gradle 见到 buildSrc 目录就自动构建,必须整体规避。
语言边界不同 ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
主题数据在构建期生成 调色板常量由上游 intellij-theme-generator 在 JVM 构建期从 IntelliJ 主题 JSON 拉取生成,鸿蒙侧需要等价的数据落地管线。
交付链路复杂 .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:主题模型内核的 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。色值语义完全由上游原版定义负责(生成管线与上游钉死同一个 IntelliJ Platform 版本),ArkTS 只负责调用 Native、解析 JSON 和展示结果。

1.2 库提供的能力

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

  • 整包主题(theme) :jewelThemeInfo({theme}) 一次返回整个 ThemeDefinition------8 组色阶全量 hex、raw 键、全局色、度量、三种字体样式、图标色板和 10 类组件样式表;
  • 色键查询(key) :请求附带 key 时返回 lookup 结果(Blue4 → #FF3574F0),支持索引键与 raw 键,越界键返回 exists:false 而不崩溃;
  • 明暗切换 :theme: light|dark 重查内核,页面全量刷新------数据来自 Native 而非 ArkTS 本地反转;
  • 版本握手 :响应携带 version: 0.27.0-ohos.1,来自真实 Native 调用而非静态文字。

响应包含:

字段 作用
ok 操作是否成功,失败时 error 带原文。
name / isDark / version 主题名、明暗标志、fork 版本握手。
palette / rawKeys 8 组色阶(#AARRGGBB 数组)与原始主题键(如 windowsPopupBorder)。
globals / metrics 边框 / outline / 文本 / 面板底色与 2dp / 24dp 度量。
textStyles default / editor / console 三种字体的 family、字号、行高、字重。
iconPalette 图标色板(调色板键解析后的 hex)。
components 10 类组件的逐状态颜色与 dp 度量(button / buttonOutlined / checkbox / radioButton / tab / slider / progress / link / divider / textField)。
lookup 可选:key 查询结果(argb + exists)。

上游 ThemeColorPalette.lookup() 对越界索引抛 IndexOutOfBoundsException------内核保留该行为并有测试钉死,桥侧用 runCatching 收敛为 exists:false,语义不跨边界走样。

1.3 实现适配

维度 要求
代码复用 主题模型(色阶、lookup 语义、全局色、度量、字体、组件样式索引)以上游源码为唯一参照逐项端口,包名与上游对齐;色值常量与上游构建期生成物逐位一致。
平台目标 根构建直接声明 ohosArm64 + ohosX64,保留 jvm 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离 根构建替换为极简 ohos 构建(cmp_qrose 模式);buildSrc 改名 buildSrc.upstream,ui / decorated-window / int-ui / markdown / ide-laf-bridge / samples 全部留盘不进构建。
桥接稳定 使用 1 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
数据管线 调色板生成脚本 + expUI 主题 JSON 快照随仓入库,可离线复现,生成物头部记录出处。
UI 完整 四 Tab 覆盖全部桥接出口:调色板、主题、组件、查询 + 明暗切换。
仓库规范 项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。Jewel 上游的全部 Composable(依赖 Compose Desktop 1.7.1 / JBR / AWT)被隔离在 src/composeMain 不参与鸿蒙编译;ArkTS Demo 把桥暴露的主题模型画出来------真机上看到的是 jewel 的真实色值,不是手绘近似。

二、实现路线图

text 复制代码
第 1 阶段:项目初始化     ── 盘点模块与依赖边界、确认"模型内核提取"路线、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、buildSrc 改名规避、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版
第 3 阶段:内核与数据落地 ── 主题模型纯 Kotlin 化、expUI 主题 JSON 快照、调色板生成管线
第 4 阶段:原生桥接       ── @CName C ABI、组件样式索引解析、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主     ── 四 Tab 页面、明暗切换、色键查询器和版本握手
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
text 复制代码
foundation/                            上游核心模块(重写为 KMP:jvm + ohosArm64 + ohosX64)
foundation/src/commonMain/             新增:主题内核(8 个手写文件 + 2 个生成文件)
foundation/src/commonTest/             新增:24 项交叉验证用例
foundation/src/composeMain/            上游 Compose 源码隔离区(42 个文件,不参与任何 target 编译)
buildSrc.upstream/                     上游构建逻辑原样改名(Gradle 自动构建规避)
example/nativeApp/                     Kotlin/Native 动态库(Bridge + MiniJson + StyleResolver)
example/harmonyApp/                    DevEco Stage、CMake N-API 和 ArkTS 页面
tools/                                 调色板生成脚本 + expUI 明暗主题 JSON 快照
README.OpenHarmony.md / _CN.md         鸿蒙适配说明(中英双份)
README.upstream.md                     上游原文保存
ui/ decorated-window/ int-ui/ markdown/ ide-laf-bridge*/ samples/ ui-tests/
                                      上游其余模块,留盘不进 settings

构建拓扑走 cmp_qrose 模式 (根构建即 ohos 构建):settings.gradle.kts 只 include :foundation 与 :example:nativeApp,rootProject.name = "ohos_jewel"。上游的 compose / compose-compiler / detekt / ktfmt / IntelliJ Platform 代码生成插件全部随根构建替换剥离------本 fork 不发 Maven Central。

buildSrc 必须改名是本仓独有的坑:Gradle 只要见到名为 buildSrc 的目录就会在每次构建前自动编译它,而上游 buildSrc 依赖的插件条目已随版本目录重写移除,一 sync 即死。改名 buildSrc.upstream 后 Gradle 不再认领,上游构建逻辑以"留盘参考"的形态存在。

1.2 工具链矩阵
项目 配置
Kotlin Multiplatform HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)
JDK 21(org.gradle.java.home 指向 Temurin 21.0.12)
OpenHarmony 目标 ohosArm64 + ohosX64(jvm 保留作交叉验证)
Gradle 8.14.1(wrapper 随适配替换;2.2.21 旧线要求的版本)
主题数据源 IntelliJ Platform idea/243.19420.21 expUI 明暗主题 JSON
DevEco product 5.1.0(18) 兼容
DevEco Studio 26.0.0 Release
ABI arm64-v8a + x86_64
Bundle org.terminator.ohos.jewel
bash 复制代码
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
模块 文件形态 依赖形态 处置
foundation 42 个 Kotlin 文件 主题模型 + Compose UI(SelectableLazyColumn / Tree / modifier)混合 拆分:模型进 commonMain,UI 进 composeMain
ui 99 个文件 全量 Compose 组件 + 4 处 java.awt 留盘不进构建
decorated-window --- AWT + JNA 窗口装饰 留盘不进构建
int-ui --- int-ui 主题值 + IntelliJ 主题生成器 样式索引表随桥进 nativeApp;模块本身留盘
markdown / ide-laf-bridge* / samples / ui-tests --- commonmark / IntelliJ Platform / CMP Desktop 留盘不进构建

盘点结论决定了路线:foundation 里真正可移植的是 ThemeColorPalette(索引访问 + lookup() 正则语义)、GlobalColors 家族、GlobalMetrics、字体样式与 ThemeDefinition 装配------除 Compose Color / TextStyle / Dp 类型外零依赖;int-ui 侧的组件样式工厂全部是"调色板索引 + dp 常量"的纯数据。这两块合起来就是鸿蒙内核的全部内容。

1.4 页面边界
text 复制代码
标题区          IntUI Light / Dark · 版本握手行(0.27.0-ohos.1)· Light-Dark 切换开关
Tab 切换        调色板 / 主题 / 组件 / 查询(底部四 Tab)
调色板页        rawKeys 芯片 + 8 色系横滑色块(Gray1..14、Blue1..13...),点击弹 key+hex
主题页          globals 卡片(以 panelBackground 为底演示边框/outline/文本色)、度量、
                三种 TextStyle 示例(editor/console 等宽渲染)、iconPalette 色块墙
组件页          默认/Outlined 按钮四态静态样本 + 可交互真按钮、复选框、单选、Tab 下划线、
                滑块、进度条、链接、输入框三态、分隔线(颜色尺寸全部来自桥数据)
查询页          色键输入框 + 快捷芯片 → lookup 色块/hex/exists + 关于卡片(版本、数据源、仓库链接)

页面启动时先调 jewelThemeInfo({theme:'light'}) 建模,切换开关重查内核。

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

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

与 KStore 不同,Jewel 没有 kotlinx 依赖矩阵可查------它的依赖是 androidx.compose.* 与 IntelliJ Platform,ohos 制品线上不存在对应物。验证落在两头:

text 复制代码
· 上游 foundation 的主题模型文件     → 仅依赖 Compose 的 Color/TextStyle/Dp 类型壳,语义纯 Kotlin ✓ 可提取
· 上游 int-ui-standalone 样式工厂   → 全部是 palette(index) 引用 + dp 常量 ✓ 可索引表化
· 上游 Composable / buildSrc 生态   → Compose Desktop 1.7.1 / JBR / AWT ✗ 必须隔离

结论:走 cmp_qrose 审核路线 (模型内核提取),而不是 KStore 的整库真编译。工具链随之钉旧线:HarmonyOS Kotlin 2.2.21-1.0.0(qrose / haze 同款),Gradle 降 8.14.1,JDK 21。

2.2 工具链钉版与仓库置前

settings.gradle.kts 把 eazytec 仓库写在最前(且必须用命名仓库形式,原因见 8.1 踩坑第 2 条):

kotlin 复制代码
pluginManagement {
    repositories {
        maven {
            name = "eazytec-public"
            url = uri("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
        }
        maven {
            name = "eazytec-harmonyos"
            url = uri("https://maven.eazytec-cloud.com/nexus/content/groups/harmonyos")
        }
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}

rootProject.name = "ohos_jewel"

include(":foundation")
include(":example:nativeApp")

版本目录收敛到一项:

toml 复制代码
[versions]
kotlin = "2.2.21-1.0.0"

[plugins]
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
2.3 编译主题内核

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

kotlin 复制代码
plugins {
    alias(libs.plugins.kotlin.multiplatform)
}

kotlin {
    applyDefaultHierarchyTemplate()
    jvmToolchain(21)

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

    sourceSets {
        commonMain.dependencies {}

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

// Upstream Compose sources are quarantined under src/composeMain and are not
// registered as a source set for any target --- same policy as cmp_qrose.

实测:

bash 复制代码
./gradlew :foundation:compileKotlinJvm :foundation:compileKotlinOhosArm64 :foundation:compileKotlinOhosX64
# BUILD SUCCESSFUL ×3
2.4 测试任务的处置

与 KStore 相同的纪律:测试统一在 JVM 跑,根构建全局禁用 ohos 测试任务:

kotlin 复制代码
subprojects {
    tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
        .configureEach { enabled = false }
}

上游 Compose 测试文件随 src/composeMain 隔离,commonTest 是新写的 24 项模型对账用例(见 6.2)。

第 3 阶段:内核与数据落地

3.1 主题模型纯 Kotlin 化

内核文件对照上游逐项端口,包名保持一致:

内核文件 上游参照 端口要点
theme/JewelColor.kt androidx.compose.ui.graphics.Color data class 承载 ARGB Long;#AARRGGBB 序列化
theme/ThemeColorPalette.kt foundation 同名类 索引访问、([a-z]+)(\d+) 正则 lookup、grey 别名、raw 回退逐位一致(含越界抛异常)
GlobalColors.kt foundation 同名类家族 仅把 Compose Color 换 JewelColor,嵌套结构不变
GlobalMetrics.kt foundation 同名类 Dp 换 Float(2dp / 24dp)
theme/TextStyleData.kt createDefaultTextStyle / createEditorTextStyle Inter 13sp×1.3、JetBrains Mono 13sp×1.2,倍率常量随行
theme/ThemeIconColors.kt ThemeIconData 只保留图标色板;值保留"调色板键"形态惰性解析(同上游 painter hints)
theme/ThemeDefinition.kt foundation 同名类 同字段装配
theme/intui/IntUiThemes.kt IntUiGlobalColors / IntUiTheme 工厂 light()/dark() 的全部索引默认值逐项端口

JewelColor 用 data class 而不是 value class 是编译器逼出来的决策:value class 在 JVM target 要求 @JvmInline,而 HarmonyOS Kotlin 的 Native target 报 Unresolved reference 'JvmInline'------两头矛盾,data class 跨平台无坑且结构相等正好匹配测试需求。

3.2 主题数据管线(与上游同源的生成链)

上游的调色板常量不在源码里------intellij-theme-generator 在 JVM 构建期从 raw.githubusercontent.com/JetBrains/intellij-community/idea/243.19420.21/.../expUI_*.theme.json 拉取并生成 Kotlin 对象。鸿蒙侧复刻同一条管线,但把数据快照进仓:

bash 复制代码
git clone --depth 1 --branch idea/243.19420.21 --filter=blob:none --sparse \
    https://github.com/JetBrains/intellij-community.git
git sparse-checkout set platform/platform-resources/src/themes/expUI
# → tools/expUI_light.theme.json / tools/expUI_dark.theme.json(随仓入库)
py tools/gen_palettes.py    # → IntUiPaletteLight.kt / IntUiPaletteDark.kt

生成物数据量:Light 88 索引色 + 1 raw 键(windowsPopupBorder)+ 9 图标色;Dark 97 索引色(暗色色阶更长:Green12 / Yellow11 / Orange11 / Purple12 / Teal12)。生成文件头部记录 JSON 出处与上游钉死版本,杜绝"手抄色表"嫌疑------色值与上游构建期生成物逐位一致由 6.2 的测试钉死。

一个容易踩的语义坑:expUI 主题 JSON 的 icons.ColorPalette 值不是 hex 而是调色板键引用 (如 Checkbox.Background.Selected = "Blue4")。上游运行期才解析,内核保持同样形态(Map<String, String> + resolveColor 惰性解析),桥在序列化时对齐调色板。

第 4 阶段:原生桥接(技术难点)

4.1 三层桥接
text 复制代码
ArkTS
  │ JSON string
  ▼
C++ N-API entry(libjewel_napi.so)
  │ const char*
  ▼
Kotlin/Native C ABI(libohosjewel.so)
  │ JewelThemeInfo
  ▼
UTF-8 JSON + DisposeString

直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;按状态拆几十个函数又会让 C ABI 面膨胀。选择单入口整包返回 :主题数据量小(几十 KB 文本),一次 JSON.parse 建模,符号验收也只需盯一个函数。

4.2 Kotlin/Native 导出函数
kotlin 复制代码
@CName("JewelThemeInfo")
fun jewelThemeInfo(request: String): String {
    return try {
        val json = MiniJson.parse(request)
        val isDark = json.str("theme").equals("dark", ignoreCase = true)
        val key = json.str("key").trim()
        val theme = IntUiThemes.theme(isDark)
        buildThemeJson(theme, key.ifBlank { null })
    } catch (t: Throwable) {
        """{"ok":false,"error":${jsonString(t.message ?: t.toString())}}"""
    }
}
C ABI 符号 ArkTS 方法 覆盖内核
JewelThemeInfo jewelThemeInfo(req) IntUiThemes.light()/dark() 装配 + 调色板 lookup + 组件样式解析
libohosjewel_symbols (内部) DisposeString 等运行时接口(llvm-nm 同样可见的全局 T 符号)

StyleResolver 是 jewel 版的"qrose Rasterizer":把 int-ui-standalone 十类组件样式工厂的调色板索引表逐项端口(默认按钮 light:底 blue(4)/hover blue(3)/pressed blue(2)/disabled gray(12)、字 gray(14)/disabled gray(8)......),在桥内解析为具体 #AARRGGBB。上游为 Color.Unspecified 的字段(checkbox content 继承环境色、dark tab 背景继承面板)按上游语义映射到 text.normal / panelBackground,不硬编码。

4.3 C++ N-API 方法分发
cpp 复制代码
napi_property_descriptor desc[] = {
    {"jewelThemeInfo", nullptr, NapiJewelThemeInfo, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};

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

4.4 CMake 和类型声明
cmake 复制代码
add_library(ohosjewel SHARED IMPORTED)
set_target_properties(ohosjewel PROPERTIES
  IMPORTED_LOCATION ${KN_SO}
  IMPORTED_NO_SONAME TRUE)

add_library(jewel_napi SHARED napi_init.cpp)
target_link_libraries(jewel_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohosjewel)

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

typescript 复制代码
export const jewelThemeInfo: (request: string) => string;

第 5 阶段:ArkUI 宿主

5.1 建模和触发查询
typescript 复制代码
private loadTheme(key: string): void {
  const req: ThemeQuery = new ThemeQuery();
  req.theme = this.dark ? 'dark' : 'light';
  req.key = key;
  const raw: string = jewelThemeInfo(JSON.stringify(req));
  const parsed: ThemeInfo = JSON.parse(raw) as ThemeInfo;
  if (!parsed.ok) { this.status = `失败: ${parsed.error}`; return; }
  this.info = parsed;                     // @State 全量刷新四个页面
  this.paletteGroups = this.buildGroups(parsed.palette);
}

请求模型用显式声明的 ThemeQuery 类构建------严格 ArkTS 禁止未定型对象字面量(arkts-no-untyped-obj-literals),这条在真机构建期翻过车(见 8.1 第 13 条)。

5.2 页面交互

四 Tab 自上而下:调色板页 rawKeys 芯片 + 8 色系横滑色块(点击弹 key = hex 对话框);主题页 globals 卡片以 panelBackground 为底色逐行演示边框 / outline / 文本色,配 metrics、三种 TextStyle 示例与 iconPalette 色块墙;组件页默认 / Outlined 按钮四态静态样本并排 + 可交互真按钮、复选框、单选、Tab 下划线条、滑块、进度条、链接、输入框(真实输入 + normal / focused / disabled 三态边框样本)、分隔线,每张卡片标注 corner / pad / min / border 数值;查询页色键输入框 + 快捷芯片(Blue4 / Gray14 / windowsPopupBorder)→ lookup 结果 + 关于卡片(版本握手、IntelliJ Platform 数据源、仓库链接)。

5.3 对比度和错误边界

页面底色仅黑白属允许的 chrome 例外,所有主题色一律来自桥数据 (grep 可证:源码硬编码 hex 仅 #000000 / #FFFFFF / 两条说明灰)。ok:false 时错误原文进状态行;Native 调用抛错同样被 try/catch 收进日志。查询越界键走 exists:false,不崩溃。

第 6 阶段:示例与验证

6.1 工程结构
text 复制代码
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohosjewel)+ cxxShared + copyKnSo*
│   └── src/ohosMain/kotlin/example/nativeapp/
│       ├── Bridge.kt                         @CName + MiniJson(扁平 JSON,继承自 cmp_qrose 审核模式)
│       └── StyleResolver.kt                  组件样式索引表 → 具体 ARGB
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.jewel、versionName 0.27.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/libohosjewel_api.h
        ├── src/main/cpp/types/libjewel_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 四 Tab 验收页面
6.2 构建、签名和安装
bash 复制代码
./gradlew :foundation:jvmTest                                          # 24 项交叉验证
./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 保持空数组)。

bash 复制代码
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.jewel -m entry

四、完整代码对照

4.1 整体架构

text 复制代码
org.jetbrains.jewel.foundation/**(主题模型内核,commonMain)
    │ ThemeColorPalette / GlobalColors / GlobalMetrics / TextStyleData
org.jetbrains.jewel.foundation.theme.intui/**(生成常量 + 装配器)
    │ IntUiPaletteLight / IntUiPaletteDark / IntUiThemes
Bridge.kt + StyleResolver + MiniJson
    │ JewelThemeInfo
libjewel_napi.so
    │ jewelThemeInfo
Index.ets
    │ JSON parse + @State 更新
    ▼
ArkUI 真机页面(四 Tab)

4.2 文件清单

文件 职责
settings.gradle.kts 极简 ohos 构建、eazytec Maven 置前(命名仓库)、ohos_jewel、上游其余模块移出
gradle/libs.versions.toml Kotlin 2.2.21-1.0.0 单条目
build.gradle.kts(根) 版本 0.27.0-ohos.1、全局禁用 Ohos*Test* 任务
foundation/build.gradle.kts 纯 KMP:jvm + ohosArm64 + ohosX64 + commonTest
foundation/src/commonMain/** 主题内核 8 个手写文件 + 2 个生成调色板
foundation/src/commonTest/** 24 项模型对账用例
tools/gen_palettes.py + tools/expUI_*.theme.json 调色板生成管线与数据快照
example/nativeApp/build.gradle.kts 双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../Bridge.kt 1 个 C ABI、MiniJson、错误通道
nativeApp/.../StyleResolver.kt 十类组件样式索引表解析
harmonyApp/.../cpp/napi_init.cpp N-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txt IMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.ets ArkUI 四 Tab 验收页面
README.OpenHarmony_CN.md 鸿蒙适配主文档(17 节)

4.3 关键 API 对照

层次 API 作用
内核 ThemeColorPalette.gray(4) / lookup("Blue4") 索引访问与键查询(语义与上游逐位一致,含越界抛异常)
内核 IntUiThemes.light() / dark() 主题装配(全局色 / 度量 / 字体 / 内容色默认值端口自上游工厂)
内核 TextStyleData.default / .editor Inter 13sp×1.3 / JetBrains Mono 13sp×1.2
内核 ThemeIconColors.resolveColor 图标色板键 → 调色板惰性解析
桥 StyleResolver.componentsJson 十类组件样式索引 → 具体 ARGB + dp 度量
Native JewelThemeInfo C ABI JSON 导出(单入口整包)
N-API jewelThemeInfo(req) 向 ArkTS 暴露能力
ArkTS loadTheme(key) 触发查询并刷新四页 UI

4.4 ArkTS 与 Kotlin 的边界

typescript 复制代码
const q = JSON.parse(jewelThemeInfo(JSON.stringify(
  { theme: 'light', key: 'Blue4' })));
// q.ok === true; q.lookup = { key: 'Blue4', argb: '#FF3574F0', exists: true }
// q.components.button.background === '#FF3574F0'(light 默认按钮底色 = Blue4)
kotlin 复制代码
val theme = IntUiThemes.theme(isDark)
val resolved = runCatching { theme.colorPalette.lookup("Blue4") }.getOrNull()
// 上游 lookup 越界抛 IndexOutOfBoundsException,桥收敛为 exists:false

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

五、关键决策说明

决策 1:模型内核提取而不是整库真编译

KStore 整库真编译的前提是"引擎零 UI 依赖 + kotlinx ohos 制品线存在"。Jewel 两个前提都不满足:全部模块依赖 Compose Desktop 1.7.1 / JBR / AWT,且上游连 KMP 骨架都没有(纯 JVM 单平台)。可移植且有价值的是主题模型------提取进 commonMain 后,真机上的每个色值仍由上游语义决定(生成管线与上游钉死同一 IntelliJ Platform 版本),不是手抄近似。

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

内核只依赖 kotlin-stdlib,没有高版本元数据压力,2.2.21 旧线(qrose / haze 同款)最稳;Gradle 随线降 8.14.1。KStore 那条"工具链跟着依赖走升 2.4.20"的经验在这里不适用------两条线的选择逻辑互为镜像:有高版本制品依赖就升线,纯内核就钉旧线。

决策 3:调色板走"构建期生成 + 数据快照"管线

上游色值本来就在 JVM 构建期生成,鸿蒙侧复刻同一管线并把 JSON 快照进 tools/:可离线复现(py tools/gen_palettes.py)、生成物头部带出处、jvmTest 钉死数值。运行期 JSON 解析和 kotlinx.serialization 依赖一并省掉(MiniJson 只处理桥请求)。

决策 4:单 C ABI 整包返回 + 组件样式表放桥侧

主题是"一次性建模、多次消费"的数据,不是高频调用------单入口整包返回让符号验收只盯一个函数,ArkTS 一次 JSON.parse 建模。组件样式索引表(哪些色阶用于哪个状态)本质是 jewel 模型数据,但为保持内核与"渲染映射"分层清晰,放 nativeApp 的 StyleResolver(qrose 把 Rasterizer 放 nativeApp 是同一先例)。

决策 5:jvmTest 交叉验证 + 测试即对账

24 项 commonTest 跑在 JVM 上,验证的是与 ohos .so 同一份内核代码:色阶形状、lookup 语义、全局色 / 字体 / 度量与上游默认值逐项对账、明暗互异、图标色解析。上游 Compose 测试随 composeMain 隔离------它们测的是被隔离的 UI,不是内核。

六、测试与验证

6.1 测试环境

本次验证使用 Windows(x64)、JDK 21(Temurin 21.0.12)、DevEco Studio 26.0、DevEco 自带 hvigor / node 命令行与鸿蒙真机(arm64)。

6.2 JVM 交叉验证

bash 复制代码
./gradlew :foundation:jvmTest

24 项用例全部通过:

测试类 用例数 钉住什么
JewelColorTest 6 hex 解析(6/8 位)、#AARRGGBB 格式化、往返一致、非法字面量拒绝、Unspecified 语义
ThemeColorPaletteTest 9 1 基索引、lookup 正则与大小写、grey 别名、raw 回退、越界抛异常(同上游)、OrNull 访问器、Empty 语义
IntUiThemesTest 9 明暗标志、色阶形状(Light 88 / Dark 97)、lookup 与索引一致、全局色逐项等于上游默认索引、字体样式(13/16.9/15.6)、内容色、2dp/24dp、图标色板解析、明暗互异

其中 IntUiThemesTest.globalColorsFollowTheUpstreamIntUiDefaults 逐项断言 borders.normal == gray(12)、panelBackground == gray(13)(light)/ gray(2)(dark)、outlines.focused == blue(4) / blue(6)......这是"内核没漂"的核心闸门。

6.3 HAP 验证

text 复制代码
:foundation:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: JewelThemeInfo / libohosjewel_symbols 均为全局 T 符号
llvm-readelf -d: DT_NEEDED 含 libc++_shared.so(copyKnSo* 已同拷)
BuildNativeWithNinja: finished(双 ABI 编出 libjewel_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

entry-default-unsigned.hap 约 6.9 MB,内含双 ABI 各三件套;HAP 内 KN so arm64 约 2.0MB、x86_64 约 1.9MB,NAPI so 约 64KB(debugShared 未 strip 时 arm64 3.7MB / x64 3.4MB,含 Kotlin/Native 运行时)。本仓的 HAP 全链路在 DevEco 命令行完成验证(node + hvigorw.js --mode module -p product=default assembleHap),ArkTS 编译错误在真机阶段清零。

6.4 功能用例

  1. 启动后标题区出现 IntUI Light 与握手行 IntUI 主题桥 · 0.27.0-ohos.1,状态行 IntUI Light · 0.27.0-ohos.1 · rawKeys 1 个(来自真实 Native 调用,非静态文字);
  2. 调色板页 8 色系横滑色块齐全,Light 下 Gray13 = #FFF7F8FA、Blue4 = #FF3574F0,点击色块弹出 key = hex;rawKeys 显示 windowsPopupBorder = #FFB9BDC9;
  3. 切换 Dark 后全页颜色重查内核刷新:Gray1 = #FF1E1F22、Gray2 = #FF2B2D30、Blue6 = #FF3574F0(与 Light Blue4 同为品牌蓝);
  4. 组件页默认按钮四态:底色 blue4 / hover blue3 / pressed blue2 / disabled gray12,字色 gray14 / disabled gray8;Outlined 按钮 light 边框 gray9、聚焦 blue4;Tab 选中下划线 light = blue4 / dark = blue6;滑块、进度条、输入框三态边框随主题切换;
  5. 主题页 globals 卡片以 panelBackground 为底,outline 宽 2、行高 24;default 字号 13 / 行高 16.9,editor / console 行高 15.6(等宽渲染并注明真实字体名 Inter / JetBrains Mono 由 JBR 提供);
  6. 查询页输入 Blue4 → #FF3574F0 exists:true;输入 Blue99 → exists:false 不崩溃;快捷芯片一键查询;
  7. hdc shell hilog | grep Jewel 可见 jewelThemeInfo request bytes=... 请求日志。

6.5 验证结论

主题模型内核、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的 jewel 主题数据会话:版本握手、8 色系调色板、明暗切换、十类组件样式与色键查询闭环(见下节效果图)。

七、运行效果

7.1 真机截图

7.2 命令速查

bash 复制代码
git clone https://atomgit.com/oh-tpc/jewel.git
cd jewel
export JAVA_HOME="/path/to/jdk-21"
./gradlew :foundation:jvmTest                                              # 24 项交叉验证
./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.jewel -m entry
hdc shell hilog | grep Jewel

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 CMP 适配,必须让 :foundation 真正编译成 ohosArm64 / ohosX64 动态库------而 jewel 连 KMP 骨架都没有,先补 kotlin("multiplatform") 再谈目标;
  2. 本机全局 ~/.gradle/init.d/china-mirrors.gradle 会把 aliyun 镜像插到所有仓库最前,且会把 pluginManagement 里的匿名 maven(url) 仓库在 remove / re-add 过程中丢弃------eazytec 仓库必须写成 maven { name = ...; url = uri(...) } 命名形式,依赖仓库模式用 PREFER_SETTINGS(PREFER_PROJECT 会被 init 脚本注入的 project 级镜像反客为主);
  3. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  4. buildSrc 目录存在即被 Gradle 自动构建------上游 buildSrc 依赖的插件已随版本目录重写移除,必须改名 buildSrc.upstream,"留盘"和"可构建"只能二选一;
  5. value class 两头受气:JVM 要求 @JvmInline、HarmonyOS Kotlin Native 报 Unresolved reference 'JvmInline'------跨平台内核的颜色类型用 data class;
  6. Kotlin 块注释支持嵌套:注释文本里出现 styling/*.kt 字样时其中的 /* 会开启嵌套注释,报"Unclosed comment"且指向文件末尾,极难定位;
  7. expUI 主题 JSON 抓取:raw.githubusercontent.com 在本网络不可达且公共镜像 404,github.com 本身可达------用 git clone --branch idea/243.19420.21 --filter=blob:none --sparse 稀疏克隆拿到主题文件后快照进仓;
  8. icons.ColorPalette 的值不是 hex 而是调色板键(Gray8、Blue4),按 hex 解析会炸------保留字符串、查询期经 palette.lookup 惰性解析(与上游行为一致);
  9. 上游 lookup() 对越界索引抛异常 而不是返回 null,第一版测试按"返回 null"理解写错了------内核必须保留上游行为,桥侧 runCatching 收敛;
  10. KN so 的 DT_NEEDED 含 libc++_shared.so,必须与 KN so 同目录部署,copyKnSo* 从 ~/.konan/dependencies 取配套文件一起落地;
  11. 根 .gitignore 的 build/ 与 .idea/* 是根锚定模式,盖不住嵌套目录------DevEco 打开工程后生成的 example/harmonyApp/.idea/、entry/libs/、oh_modules/ 要单独追加,否则混进提交(本仓实测 .idea/.deveco 缓存差点入库);
  12. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套缺一不可;
  13. 严格 ArkTS 的真机构建坑(DevEco 编译期才暴露,共 6 处):请求对象必须用显式声明的类构建(arkts-no-untyped-obj-literals);@State 属性不能与 CustomComponent 基类方法重名(tabIndex 改名 mainTab);Slider.blockSize 要 SizeOptions 不是 number;TextDecorationType 枚举成员是驼峰 Underline 不是 UNDERLINE;
  14. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();签名材料由 DevEco 自动写入 build-profile.json5,仓库内保持空数组,证书绝不进入 AtomGit。

8.2 已知问题

  • 不含 Compose Composable:上游 UI 层(ui / decorated-window / int-ui / markdown / ide-laf-bridge / samples)未编译到鸿蒙,这是路线本身的决定------等社区 compose.ui ohos 制品与上游版本对齐后可评估把 composeMain 接回;
  • 组件样式表为精选集:当前十类(button / buttonOutlined / checkbox / radioButton / tab / slider / progress / link / divider / textField),上游 int-ui-standalone 还有 chip / comboBox / menu / scrollbar / tree / segmentedControl 等约二十类未透出;
  • checkbox / radio 的 content 色上游为 Unspecified(继承环境色),内核取 text.normal,方框描边走 GlobalColors.outlines------按上游语义映射,已在文档标注;
  • 图标只透出 iconPalette 颜色映射;iconOverrides(IntelliJ 图标路径)与 selectionColorPalette 属 Swing / Compose painter 管线,不入内核(dark 主题本就无图标色板);
  • Inter / JetBrains Mono 为 JBR 桌面字体,Demo 用系统字体渲染并注明真实字体名;textStyles 透出 family / size / lineHeight / weight 四要素,decoration / shadow 未建模;
  • android / js / apple 目标不存在于上游(纯 JVM 库),无目标剥离问题;发布、dokka、kover 一并剥离,本 fork 不发 Maven Central。

8.3 未来优化方向

  • 扩展组件样式表(chip / comboBox / menu / scrollbar / tree / segmentedControl......StyleResolver 每类一个 xxxJson,无新机制);
  • 把内核发布为 ohos KLIB / .har 形态,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
  • 补充真机(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本;
  • 跟踪社区 Compose for OpenHarmony 制品线,版本对齐后评估 UI 层接回方案。

九、总结

9.1 核心难点回顾

text 复制代码
上游 foundation + int-ui-standalone(主题模型 + Compose UI 混合)
    → 路线判定:无纯引擎 → cmp_qrose"模型内核提取"
    → 工具链钉旧线 Kotlin 2.2.21-1.0.0(Gradle 8.14.1)
    → 根构建替换 + buildSrc 改名规避 + Compose 源码隔离 composeMain
    → 主题数据管线(expUI JSON 快照 + 生成脚本,与上游同源)
    → ohosArm64 / ohosX64 KLIB + 24 项 jvmTest 对账
    → C ABI(@CName × 1,整包 JSON)+ 组件样式索引表
    → C++ N-API(DisposeString 纪律)
    → ArkTS JSON 解析
    → ArkUI 真机四 Tab 验收(调色板 / 主题 / 组件 / 查询 + 明暗切换)

9.2 封装层次

  • 内核层:ThemeColorPalette 索引与 lookup 语义、GlobalColors / GlobalMetrics / TextStyleData / ThemeDefinition,包名与上游对齐;
  • 数据层:生成的 IntUI Light / Dark 调色板常量(与上游构建期生成物逐位一致)+ IntUiThemes 装配器;
  • Native 层:生成双 ABI 动态库并通过 1 个 C ABI 输出整包 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、JSON 建模、明暗切换、查询分发与错误文本;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 适配路线由"库的依赖形态"决定,不能套模板:KStore 有纯引擎加 kotlinx 制品线,正确答案是整库真编译;Jewel 全库 Compose 化,正确答案是模型内核提取------两者的共同底线是"真机上的数据 / 行为由上游原版语义决定",整库也好内核也好,手抄就是漂移;
  2. 数据型库的交叉验证要钉到"数值":24 项用例里最有说服力的是全局色逐项等于上游索引默认值、色阶形状等于 expUI JSON、lookup 越界行为与上游一致------测试即对账,而不是覆盖率数字;
  3. 构建期陷阱要在"第一次构建"前排掉:buildSrc 自动构建、init 脚本仓库重排、嵌套注释、ArkTS 严格模式字面量------这些坑都不在 Kotlin 或 ArkTS 的语言面里,而在工具链接缝上,且全部只在真实构建时暴露。

9.4 适配成果

当前适配已完成:

  • Jewel 0.27.0(上游 main @ 8d24db5b)主题模型内核的双 ABI Kotlin/Native 编译(8 个手写内核文件 + 2 个生成调色板 + 24 项交叉验证);
  • 1 个 C ABI 符号(JewelThemeInfo,llvm-nm 验证全局 T 符号)和 C++ N-API 包装层;
  • DevEco Stage ArkUI 四 Tab 验收页面(调色板、主题、组件、查询、明暗切换、版本握手);
  • 24 项 JVM 交叉验证全绿 + HAP 构建(双 ABI 六件套 so,约 6.9MB)、签名、设备安装和真机验证;
  • 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。

参考文档

相关推荐
2601_949480062 小时前
开源鸿蒙平台 KMP 三方库「AutoLinkText」适配全流程
华为·开源·harmonyos
JoyCong19982 小时前
ToDesk鸿蒙PC端又焕新!远控更稳、操作更准,跨端办公如本地
运维·华为·智能手机·harmonyos·远程工作
大雷神3 小时前
HarmonyOS7应用《拼棋派对》实战:深浅主题、控件对比度与统一样式
harmonyos·arkui·harmonyos 7.0·玩转 arkts
OH_TPC3 小时前
HarmonyOS APP开发---"动效君"UI动效素材库App,需要用到这个库
harmonyos
移动开发者实验室3 小时前
被 App Store 截图折腾了几次后,我开源了一个 AI 截图生成工具
人工智能·开源
miofly3 小时前
GitHub 今日推荐|Edge0:让大号 MoE 模型在一台 Mac 上流式跑起来
python·开源·github
行者-全栈开发4 小时前
【鸿蒙心迹】三方库移植(一)——NAPI 把 C/C++ 库桥接到 ArkTS 的完整链路(HarmonyOS 7.x)
harmonyos·arkts·交叉编译·c/c++·napi·三方库移植
2601_949480064 小时前
开源鸿蒙平台 CMP 三方库「reveal」适配全流程
华为·开源·harmonyos
2401_zq136y034 小时前
开源鸿蒙平台 CMP 三方库「KToast」适配全流程
华为·开源·harmonyos