Kotlin/Native 构建(二)

1,093 阅读8分钟

文接上一篇# Kotlin/Native 构建(一)

Kotlin/Native 预构建工具链

Kotlin/Native 项目在 AS 中构建时,会下载 Kotlin/Native 预构建工具链Kotlin/Native 项目所需依赖库文件。它们下载位置位于:

  • Kotlin/Native 预构建工具链:~/.konan/kotlin-native-prebuilt-macos-aarch64-2.1.10-RC2
  • Kotlin/Native 项目所需依赖库:~/.konan/dependencies

目录结构:

.konan

├── dependencies

│   ├── aarch64-unknown-linux-gnu-gcc-8.3.0-glibc-2.25-kernel-4.9-2  # arm64 Linux 交叉编译工具链

│   ├── cache

│   ├── libffi-3.3-1-macos-arm64

│   ├── lldb-4-macos # LLVM 调试器

│   ├── llvm-16.0.0-aarch64-macos-essentials-63 # arm64 macos LLVM 16 工具链

│   ├── msys2-mingw-w64-x86_64-2 # Windows 交叉编译工具链

│   ├── target-sysroot-1-android_ndk # Android NDK 交叉编译的系统根目录

│   ├── target-toolchain-2-osx-android_ndk # macOS 到 Android 的 NDK 交叉编译工具链

│   └── x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2 # x86_64 Linux 交叉编译工具链

└── kotlin-native-prebuilt-macos-aarch64-2.1.10-RC2

    ├── bin # 存放编译和调试工具

    ├── klib # 用于存储预编译库(类似于 Java 的 `.jar`)

    ├── konan # 用于存储运行时库和目标平台支持

    ├── licenses # 许可证文件

    ├── provisioned.ok

    ├── sources # Kotlin 标准库源码

    └── tools # 工具目录
  • kotlin-native-prebuilt 文件:有构建工具链(负责前端编译,后端编译,链接),平台适配(macOS aarch64 架构的交叉编译配置,与 macOS 系统 API交互的必要头文件和符号)等。
  • dependencies 文件:有 Kotlin/Native 标准库,标准 C 运行时库,交叉编译工具链,目标平台的 sysroot(系统根目录)等。

kotlin-native-prebuilt

目录结构:

.

├── bin

│   ├── cinterop # C 互操作工具(生成 .klib)

│   ├── generate-platform # 生成目标平台定义

│   ├── jsinterop # JavaScript 互操作(实验性)

│   ├── klib # 处理 .klib 库文件

│   ├── konan-lldb # LLDB 调试工具

│   ├── konanc # Kotlin/Native 编译器

│   ├── kotlinc-native # Kotlin/Native 编译器(可执行入口)

│   └── run_konan # Kotlin/Native 运行时入口

├── klib

│   ├── cache # 已编译的 klib 缓存

│   ├── common # 公共 Kotlin/Native 库

│   ├── commonized # 针对不同平台的库

│   └── platform # 平台相关库(iOS、macOS、Linux)

├── konan

│   ├── konan.properties # 配置 Kotlin/Native 版本和路径

│   ├── lib # Kotlin/Native 运行时库

│   ├── nativelib # 目标平台的本机库

│   ├── platformDef # 平台定义文件

│   ├── platforms # 不同目标平台配置

│   ├── swift_export # Swift 互操作相关工具

│   └── targets # 交叉编译的目标平台文件

├── licenses

│   ├── COPYRIGHT.txt

│   ├── COPYRIGHT_HEADER.txt

│   ├── LICENSE.txt

│   ├── NOTICE.txt

│   ├── README.md

│   └── third_party

├── provisioned.ok

├── sources

│   └── kotlin-stdlib-native-sources.zip # Kotlin 标准库源码

└── tools

    ├── env_blacklist

    └── konan_lldb.py

这里主要介绍一下 bin 目录。

bin 目录

bin/ 目录包含 Kotlin/Native 核心工具,用于编译、调试、C 互操作、库管理等。

konanc

konanc:Kotlin/Native 编译器。

新建一个 Main.kt:

import kotlin.experimental.ExperimentalNativeApi

fun main() {
    println("Hello from Kotlin/Native")
}

@OptIn(ExperimentalNativeApi::class)
@CName("nativeFunction")
fun nativeFunction(): String {
    return "Hello from Kotlin/Native."
}

生成目标平台 linux_arm64 动态库 *.so:

konanc Main.kt -o kn -p dynamic -target linux_arm64

在 Main.kt 同级目录下会生成:

.

├── Main.kt

├── kn_api.h # 头文件

└── libkn.so # arm64_v8a so

生成目标平台 ios_arm64 *.framework:

