Post

Kotlin/Native 编译器开发技巧

Kotlin/Native 编译器开发技巧

Gradle并发下载依赖

Screenshot 2025-09-26 at 9.43.55 AM.png

通过IDEA调试gradle任务

  1. clone kotlin项目https://github.com/linhandev/kuiklybase-kotlin
  2. 使用intellj idea打开kotlin项目
  3. 在kotlin项目右上角添加构建compose-sample项目句gradle任务配置

image.png

image.png

其中gradle project被构建的项目地址,如ovcomposesample项目地址。run命令填写构建那个项目的命令,如 ./gradlew publishDebugBinariesToHarmonyApp

  1. 在kotlin仓库,kotlin编译器代码中打断点,进行调试

调试gradle命令

  1. gradle命令添加-Dorg.gradle.debug=true,如
1
2
3
./gradlew :kotlin-native:dist
# 添加后
./gradlew :kotlin-native:dist -Dorg.gradle.debug=true
  1. 添加jvm remote debug profile,所有东西都默认

image.png

调试java命令

如果命令不是gradle启动的,是直接调用了konanc这种脚本没法添加 -Dorg.gradle.debug=true,需要手动修改java命令插入调试相关选项

  1. 获取jvm中运行的命令,找到 ~/.konan 目录下对应版本的prebuilt文件夹,使用腾讯版本文件夹名带kba,使用ohos版本文件夹名带ohos,使用本地指定的的就是 kotlin.native.home 指向的文件夹。修改其中的 bin/run_konan 脚本,将最后一行的 java 命令打到文件中

image.png

  1. 在java命令中插入 -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005。其中 suspend=y 的意思是debug attach之后再开始执行,kotlin单个构建任务很快,不加这个容易还没 attach 就执行完了

比如

1
2
3
/Library/Java/JavaVirtualMachines/zulu-21.jdk/Contents/Home/bin/java -ea -Xmx3G -XX:TieredStopAtLevel=1 -Dfile.encoding=UTF-8 -Dkonan.home=/Volumes/disk/git/kmp/kotlin22-ohos/kotlin-native/dist -cp /Volumes/disk/git/kmp/kotlin22-ohos/kotlin-native/dist/konan/lib/kotlin-native-compiler-embeddable.jar org.jetbrains.kotlin.cli.utilities.MainKt konanc main.kt -o hello.kexe -verbose

/Library/Java/JavaVirtualMachines/zulu-21.jdk/Contents/Home/bin/java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 -ea -Xmx3G -XX:TieredStopAtLevel=1 -Dfile.encoding=UTF-8 -Dkonan.home=/Volumes/disk/git/kmp/kotlin22-ohos/kotlin-native/dist -cp /Volumes/disk/git/kmp/kotlin22-ohos/kotlin-native/dist/konan/lib/kotlin-native-compiler-embeddable.jar org.jetbrains.kotlin.cli.utilities.MainKt konanc main.kt -o hello.kexe -verbose
  1. 在kotlin项目中添加 remote jvm debug 配置,所有选项默认

image.png

  1. 运行修改过的java命令,在kotlin项目中通过remove jvm debug配置attach,正常debug

运行具体任务

子项目名通常是文件夹名,tasks列出这个文件夹的所有gradle任务。如果是kn的文件夹需要传配置

1
./gradlew :kotlin-native:runtime:tasks -Pbootstrap.local=true -Pkotlin.native.enabled --dependency-verification=off

在输出的tasks中找可能感兴趣的任务,如单编ohos的runtime

1
./gradlew :kotlin-native:runtime:ohos_arm64Runtime -Pbootstrap.local=true -Pkotlin.native.enabled --dependency-verification=off

单打platformlibs

1
./gradlew :kotlin-native:ohos_arm64PlatformLibs -Pbootstrap.local=true -Pkotlin.native.enabled --dependency-verification=off

单独构建一个def

1
./gradlew :kotlin-native:platformLibs:compileKonanOhos_arm64-WifiOhos_arm64 -Pbootstrap.local=true -Pkotlin.native.enabled --dependency-verification=off 

单打runtime

准备

  1. 在sample根目录的gradle.properties中设置kotlin.native.home到本地的 kotlin/kotlin-native/dist 文件夹

image.png

  1. 设置下面脚本里的三个路径

过程:

  1. kotlin项目中跑 :kotlin-native:runtime:ohos_arm64Runtime task将runtime从cpp代码打成bc,输出在 kotlin/kotlin-native/runtime/build/bitcode/main/ohos_arm64
  2. 手动将 kotlin/kotlin-native/runtime/build/bitcode/main/ohos_arm64 中的bc复制到 kotlin/kotlin-native/dist/konan/targets/ohos_arm64/native
  3. 重新打包ovcomposesample
  4. 启动应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
