52. 附录D:工具链文件模板集

引言:施工队长的“跨国驾照”

在第八章中,我们的 CMake“施工队长”曾多次远赴海外工地(交叉编译),每次都依靠一份名为工具链文件(Toolchain File)的“跨国工程护照”。这份文件告诉队长:当地的官方语言是什么(目标系统)、司机该用哪国的驾照(编译器)、建材该去哪家市场采购(查找路径),以及遵守什么样的当地法规(链接标志)。

但在实际工程中,很多新手面对一张白纸开始写工具链文件时,往往会陷入迷茫:变量名该怎么拼?路径该怎么设?ONLYBOTH到底有什么区别?为了让你不必每次都从零“手绘护照”,这个附录准备了几份可直接拿来改改路径就能用的模板。它们就像队长抽屉里常备的几份空白表格——填上目的地和日期,盖章就能出发。

通用交叉编译模板

这是最基础、最万金油的一份模板。无论你交叉编译的目标是什么嵌入式 Linux 设备,只要它有一个标准的 GCC/L Clang 工具链,都可以先套用这份骨架,再根据具体平台微调。

基础结构

工具链文件的核心是告诉 CMake“我现在不是在为本机编译,而是在为另一台机器编译”。因此必须设置以下两个“身份标识”:

  • CMAKE_SYSTEM_NAME:目标操作系统名称,如 LinuxGeneric 等。
  • CMAKE_SYSTEM_PROCESSOR:目标 CPU 架构,如 armaarch64x86_64mips 等。

关键变量设置

除了身份标识,还需要配置“五金件”和“采购规则”:

  • 编译器:通过 CMAKE_C_COMPILERCMAKE_CXX_COMPILER 指定交叉编译器。
  • 系统根目录(sysroot):通过 CMAKE_SYSROOTCMAKE_FIND_ROOT_PATH 指向目标平台的根文件系统,里面包含了目标平台的头文件和库。
  • 查找策略:控制 CMake 在寻找程序、库、头文件和包时的搜索范围,防止把宿主机上的库错链进目标程序。
# generic-toolchain.cmake
# CMake 通用交叉编译工具链模板

# 1. 声明目标平台身份
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)

# 2. 指定交叉编译器(请按实际安装路径修改)
set(CMAKE_C_COMPILER   /opt/cross/bin/aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER /opt/cross/bin/aarch64-linux-gnu-g++)

# 3. 指定 sysroot(可选但强烈推荐)
set(CMAKE_SYSROOT /path/to/your/target/rootfs)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})

# 4. 配置查找策略
#    PROGRAM:只在宿主机查找(如 cmake 自身的辅助工具)
#    LIBRARY/INCLUDE/PACKAGE:只在 sysroot 里查找
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

# 5. 额外的编译/链接标志(按需启用)
# set(CMAKE_C_FLAGS_INIT "-mcpu=cortex-a72" CACHE STRING "" FORCE)
# set(CMAKE_CXX_FLAGS_INIT "-mcpu=cortex-a72" CACHE STRING "" FORCE)
# set(CMAKE_EXE_LINKER_FLAGS_INIT "-Wl,-rpath-link,${CMAKE_SYSROOT}/usr/lib/aarch64-linux-gnu")

使用这份模板时,你通常只需要改三处:

  1. aarch64 换成你的目标架构(如 armriscv64);
  2. 把编译器路径改成你工具链的实际安装位置;
  3. CMAKE_SYSROOT 指向你的目标根文件系统(如果你有的话)。

Android NDK 模板

Android 是交叉编译的高频场景,但 NDK 有自己独特的目录结构和 ABI 体系。如果直接套用通用模板,很容易在 STL 类型、API 级别或架构名称上踩坑。下面这份模板专门为现代 NDK(r21+,基于 LLVM 工具链)设计。

NDK 路径与 ABI 选择

现代 NDK 已经把 GCC 移除,统一使用 Clang。不同架构的编译器放在 toolchains/llvm/prebuilt/<host-os>/bin/ 下,命名规则是:

<arch>-linux-android<api-level>-clang[++]

常见的 CMAKE_ANDROID_ARCH_ABI 取值包括 armeabi-v7aarm64-v8ax86x86_64

STL 与平台配置

Android 的 C++ 标准库需要显式声明。推荐使用 c++_sharedc++_static。同时,CMAKE_SYSTEM_VERSION 对应的是 Android 的 API Level(如 21、28、33),而非 Android 版本号。

# android-ndk-toolchain.cmake
# Android NDK 交叉编译工具链模板(NDK r21+)

set(CMAKE_SYSTEM_NAME Android)

# 1. API Level(最低支持的 Android 版本)
set(CMAKE_SYSTEM_VERSION 21)

# 2. 目标 ABI
set(CMAKE_ANDROID_ARCH_ABI arm64-v8a)
# 其他可选值:armeabi-v7a, x86, x86_64

