本文记录 kotlin-multiplatform-diff 适配 OpenHarmony 的完整过程:从 Kotlin Multiplatform 目标配置、OpenHarmony Kotlin/Native 工具链对齐,到独立消费工程、Kotlin/Native 动态库、C++ N-API 桥接、ArkTS 交互页面和 HAP 构建。

这次适配的重点是让原有公共 diff 算法继续复用,同时为 OpenHarmony ARM64 增加真实可运行的消费示例。示例页面不是只展示“测试通过”,而是可以输入两段文本,调用 Kotlin/Native 中的真实库 API,显示补丁段、行内差异、逐行差异和应用补丁后的文本。

项目地址: AtomGit/oh-tpc/ohos_kotlin-multiplatform-diff

开发工具: 华为云码道


一、项目背景

1.1 kotlin-multiplatform-diff 是什么

kotlin-multiplatform-diffjava-diff-utils 的 Kotlin 多平台实现。库提供 Myers 差异算法、补丁生成与应用、行级差异、行内差异、HTML 标记、空白处理以及变更类型建模等能力。

公共代码位于 src/commonMain,原有工程已经支持 JVM、JavaScript、Wasm 和多种 Kotlin/Native 目标。OpenHarmony 应用需要 ARM64 Kotlin/Native 产物,因此本次适配增加 ohosArm64,并将它接入独立的示例工程。

1.2 为什么需要 OpenHarmony 目标

普通 JVM 或其他 Native 产物不能直接作为 OpenHarmony 应用的原生库。要让 ArkTS 页面使用同一套 diff 逻辑,需要解决以下问题:

障碍具体问题
目标缺失原工程没有 ohosArm64(),无法产生 OpenHarmony ARM64 KLIB。
工具链不一致Kotlin/Native 编译器、OpenHarmony sysroot 和 KMP 插件必须使用匹配的发行版本。
构建范围过大全量配置所有平台会下载大量无关工具链,影响迭代速度。
消费方式不同仅编译根工程不能证明第三方工程能通过 Maven metadata 和 KLIB 使用库。
应用边界不同Kotlin/Native 代码最终需要通过 C ABI 和 N-API 被 ArkTS 调用。
验收形式不同编译通过不能说明用户真的能执行 diff 操作,需要一个可以输入文本并显示结果的页面。

适配的原则是保持 commonMain 中的算法和 API 不变,把 OpenHarmony 差异收敛在目标配置、消费工程、原生桥接和应用打包层。

1.3 适配后提供的能力

完成适配后,仓库提供:

  • ohosArm64 Kotlin/Native 目标,面向 OpenHarmony ARM64 设备。
  • -PopenharmonyOnly=true focused build,减少本地开发时的无关目标配置。
  • 独立的 example/ 消费工程,通过 mavenLocal() 使用已发布库坐标。
  • shared 模块中的 JVM 和 OHOS 共享验收逻辑。
  • nativeApp 模块生成 libdiff.so,并导出 DiffSampleCompareDiffSampleRunChecksDiffSampleFree
  • C++ N-API 模块,将 ArkTS 字符串参数传给 Kotlin/Native,再把 JSON 结果返回给 ArkTS。
  • ArkTS 交互式 diff 页面,支持编辑原文、新文和重新比较。
  • 构建、签名工程准备、HAP 构建和 ARM64 ELF 依赖检查脚本。

1.4 版本矩阵

组件版本或要求
适配库1.3.0-ohos.1
Maven 坐标io.github.petertrr:kotlin-multiplatform-diff:1.3.0-ohos.1
Kotlin Gradle plugin / compiler / Native2.2.21-1.0.0
Kotlin Coroutines本项目核心库不依赖协程
OpenHarmony targetohosArm64
应用 ABIarm64-v8a
HarmonyOS target / compatible API6.0.0(20)
Gradle8.14.3
JDK21
DevEco Studio6 或更高版本

