kotlin-semver OpenHarmony KMP_CMP 适配从 0 到 1 实战
本文记录 Kotlin Multiplatform 版本库
kotlin-semver适配 OpenHarmony 的完整过程,包含项目初始化、ohosArm64目标配置、依赖版本对齐、独立消费工程、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkTS 版本实验台、HAP 构建和真机验收。
与“把版本判断逻辑重写一份 ArkTS”不同,本次适配保持commonMain中的 SemVer 实现不变,让 OpenHarmony 应用通过真实的 Kotlin/Native 产物调用同一套解析、比较、约束和递增 API。
本项目的核心是 KMP 库,示例 UI 使用 ArkTS Stage 页面;KMP/CMP 的工程分层和原生桥接思路保持一致,但页面没有复制一份 SemVer 算法。
项目地址: AtomGit/oh-tpc/ohos_kotlin-semver
开发工具: 华为云码道
一、背景
1.1 为什么 KMP/CMP 项目需要 OpenHarmony 目标
语义化版本(Semantic Versioning,SemVer)使用 主版本.次版本.补丁 表达软件发布版本,例如 1.4.2-beta.1+ohos。其中主版本、次版本和补丁决定版本优先级,预发布标识影响正式版之前的排序,构建元数据用于标记构建来源但不参与优先级比较。
kotlin-semver 已经通过 Kotlin Multiplatform 覆盖 JVM、JavaScript、Wasm 和多种 Native 平台,公共实现位于 src/commonMain。但是普通 Kotlin Multiplatform 工程没有 OpenHarmony 目标,鸿蒙设备也不能直接消费 JVM 或普通 Native 产物。要在 OpenHarmony ARM64 应用中使用同一套版本规则,需要解决以下问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | Gradle/Kotlin 配置没有 ohosArm64(),无法生成 OpenHarmony KLIB。 |
| 工具链不一致 | OpenHarmony Kotlin/Native 发行版、KMP 插件、LLVM 和 sysroot 必须使用同一版本矩阵。 |
| 依赖变体缺失 | 普通 Maven 版本不一定包含 ohosArm64 变体,需要使用社区仓库的兼容发行版。 |
| 消费验证不足 | 根工程能编译,不代表独立消费者能解析 Maven metadata、KLIB 和动态库。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,必须经过 C ABI、C++ N-API 和 JSON。 |
| HAP 交付复杂 | 动态库构建成功后,还需要 CMake、资源、签名和 ARM64 ELF 依赖检查。 |
适配目标不是复制一个“鸿蒙专用版本库”,而是继续复用 commonMain,将差异收敛在构建目标、仓库依赖、原生桥接和示例应用。
1.2 库提供的能力
kotlin-semver 的公共 API 覆盖完整的 SemVer 2.0.0 使用场景:
- 严格解析:解析
1.2.3、1.2.3-alpha.1+build.7等完整版本。 - 宽松解析:将
v1、v1.2归一化为1.0.0、1.2.0。 - 字段访问:读取
major、minor、patch、preRelease、buildMetadata。 - 稳定性判断:通过
isStable和isPreRelease判断发布状态。 - 版本比较:支持
compareTo、比较运算符和sorted。 - 约束解析:支持
>=、<、^、~、通配符、连字符范围和 OR/AND。 - Maven 范围:支持
[1.0,2.0)等 Maven 风格约束。 - 集合判断:支持
satisfiesAll、satisfiesAny和satisfiedByAll。 - 版本递增:支持 MAJOR、MINOR、PATCH、PRE_RELEASE 四种递增。
- 对象转换:支持
copy、withoutSuffixes、解构/构造和序列化。
这些能力可以用于应用升级检查、插件兼容性判断、依赖版本选择和自动发布。库只判断版本规则,业务应用仍然负责下载、安装和升级。
1.3 实现目标
| 维度 | 要求 |
|---|---|
| API 稳定性 | 复用 commonMain,不改变 Version、Constraint 和 inc 的公开语义。 |
| 平台支持 | 增加 ohosArm64,产出面向 arm64-v8a 的 OpenHarmony KLIB 和动态库。 |
| 依赖隔离 | OpenHarmony 依赖只在适配目标和示例消费工程中解析。 |
| 消费体验 | 先发布到 mavenLocal(),再由独立 example 按坐标消费。 |
| UI 完整性 | 页面要表现解析、比较、约束、递增、序列化和库自检,不沿用上一个 diff 示例 UI。 |
| 可测试性 | JVM 测试、OHOS 编译、Native 链接、N-API、HAP 和设备操作分别验证。 |
| 交付安全 | 源码仓库不保存本机签名证书、密码、构建缓存和生成的 .so。 |
| 工程规范 | README、适配文章、效果图、脚本和 AtomGit 地址保持一致。 |
二、实现路线图
第 1 阶段:项目初始化 ── 盘点公共 API、目标矩阵和示例边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、仓库和 focused build
第 3 阶段:数据与序列化 ── 建立版本分析模型、约束模型和 JSON 契约
第 4 阶段:原生桥接 ── Kotlin/Native C ABI、C++ N-API 和内存释放
第 5 阶段:能力封装 ── ArkTS 四页版本实验台和错误状态
第 6 阶段:示例与验证 ── 独立消费、HAP、ELF 审计和真机验收
每个阶段都使用真实产物作为下一阶段输入:根工程先生成 KLIB,根工程发布到本地 Maven,example/shared 再消费坐标,nativeApp 最后把共享逻辑链接为 libsemver.so,ArkTS 页面通过 libentry.so 调用它。
三、逐步实现过程
第 1 阶段:项目初始化
1.1 盘点原库源码和公共 API
先确认库的核心实现全部位于 src/commonMain/kotlin/io/github/z4kn4fein/semver:
Version.kt 版本对象、解析、比较、字段和序列化入口
VersionExtensions.kt nextMajor / nextMinor / nextPatch / inc / satisfies
StringExtensions.kt toVersion / toVersionOrNull
constraints/Constraint.kt 约束解析和匹配
constraints/Condition.kt 等于、上下界、范围条件
constraints/*Serializer.kt 约束 JSON 序列化
VersionSerializer.kt 版本 JSON 序列化
本次没有把这些文件搬到 ArkTS。示例的共享层只调用公开 API,并把结果转换成适合跨语言传输的数据类。
1.2 固定工具链和版本矩阵
| 组件 | 版本 |
|---|---|
| semver 适配发布版 | 3.1.0-ohos.1 |
| Kotlin Multiplatform / Native | 2.2.21-1.0.0 |
| kotlinx.serialization | 1.9.1-1.0.0 |
| Gradle Wrapper | 8.14.3 |
| JDK | 21 |
| OpenHarmony target / compatible API | 6.0.0(20) |
| HAP ABI | arm64-v8a |
OpenHarmony Kotlin 发行版和相关 OHOS 变体从 settings.gradle.kts 中的社区 Maven 仓库解析。普通 Maven Central 版本不一定含有 ohosArm64 产物,因此不能只保留 Maven Central。
1.3 创建适配示例目录
参考独立消费工程的架构,新示例由三个层次组成:
example/
├── shared/ # 调用已发布 semver 坐标,保存共享模型和测试
├── nativeApp/ # ohosArm64 Kotlin/Native shared library
└── ohosApp/ # DevEco Stage 工程、N-API 和 ArkTS 页面
三个模块的依赖方向是:
ohosApp → nativeApp → shared → io.github.z4kn4fein:semver
这样可以把“库本身可以编译”和“第三方工程能够消费”分成两次验证。
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64 目标
根工程的 Kotlin 配置增加 ohosArm64(),并保留原有目标矩阵:
kotlin {
explicitApi()
jvm {
compilerOptions.jvmTarget = JvmTarget.JVM_1_8
}
ohosArm64()
if (!providers.gradleProperty("openharmonyOnly")
.map(String::toBoolean)
.getOrElse(false)) {
js { /* browser + nodejs */ }
wasmJs { /* browser + nodejs */ }
wasmWasi { nodejs() }
macosX64()
macosArm64()
iosX64()
iosArm64()
iosSimulatorArm64()
linuxX64()
linuxArm64()
mingwX64()
// 其他上游目标继续保留
}
}
ohosArm64 是库的正式发布目标,不能只写在示例模块里。示例模块只负责引用已经生成的 KLIB。
2.2 focused build 的作用
完整多平台工程会配置许多 JS、Wasm 和 Native 工具链。开发适配时使用:
./gradlew -PopenharmonyOnly=true tasks
该属性在配置阶段跳过无关目标,仅保留 JVM 和 OpenHarmony,减少下载和配置时间。没有这个属性时,上游多平台目标矩阵仍然有效。
2.3 配置插件仓库和依赖仓库
根工程和 example 工程都配置相同的仓库:
pluginManagement {
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
mavenLocal()
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral()
}
}
其中 mavenLocal() 负责消费刚刚发布的适配库,社区仓库负责解析带 OpenHarmony 变体的 Kotlin 和序列化产物。
2.4 通过发布坐标消费库
example/shared/build.gradle.kts 不使用根工程的 project() 依赖,而是:
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
}
kotlin {
jvm()
jvmToolchain(21)
ohosArm64()
sourceSets {
commonMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.1-1.0.0")
implementation("io.github.z4kn4fein:semver:3.1.0-ohos.1")
}
commonTest.dependencies {
implementation(kotlin("test"))
}
}
}
独立消费可以覆盖 Maven metadata、KLIB 变体、版本号和依赖传递问题,是适配中不可省略的一步。
第 3 阶段:数据与序列化
3.1 为什么需要统一 JSON 契约
ArkTS、C++ 和 Kotlin/Native 之间不能直接共享 Kotlin 对象。为了让边界稳定,示例使用一个 JSON 请求和一个 JSON 响应:
ArkTS request object
↓ JSON.stringify
C++ string
↓ C ABI
Kotlin EvaluationRequest
↓
SemverResult / error
↓ JSON.encodeToString
C++ UTF-8 buffer
↓
ArkTS JSON.parse
这样版本判断始终在 Kotlin common 逻辑中完成,ArkTS 不需要了解 Version 或 Constraint 的内部结构。
3.2 EvaluationRequest 设计
@Serializable
data class EvaluationRequest(
val left: String,
val right: String,
val constraints: String,
val strict: Boolean = true,
val maven: Boolean = false,
val preRelease: String = "",
)
left 和 right 是待比较版本;constraints 支持多行规则;strict 控制严格/宽松解析;maven 控制约束解析器;preRelease 用于计算下一版本时设置预发布标识。
3.3 SemverResult 设计
@Serializable
data class SemverResult(
val left: VersionSummary,
val right: VersionSummary,
val comparison: Int,
val equal: Boolean,
val sorted: List<String>,
val constraints: List<ConstraintSummary>,
val allConstraints: Boolean,
val anyConstraint: Boolean,
val nextMajor: String,
val nextMinor: String,
val nextPatch: String,
val nextPreRelease: String,
val withoutSuffixes: String,
val constructed: String,
val copied: String,
val versionJson: String,
val restoredVersion: String,
)
这个模型没有让 ArkTS 自己重新计算字段,而是由 Kotlin common API 一次性生成。页面展示的每一项都能追溯到真实库操作。
3.4 错误响应和长度限制
错误通过 EvaluationResponse 返回:
@Serializable
data class EvaluationResponse(
val result: SemverResult? = null,
val error: String? = null,
)
fun evaluateJson(request: String): String = try {
require(request.length <= 8192) { "请求过长" }
Json.encodeToString(
EvaluationResponse(
result = evaluateSemver(
Json.decodeFromString<EvaluationRequest>(request)
)
)
)
} catch (error: Exception) {
Json.encodeToString(
EvaluationResponse(error = error.message ?: error.toString())
)
}
Kotlin 层限制请求总长度,C++ 层再次限制字符串大小。非法版本、非法范围和非法预发布标识都不会穿过 C ABI 抛出未处理异常。
第 4 阶段:原生桥接(技术难点)
4.1 问题:Kotlin/Native 对象不能直接交给 ArkTS
Kotlin/Native 的 Version、Constraint、List 和异常对象属于 Kotlin 运行时对象。ArkTS 通过 N-API 接收到的是 JavaScript 值,C++ 不能安全地把 Kotlin 对象地址当成 JavaScript 对象使用。
最终采用三层桥接:
ArkTS
│ JSON string
▼
C++ N-API entry
│ const char*
▼
Kotlin/Native C ABI
│ Kotlin common API
▼
UTF-8 JSON + explicit free
4.2 方案对比
| 方案 | 优点 | 缺点 | 选用 |
|---|---|---|---|
| 直接导出 Kotlin 对象 | 代码少 | ABI、生命周期和类型不可控 | ❌ |
| 导出基础字段数组 | 不需要 JSON | 字段扩展和错误处理困难 | ❌ |
| C ABI + JSON | 边界清晰、易扩展、易调试 | 有一次序列化开销 | ✅ |
| 在 ArkTS 重写 SemVer | 页面调用简单 | 逻辑重复,结果可能与库不一致 | ❌ |
4.3 Kotlin/Native 导出函数
NativeChecks.kt 只导出三个函数:
@CName("SemverSampleRunChecks")
fun runNativeChecks(): CPointer<ByteVar> = textBuffer(acceptanceJson())
@CName("SemverSampleEvaluate")
fun evaluateNative(request: String): CPointer<ByteVar> =
textBuffer(evaluateJson(request))
@CName("SemverSampleFree")
fun freeNative(result: CPointer<ByteVar>?) {
if (result != null) nativeHeap.free(result)
}
返回值使用 nativeHeap.allocArray<ByteVar> 分配,以 0 结尾;C++ 读取完成后必须调用 SemverSampleFree。
4.4 C++ N-API 方法分发
C++ 模块注册两个 ArkTS 方法:
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor methods[] = {
{"runChecks", nullptr, RunChecks, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"evaluate", nullptr, Evaluate, nullptr, nullptr, nullptr,
napi_default, nullptr},
};
napi_define_properties(env, exports, 2, methods);
return exports;
}
evaluate 只接收一个 JSON 字符串,避免在 N-API 层维护与 Kotlin 数据类重复的多参数签名。C++ 先进行字符串类型、长度和 NUL 字节检查,再调用 Kotlin/Native。
4.5 N-API 生命周期
ArkTS evaluate(request)
│
▼
ReadString + length check
│
▼
SemverSampleEvaluate(request.c_str())
│
▼
napi_create_string_utf8(...)
│
▼
SemverSampleFree(nativeBuffer)
│
▼
return JS string
这条链路把内存释放责任写在桥接层里,页面调用者不需要知道 Kotlin/Native 的堆实现。
第 5 阶段:能力封装
5.1 版本分析能力封装
共享层的 evaluateSemver 将库 API 组合成页面可展示的结果:
val left = request.left.toVersion(request.strict)
val right = request.right.toVersion(request.strict)
val constraints = request.constraints.lines()
.filter { it.isNotBlank() }
.map { if (request.maven) it.toMavenConstraint() else it.toConstraint() }
return SemverResult(
left = left.summary(),
right = right.summary(),
comparison = left.compareTo(right),
equal = left == right,
sorted = listOf(left, right).sorted().map { it.toString() },
constraints = constraints.map { it.toSummary(left, right) },
allConstraints = left satisfiesAll constraints,
anyConstraint = left satisfiesAny constraints,
nextMajor = left.inc(Inc.MAJOR, preRelease).toString(),
nextMinor = left.inc(Inc.MINOR, preRelease).toString(),
nextPatch = left.inc(Inc.PATCH, preRelease).toString(),
nextPreRelease = left.inc(Inc.PRE_RELEASE, preRelease).toString(),
withoutSuffixes = left.withoutSuffixes().toString(),
constructed = reconstruct(left),
copied = left.copy(buildMetadata = "demo").toString(),
versionJson = Json.encodeToString(VersionSerializer, left),
restoredVersion = Json.decodeFromString(VersionSerializer, versionJson).toString(),
)
页面不需要理解 Condition、VersionDescriptor 或 PreRelease 的内部类型,只使用这个稳定的结果模型。
5.2 ArkTS 页面分区
示例 UI 分成四页,避免把所有功能堆在同一个滚动页面:
| 页面 | 表现的库能力 |
|---|---|
| 01 概览 | 解析、字段读取、稳定性、比较、排序和相等。 |
| 02 约束 | SemVer/Maven 约束、当前/目标匹配、ALL/ANY 和序列化。 |
| 03 工具 | MAJOR、MINOR、PATCH、PRE_RELEASE、copy 和后缀清除。 |
| 04 自检 | 在设备端执行 Kotlin common 验收场景。 |
5.3 ArkTS 状态和错误状态
当输入发生变化时,页面清空旧结果并设置 dirty,避免用户误以为旧结果仍对应新输入:
private changed(): void {
this.dirty = true;
this.result = undefined;
this.errorMessage = '';
}
private runEvaluate(): void {
try {
const request: EvaluationRequest = {
left: this.leftText,
right: this.rightText,
constraints: this.constraintText,
strict: this.strict,
maven: this.maven,
preRelease: this.preRelease
};
const response = JSON.parse(evaluate(JSON.stringify(request)))
as EvaluationResponse;
this.result = response.result;
this.errorMessage = response.error ?? '';
this.dirty = false;
} catch (error) {
this.result = undefined;
this.errorMessage = String(error);
}
}
5.4 页面交互预设
页面提供三个快捷按钮:
- 预发布:展示
1.4.2-beta.1+ohos与2.0.0的比较。 - 宽松解析:展示
v1.2、v2的归一化。 - Maven 范围:展示
[1.0,2.0)等范围约束。
这些预设只是输入便利,不绕过 Kotlin/Native 计算路径。
第 6 阶段:示例与验证
6.1 example 工程结构
example/
├── build.gradle.kts
├── settings.gradle.kts
├── shared/
│ ├── build.gradle.kts
│ └── src/
│ ├── commonMain/kotlin/.../SemverSample.kt
│ └── commonTest/kotlin/.../SemverSampleTest.kt
├── nativeApp/
│ ├── build.gradle.kts
│ └── src/ohosArm64Main/
│ ├── kotlin/.../NativeChecks.kt
│ └── linker/shared-library.map
└── ohosApp/
└── entry/
├── src/main/ets/pages/Index.ets
└── src/main/cpp/napi_init.cpp
6.2 原生模块注册
entry/src/main/cpp/napi_init.cpp 注册 entry N-API 模块:
static napi_module semverModule = {
1, 0, nullptr, Init, "entry", nullptr, {0}
};
extern "C" __attribute__((constructor))
void RegisterSemverModule() {
napi_module_register(&semverModule);
}
ArkTS 类型声明与注册方法对应:
export const runChecks: () => string;
export const evaluate: (request: string) => string;
6.3 Native 动态库准备
nativeApp 的 prepareOhos 任务负责复制动态库和头文件:
val prepareOhos by tasks.registering(Copy::class) {
dependsOn("linkDebugSharedOhosArm64")
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libsemver.so")
into("libs/arm64-v8a")
}
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libsemver_api.h")
into("src/main/cpp/include")
}
into(rootProject.layout.projectDirectory.dir("ohosApp/entry"))
}
CMake 使用 imported library 链接 Kotlin/Native 产物:
add_library(semver SHARED IMPORTED)
set_target_properties(semver PROPERTIES
IMPORTED_LOCATION
"${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libsemver.so")
add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE semver libace_napi.z.so)
6.4 构建与安装
先构建根工程和独立示例:
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
准备 DevEco 工程:
python3 scripts/prepare-signing-project.py \
"$HOME/ohos_kotlin_semver_signing"
在 DevEco Studio 中配置签名后构建 HAP:
./scripts/build-hap.sh "$HOME/ohos_kotlin_semver_signing"
设备安装前再执行 ARM64 ELF 依赖检查,避免把动态库链接问题带到设备阶段。
四、完整代码对照
4.1 整体架构
┌─────────────────────────────────────────────────────────┐
│ kotlin-semver 根工程 │
│ │
│ commonMain │
│ ├─ Version / Constraint / Inc │
│ ├─ parser / comparator / serializer │
│ └─ JVM + ohosArm64 + 上游其他目标 │
│ │ publishToMavenLocal │
└─────────────────────────┼───────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ example 消费工程 │
│ │
│ shared │
│ ├─ evaluateSemver │
│ ├─ evaluateJson │
│ └─ runAcceptanceChecks │
│ │ │
│ ▼ │
│ nativeApp / libsemver.so │
│ ├─ SemverSampleEvaluate │
│ ├─ SemverSampleRunChecks │
│ └─ SemverSampleFree │
│ │ C ABI │
│ ▼ │
│ ohosApp / libentry.so │
│ ├─ C++ N-API wrapper │
│ └─ ArkTS Index.ets │
└─────────────────────────────────────────────────────────┘
4.2 文件清单
| 文件 | 职责 |
|---|---|
build.gradle.kts | 根工程 Kotlin 目标、序列化依赖和 focused build。 |
settings.gradle.kts | AtomGit 工程使用的插件和依赖仓库。 |
gradle.properties | group、适配版本和 Gradle 参数。 |
example/shared/build.gradle.kts | 以 Maven 坐标消费 semver。 |
example/shared/.../SemverSample.kt | 版本分析模型、JSON 和验收逻辑。 |
example/shared/.../SemverSampleTest.kt | 独立消费者 JVM 测试。 |
example/nativeApp/build.gradle.kts | ohosArm64 shared library 和复制任务。 |
example/nativeApp/.../NativeChecks.kt | C ABI 导出和 nativeHeap 内存管理。 |
shared-library.map | 控制 libsemver.so 导出的三个符号。 |
napi_init.cpp | C++ N-API 参数校验和函数分发。 |
Index.ets | 版本实验台、四页 UI 和错误状态。 |
scripts/build-openharmony.sh | 发布库、跑测试、编译 OHOS 和准备动态库。 |
scripts/prepare-signing-project.py | 复制干净 DevEco 签名工程。 |
scripts/build-hap.sh | 安装 OHPM 依赖并构建 HAP。 |
scripts/check-native-deps.py | 审计 HAP 中 ARM64 ELF 依赖。 |
docs/images/semver-openharmony-demo.jpg | 版本实验台运行效果图。 |
4.3 关键 API 对照
| 能力 | 本项目 OpenHarmony 实现 | 常见平台对应物 |
|---|---|---|
| 版本解析 | Version.parse / toVersion | JVM/JS/Native common API |
| 版本比较 | compareTo、比较运算符 | Java Comparable 等价能力 |
| 约束匹配 | Constraint + satisfies | npm/Maven 版本范围判断 |
| 版本递增 | Version.inc(Inc.*) | 发布脚本的版本计算 |
| JSON 序列化 | VersionSerializer / ConstraintSerializer | Kotlinx Serialization |
| OpenHarmony 产物 | ohosArm64 KLIB / libsemver.so | Android .so / iOS framework |
| 应用桥接 | C ABI + C++ N-API | JNI / Objective-C bridge |
| UI 验收 | ArkTS Stage Index.ets | Compose / UIKit / SwiftUI 页面 |
4.4 ArkTS 与 TypeScript / Kotlin 的语法差异(本次实际踩到的)
| 常见写法 | ArkTS 要求 | 说明 |
|---|---|---|
JSON.parse(value) as Any | 使用显式 interface | 避免 ArkTS 严格模式下的隐式对象类型。 |
any | 使用明确接口或 Object | N-API 返回的数据需要先定义 EvaluationResponse。 |
value ?? '' | 保证左右类型一致 | `string |
多根节点 build() | 必须返回唯一容器 | 使用 Column、Scroll 或 Row 包裹页面。 |
| Kotlin nullable 字段 | 使用 `string | null` |
| Kotlin 异常 | 使用 error 字段 | 异常不直接跨过 C ABI。 |
| native 字符串 | 必须调用 Free | C++ 复制字符串后调用 SemverSampleFree。 |
五、关键决策说明
决策 1:把 ohosArm64 加入库的公共构建约定
背景:如果只在 example/nativeApp 添加 OHOS 目标,示例能链接,但库本身不能作为 OpenHarmony 依赖发布。
决策:在根工程的 Kotlin 配置中增加 ohosArm64(),并保留 openharmonyOnly focused build。这样目标、版本和发布 metadata 都由库统一管理。
维护策略:新模块必须通过公共 target 配置获得 ohosArm64,不在单个示例中复制另一套 Kotlin 目标逻辑。
决策 2:独立消费者必须通过 Maven 坐标
背景:项目依赖可以绕过发布 metadata,无法发现用户接入时可能遇到的 KLIB 变体问题。
决策:根工程先执行 publishToMavenLocal,example/shared 再使用:
implementation("io.github.z4kn4fein:semver:3.1.0-ohos.1")
维护策略:任何新增 OpenHarmony 目标都要同时验证库发布和独立消费者解析。
决策 3:JSON 作为跨语言数据契约
背景:Kotlin/Native 对象不能直接作为 N-API JavaScript 对象使用,直接传递对象还会带来生命周期和 ABI 问题。
决策:输入统一为 EvaluationRequest JSON,输出统一为 EvaluationResponse JSON,错误也通过 JSON 返回。
| 阶段 | 数据形式 |
|---|---|
| ArkTS → C++ | UTF-8 JSON 字符串 |
| C++ → Kotlin | const char* |
| Kotlin → C++ | nativeHeap UTF-8 缓冲区 |
| C++ → ArkTS | N-API JS 字符串 |
维护策略:新增页面能力时优先扩展 Kotlin 数据类,不在 C++ 和 ArkTS 各复制一份版本算法。
决策 4:桥接层只开放三个 C ABI 入口
背景:导出整个 Kotlin/Native 对象图会增加符号、链接依赖和 ABI 风险。
决策:只导出 SemverSampleEvaluate、SemverSampleRunChecks 和 SemverSampleFree,其余符号通过 version script 隐藏。
维护策略:所有新功能通过 JSON 请求扩展,只有真正需要新的生命周期能力时才增加 C ABI 入口。
决策 5:页面按照库能力重新设计
背景:上一个 diff 示例使用双文本区域和逐行差异布局,无法体现版本库的字段、约束和递增能力。
决策:采用四页底部导航:
01 概览 → 解析 / 字段 / 比较 / 排序
02 约束 → SemVer / Maven / ALL / ANY
03 工具 → 递增 / copy / 后缀 / JSON
04 自检 → 设备端真实库验收
维护策略:新增库 API 时优先归入对应能力页,避免首页变成不可滚动的结果列表。
决策 6:把库验证和设备验证分开
背景:JVM 通过只能说明公共逻辑可运行,不能证明 KLIB、C ABI、HAP 和设备安装正确。
决策:分别记录 JVM 测试、compileKotlinOhosArm64、Native 链接、HAP 构建、ELF 审计和自检页面结果。
维护策略:适配发布说明必须给出每一层的命令和结果,不把“Gradle BUILD SUCCESSFUL”直接等同于“真机通过”。
六、测试与验证
6.1 测试环境
| 项目 | 版本或状态 |
|---|---|
| 主机 | macOS ARM64 |
| JDK | 21 |
| Kotlin | 2.2.21-1.0.0 |
| kotlinx.serialization | 1.9.1-1.0.0 |
| Gradle | 8.14.3 |
| OpenHarmony target/compatible API | 6.0.0(20) |
| ABI | arm64-v8a |
| DevEco Studio | 6+ |
| Native SDK | OpenHarmony API 20 ARM64 |
| 测试设备 | ARM64 OpenHarmony 设备或模拟器 |
版本获取方式:
java -version
./gradlew --version
hdc list targets
hdc shell param get const.product.model
6.2 静态检查与单元测试
根工程:
./gradlew -PopenharmonyOnly=true \
jvmTest \
compileKotlinOhosArm64
独立消费者:
cd example
./gradlew -PopenharmonyOnly=true \
:shared:jvmTest \
:nativeApp:prepareOhos
本次实际结果:
| 测试 | 结果 |
|---|---|
| 根工程 JVM 测试 | 63 tests,0 failures,0 errors |
example/shared JVM 测试 | 5 tests,0 failures,0 errors |
根工程 compileKotlinOhosArm64 | 成功 |
nativeApp:compileKotlinOhosArm64 | 成功 |
linkDebugSharedOhosArm64 | 成功 |
nativeApp:prepareOhos | 成功 |
共享验收场景覆盖严格解析、宽松解析、稳定性、比较、约束、Maven 范围、ALL/ANY、四种递增、错误响应和序列化往返。
6.3 原生桥接和 HAP 验证
ArkTS 工程构建:
./scripts/build-hap.sh "$HOME/ohos_kotlin_semver_signing"
构建日志中应包含:
BuildNativeWithCmake
BuildNativeWithNinja
CompileArkTS
PackageHap
BUILD SUCCESSFUL
HAP 解压后检查:
mkdir -p /tmp/semver-hap
unzip -o entry-default-signed.hap 'libs/*' -d /tmp/semver-hap
python3 scripts/check-native-deps.py \
"$DEVECO_SDK_HOME/default/openharmony/native" \
/tmp/semver-hap/libs/arm64-v8a
预期结果:
libc++_shared.so: 0 unresolved strong imports
libentry.so: 0 unresolved strong imports
libsemver.so: 0 unresolved strong imports
6.4 功能验证用例
用例 1:严格解析和比较
输入:
当前版本:1.4.2-beta.1+ohos
目标版本:2.0.0
严格模式:开启
预期:
- 当前版本规范化字符串保持不变。
major=1、minor=4、patch=2。- 状态显示为预发布版本。
- 比较结果为“当前版本 < 目标版本”。
用例 2:宽松版本解析
输入:
当前版本:v1.2
目标版本:v2
严格模式:关闭
预期结果为:
当前版本:1.2.0
目标版本:2.0.0
用例 3:SemVer 约束匹配
输入:
约束:>=1.0 <2.0
格式:SemVer 条件
预期:
- 当前版本
1.4.2-beta.1+ohos满足规则。 - 目标版本
2.0.0不满足<2.0。 - 页面显示当前版本和目标版本的独立匹配结果。
用例 4:Maven 范围匹配
输入:
约束:[1.0,2.0)
格式:Maven 范围
预期:
[1.0,2.0)被 Maven parser 正确解析。- 标准格式、Maven 格式和 JSON 结果都能显示。
- 标准 serializer 和 Maven serializer 解码后的对象保持相等。
用例 5:版本递增和预发布标识
输入:
当前版本:1.4.2
预发布标识:rc
预期会显示:
MAJOR 2.0.0-rc
MINOR 1.5.0-rc
PATCH 1.4.3-rc
PRE_RELEASE 1.4.3-rc
用例 6:库自检和错误处理
操作:
- 输入非法版本
1.0并保持严格模式。 - 输入非法约束
not-a-range。 - 切换到自检页。
预期:
- 页面显示错误消息,不保留上一次成功结果。
- 自检通过项和失败项分别统计。
- 测试逻辑来自 Kotlin/Native,不是 ArkTS 模拟。
6.5 验证结论
根工程、独立消费者、Kotlin/Native 动态库、C++ N-API、ArkTS 编译和 ELF 依赖检查均已完成。HAP 在没有配置个人签名时可以完成编译和打包,但安装到设备前必须在独立 DevEco 工程中配置有效签名。
七、运行效果
7.1 获取运行截图
构建并安装签名 HAP 后,可以使用 DevEco 投屏或 hdc 截取页面:
hdc list targets
hdc shell aa start -a EntryAbility -b com.example.kotlinsemver
hdc shell snapshot_display -f /data/local/tmp/semver.png
hdc file recv /data/local/tmp/semver.png ./docs/images/semver-device.png
也可以直接使用 DevEco Studio 的设备投屏功能截取效果图。仓库中的固定效果图为:

7.2 界面文本快照
首页主要文本如下:
SEMVER / 版本实验台
解析 · 比较 · 匹配 · 发布
2.0.0 SPEC
预发布 宽松解析 Maven 范围
当前版本 1.4.2-beta.1+ohos
目标版本 2.0.0
严格模式
分析版本 →
当前版本 < 目标版本
升序:1.4.2-beta.1+ohos → 2.0.0
相等判断:不相等 · 构建元数据不参与优先级比较
01 概览 02 约束 03 工具 04 自检
页面底部的四个导航按钮用于切换不同能力区,避免把约束和序列化结果全部挤在概览页。
7.3 验证命令速查
# 根工程:发布、测试和 OHOS 编译
./gradlew -PopenharmonyOnly=true \
-PsignPublications=false \
publishToMavenLocal jvmTest compileKotlinOhosArm64
# 独立消费者:测试并准备 Native 动态库
(cd example && ./gradlew -PopenharmonyOnly=true \
:shared:jvmTest :nativeApp:prepareOhos)
# 准备 DevEco 签名工程
python3 scripts/prepare-signing-project.py \
"$HOME/ohos_kotlin_semver_signing"
# 构建 HAP
./scripts/build-hap.sh "$HOME/ohos_kotlin_semver_signing"
# 审计 ARM64 native 依赖
python3 scripts/check-native-deps.py \
"$DEVECO_SDK_HOME/default/openharmony/native" \
/tmp/semver-hap/libs/arm64-v8a
八、遗留问题与改进方向
8.1 踩坑复盘
| # | 踩坑点 | 现象 | 根因与解决方式 |
|---|---|---|---|
| 1 | 把 OHOS 当作普通 Native target | 找不到兼容的 Kotlin/Native 编译器或 sysroot。 | 使用 OpenHarmony 社区 Kotlin 发行版、固定 Kotlin 版本并配置 ohosArm64()。 |
| 2 | 只在示例中配置目标 | 示例项目能编译,发布库没有 OHOS 变体。 | 将 ohosArm64() 放在根库构建配置中。 |
| 3 | 只使用 Maven Central | commonMain 无法解析 ohosArm64 依赖。 | 保留社区 Maven 仓库和 mavenLocal()。 |
| 4 | 用 project dependency 代替发布坐标 | 根工程通过,独立消费者解析失败。 | 先 publishToMavenLocal,再由 example/shared 消费坐标。 |
| 5 | 把 SemVer 逻辑复制到 ArkTS | Kotlin 和 ArkTS 结果可能不一致。 | 通过 Kotlin/Native 计算,ArkTS 只显示 JSON。 |
| 6 | 直接把 Kotlin 对象交给 N-API | 类型和内存生命周期不可控。 | 使用 JSON 字符串和显式 SemverSampleFree。 |
| 7 | 忘记释放 nativeHeap 缓冲区 | 页面反复分析时可能出现 native 内存泄漏。 | C++ 创建 JS 字符串后立即调用 Free 函数。 |
| 8 | 只看 JVM 测试 | JVM 通过但 Native 链接或 HAP 失败。 | 增加 OHOS 编译、Native 链接、HAP 和 ELF 审计。 |
| 9 | 把未签名 HAP 当成安装成功 | HAP 能打包但设备报没有签名文件。 | 签名工程与源码分离,安装前配置 DevEco profile。 |
| 10 | 使用 JDK 25 运行旧 Kotlin DSL | Kotlin JavaVersion 解析失败。 | 统一使用 JDK 21。 |
8.2 已知问题
- 当前适配产物主要用于本地社区 Maven 验证,尚未替代上游 Maven Central 发布版本。
- 示例 UI 使用 ArkTS,不提供 Compose Multiplatform UI 组件;库本身也不依赖 UI 框架。
- HAP 的最终安装需要使用者提供自己的签名配置,仓库不能提供通用签名材料。
- 当前 Native bridge 以 JSON 传输,适合验收和交互式分析;高频批量场景可以增加批量接口减少序列化次数。
- 当前页面展示两条版本和多条约束,业务应用可以直接调用 common API 建立自己的缓存和更新策略。
8.3 未来优化方向
- 增加
ohosX64或其他 OpenHarmony ABI 的实验性目标。 - 增加批量版本比较和批量约束匹配接口。
- 将版本分析结果保存到页面历史记录,支持重复查看。
- 增加直接从文本文件读取版本清单的示例。
- 为下游消费者提供预构建 OHOS KLIB/HAR 交付方式。
- 在 CI 中增加 OpenHarmony 编译、HAP 构建和 native 依赖审计任务。
- 增加设备自动化测试,记录页面输入、按钮点击和结果文本。
九、总结
9.1 核心难点回顾
| 难点 | 本质 | 解法 |
|---|---|---|
| 目标和工具链对齐 | OpenHarmony target、Kotlin/Native、Gradle 和 sysroot 必须匹配。 | ohosArm64() + 社区发行包 + JDK 21 + 固定版本矩阵。 |
| 发布产物可消费 | 根工程通过不代表下游能解析 KLIB 和 metadata。 | publishToMavenLocal + 独立 example/shared。 |
| Kotlin/Native 到 ArkTS | 两个运行时不能直接共享 Kotlin 对象。 | C ABI + C++ N-API + JSON + 显式内存释放。 |
| 页面要完整表现库能力 | 单一输入页面容易漏掉约束、递增和序列化。 | 四页版本实验台按 API 能力分区。 |
| 构建和设备验收分离 | HAP 打包成功不代表签名安装和运行成功。 | JVM、OHOS、Native、HAP、ELF 和设备分层验证。 |
9.2 封装层次
第一层:kotlin-semver commonMain
版本解析、比较、约束和递增
第二层:example/shared
结果模型、JSON 契约、共享验收
第三层:example/nativeApp
Kotlin/Native shared library、C ABI、nativeHeap
第四层:example/ohosApp
C++ N-API、ArkTS 页面、HAP
第五层:DevEco / 设备
签名、安装、真机交互和最终验收
每层只处理自己负责的问题,避免 UI 层复制库逻辑,也避免库源码依赖 OpenHarmony 应用细节。
9.3 三条经验
- 先固定工具链,再添加目标。 OpenHarmony KMP 适配首先是版本和变体匹配问题,不能从 UI 开始排查。
- 独立消费者是必须的验收环节。 只有通过 Maven 坐标消费,才能证明适配产物具备真实接入价值。
- 桥接边界越小越稳定。 少量 C ABI、JSON 数据和明确 Free 函数,比直接导出复杂 Kotlin 对象更容易维护。
9.4 适配成果
本次适配完成:
kotlin-semver的ohosArm64Kotlin/Native 目标。3.1.0-ohos.1本地 Maven 适配产物。- 独立
example/shared消费工程和 JVM 验收。 libsemver.so、C ABI、C++ N-API 和 ArkTS 四页版本实验台。- 严格解析、宽松解析、比较、约束、Maven 范围、递增、序列化和错误处理展示。
- HAP 构建、ARM64 ELF 依赖检查和签名工程准备脚本。
- AtomGit 项目地址、中文/英文适配说明和版本实验台效果图。
项目地址:
git clone https://atomgit.com/oh-tpc/ohos_kotlin-semver.git
参考文档
更多推荐



所有评论(0)