# 3. NDK 根目录(请按实际路径修改)
set(CMAKE_ANDROID_NDK /home/user/Android/Sdk/ndk/25.2.9519653)

# 4. STL 类型
set(CMAKE_ANDROID_STL_TYPE c++_shared)
# 可选:c++_static, none, system

# 5. 显式指定编译器(可选,CMake 3.21+ 通常能自动推导)
set(CMAKE_C_COMPILER
    ${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang)
set(CMAKE_CXX_COMPILER
    ${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang++)

# 6. 查找策略(防止 CMake 找到宿主机上的库)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

# 7. 若使用 c++_shared,记得把 libc++_shared.so 打包进 APK
# 可以通过 CMake 的 install 或 Gradle 配置完成

队长提示:如果你发现 CMake 报 “cannot find -lc++” 之类的链接错误,通常是因为 c++_shared 运行时库没有正确拷贝到设备。NDK 里这个库位于 sources/cxx-stl/llvm-libc++/libs/<abi>/libc++_shared.so,务必在打包 APK 时把它塞进去。

嵌入式 Linux 模板

树莓派、工业网关、车载系统等嵌入式 Linux 设备,通常使用 ARM Cortex-A 系列或新兴的 RISC-V 架构。它们的工具链往往由芯片厂商或社区提供(如 arm-linux-gnueabihfaarch64-linux-gnuriscv64-linux-gnu)。

ARM 架构配置

ARM 嵌入式世界有个容易混淆的概念:硬浮点(hf)软浮点(sf)。如果你的目标板子带 FPU/VFP,通常使用 arm-linux-gnueabihf;如果是老旧的软浮点处理器,则使用 arm-linux-gnueabi。选错会导致程序在目标机上出现 “Illegal instruction” 或直接跑不起来。

根文件系统与查找路径

嵌入式开发中,CMAKE_FIND_ROOT_PATH 通常指向 SDK 提供的 sysroot 或你自己用 Buildroot/Yocto 构建的根文件系统。如果目标板的库路径比较特殊(例如某些库放在 /usr/lib/arm-linux-gnueabihf 而不是直接放在 /usr/lib),你可能需要额外配置链接器的 -rpath-link,防止二级依赖找不到。

# embedded-linux-armhf-toolchain.cmake
# 嵌入式 Linux (ARM hard-float) 工具链模板

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

# 1. 交叉编译器(以 Linaro/Debian 工具链为例)
set(CMAKE_C_COMPILER   arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)

# 2. sysroot(来自 Buildroot/Yocto/厂商 SDK)
set(CMAKE_SYSROOT /opt/myboard/sysroot)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})

# 3. 查找策略
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

# 4. 针对 ARM 的常用编译标志
#    -march:指令集架构
#    -mfpu:浮点单元类型
#    -mfloat-abi:浮点 ABI(hard/softfp/soft)
set(CMAKE_C_FLAGS_INIT "-march=armv7-a -mfpu=neon -mfloat-abi=hard" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS_INIT "-march=armv7-a -mfpu=neon -mfloat-abi=hard" CACHE STRING "" FORCE)

# 5. 链接器标志:处理非标准库路径和二级依赖
set(CMAKE_EXE_LINKER_FLAGS_INIT
    "-Wl,-rpath-link,${CMAKE_SYSROOT}/usr/lib/arm-linux-gnueabihf 
     -Wl,-rpath-link,${CMAKE_SYSROOT}/lib/arm-linux-gnueabihf"
    CACHE STRING "" FORCE
)

# 6. 如果目标平台没有 pkg-config,禁用避免报错
# set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_SYSROOT}/usr/lib/arm-linux-gnueabihf/pkgconfig")
# set(ENV{PKG_CONFIG_SYSROOT_DIR} ${CMAKE_SYSROOT})

如果你的目标板是 ARM64 (AArch64),只需把上面模板里的 arm 改成 aarch64arm-linux-gnueabihf 改成 aarch64-linux-gnu,并删除 -mfpu-mfloat-abi 相关标志(因为 AArch64 的浮点单元是强制标配,不再需要显式指定)。

小结:三份模板,三种工地

把这三份模板想象成队长办公桌上的三个文件夹:

  • 蓝色文件夹(通用模板):适用于大多数标准的 GCC/Clang 交叉编译场景,结构最简洁,改改路径就能用。
  • 绿色文件夹(Android NDK):专门针对 Android 这片特殊“经济特区”,内置了 ABI、API Level 和 STL 的规范。
  • 橙色文件夹(嵌入式 Linux):为 ARM/RISC-V 等嵌入式板子准备,重点关注浮点 ABI、sysroot 和二级库路径。

熟练掌握它们之后,你就可以在交叉编译的世界里“说走就走”——无论下一片工地是在 Android 手机、工业网关还是车载控制器上,队长都能掏出对的执照,带领团队顺利开工。

请登录后发表评论

    没有回复内容

正在唤醒异次元光景……