这里的 ohosArm64 是 Kotlin/Native 目标名称,arm64-v8a 是 DevEco/HAP 使用的 ABI 名称,两者需要同时配置。


二、整体实现路线

本次适配分为六个阶段:

第 1 阶段:盘点工程边界     ── 确认 commonMain、测试和原有多平台目标
第 2 阶段:增加 OHOS 目标    ── 加入 ohosArm64 和 focused build
第 3 阶段:独立消费验证      ── 通过 Maven 坐标构建 shared 模块
第 4 阶段:Native 桥接        ── Kotlin/Native 动态库、C ABI、C++ N-API
第 5 阶段:交互页面          ── ArkTS 输入文本并展示真实 diff 结果
第 6 阶段:构建和验收         ── JVM、OHOS、HAP、ELF 和设备分层验证

最终依赖方向如下:

ohosApp → nativeApp → shared → kotlin-multiplatform-diff

shared 通过发布坐标消费库,nativeApp 依赖 shared 并生成动态库,ohosApp 通过 C++ N-API 加载动态库。


三、根工程增加 OpenHarmony 目标

3.1 配置仓库和版本

OpenHarmony Kotlin/Native 发行包来自社区 Maven 仓库,因此插件仓库和依赖仓库都要加入该地址:

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()
    }
}

gradle.properties 固定 Kotlin/Native 从 Maven 下载,并声明适配版本:

kotlin.native.ignoreDisabledTargets=true
kotlin.mpp.stability.nowarn=true
kotlin.native.distribution.downloadFromMaven=true

version=1.3.0-ohos.1

项目的发布元数据和 SCM 地址统一指向 AtomGit:

mavenPublishing {
    coordinates(
        groupId = project.group.toString(),
        artifactId = project.name,
        version = project.version.toString(),
    )

    pom {
        url.set("https://atomgit.com/oh-tpc/ohos_kotlin-multiplatform-diff")
        scm {
            url.set("https://atomgit.com/oh-tpc/ohos_kotlin-multiplatform-diff")
            connection.set(
                "scm:git:https://atomgit.com/oh-tpc/ohos_kotlin-multiplatform-diff.git",
            )
        }
    }
}

3.2 声明 ohosArm64

根工程保留原有 JVM、JS、Wasm 和 Native 目标,在公共目标之外增加:

kotlin {
    explicitApi()

    compilerOptions {
        apiVersion = KotlinVersion.KOTLIN_2_2
        languageVersion = KotlinVersion.KOTLIN_2_2
    }

    ohosArm64()

    jvm {
        // 原有 JVM 配置
    }

    if (!providers.gradleProperty("openharmonyOnly")
            .map(String::toBoolean)
            .getOrElse(false)) {
        js {
            browser()
            nodejs()
        }

        wasmJs {
            browser()
            nodejs()
        }

        // 原有 macOS、iOS、Linux、Windows、Android Native 等目标
    }
}

openharmonyOnly 只改变目标配置范围,不改变公共源码:

./gradlew -PopenharmonyOnly=true tasks

在 focused build 中仍保留 JVM 和 OpenHarmony,跳过 JS、Wasm 以及其他不相关 Native 工具链。

3.3 为什么不能只改示例

如果只在 example/nativeApp 中声明 ohosArm64(),示例可能拥有一个 Native 目标,但根库不会产生 OpenHarmony KLIB。正确的关系是:

根库 commonMain
    ├─ JVM publication
    ├─ Kotlin Multiplatform metadata
    └─ OHOS ARM64 publication
              │
              ▼
example/shared 通过 Maven 坐标解析

因此 ohosArm64() 必须出现在根库构建配置中,示例工程再单独声明自己的目标。


四、独立消费工程

4.1 为什么新增独立 example/

根工程里的单元测试只能证明当前源码能运行,不能证明一个外部消费者能正确解析:

  • Kotlin Multiplatform metadata。
  • JVM artifact。
  • OpenHarmony ARM64 artifact。
  • 版本号和 POM。
  • mavenLocal() 中的发布坐标。

