当前位置: 首页 > news >正文

cmake之旅(11)

cmake之旅(11)

  • cmake之旅(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之旅(8):Modern CMake 与 target 思维
cmake之旅(9):安装与导出
cmake之旅(10):自动化测试与 CTest
cmake之旅(11):交叉编译与工具链文件
cmake之旅(12):CPack 打包与发布

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-g++ARM 平台的 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 就知道这是交叉编译了。

常见的值:LinuxWindowsDarwin(macOS)、AndroidQNX等。

如果目标系统是裸机(没有操作系统的嵌入式系统),使用Generic

set(CMAKE_SYSTEM_NAME Generic)

CMAKE_FIND_ROOT_PATH_MODE_xxx

这些变量控制find_packagefind_libraryfind_path等查找命令的行为。在交叉编译时,你不希望 CMake 找到宿主机上的库(那些是 x86 的,不能给 ARM 用),所以把 LIBRARY、INCLUDE、PACKAGE 的查找模式设为ONLY——只在目标平台的路径中搜索。

但 PROGRAM 设为NEVER——因为像protocflatc这样的代码生成工具需要在宿主机上运行(它们在编译阶段运行,不是在目标机上运行)。

4 使用工具链文件

使用方式非常简单——在第一次运行cmake时通过CMAKE_TOOLCHAIN_FILE指定工具链文件:

mkdirbuild-arm&&cdbuild-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-native&&cdbuild-native cmake..make# 交叉编译(同一套源码,只是换了工具链文件)mkdirbuild-rpi4&&cdbuild-rpi4 cmake-DCMAKE_TOOLCHAIN_FILE=../toolchain-rpi4.cmake..make

编译完成后,把可执行文件拷贝到树莓派上运行即可:

scpCalculator pi@192.168.1.100:~/sshpi@192.168.1.100"./Calculator"

6 Android 交叉编译

Android NDK 自带了工具链文件,这是一个更复杂但非常实用的交叉编译场景:

cmake\-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake\-DANDROID_ABI=arm64-v8a\-DANDROID_PLATFORM=android-24\..

Android 的工具链文件由 Google 维护,支持通过额外变量(如ANDROID_ABIANDROID_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 / Generic
CMAKE_SYSTEM_PROCESSOR目标处理器架构aarch64 / armv7 / x86_64
CMAKE_C_COMPILERC 编译器aarch64-linux-gnu-gcc
CMAKE_CXX_COMPILERC++ 编译器aarch64-linux-gnu-g++
CMAKE_SYSROOT目标平台的系统根目录/path/to/sysroot
CMAKE_FIND_ROOT_PATH额外的查找根路径/path/to/arm-libs
CMAKE_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之旅(12):CPack 打包与发布,我们来学习如何把项目打包成 deb、rpm、zip 等安装包。

http://www.jsqmd.com/news/624806/

相关文章:

  • Kd-tree在三维点云中的5个常见误区及解决方案
  • SDD基于规范编程-OpenSpec及SuperPowers们
  • 如何用Flight Review从飞行数据中快速发现无人机问题?5步诊断指南
  • 从零实现一个轻量级数据库——MYDB的核心架构解析
  • PDF Arranger:免费开源PDF编辑工具,让你的文档管理效率提升300%
  • [具身智能-348]:MCP Client代码示例
  • GLM-4.1V-9B-Base部署指南:supervisor日志轮转+磁盘空间自动清理
  • 如何高效使用网盘直链下载助手:八大网盘文件下载神器完整教程
  • AudioSeal Pixel Studio快速上手:Streamlit界面下16位十六进制水印定制指南
  • Python的枚举类型Enum与整数标志位在状态管理中的最佳实践
  • 颠覆性方案:FastbootEnhance如何重新定义Android设备底层管理
  • 【DDU】DDU官网下载:Display Driver Uninstaller显卡驱动卸载工具使用全攻略 - xiema
  • 如何在Linux系统上安装Photoshop CC 2022:开源工具的完整解决方案
  • PDF-Parser-1.0多语言支持:从中文文档到全球化解决方案
  • Python与MyBatis的无缝集成:跨语言数据库操作实践
  • 深入解析Linux SDIO驱动架构与PCI设备注册流程
  • 微带天线设计指南:从基础结构到实际应用
  • 从模型漂移到流量撕裂:AI原生系统灰度发布失败全因分析,工程师必须在48小时内掌握
  • 2026年走心机直销厂家推荐,双主轴走心机/数控凸轮机/走心机,走心机企业怎么选择 - 品牌推荐师
  • 使用Antigravity库优化春联生成模型的训练过程
  • 终极指南:如何用D3KeyHelper暗黑3智能助手提升游戏效率
  • Pixel Dimension Fissioner 内存优化技巧:在有限显存下运行大模型
  • 别再吹牛了,% Vibe Coding 存在无法自洽的逻辑漏洞!醇
  • 在银河麒麟V10上,用linuxdeployqt打包Qt5.14.2应用的保姆级避坑指南
  • 乘 AI 教育东风 筑育人强国根基——赶考集团深耕 “人工智能 + 教育” 打造行业标杆 - 速递信息
  • Allegro PCB设计避坑指南:引脚交换后必须做的3项检查(以差分对为例)
  • 招剪辑师没用了!电商视频进入“AI智能体”时代,易元AI让素材生产实现“无人驾驶”
  • Ostrakon-VL 终端 Java 面试题精讲:高并发场景下模型服务调优策略
  • 如何在2025年完美访问Flash内容:CefFlashBrowser完整使用指南
  • 别再傻傻分不清!手把手教你根据引脚丝印识别12864液晶驱动芯片(KS0108/RA6963/RA8816)