Kotlin/Native 编译器开发技巧
Gradle并发下载依赖
通过IDEA调试gradle任务
- clone kotlin项目https://github.com/linhandev/kuiklybase-kotlin
- 使用intellj idea打开kotlin项目
- 在kotlin项目右上角添加构建compose-sample项目句gradle任务配置
其中gradle project被构建的项目地址,如ovcomposesample项目地址。run命令填写构建那个项目的命令,如 ./gradlew publishDebugBinariesToHarmonyApp
- 在kotlin仓库,kotlin编译器代码中打断点,进行调试
调试gradle命令
- gradle命令添加-Dorg.gradle.debug=true,如
1
2
3
./gradlew :kotlin-native:dist
# 添加后
./gradlew :kotlin-native:dist -Dorg.gradle.debug=true
- 添加jvm remote debug profile,所有东西都默认
调试java命令
如果命令不是gradle启动的,是直接调用了konanc这种脚本没法添加 -Dorg.gradle.debug=true,需要手动修改java命令插入调试相关选项
- 获取jvm中运行的命令,找到 ~/.konan 目录下对应版本的prebuilt文件夹,使用腾讯版本文件夹名带kba,使用ohos版本文件夹名带ohos,使用本地指定的的就是 kotlin.native.home 指向的文件夹。修改其中的 bin/run_konan 脚本,将最后一行的 java 命令打到文件中
- 在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
- 在kotlin项目中添加 remote jvm debug 配置,所有选项默认
- 运行修改过的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
准备
- 在sample根目录的gradle.properties中设置kotlin.native.home到本地的 kotlin/kotlin-native/dist 文件夹
- 设置下面脚本里的三个路径
过程:
- kotlin项目中跑 :kotlin-native:runtime:ohos_arm64Runtime task将runtime从cpp代码打成bc,输出在 kotlin/kotlin-native/runtime/build/bitcode/main/ohos_arm64
- 手动将 kotlin/kotlin-native/runtime/build/bitcode/main/ohos_arm64 中的bc复制到 kotlin/kotlin-native/dist/konan/targets/ohos_arm64/native
- 重新打包ovcomposesample
- 启动应用
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,验证修改生效了
kotlin-native文件夹下代码跳转
Kotlin Native构建的时候需要特殊的gradle.properties参数,kotlin项目拉下来之后直接sync后,kotlin-native部分的代码都无法点击跳转到定义
- 拉取项目, bash scripts/build-ohos.sh 运行构建
- 本地出包完成后,在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
- 执行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
- 找到dev版本kotlin社区llvm,如kn2.2的在 ~/.konan/dependencies/llvm-19-aarch64-macos-dev-75
- llvm-dis 命令在 bin 目录下,可以将bin目录export到PATH中:export PATH=$PATH:~/.konan/dependencies/llvm-19-aarch64-macos-dev-75/bin
- 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中构建执行日志):
- 依赖 linkRelease/DebugSharedOhosArm64 任务,构建kotlin项目
- 将kotlin项目打包的 .so 和 .h 复制到鸿蒙应用中
后面的步骤都在鸿蒙应用中执行
ohpm i,这步不改依赖就不需要重复执行,不过重复执行耗时也只是几秒,加上这个步骤比较保险- 运行 hvigor sync 同步项目
- 打包鸿蒙应用
- hdc install 安装hap到手机
- 启动应用
现网问题反解
基本流程为出发布包时打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 文件夹下。线下传递版本
- 打包要传递的版本前最好先删除build文件夹再出包,避免文件夹中存在多个版本,压缩没用的东西
- 在出包机器上打包kotlin项目,zip build/repo 文件夹
- 传递zip,在运行机器上解压
- 在sample项目中添加 maven(”/path/to/build/repo”) 指向解压的本地仓库
- 在 gradle/libs.version.toml 中切换kotlin版本为传递的zip包中版本
- kotlin native编译器在 ~/.konan 文件夹中的依赖不受gradle管理,保险起见可以删除本地的 ~/.konan 文件夹,避免部分依赖因为已经存在不被更新
- 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 可以让任务执行状态不刷掉旧的,执行完成后也可以查看