因此示例工程不直接 project(":") 依赖根工程,而是使用真实坐标:

kotlin {
    jvm()
    jvmToolchain(21)
    ohosArm64()

    sourceSets {
        commonMain.dependencies {
            implementation(
                "io.github.petertrr:kotlin-multiplatform-diff:1.3.0-ohos.1",
            )
        }

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

使用前先发布到本地 Maven:

./gradlew -PopenharmonyOnly=true \
  -PsignPublications=false \
  publishToMavenLocal

4.2 示例工程目录

example/
  shared/
    build.gradle.kts
    src/commonMain/kotlin/.../AcceptanceChecks.kt
    src/commonTest/kotlin/.../AcceptanceChecksTest.kt
  nativeApp/
    build.gradle.kts
    src/ohosArm64Main/kotlin/.../NativeChecks.kt
    src/ohosArm64Main/linker/shared-library.map
  ohosApp/
    AppScope/
    entry/
      src/main/ets/pages/Index.ets
      src/main/cpp/napi_init.cpp
      src/main/cpp/CMakeLists.txt

4.3 共享验收场景

shared 直接调用发布坐标中的 API,当前包含 9 类场景:

  1. 字符串 diff 和补丁应用。
  2. CRLFCRLF 换行归一化。
  3. 行内 diff。
  4. 插入、删除和修改 delta 类型。
  5. includeEqualParts 包含相等部分。
  6. DiffRowGenerator 行差异和 HTML 标记。
  7. 忽略空白差异。
  8. Unicode 和空输入。
  9. 交互页面使用的比较模型。

示例测试仍然保持最小边界:

@Test
fun allScenariosPass() {
    val results = runAcceptanceChecks()
    assertEquals(9, results.size)
    assertTrue(results.all { it.passed })
}

测试通过只能说明共享逻辑正确,后续还要继续经过 OHOS 编译、Native 链接和 HAP 构建。


五、把 diff 能力做成真实交互页面

5.1 不再只显示 PASS

验收页最初可以只显示每个检查是否通过,但这种页面不能让使用者感知库的实际功能。当前页面增加两个多行输入框:

  • 原文 / Original:输入待比较的原始文本。
  • 新文 / Revised:输入修改后的文本。

点击“比较 / Compare”后,ArkTS 将两段文本传给 Native bridge。Native bridge 再调用 Kotlin/Native 中的 compareTexts,结果返回:

{
  "patchDeltaCount": 1,
  "inlineDeltaCount": 2,
  "appliedText": "alpha\nbeta changed\ndelta\ngamma",
  "rows": [
    {"tag":"EQUAL", "oldLine":"alpha", "newLine":"alpha"},
    {"tag":"CHANGE", "oldLine":"beta", "newLine":"beta changed"},
    {"tag":"INSERT", "oldLine":"", "newLine":"delta"},
    {"tag":"EQUAL", "oldLine":"gamma", "newLine":"gamma"}
  ]
}

页面会展示补丁段数量、行内差异数量、逐行结果以及应用补丁后的完整文本。

5.2 Kotlin 共享比较模型

共享模块把库 API 组合为页面需要的结果模型:

data class ComparisonResult(
    val patchDeltaCount: Int,
    val inlineDeltaCount: Int,
    val appliedText: String,
    val rows: List<DiffRow>,
)

fun compareTexts(source: String, target: String): ComparisonResult {
    val sourceLines = source.split(lineBreak)
    val targetLines = target.split(lineBreak)
    val patch = diff(sourceLines, targetLines)

    return ComparisonResult(
        patchDeltaCount = patch.deltas.size,
        inlineDeltaCount = diffInline(source, target).deltas.size,
        appliedText = patch.applyTo(sourceLines).joinToString("\n"),
        rows = DiffRowGenerator(
            showInlineDiffs = true,
            reportLinesUnchanged = true,
        ).generateDiffRows(sourceLines, targetLines),
    )
}

这里的 diffdiffInlineDiffRowGeneratorPatch.applyTo 都来自被适配的三方库,没有在 ArkTS 中重写一份算法。

5.3 ArkTS 页面操作

页面提供“比较 / Compare”和“示例 / Example”两个按钮。示例按钮填入一组默认文本,比较按钮执行真实调用:

private runComparison(): void {
  try {
    const value = JSON.parse(
      compare(this.sourceText, this.targetText),
    ) as ComparisonResult;
    this.comparison = value;
    this.errorMessage = '';
  } catch (error) {
    this.comparison = undefined;
    this.errorMessage = String(error);
  }
}

ForEach 根据 rows 显示 EQUALCHANGEINSERTDELETE,应用补丁后的内容单独显示在结果区域。底部的库自检只用于确认动态库和库 API 已正确加载。

5.4 交互效果图

下面是示例在 OpenHarmony 页面中的实际效果。页面可以编辑两段文本,点击比较后查看结果:

在这里插入图片描述

图中可以看到:

  • 输入区域支持多行文本。
  • “比较 / Compare”触发真实 diff 计算。
  • “示例 / Example”快速填充演示内容。
  • 结果区域显示补丁段和行内差异数量。
  • 逐行差异展示 EQUAL 等 delta 标签。
  • 页面底部保留库自检结果,但它不替代交互功能演示。

六、Kotlin/Native 到 ArkTS 的桥接

6.1 调用链

ArkTS Index.ets
    │ compare(source, target)
    ▼
C++ N-API module
    │ DiffSampleCompare(const char*, const char*)
    ▼
Kotlin/Native libdiff.so
    │ compareTexts(source, target)
    ├─ diff(sourceLines, targetLines)
    ├─ diffInline(source, target)
    ├─ DiffRowGenerator(...)
    └─ Patch.applyTo(sourceLines)
    │ JSON string + DiffSampleFree()
    ▼
ArkTS JSON.parse -> page state -> result cards

库本身不直接暴露 ArkTS 类型。只有示例 Native 模块提供稳定的 C ABI,避免把 Kotlin 对象布局和生命周期泄漏到 C++。

6.2 Kotlin/Native 导出入口

@CName("DiffSampleCompare")
fun compareNative(source: String, target: String): CPointer<ByteVar> =
    try {
        textBuffer(comparisonJson(compareTexts(source, target)))
    } catch (error: Throwable) {
        textBuffer("{\"error\":${quote(error.message ?: error.toString())}}")
    }

@CName("DiffSampleFree")
fun freeNativeChecks(result: CPointer<ByteVar>?) {
    if (result != null) nativeHeap.free(result)
}

runNativeChecks 仍然保留,用于执行 9 个共享自检;compareNative 用于用户输入的实时比较。返回值统一是 UTF-8 JSON 字符串,C++ 复制完成后调用 DiffSampleFree 释放 Native 堆内存。

6.3 C++ N-API 参数转换

C++ 从 ArkTS 回调中读取两个字符串,调用 Native 导出函数,再把 JSON 转换成 ArkTS 字符串:

static napi_value Compare(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value args[2] = {nullptr, nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    std::string source;
    std::string target;
    if (!ReadString(env, args[0], source) ||
        !ReadString(env, args[1], target)) {
        napi_throw_type_error(env, nullptr, "compare arguments must be strings");
        return nullptr;
    }

    auto text = DiffSampleCompare(source.c_str(), target.c_str());
    napi_value result = nullptr;
    const auto status = napi_create_string_utf8(
        env,
        reinterpret_cast<const char*>(text),
        NAPI_AUTO_LENGTH,
        &result);
    DiffSampleFree(text);
    return status == napi_ok ? result : nullptr;
}

N-API 只处理字符串和函数调用;差异算法、补丁应用和结果构造全部留在 Kotlin/Native 侧。

6.4 导出表

动态库使用链接器 map,只导出需要被 C++ 调用的入口:

{
    global:
        DiffSampleCompare;
        DiffSampleRunChecks;
        DiffSampleFree;
    local: *;
};

这样可以避免导出无关符号,也让 CMake 链接阶段能明确找到交互入口。


七、构建和签名

7.1 一键构建

使用 JDK 21 执行:

export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh

脚本依次完成:

  1. 根库发布到 mavenLocal()
  2. 根库 JVM 测试。
  3. 根库 ohosArm64 编译。
  4. 独立 shared JVM 测试。
  5. nativeApp:prepareOhos,生成并复制 libdiff.so 和头文件。

也可以分步执行:

./gradlew -PopenharmonyOnly=true \
  jvmTest compileKotlinOhosArm64 compileTestKotlinOhosArm64

(cd example && ./gradlew -PopenharmonyOnly=true \
  :shared:jvmTest :nativeApp:prepareOhos)

产物位置:

build/classes/kotlin/ohosArm64/main/klib/
example/ohosApp/entry/libs/arm64-v8a/libdiff.so
example/ohosApp/entry/src/main/cpp/include/libdiff_api.h

动态库和生成头文件通过 .gitignore 排除,不进入源码提交。

7.2 准备 DevEco 工程

DevEco 对包含特殊字符或非 ASCII 字符的目录路径比较敏感。可以把应用复制到独立的签名目录:

python3 scripts/prepare-signing-project.py \
  "$HOME/ohos_kotlin_multiplatform_diff_signing"

脚本会复制 ArkTS、C++、资源和配置,排除构建缓存、OHPM 模块、CMake 临时目录和证书文件。如果目标目录已经有 build-profile.json5,脚本会保留目标目录中的签名配置。

7.3 构建 HAP

DEVECO_HOME="/Applications/DevEco-Studio.app/Contents" \
DEVECO_SDK_HOME="/Applications/DevEco-Studio.app/Contents/sdk" \
./scripts/build-hap.sh "$HOME/ohos_kotlin_multiplatform_diff_signing"

脚本执行 ohpm install --allhvigorw ... assembleHap。未配置签名时可以完成编译和打包流程,但最终设备安装需要在 DevEco 中配置有效签名。

7.4 ELF 依赖审计

对解压后的 HAP 原生库检查架构和强符号:

python3 scripts/check-native-deps.py \
  /path/to/sdk/20/native \
  /path/to/extracted-hap/libs/arm64-v8a

该脚本检查:

  • 产物是否为 AArch64 ELF。
  • OHOS SDK sysroot 和应用动态库是否提供所需符号。
  • 是否存在未解析的强依赖。

这是静态检查,不能替代签名后的真机运行。


八、验证结果

8.1 已完成的自动化验证

当前适配已经完成以下检查:

检查结果
根库 JVM 测试通过
根库 compileKotlinOhosArm64通过
根库 compileTestKotlinOhosArm64通过
独立示例 shared:jvmTest通过,9 个场景
nativeApp:prepareOhos通过
ArkTS 编译通过
CMake/Ninja 原生桥接编译通过
未签名 HAP 构建流程通过

测试结果中的“通过”只代表对应层级已经通过。JVM 测试通过不能替代 OHOS 编译,OHOS 编译通过也不能替代设备安装。

8.2 真机验收步骤

  1. 运行 scripts/build-openharmony.sh
  2. 使用 prepare-signing-project.py 复制签名工程。
  3. 在 DevEco Studio 中配置 API 20 ARM64 签名。
  4. 构建并安装 HAP。
  5. 修改原文和新文,点击“比较”。
  6. 确认页面显示新的补丁段、行内差异、逐行结果和应用补丁后的文本。
  7. 点击“示例”验证预置案例,再切换后台和恢复前台。

当前仓库没有提交任何设备证书、私钥、密码或签名产物。真机结果应由实际设备和签名环境记录。


九、关键文件

文件作用
build.gradle.kts根工程 KMP 目标、版本、发布和 OHOS focused build。
settings.gradle.ktsAtomGit 相关仓库所需的插件与依赖仓库配置。
example/shared/.../AcceptanceChecks.kt共享库验收和交互比较模型。
example/nativeApp/.../NativeChecks.ktKotlin/Native C ABI 入口和 JSON 序列化。
example/nativeApp/.../shared-library.map动态库导出符号控制。
example/ohosApp/entry/src/main/cpp/napi_init.cppC++ N-API 参数读取和 Native 调用。
example/ohosApp/entry/src/main/ets/pages/Index.ets交互式文本输入、比较按钮和结果展示。
scripts/build-openharmony.sh发布、测试、OHOS 编译和 Native 库准备。
scripts/prepare-signing-project.py复制 DevEco 签名工程。
scripts/build-hap.shOHPM 安装和 HAP 构建。
scripts/check-native-deps.pyARM64 ELF 和符号依赖检查。
docs/images/kotlin-multiplatform-diff-openharmony-demo.jpgOpenHarmony 交互页面效果图。

十、适配中的关键经验

10.1 目标配置要放在库构建层

只在示例中声明 ohosArm64,无法生成真正可消费的库产物。目标必须和根库的 KMP 配置一起定义,示例再以发布坐标接入。

10.2 用 focused build 控制工具链下载

OpenHarmony 适配需要额外的 Native 编译器、LLVM 和 sysroot。openharmonyOnly 让开发者只配置 JVM/OHOS,普通构建则保留原有平台矩阵。

10.3 交互验证比单纯 PASS 更有说服力

自检列表适合发现回归,但不能表现用户真正如何使用 diff 库。交互页面把输入、算法计算和结果输出串在一起,可以直接观察补丁和行差异是否符合预期。

10.4 C ABI 只传字符串

通过 JSON 字符串传输结果,可以把 Kotlin 对象、Kotlin 集合和 Native 内存生命周期留在 Kotlin/Native 内部。C++ 和 ArkTS 只需要处理 UTF-8 字符串,桥接边界更稳定。

10.5 构建、打包和真机分层

应分别记录 JVM、KLIB、动态库、CMake、ArkTS、HAP 和设备结果。一个层级通过不能自动推出下一个层级通过。

10.6 AtomGit 地址必须全链路一致

源码文档、POM SCM、问题反馈入口和 Git remote 全部使用:

https://atomgit.com/oh-tpc/ohos_kotlin-multiplatform-diff

克隆和更新远端:

git clone https://atomgit.com/oh-tpc/ohos_kotlin-multiplatform-diff.git
cd ohos_kotlin-multiplatform-diff
git remote -v

十一、总结

本次适配没有复制一套鸿蒙专用 diff 算法,而是保留 commonMain 的公共实现,在构建层增加 ohosArm64,在示例层增加真实消费和原生桥接,在应用层提供可操作的文本比较页面。

最终链路如下:

commonMain diff API
        │
        ▼
Kotlin/Native ohosArm64
        │ libdiff.so
        ▼
C++ N-API
        │ JSON
        ▼
ArkTS interactive page
        │
        ├─ 输入原文和新文
        ├─ 显示补丁段和行内差异
        ├─ 显示逐行 DiffRow
        └─ 显示应用补丁后的文本

适配完成后,OpenHarmony 使用者可以像其他 KMP 消费者一样通过 Maven 坐标接入库,也可以直接打开示例页面操作真实 diff 功能。项目地址为 AtomGit/oh-tpc/ohos_kotlin-multiplatform-diff


参考文档

环境: Kotlin 2.2.21-1.0.0 · HarmonyOS API 20 · Gradle 8.14.3 · JDK 21 · OpenHarmony ohosArm64

Logo

一站式 AI 云服务平台

更多推荐