Kotlin/Native 构建(一)

1,687 阅读5分钟

前言

为了让 Kotlin Multiplatform 的逻辑共享更丝滑的支持 Android, iOS, Harmony 平台,如访问数据库,网络,多线程,多媒体逻辑等,采用 Kotlin/Native 是一种不错的方式。

没有 JVM 环境,也能让 Kotlin 代码在 macOS, Linux, Windows, AndroidiOS, Harmony 等平台上运行,这就是 Kotlin Multiplatform 中 Kotlin/Native 所做的事情。

Kotlin/Native 可以将 Kotlin 代码生成可运行的本机二进制文件:*.so*.framework*.so 可以给 Android 和 Harmony使用,*.framework 可以给 iOS使用。

Kotlin/Native 能生成可运行的本机二进制文件,主要是依靠:一个基于LLVM的 Kotlin 编译器后端和一个 Kotlin 标准库的本机实现。

LLVM

LLVM 项目是模块化和可重用编译器和工具链技术的集合。尽管 LLVM 的名称如此,但它与传统虚拟机几乎没有关系。名称“LLVM”本身并不是缩写;它是项目的全名。

LLVM 最初是伊利诺伊大学的一个研究项目,其目标是提供一种基于 SSA 的现代编译策略,能够支持任意编程语言的静态和动态编译。从那时起,LLVM 已发展成为一个由许多子项目组成的综合项目,其中许多子项目被各种 商业和开源项目用于生产,并被广泛用于学术研究。LLVM 项目中的代码根据 “Apache 2.0 许可证(LLVM 例外)”获得许可。

LLVM 主要用于程序的编译、优化和代码生成。

LLVM 的工作流程:

graph TD
    A[源代码] --> |前端| B[LLVM IR]
    B --> |优化| C[优化后的 LLVM IR]
    C --> |后端| D[目标机器代码]
    D --> |链接器| E[可执行文件]
    
    subgraph "LLVM 架构"
        B --> |分析 & 变换| C
        C --> |指令选择 & 寄存器分配| D
    end

上面工作流程中的重要组成部分包含:

  • 源代码: 开发者编写的各种编程语言的源代码。

  • 前端: 不同的编程语言有不同的前端,负责将源代码转换为 LLVM IR。

  • LLVM IR: 一种平台无关的中间表示形式,方便后续的优化和代码生成。

  • 优化器: 对 LLVM IR 进行各种优化,提高代码执行效率。

  • 后端: 将优化后的 LLVM IR 转换为目标平台的机器码。

  • 机器码: 可以在目标平台上直接运行的二进制代码。

  • 链接器: 将生成的目标代码与库文件链接,形成最终的可执行文件或库。

在 Kotlin/Native 中,通过 LLVM 实现 Kotlin 代码到本机二进制的转换。

C/C++ 项目构建工具和构建流程

Kotlin/Native 主要是与C/C++交互,产物也是对接 Native 项目,所以先了解一下 C/C++项目构建工具和构建流程。

C/C++ 项目构建工具有:Make, CMake, Bazel, Ninja, Meson, Autotools。在 Android Native 和 Harmony Native 项目中,都使用 CMake 构建工具。

CMake 适合跨平台中大型项目,通过 CMakeLists.txt 配置构建项目:

# 指定最低 CMake 版本
cmake_minimum_required(VERSION 3.10)

# 定义项目名称
project(MyProject)

# 设置 C++ 标准版本
set(CMAKE_CXX_STANDARD 17)
# 强制使用指定 C++ 版本
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 添加子目录
add_subdirectory(external/SomeLibrary)

# 头文件和源文件
include_directories(${PROJECT_SOURCE_DIR}/include)
aux_source_directory(src SRC_LIST)

# 目标
add_executable(my_program ${SRC_LIST})
target_link_libraries(my_program SomeLibrary)

# 编译选项
target_compile_options(my_program PRIVATE -Wall -Wextra -O2)