kotlin_repo=/Volumes/disk/git/kmp/kotlin # kotlin仓库根目录地址
sample_repo=/Volumes/disk/git/cmp/ovCompose-sample-ohos # sample仓库根目录地址
deveco_studio_install_path=/Volumes/disk/application/DevEco-Studio-5.1.1.823.app # deveco studio安装文件夹,下一层应该是 Content

set -ex

cd $kotlin_repo
./gradlew :kotlin-native:runtime:ohos_arm64Runtime -Pbootstrap.local=true -Pkotlin.native.enabled --dependency-verification=off

rm -rf $kotlin_repo/kotlin-native/dist/konan/targets/ohos_arm64/native/*
cp $kotlin_repo/kotlin-native/runtime/build/bitcode/main/ohos_arm64/*.bc $kotlin_repo/kotlin-native/dist/konan/targets/ohos_arm64/native/

cd $sample_repo
./gradlew publishDebugBinariesToHarmonyApp --rerun-tasks

cd $sample_repo/harmonyApp
$deveco_studio_install_path/Contents/tools/node/bin/node $deveco_studio_install_path/Contents/tools/hvigor/bin/hvigorw.js --mode module -p module=entry@default -p product=default -p buildMode=debug -p requiredDeviceType=phone assembleHap --analyze=normal --parallel --incremental --daemon

hdc uninstall com.tencent.compose
hdc install $sample_repo/harmonyApp/entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.tencent.compose

效果:runtime中修改了一行代码,开始编译到启动应用耗时1分02s,验证修改生效了

image.png

kotlin-native文件夹下代码跳转

Kotlin Native构建的时候需要特殊的gradle.properties参数,kotlin项目拉下来之后直接sync后,kotlin-native部分的代码都无法点击跳转到定义

  1. 拉取项目, bash scripts/build-ohos.sh 运行构建
  2. 本地出包完成后,在gradle.properties中添加,冲突的key注掉原有的
1
2
3
4
5
6
7
8
bootstrap.kotlin.default.version=2.2.255-SNAPSHOT
deployVersion=2.2.255-SNAPSHOT
versions.kotlin-native=2.2.255-SNAPSHOT
konanVersion=2.2.255-SNAPSHOT
bootstrap.kotlin.version=2.2.255-SNAPSHOT
kotlin.native.enabled=true
bootstrap.local=true
bootstrap.local.version=2.2.255-SNAPSHOT
  1. 执行sync

查看runtime bc内容

  • bc是二进制格式llvm ir无法打开查看,可以通过 llvm-dis 命令将二进制的llvm ir转换为文本格式的 llvm ir 查看,二者内容完全一致
  • llvm-dis对版本一致性要求严格,用哪个版本的llvm打包的bc就需要使用哪个版本的llvm-dis转换格式
  • konan依赖中llvm有两个版本,dev版本在kotlin项目出包时使用,包含的工具更多。为了减小体积,构建kn应用时会下载essential版本,只有kn构建过程中必须的依赖,不包含llvm-dis
  1. 找到dev版本kotlin社区llvm,如kn2.2的在 ~/.konan/dependencies/llvm-19-aarch64-macos-dev-75
  2. llvm-dis 命令在 bin 目录下,可以将bin目录export到PATH中:export PATH=$PATH:~/.konan/dependencies/llvm-19-aarch64-macos-dev-75/bin
  3. llvm-dis runtime.bc 会生成 runtime.ll,或者 llvm-dis runtime.bc -o - > temp 指定输出文件名

一步启动鸿蒙应用

参考 composeApp/build.gradle.kts 中 startHarmonyApp 任务的实现

任务配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
# DevEco Studio 安装路径
devEcoStudioDir=/Applications/DevEco-Studio.app
# 鸿蒙工程的路径:如果写相对路径是相对kotlin项目的根目录,如果写绝对路径就指哪就是哪
harmonyAppDir=harmonyApp
# 鸿蒙应用中入口模块所在的文件夹
harmonyAppEntryModuleDir=entry/
# 鸿蒙应用入口模块中,gradle打出来的kn项目 .h 放在哪个文件夹
hFileDir=src/main/cpp/include/
# 鸿蒙应用入口模块中,gradle打出来的kn项目 .so 放在哪个文件夹
soFileDir=libs/arm64-v8a/
# 启动应用中ability的名字,可以通过在DevEco Studio中点击运行,查看'hdc shell aa start -a'后面的参数,就是这个ability名字
abilityName = EntryAbility

执行过程(这里只简介,详细命令参考gradle任务实现或DevEco Studio中构建执行日志):

  1. 依赖 linkRelease/DebugSharedOhosArm64 任务,构建kotlin项目
  2. 将kotlin项目打包的 .so 和 .h 复制到鸿蒙应用中

后面的步骤都在鸿蒙应用中执行

  1. ohpm i,这步不改依赖就不需要重复执行,不过重复执行耗时也只是几秒,加上这个步骤比较保险
  2. 运行 hvigor sync 同步项目
  3. 打包鸿蒙应用
  4. hdc install 安装hap到手机
  5. 启动应用

现网问题反解

基本流程为出发布包时打release且debuggable的so,这个so中带符号表,保留下来用于出现问题时进行反解。在 harmonyApp/entry/build-profile.json5 中配置打 release 模式的hap包时对so进行strip,这样最终进入hap的so会被去掉符号表,可以通过解压 entry/build/default/outputs/default/entry-default-signed.hap 获取其中的so确定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"buildOptionSet": [
  {
    "name": "release",
    "arkOptions": {
      "obfuscation": {
        "ruleOptions": {
          "enable": false,
          "files": [
            "./obfuscation-rules.txt"
          ]
        }
      }
    },
    "nativeLib": {
      "debugSymbol": {
        "strip": true,
        "exclude": []
      }
    }
  },

当现网出现崩溃时,使用现网崩溃的地址+本地保留的带debug符号的release so,用addr2line反解,如

1
llvm-addr2line -fCe libkn.so cf2ec3

如需将debug符号上传到外部平台,可以去掉debuggable so中可执行代码部分,只保留debug符号表

1
2
llvm-objcopy --only-keep-debug libkn.so libkn.debug # 将debug符号复制到 libkn.debug 中,给问题监控平台上传这个so
llvm-objdump -dS libkn.debug > asm # 验证步骤,dump so中所有汇编代码,结果应该为空,确认so中不包含可执行代码

崩溃栈通常有多层,可以写个简单的脚本批量通过偏移量解析文件名和行号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
"""
➜  stack ls -lah 
total 30M
drwxr-xr-x 2 user user 4.0K Sep  5 20:19 .
drwxr-xr-x 5 user user 4.0K Sep  5 09:11 ..
-rwxr-xr-x 1 user user  23M Sep  5 09:14 libkn.debug
-rwxr-xr-x 1 user user 6.9M Sep  5 09:12 llvm-addr2line
-rw-r--r-- 1 user user  764 Sep  5 15:01 symbolicate.py
"""

import subprocess

so_name = "libkn.debug"
addresses = input("Plz input addresses:\n")

addresses = addresses.strip().split(" ")
addresses = [addr for addr in addresses if addr and len(addr) != 0]

for address in addresses:
    cmd = ["./llvm-addr2line", "-fCe", so_name, address]
    try:
        result = subprocess.run(cmd, check=True, capture_output=True, text=True)
        res = result.stdout.strip().split("\n")
        # res[1] = res[1].replace("/path/to/ci/runner/project/", "/path/to/local/project/") # 将出包机器的项目文件夹路径换成自己本地的,方便跳转
        print(f"{address}\tfile://{res[1]}\t{res[0]}")
    except subprocess.CalledProcessError as e:
        print(f"Error running command for address {address}: {e.stderr}")

反解完成后命令行会输出文件路径。vscode中ctrl+鼠标左键,intellj idea或as中左键点击地址可以直接打开对应对应代码行

传递kotlin版本

Kotlin / KuiklyBase-Kotlin仓库(包含kotlin标准库,KN编译器等)运行出包脚本后,打包的产物都在 build/repo 文件夹下。线下传递版本

  1. 打包要传递的版本前最好先删除build文件夹再出包,避免文件夹中存在多个版本,压缩没用的东西
  2. 在出包机器上打包kotlin项目,zip build/repo 文件夹
  3. 传递zip,在运行机器上解压
  4. 在sample项目中添加 maven(”/path/to/build/repo”) 指向解压的本地仓库
  5. 在 gradle/libs.version.toml 中切换kotlin版本为传递的zip包中版本
  6. kotlin native编译器在 ~/.konan 文件夹中的依赖不受gradle管理,保险起见可以删除本地的 ~/.konan 文件夹,避免部分依赖因为已经存在不被更新
  7. sample项目中首次使用本地版本建议gradle命令添加 —refresh-dependencies —rerun-tasks ,确保用上更新了的依赖

单打鸿蒙

EnabledTargets.kt

1
fun enabledTargets(platformManager: PlatformManager) = listOf(KonanTarget.OHOS_ARM64) // 单出鸿蒙

gradle task详细状态

命令行执行gradle,任务名称在命令行最下面动态刷新,执行完看不到每个task这次任务是否被执行。配置gradle.properties org.gradle.console=verbose 可以让任务执行状态不刷掉旧的,执行完成后也可以查看

image.png

All Rights Reserved.