本文记录 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.31.2.3-alpha.1+build.7 等完整版本。
  • 宽松解析:将 v1v1.2 归一化为 1.0.01.2.0
  • 字段访问:读取 majorminorpatchpreReleasebuildMetadata
  • 稳定性判断:通过 isStableisPreRelease 判断发布状态。
  • 版本比较:支持 compareTo、比较运算符和 sorted
  • 约束解析:支持 >=<^~、通配符、连字符范围和 OR/AND。
  • Maven 范围:支持 [1.0,2.0) 等 Maven 风格约束。
  • 集合判断:支持 satisfiesAllsatisfiesAnysatisfiedByAll
  • 版本递增:支持 MAJOR、MINOR、PATCH、PRE_RELEASE 四种递增。
  • 对象转换:支持 copywithoutSuffixes、解构/构造和序列化。

这些能力可以用于应用升级检查、插件兼容性判断、依赖版本选择和自动发布。库只判断版本规则,业务应用仍然负责下载、安装和升级。

项目地址https://atomgit.com/oh-tpc/ohos_kotlin-semver

1.3 实现目标

维度要求
API 稳定性复用 commonMain,不改变 VersionConstraintinc 的公开语义。
平台支持增加 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 / Native2.2.21-1.0.0
kotlinx.serialization1.9.1-1.0.0
Gradle Wrapper8.14.3
JDK21
OpenHarmony target / compatible API6.0.0(20)
HAP ABIarm64-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 不需要了解 VersionConstraint 的内部结构。

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 = "",
)

leftright 是待比较版本;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 的 VersionConstraintList 和异常对象属于 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(),
)

页面不需要理解 ConditionVersionDescriptorPreRelease 的内部类型,只使用这个稳定的结果模型。

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+ohos2.0.0 的比较。
  • 宽松解析:展示 v1.2v2 的归一化。
  • 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 动态库准备

nativeAppprepareOhos 任务负责复制动态库和头文件:

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.ktsAtomGit 工程使用的插件和依赖仓库。
gradle.propertiesgroup、适配版本和 Gradle 参数。
example/shared/build.gradle.kts以 Maven 坐标消费 semver。
example/shared/.../SemverSample.kt版本分析模型、JSON 和验收逻辑。
example/shared/.../SemverSampleTest.kt独立消费者 JVM 测试。
example/nativeApp/build.gradle.ktsohosArm64 shared library 和复制任务。
example/nativeApp/.../NativeChecks.ktC ABI 导出和 nativeHeap 内存管理。
shared-library.map控制 libsemver.so 导出的三个符号。
napi_init.cppC++ 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 / toVersionJVM/JS/Native common API
版本比较compareTo、比较运算符Java Comparable 等价能力
约束匹配Constraint + satisfiesnpm/Maven 版本范围判断
版本递增Version.inc(Inc.*)发布脚本的版本计算
JSON 序列化VersionSerializer / ConstraintSerializerKotlinx Serialization
OpenHarmony 产物ohosArm64 KLIB / libsemver.soAndroid .so / iOS framework
应用桥接C ABI + C++ N-APIJNI / Objective-C bridge
UI 验收ArkTS Stage Index.etsCompose / UIKit / SwiftUI 页面

4.4 ArkTS 与 TypeScript / Kotlin 的语法差异(本次实际踩到的)

常见写法ArkTS 要求说明
JSON.parse(value) as Any使用显式 interface避免 ArkTS 严格模式下的隐式对象类型。
any使用明确接口或 ObjectN-API 返回的数据需要先定义 EvaluationResponse
value ?? ''保证左右类型一致`string
多根节点 build()必须返回唯一容器使用 ColumnScrollRow 包裹页面。
Kotlin nullable 字段使用 `stringnull`
Kotlin 异常使用 error 字段异常不直接跨过 C ABI。
native 字符串必须调用 FreeC++ 复制字符串后调用 SemverSampleFree

五、关键决策说明

决策 1:把 ohosArm64 加入库的公共构建约定

背景:如果只在 example/nativeApp 添加 OHOS 目标,示例能链接,但库本身不能作为 OpenHarmony 依赖发布。

决策:在根工程的 Kotlin 配置中增加 ohosArm64(),并保留 openharmonyOnly focused build。这样目标、版本和发布 metadata 都由库统一管理。

维护策略:新模块必须通过公共 target 配置获得 ohosArm64,不在单个示例中复制另一套 Kotlin 目标逻辑。

决策 2:独立消费者必须通过 Maven 坐标

背景:项目依赖可以绕过发布 metadata,无法发现用户接入时可能遇到的 KLIB 变体问题。

决策:根工程先执行 publishToMavenLocalexample/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++ → Kotlinconst char*
Kotlin → C++nativeHeap UTF-8 缓冲区
C++ → ArkTSN-API JS 字符串

维护策略:新增页面能力时优先扩展 Kotlin 数据类,不在 C++ 和 ArkTS 各复制一份版本算法。

决策 4:桥接层只开放三个 C ABI 入口

背景:导出整个 Kotlin/Native 对象图会增加符号、链接依赖和 ABI 风险。

决策:只导出 SemverSampleEvaluateSemverSampleRunChecksSemverSampleFree,其余符号通过 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
JDK21
Kotlin2.2.21-1.0.0
kotlinx.serialization1.9.1-1.0.0
Gradle8.14.3
OpenHarmony target/compatible API6.0.0(20)
ABIarm64-v8a
DevEco Studio6+
Native SDKOpenHarmony 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=1minor=4patch=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. 输入非法版本 1.0 并保持严格模式。
  2. 输入非法约束 not-a-range
  3. 切换到自检页。

预期:

  • 页面显示错误消息,不保留上一次成功结果。
  • 自检通过项和失败项分别统计。
  • 测试逻辑来自 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 CentralcommonMain 无法解析 ohosArm64 依赖。保留社区 Maven 仓库和 mavenLocal()
4用 project dependency 代替发布坐标根工程通过,独立消费者解析失败。publishToMavenLocal,再由 example/shared 消费坐标。
5把 SemVer 逻辑复制到 ArkTSKotlin 和 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 DSLKotlin JavaVersion 解析失败。统一使用 JDK 21。

8.2 已知问题

  1. 当前适配产物主要用于本地社区 Maven 验证,尚未替代上游 Maven Central 发布版本。
  2. 示例 UI 使用 ArkTS,不提供 Compose Multiplatform UI 组件;库本身也不依赖 UI 框架。
  3. HAP 的最终安装需要使用者提供自己的签名配置,仓库不能提供通用签名材料。
  4. 当前 Native bridge 以 JSON 传输,适合验收和交互式分析;高频批量场景可以增加批量接口减少序列化次数。
  5. 当前页面展示两条版本和多条约束,业务应用可以直接调用 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 三条经验

  1. 先固定工具链,再添加目标。 OpenHarmony KMP 适配首先是版本和变体匹配问题,不能从 UI 开始排查。
  2. 独立消费者是必须的验收环节。 只有通过 Maven 坐标消费,才能证明适配产物具备真实接入价值。
  3. 桥接边界越小越稳定。 少量 C ABI、JSON 数据和明确 Free 函数,比直接导出复杂 Kotlin 对象更容易维护。

9.4 适配成果

本次适配完成:

  • kotlin-semverohosArm64 Kotlin/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

参考文档

Logo

一站式 AI 云服务平台

更多推荐