cmake之旅11cmake之旅11交叉编译与工具链文件1 什么是交叉编译2 交叉编译的前提安装交叉编译工具链3 工具链文件3.1 基本结构3.2 关键变量说明4 使用工具链文件5 实战为树莓派交叉编译6 Android 交叉编译7 交叉编译中的常见问题8 本篇命令速查表9 总结与下一篇预告同系列文章cmake之旅(1):构建的过程cmake之旅(2):CMakeLists.txt 核心语法cmake之旅(3):多目录项目管理cmake之旅(4):静态库与动态库cmake之旅5):函数、宏与 .cmake 模块cmake之旅6查找和使用第三方库cmake之旅7编译选项与条件编译cmake之旅8Modern CMake 与 target 思维cmake之旅9安装与导出cmake之旅10自动化测试与 CTestcmake之旅11交叉编译与工具链文件cmake之旅12CPack 打包与发布cmake之旅11交叉编译与工具链文件前面的篇章中我们编译出来的程序都是在本机上运行的——在 x86 的电脑上编译在 x86 的电脑上运行。但实际工作中有一个很常见的场景在一种平台上编译在另一种平台上运行。比如在 x86 电脑上编译但程序要跑在 ARM 嵌入式开发板上。这就是交叉编译Cross Compilation。CMake 通过工具链文件Toolchain File来支持交叉编译。工具链文件本身就是一个.cmake文件——看到了吗第五篇学的.cmake文件知识又派上用场了。1 什么是交叉编译先明确几个概念术语含义示例宿主机Host你正在使用的开发机器x86_64 Linux 电脑目标机Target程序最终要运行的机器ARM 开发板、树莓派本地编译宿主机和目标机是同一平台x86 上编译x86 上运行交叉编译宿主机和目标机是不同平台x86 上编译ARM 上运行为什么需要交叉编译因为目标机器往往性能有限嵌入式设备可能只有几百 MHz 的 CPU 和几十 MB 的内存在上面直接编译大型项目非常缓慢甚至不可能。而开发机性能强劲编译速度快得多。2 交叉编译的前提安装交叉编译工具链交叉编译需要一套专门的编译器和工具它们能生成目标平台的机器码。以 ARM 为例在 Ubuntu 上安装交叉编译工具链sudoapt-getinstallgcc-aarch64-linux-gnu g-aarch64-linux-gnu安装后你就有了以下工具工具用途aarch64-linux-gnu-gccARM 平台的 C 编译器aarch64-linux-gnu-gARM 平台的 C 编译器aarch64-linux-gnu-arARM 平台的静态库打包工具aarch64-linux-gnu-ldARM 平台的链接器这些工具运行在 x86 机器上但生成的是 ARM 平台的代码。3 工具链文件工具链文件是一个.cmake文件它告诉 CMake不要用本机的编译器去用交叉编译器。3.1 基本结构一个典型的 ARM 交叉编译工具链文件toolchain-aarch64.cmake# 目标系统信息 set(CMAKE_SYSTEM_NAME Linux) # 目标操作系统 set(CMAKE_SYSTEM_PROCESSOR aarch64) # 目标处理器架构 # 交叉编译工具链路径前缀 set(CROSS_COMPILE_PREFIX aarch64-linux-gnu) # 指定 C 和 C 编译器 set(CMAKE_C_COMPILER ${CROSS_COMPILE_PREFIX}-gcc) set(CMAKE_CXX_COMPILER ${CROSS_COMPILE_PREFIX}-g) # 指定其他工具可选CMake 通常能自动推导 set(CMAKE_AR ${CROSS_COMPILE_PREFIX}-ar) set(CMAKE_LINKER ${CROSS_COMPILE_PREFIX}-ld) # Sysroot目标平台的系统根目录包含目标平台的标准库和头文件 # 如果你有单独的 sysroot取消下面这行的注释并修改路径 # set(CMAKE_SYSROOT /path/to/arm-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) # 包只在目标平台找3.2 关键变量说明CMAKE_SYSTEM_NAME这是工具链文件中最重要的一个变量。当你设置了CMAKE_SYSTEM_NAME且它与当前宿主机系统不同时CMake 就知道这是交叉编译了。常见的值Linux、Windows、DarwinmacOS、Android、QNX等。如果目标系统是裸机没有操作系统的嵌入式系统使用Genericset(CMAKE_SYSTEM_NAME Generic)CMAKE_FIND_ROOT_PATH_MODE_xxx这些变量控制find_package、find_library、find_path等查找命令的行为。在交叉编译时你不希望 CMake 找到宿主机上的库那些是 x86 的不能给 ARM 用所以把 LIBRARY、INCLUDE、PACKAGE 的查找模式设为ONLY——只在目标平台的路径中搜索。但 PROGRAM 设为NEVER——因为像protoc、flatc这样的代码生成工具需要在宿主机上运行它们在编译阶段运行不是在目标机上运行。4 使用工具链文件使用方式非常简单——在第一次运行cmake时通过CMAKE_TOOLCHAIN_FILE指定工具链文件mkdirbuild-armcdbuild-arm cmake-DCMAKE_TOOLCHAIN_FILE../toolchain-aarch64.cmake..make注意事项第一CMAKE_TOOLCHAIN_FILE必须在第一次运行cmake时指定。它不能在 CMakeLists.txt 中设置也不能在第二次运行时更改因为它影响的是编译器检测等初始化步骤。第二建议为交叉编译创建独立的构建目录如build-arm与本地构建的目录如build分开。第三交叉编译出来的可执行文件不能在宿主机上直接运行./Calculator# 报错这是 ARM 程序x86 机器跑不了可以用file命令确认编译结果fileCalculator# 输出类似Calculator: ELF 64-bit LSB executable, ARM aarch64, ...5 实战为树莓派交叉编译树莓派 4 使用 ARM Cortex-A72 处理器。我们来写一个完整的交叉编译示例。toolchain-rpi4.cmake# 目标系统 set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) # 编译器 set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g) # 查找策略 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)项目结构和之前的 Calculator 完全一样CMakeLists.txt 不需要做任何修改。这就是工具链文件的优势——构建逻辑和平台信息完全分离。# 本地构建mkdirbuild-nativecdbuild-native cmake..make# 交叉编译同一套源码只是换了工具链文件mkdirbuild-rpi4cdbuild-rpi4 cmake-DCMAKE_TOOLCHAIN_FILE../toolchain-rpi4.cmake..make编译完成后把可执行文件拷贝到树莓派上运行即可scpCalculator pi192.168.1.100:~/sshpi192.168.1.100./Calculator6 Android 交叉编译Android NDK 自带了工具链文件这是一个更复杂但非常实用的交叉编译场景cmake\-DCMAKE_TOOLCHAIN_FILE$ANDROID_NDK/build/cmake/android.toolchain.cmake\-DANDROID_ABIarm64-v8a\-DANDROID_PLATFORMandroid-24\..Android 的工具链文件由 Google 维护支持通过额外变量如ANDROID_ABI、ANDROID_PLATFORM来配置目标架构和 API 级别。你不需要自己编写工具链文件直接使用 NDK 提供的即可。这个例子告诉我们很多平台Android、iOS、QNX 等都提供了现成的工具链文件。在交叉编译之前先看看目标平台是否已经提供了工具链文件避免重复造轮子。7 交叉编译中的常见问题问题一find_package 找不到目标平台的库交叉编译时find_package默认不会在宿主机路径中搜索因为我们设了ONLY。如果目标平台的库安装在非标准路径需要设置CMAKE_FIND_ROOT_PATH# 在工具链文件中 set(CMAKE_FIND_ROOT_PATH /path/to/arm-sysroot /path/to/arm-libs)或者在命令行指定cmake-DCMAKE_FIND_ROOT_PATH/path/to/arm-libs...问题二try_run 失败CMake 有些模块或configure_file检查需要编译并运行测试程序。在交叉编译环境下编译出来的程序是目标平台的无法在宿主机上运行。解决方式一在工具链文件中设置模拟器set(CMAKE_CROSSCOMPILING_EMULATOR qemu-aarch64)这样 CMake 会通过 QEMU 模拟器来运行测试程序。解决方式二如果不需要try_run可以通过缓存变量跳过检查。问题三CTest 在交叉编译时怎么用同样需要设置CMAKE_CROSSCOMPILING_EMULATOR。设置后ctest会自动通过模拟器运行测试# 工具链文件中 set(CMAKE_CROSSCOMPILING_EMULATOR qemu-aarch64)ctest# 自动用 qemu-aarch64 运行每个测试8 本篇命令速查表工具链文件中的关键变量变量含义示例CMAKE_SYSTEM_NAME目标操作系统Linux / Windows / Android / GenericCMAKE_SYSTEM_PROCESSOR目标处理器架构aarch64 / armv7 / x86_64CMAKE_C_COMPILERC 编译器aarch64-linux-gnu-gccCMAKE_CXX_COMPILERC 编译器aarch64-linux-gnu-gCMAKE_SYSROOT目标平台的系统根目录/path/to/sysrootCMAKE_FIND_ROOT_PATH额外的查找根路径/path/to/arm-libsCMAKE_CROSSCOMPILING_EMULATOR交叉编译模拟器qemu-aarch64查找模式设置变量推荐值说明CMAKE_FIND_ROOT_PATH_MODE_PROGRAMNEVER工具在宿主机上找CMAKE_FIND_ROOT_PATH_MODE_LIBRARYONLY库只在目标平台找CMAKE_FIND_ROOT_PATH_MODE_INCLUDEONLY头文件只在目标平台找CMAKE_FIND_ROOT_PATH_MODE_PACKAGEONLY包只在目标平台找9 总结与下一篇预告这一篇我们学习了交叉编译的概念、工具链文件的编写和使用以及交叉编译中的常见问题。工具链文件的核心价值在于把平台信息从构建逻辑中完全分离——同一套 CMakeLists.txt 可以通过不同的工具链文件编译到不同的目标平台无需修改任何构建代码。到此为止我们的项目可以构建、测试、安装、还能交叉编译到不同平台。最后一个环节是怎么把这一切打包成一个安装包分发给最终用户总不能让用户自己cmake make make install吧。下一篇——cmake之旅12CPack 打包与发布我们来学习如何把项目打包成 deb、rpm、zip 等安装包。