引言:施工队长的“跨国驾照”
在第八章中,我们的 CMake“施工队长”曾多次远赴海外工地(交叉编译),每次都依靠一份名为工具链文件(Toolchain File)的“跨国工程护照”。这份文件告诉队长:当地的官方语言是什么(目标系统)、司机该用哪国的驾照(编译器)、建材该去哪家市场采购(查找路径),以及遵守什么样的当地法规(链接标志)。
但在实际工程中,很多新手面对一张白纸开始写工具链文件时,往往会陷入迷茫:变量名该怎么拼?路径该怎么设?ONLY和BOTH到底有什么区别?为了让你不必每次都从零“手绘护照”,这个附录准备了几份可直接拿来改改路径就能用的模板。它们就像队长抽屉里常备的几份空白表格——填上目的地和日期,盖章就能出发。
通用交叉编译模板
这是最基础、最万金油的一份模板。无论你交叉编译的目标是什么嵌入式 Linux 设备,只要它有一个标准的 GCC/L Clang 工具链,都可以先套用这份骨架,再根据具体平台微调。
基础结构
工具链文件的核心是告诉 CMake“我现在不是在为本机编译,而是在为另一台机器编译”。因此必须设置以下两个“身份标识”:
CMAKE_SYSTEM_NAME:目标操作系统名称,如Linux、Generic等。CMAKE_SYSTEM_PROCESSOR:目标 CPU 架构,如arm、aarch64、x86_64、mips等。
关键变量设置
除了身份标识,还需要配置“五金件”和“采购规则”:
- 编译器:通过
CMAKE_C_COMPILER和CMAKE_CXX_COMPILER指定交叉编译器。 - 系统根目录(sysroot):通过
CMAKE_SYSROOT或CMAKE_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")
使用这份模板时,你通常只需要改三处:
- 把
aarch64换成你的目标架构(如arm、riscv64); - 把编译器路径改成你工具链的实际安装位置;
- 把
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-v7a、arm64-v8a、x86、x86_64。
STL 与平台配置
Android 的 C++ 标准库需要显式声明。推荐使用 c++_shared 或 c++_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-gnueabihf、aarch64-linux-gnu、riscv64-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 改成 aarch64,arm-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 手机、工业网关还是车载控制器上,队长都能掏出对的执照,带领团队顺利开工。


没有回复内容