konanc  Main.kt -o kn  -p framework -target ios_arm64

在 Main.kt 同级目录下会生成:

.

├── Main.kt

├── kn.framework

│   ├── Headers

│   │   └── kn.h

│   ├── Info.plist

│   ├── Modules

│   │   └── module.modulemap

│   └── kn

└── kn.framework.dSYM

    └── Contents

        ├── Info.plist

        └── Resources

            ├── DWARF

            │   └── kn

            └── Relocations

                └── aarch64

                    └── kn.yml

直接编译 kotlin 源码:

konanc  Main.kt -o kn

生成 .kexe 可运行文件:

.

├── Main.kt

├── kn.kexe

└── kn.kexe.dSYM

    └── Contents

        ├── Info.plist

        └── Resources

            ├── DWARF

            │   └── kn.kexe

            └── Relocations

                └── aarch64

                    └── kn.kexe.yml

执行 kn.kexe:

./kn.kexe

Hello from Kotlin/Native
kotlinc-native

kotlinc-native:Kotlin/Native 编译器入口。提供与 konanc 相同操作命令,在 Kotlin/Native 项目中,使用 kotlinc-native 更好。

指定本地 llvm 路径:

kotlinc-native -Xllvm-variant={dev|user|absolute path to llvm}

dev: 包含额外开发工具的较大 LLVM 发行版。

user: 仅包含必要工具的较小 LLVM 发行版。

<absolute path>: 本地 LLVM 发行版的绝对路径。

更多高级编译器选项:kotlinc-native -X

cinterop

cinterop:C 互操作工具。

与 C 库 curl 交互,定义一个文件 curl.def查看def文件定义规则):