# 产物输出
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

# 平台相关
if(WIN32)
    message(STATUS "Building for Windows")
    add_definitions(-DPLATFORM_WINDOWS)
elseif(APPLE)
    message(STATUS "Building for macOS")
elseif(UNIX)
    message(STATUS "Building for Linux")
endif()

C/C++ 项目构建流程主要包含:预处理、编译、汇编、链接。

阶段主要作用工具产物
预处理(Preprocessing)处理宏、头文件包含、条件编译指令预处理器(如 cpp.i(预处理后的代码)
编译(Compilation)将预处理后的代码转换为汇编代码编译器(如 gccclangMSVC.s(汇编代码)
汇编(Assembly)将汇编代码转换为机器码(目标文件)汇编器(如 as.o 或 .obj(目标文件)
链接(Linking)合并目标文件和库,生成可执行文件或共享库链接器(如 ldlldMSVC link.exe可执行文件(如 appapp.exe)或动态库(如 .so.dll

Kotlin/Native 项目构建工具和构建流程

Kotlin/Native 项目构建工具:Gradle。通过 build.gradle.kts 配置构建项目:

//构建二进制文件
kotlin {
   //生成ios平台下的 .framework
    val xcf = XCFramework()
    val iosTargets = listOf(iosX64(), iosArm64(), iosSimulatorArm64())
    iosTargets.forEach {
        it.binaries.framework {
            baseName = "shared"
            xcf.add(this)
        }
    }
    //生成android平台下的 .so
    val androidTargets = listOf(androidNativeArm64(), androidNativeX64())
    androidTargets.forEach {
        it.binaries {
            executable {

            }
            sharedLib {
                baseName = "shared"
            }
        }
    }
    //生成linux平台下的 .so
    val linuxTargets = listOf(linuxArm64(), linuxX64())
    linuxTargets.forEach {
        it.binaries {
            executable {

            }
            sharedLib {
                baseName = "shared"
            }
        }
    }
}

Kotlin/Native 项目构建流程主要包含:前端(frontend)编译,后端(backend)编译,链接(linking)

  • 前端(frontend)编译 :解析 Kotlin 代码,生成中间表示(IR)。
  • 后端(backend)编译 :优化 IR,并用 LLVM 生成目标平台的机器码。
  • 链接(linking) :与 C/C++ 代码、标准库和其他依赖项链接,生成最终的可执行文件或动态库。

KMP 项目使用 K2 编译器:

k2-compiler-architecture.svg

在 Kotlin/Native 项目中,经过前端编译后得到 Kotlin IR,Native backend 首先将 Kotlin IR 转换为 LLVM IR,LLVM IR 再通过 LLVM 后端生成目标平台机器码,机器码最后经过链接生成最终可执行文件。

graph TD
    A[Kotlin 源代码 *.kt] -->|解析| B[Kotlin 编译器 K2]
    B -->|生成 IR| C[Kotlin IR]
    C -->|优化 & 转换| D[LLVM IR]
    D -->|优化 & 代码生成| E[目标架构的汇编代码]
    E -->|链接 C 库 & 运行时| F[目标产物 .kexe / .so / .dylib / .framework]
    
    subgraph Kotlin/Native 构建流程
        A --> B --> C --> D --> E --> F
    end

总结

在 Kotlin/Native 项目中使用 Gradle 工具构建,在 C/C++ 项目中使用 Cmake 工具构建。在 Kotlin/Native 项目中的主要构建流程是前端编译、后段编译、链接,在 C/C++项目中的主要构建流程是预处理、编译、汇编、链接。

了解 C/C++ 项目与 Kotlin/Native 项目在构建工具和构建流程上的差异,有助于解决 Kotlin/Native 产物在Andriod, iOS, Harmony, Linux, Macos, Windows等上遇到的跨平台问题,比如找不到符号,找不到依赖库,交叉编译失败等。

下一篇Kotlin/Native 预构建工具链