headers = curl/curl.h # 指定要导入的 C/C++ 头文件
headerFilter = curl/* # 指定要过滤的头文件内容
compilerOpts.osx = -I/opt/homebrew/include # 指定 macOS 平台编译器的选项
linkerOpts.osx = -L/opt/homebrew/lib -lcurl # 指定 macOS 平台链接器的选项,在`/opt/homebrew/lib` 目录下找 curl

生成 macOS平台 klib 文件:

cinterop -def curl.def -o curl

在 curl.def 同级目录下会生成:

.

├── curl-build

│   ├── manifest.properties

│   └── natives

│       └── cstubs.bc

├── curl.def

└── curl.klib

通过 curl.klib 就可以直接使用 curl:

import curl.CURLOPT_URL
import curl.curl_easy_cleanup
import curl.curl_easy_init
import curl.curl_easy_perform
import curl.curl_easy_setopt
import kotlin.experimental.ExperimentalNativeApi
import kotlinx.cinterop.*


@OptIn(ExperimentalForeignApi::class)
fun main() {
    memScoped {
        val curl = curl_easy_init()
        if (curl != null) {
            val url = "https://www.bilibili.com"
            curl_easy_setopt(curl, CURLOPT_URL, url)
            curl_easy_perform(curl)
            curl_easy_cleanup(curl)
        } else {
            println("Failed to initialize curl")
        }
    }
}
klib

klib:管理 Kotlin/Native 预编译库 (.klib),类似 Java 的 .jar。 查看 klib 信息:

klib info curl.klib


Full path: /android-workplace/kmp-native-wizard/src/nativeInterop/cinterop/curl.klib

Module name (metadata): <curl>

Non-empty package FQNs (1):

  curl

Has IR: false

Has LLVM bitcode: true

Manifest properties:

  abi_version=1.201.0

  builtins_platform=NATIVE

  compilerOpts.osx=-I/opt/homebrew/include

  compiler_version=2.1.10-RC2

  depends=stdlib org.jetbrains.kotlin.native.platform.posix

  exportForwardDeclarations=cnames.structs.curl_mime cnames.structs.curl_mimepart cnames.structs.curl_pushheaders cnames.structs.Curl_URL

  headerFilter=curl/*

  headers=curl/curl.h

  includedForwardDeclarations=cnames.structs.curl_mime cnames.structs.curl_mimepart cnames.structs.curl_pushheaders cnames.structs.Curl_URL

  includedHeaders=usr/include/curl/curl.h usr/include/curl/curlver.h usr/include/curl/system.h usr/include/curl/easy.h usr/include/curl/multi.h usr/include/curl/urlapi.h usr/include/curl/options.h usr/include/curl/header.h usr/include/curl/websockets.h 738c17d164114f55f5df47f14b063c84ed7a4004dfcf35b65466dcc2c87c9d96

  interop=true

  ir_provider=kotlin.native.cinterop

  ir_signature_versions=1,2

  linkerOpts.osx=-L/opt/homebrew/lib -lcurl

  metadata_version=1.4.1

  native_targets=macos_arm64

  package=curl

  unique_name=curl

generate-platform

generate-platform:生成 Kotlin/Native 的目标平台配置,用于跨平台支持。

生成平台 ios_arm64 配置:

generate-platform -target ios_arm64

默认输入路径为:.konan/kotlin-native-prebuilt-macos-aarch64-2.1.10-RC2/konan/platformDef/linux_arm64
默认输出路径为:.konan/kotlin-native-prebuilt-macos-aarch64-2.1.10-RC2/klib/platform/linux_arm64
jsinterop(已被移除)

jsinterop:与 JavaScript 进行互操作(实验性)。

konan-lldb

konan-lldb:提供 LLDB 调试支持,用于断点调试和查看变量。

konan-lldb kn.kexe
run_konan

run_konan:运行 .kexe 文件。

dependencies

目录结构:

.

├── aarch64-unknown-linux-gnu-gcc-8.3.0-glibc-2.25-kernel-4.9-2 # arm64 Linux 交叉编译工具链

├── cache

├── libffi-3.3-1-macos-arm64

├── lldb-4-macos # LLVM 调试器

├── llvm-16.0.0-aarch64-macos-essentials-63 # macos arm64 LLVM 16 工具链

├── msys2-mingw-w64-x86_64-2

├── target-sysroot-1-android_ndk

├── target-toolchain-2-osx-android_ndk

└── x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2

这里主要介绍 llvm-16.0.0-aarch64-macos-essentials-63 目录。

llvm-16.0.0-aarch64-macos-essentials-63

llvm-16.0.0-aarch64-macos-essentials-63/ 目录包含 arm macos LLVM 16 工具链(clanglldllvm-ar 等),用于Kotlin/Native 代码编译、优化和链接。 目录结构:

.

├── bin

│   ├── clang -> clang-16

│   ├── clang++ -> clang

│   ├── clang-16

│   ├── clang-cache -> clang

│   ├── ld.lld -> lld

│   ├── lld

│   ├── llvm-ar

│   ├── llvm-cov

│   ├── llvm-profdata

│   └── wasm-ld -> lld

└── lib

    ├── clang

    └── libclang.dylib

bin/目录包含 macos arm64 LLVM 执行工具,用于 Kotlin/Native 编译过程。

clang/clang++

C/C++ 项目构建流程主要包含:预处理、编译、汇编、链接。流程阶段产物:.i.s.o.so or .dylib

新建一个 Main.cpp:

#include <iostream>

int main() {
    std::cout << "Hello from Kotlin/Native." << std::endl;
    return 0;
}

编译 c++ 代码:

clang++ Main.cpp -o Main  --sysroot=$(xcrun --show-sdk-path)

xcrun --show-sdk-path=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk

在 Main.cpp 同级目录下会生成 Main。Main 是可执行文件:

./Main
Hello from Kotlin/Native.

编译生成汇编代码.s

clang++ -S Main.cpp -o Main.s  --sysroot=$(xcrun --show-sdk-path)

编译生成目标文件.o

clang++ -c Main.cpp -o Main.o  --sysroot=$(xcrun --show-sdk-path)

链接成可执行文件.dylib

clang++ -shared -o Main.dylib  Main.o --sysroot=$(xcrun --show-sdk-path)

查看 .dylib信息:

dyld_info Main.dylib
Main.dylib [arm64]:

    -platform:

        platform     minOS      sdk

           macOS     14.0      14.0   

    -segments:

        load-offset   segment section        sect-size  seg-size perm

        0x00000000    __TEXT                                16KB r.x

        0x00003170             __text             3128

        0x00003DA8             __stubs             192

        0x00003E68             __gcc_except_tab    124

        0x00003EE4             __cstring            26

        0x00003F00             __unwind_info       256

        0x00004000    __DATA_CONST                          16KB rw.

        0x00004000             __got               152

    -dependents:

        attributes     load path

                       /usr/lib/libc++.1.dylib

                       /usr/lib/libSystem.B.dylib

Main.dylib依赖 C++ 标准库(libc++):/usr/lib/libc++.1.dylib,和核心系统库(libc标准 C 库、libm数学库、libpthread线程库等):/usr/lib/libSystem.B.dylib

交叉编译

查看clang/clang++ 版本信息:

./clang++ --version

clang++ version 16.0.0 (https://github.com/Kotlin/llvm-project 4e2cc6a8ac2afae0d0ddde4a51818642f0f5304f)

Target: arm64-apple-darwin23.3.0

Thread model: posix

InstalledDir: ~/.konan/dependencies/llvm-16.0.0-aarch64-macos-essentials-63/bin/.

首先 llvm 版本为 16.0.0,交叉编译需要关注一下版本号匹配。

Target 组成为:<CPU架构>-<厂商>-<操作系统>-<环境>,Target: arm64-apple-darwin23.3.0 表示 arm64 CPU架构,apple 厂商,darwin23.3.0 操作系统。

指定交叉编译 aarch64-unknown-linux-gnu 目标平台:arm64 CPU架构,未知厂商,linux 系统,gnu 环境

./clang++  -target aarch64-unknown-linux-gnu

指定交叉编译 aarch64-unkonwn-linux-android30 目标平台:arm64 CPU架构,未知厂商,linux 系统,android30 环境

./clang++  -target aarch64-unkonwn-linux-android30

交叉编译通常需要指定参数:指定头文件搜索路径(#include):./clang++ -I <dir>;指定库文件搜索路径(.so, .a):./clang++ -L <dir>;指定系统根目录,头文件 & 库的默认路径:-isysroot <dir>

使用 llvm-16.0.0-aarch64-macos-essentials-63/bin/clang++ 生成 arm64 Android 平台 *.so

./clang++  -target aarch64-unknown-linux-android30 -shared -o libMain.so Main.cpp -fuse-ld=lld --sysroot=/Users/wangjiang/Public/software/android-sdk-macos/ndk/28.0.12916984/toolchains/llvm/prebuilt/darwin-x86_64/sysroot -L /Users/wangjiang/Public/software/android-sdk-macos/ndk/28.0.12916984/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/19/lib/linux/aarch64

这里 -shared 表示生成共享库;-fuse-ld=lld 表示指定 llvm 的 ld.lld 链接器;ndk 版本为 28.0.12916984。

查看生成的 libMain.so:

file libMain.so

libMain.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, not stripped
ld.lld

ld.lld 链接 *.o文件生成目标产物.so / .dylib

链接生成 libMain.so:

./ld.lld -shared -o libMain.so Main.o --sysroot=/Users/wangjiang/Public/software/android-sdk-macos/ndk/28.0.12916984/toolchains/llvm/prebuilt/darwin-x86_64/sysroot -L /Users/wangjiang/Public/software/android-sdk-macos/ndk/28.0.12916984/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/19/lib/linux/aarch64

使用 clang/clang++ 与 ld.lld 直接链接生成的目标产物 .so/.dylib是有所不同的,clang/clang++ 生成时,会默认链接链接 C++ 标准库(如 libc++_shared.so)和 C 标准库(如 libc.so)等。而 ld.lld 直接链接生成时,不会自动引入标准库,除非手动指定。

构建流程汇总

在 Kotlin/Native 项目构建过程中,使用 kotlin-native-prebuilt 和 dependencies 相关流程为:

graph TD
    A[Source Code Kotlin] -->|Kotlin Compiler| B[kotlinc-native]
    B -->|Generates| C[IR Intermediate Representation]
    C -->|LLVM Backend| D[LLVM IR]
    D -->|Optimized by LLVM| E[Optimized LLVM IR]
    E -->|LLVM Code Generation| F[Machine Code]
    
    subgraph kotlin-native-prebuilt-macos-aarch64-2.1.10-RC2
        B[kotlinc-native] -->|Calls| G[konanc]
        G -->|Interacts with| H[cinterop]
        G -->|Uses| I[klib]
        G -->|Debugging Support| J[konan-lldb]
    end
    
    subgraph dependencies
        K[LLVM Toolchain]
        L[Clang]
        M[LLD Linker]
        N[Sysroot / Libs]
    end
    
    G -->|Uses| K
    K -->|Provides| L
    K -->|Provides| M
    K -->|Provides| N
    L -->|Compiles| F
    M -->|Links| O[Executable .kexe or Shared Library .so/.dylib/.framework]

总结

Kotlin/Native 项目通过 kotlin-native-prebuilt预构建工具链和 dependencies依赖库一起完成从 Kotlin 代码生成可运行的本机二进制文件的任务。

在构建过程中,Kotlin 代码首先通过 Kotlin 编译器生成 IR,IR 通过 LLVM Backend 生成 LLVM IR,LLVM IR 通过 LLVM 生成机器码,最后机器码通过链接器生成目标平台产物 .so\.framework

这其中,kotlinc-native 与 clang/clang++ 以及 ld.lld 工具一起工作。Kotlin/Native 和 C/C++ 项目构建最后一步相同,把机器码.o链接成二进制文件.so\.framework

在 AS 中构建 Kotlin/Native 项目,看不到构建过程,通过了解底层kotlinc-native、clang/clang++、ld.lld的工作方式,能解决Kotlin/Native 项目支持Android,iOS,Harmony所遇到的构建问题。