引言:施工队长的”海外工程图鉴”
在上一节(8.1)中,我们的 CMake “施工队长”拿到了《交叉编译通用护照》——学会了工具链文件(Toolchain File)的基本结构和关键变量。但理论签证只是第一步,真正到了异国工地,面对不同的”当地法规、气候土壤和建筑材料”,队长还得有具体的施工手册。
这一节,我们把镜头对准六大热门”海外工地”:Android、iOS/macOS、嵌入式 Linux、Windows (MinGW) 以及 WebAssembly。每个平台我们都会给出可直接落地的工具链文件模板和 CMake 调用方式,让你的代码真正做到”一次编写,到处编译”。
Android NDK:在移动端工地搭脚手架
Android 是交叉编译最常见的目的地之一。Google 提供的 NDK(Native Development Kit)已经内置了一个官方工具链文件,但在现代 CMake 项目中,我们更推荐显式控制 ABI 和 API Level。
工具链文件模板(android-ndk.cmake)
以下是一个精简而实用的自定义工具链文件,假设 NDK 路径通过环境变量 ANDROID_NDK 传入:
# android-ndk.cmake
set(CMAKE_SYSTEM_NAME Android)
set(CMAKE_SYSTEM_VERSION 21) # API Level 21 (Android 5.0)
set(CMAKE_ANDROID_ARCH_ABI arm64-v8a) # 目标 ABI: arm64-v8a, armeabi-v7a, x86, x86_64
set(CMAKE_ANDROID_NDK $ENV{ANDROID_NDK})
set(CMAKE_ANDROID_STL_TYPE c++_shared) # 推荐使用 libc++_shared
# 让 CMake 自动推导编译器路径
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++)
# 查找根路径设置
set(CMAKE_FIND_ROOT_PATH ${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/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)
配置与构建命令
# 配置阶段
cmake -B build/android-arm64
-DCMAKE_TOOLCHAIN_FILE=cmake/android-ndk.cmake
-DCMAKE_BUILD_TYPE=Release
-DANDROID_NDK=/path/to/ndk/25.2.9519653
# 构建阶段
cmake --build build/android-arm64 --parallel
ABI 选择速查表
选择合适的 ABI 就像选择合适的地基类型,直接决定你的 App 能运行在哪些设备上:
arm64-v8a:现代 Android 设备首选(2016年后主流),64位 ARM,性能最佳。armeabi-v7a:32位 ARM,兼容老旧设备,但 Google Play 已要求必须提供 64 位版本。x86_64/x86:主要用于 Android 模拟器,真机极少使用。
iOS/macOS:Xcode 工地的特殊安全帽
iOS 是一个”封闭式工地”:你必须使用 Apple 的 Xcode 工具链,还要区分真机(iOS Device)和模拟器(iOS Simulator)两套完全不同的 SDK。
真机工具链文件(ios-device.cmake)
# ios-device.cmake
set(CMAKE_SYSTEM_NAME iOS)
set(CMAKE_OSX_DEPLOYMENT_TARGET 14.0) # 最低支持 iOS 14.0
# 对于真机,指定 ARCHS 和 SYSROOT
set(CMAKE_OSX_ARCHITECTURES arm64)
execute_process(
COMMAND xcrun --sdk iphoneos --show-sdk-path
OUTPUT_VARIABLE CMAKE_OSX_SYSROOT
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(CMAKE_C_COMPILER xcrun --sdk iphoneos clang)
set(CMAKE_CXX_COMPILER xcrun --sdk iphoneos clang++)
set(CMAKE_AR xcrun --sdk iphoneos ar)
# iOS 不允许编译可执行文件直接运行(沙盒限制),通常只编译静态库/动态库/Framework
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
模拟器工具链文件(ios-simulator.cmake)
模拟器运行在 x86_64 或 arm64(Apple Silicon Mac)上,SDK 路径截然不同:
# ios-simulator.cmake
set(CMAKE_SYSTEM_NAME iOS)
set(CMAKE_OSX_DEPLOYMENT_TARGET 14.0)
set(CMAKE_OSX_ARCHITECTURES x86_64;arm64) # 支持 Intel 和 Apple Silicon Mac
execute_process(
COMMAND xcrun --sdk iphonesimulator --show-sdk-path
OUTPUT_VARIABLE CMAKE_OSX_SYSROOT
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(CMAKE_C_COMPILER xcrun --sdk iphonesimulator clang)
set(CMAKE_CXX_COMPILER xcrun --sdk iphonesimulator clang++)
构建技巧
- 使用
-G Xcode生成器可以大幅简化多架构管理,CMake 会自动处理 SDK 切换。 - 如果要在真机调试,需要在 Xcode 中处理代码签名(Code Signing),CMake 本身不管理证书。
CMAKE_TRY_COMPILE_TARGET_TYPE设置为STATIC_LIBRARY是因为 iOS 真机环境下 CMake 的测试编译(try_compile)无法链接可执行文件。
Universal Binary:一栋楼盖两套地基
Apple 从 Intel 迁移到 Apple Silicon(arm64)的过程中,诞生了一种”双面建筑”需求:同一个可执行文件或库,内部同时包含 x86_64 和 arm64 两套机器码,运行时由操作系统自动选择。这就是 Universal Binary(通用二进制),俗称 FAT 文件。
CMake 原生支持
在 macOS 上构建 Universal Binary 非常简单,甚至不需要交叉编译工具链,直接在 Apple Silicon Mac 上执行:
cmake -B build/universal -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"
cmake --build build/universal
注意事项
- 所有依赖库(如 OpenSSL、libpng)也必须提供 Universal Binary 版本,否则链接阶段会报架构不匹配错误。
- 如果依赖库只有单架构版本,你需要分别编译 x86_64 和 arm64 版本,再用
lipo -create合并。 - 对于 Intel Mac 用户,要构建 arm64 代码仍需交叉编译(因为旧 Intel Mac 不能原生运行 arm64 指令)。
嵌入式 Linux:ARM、RISC-V 与 MIPS 的极简工地
嵌入式开发是交叉编译最古老也最硬核的领域。这里的”外地”不仅是不同架构,往往连标准库(libc)都是裁剪过的。
ARM 交叉编译工具链文件(arm-linux.cmake)
以主流的 ARM64(aarch64)交叉编译器为例,通常从 Linaro 或发行版仓库获取:
# arm-linux.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
# 指定交叉编译器前缀
set(CROSS_PREFIX aarch64-linux-gnu)
set(CMAKE_C_COMPILER ${CROSS_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${CROSS_PREFIX}-g++)
set(CMAKE_AR ${CROSS_PREFIX}-ar)
set(CMAKE_RANLIB ${CROSS_PREFIX}-ranlib)
set(CMAKE_STRIP ${CROSS_PREFIX}-strip)
# 关键:sysroot 指向目标设备的根文件系统(包含 lib/ 和 usr/include/)
set(CMAKE_SYSROOT /path/to/target/rootfs)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_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)
# 若目标设备没有完整的开发环境,需关闭某些编译特性检测
set(CMAKE_C_COMPILER_WORKS 1)
set(CMAKE_CXX_COMPILER_WORKS 1)
Staging 前缀:别把家具搬进别人家客厅
嵌入式开发中,make install 默认会把文件装到 CMAKE_SYSROOT 里,这可能污染你精心制作的目标根文件系统。正确的做法是设置 Staging Prefix:
cmake -B build/arm64
-DCMAKE_TOOLCHAIN_FILE=cmake/arm-linux.cmake
-DCMAKE_INSTALL_PREFIX=/path/to/staging
-DCMAKE_BUILD_TYPE=Release
这样编译产物会先安装到 Staging 目录,经过测试后再通过 rsync/scp 部署到真实设备。
RISC-V 与 MIPS
对于 RISC-V(riscv64-linux-gnu-gcc)和 MIPS(mipsel-linux-gnu-gcc),工具链文件的结构与 ARM 几乎完全一致,只需修改 CMAKE_SYSTEM_PROCESSOR 和编译器前缀即可。但需特别注意:
- RISC-V:确认是否启用原子指令扩展(A Extension)和浮点单元(F/D Extension),这通常通过编译器标志
-march=rv64gc控制。 - MIPS:大小端(Big/Little Endian)选择至关重要,工具链名称中的
mipsel(小端)和mips(大端)不可混用。
MinGW-w64 与 MSYS2:在 Linux 上盖 Windows 的楼
有时你的主力开发机是 Linux,但需要生成 Windows 可执行文件(.exe)。MinGW-w64 工具链让 Linux 机器变成了”Windows 预制构件厂”。
工具链文件(mingw-w64.cmake)
# mingw-w64.cmake
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(CROSS_PREFIX x86_64-w64-mingw32)
set(CMAKE_C_COMPILER ${CROSS_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${CROSS_PREFIX}-g++)
set(CMAKE_RC_COMPILER ${CROSS_PREFIX}-windres) # Windows 资源编译器
set(CMAKE_AR ${CROSS_PREFIX}-ar)
set(CMAKE_RANLIB ${CROSS_PREFIX}-ranlib)
set(CMAKE_FIND_ROOT_PATH /usr/${CROSS_PREFIX})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
MSYS2 环境注意事项
如果你在 Windows 本地使用 MSYS2 进行类似 Linux 的开发体验,通常不需要交叉编译工具链文件——因为 MSYS2 的 MinGW-w64 shell 本身就是原生环境。此时只需直接运行:
cmake -B build -G "MinGW Makefiles"
cmake --build build
只有在 Linux → Windows 的真·交叉编译场景下,才需要上面的工具链文件。
WebAssembly:把大楼搬到浏览器里
WebAssembly(Wasm)是最特殊的”工地”:你的代码最终运行在浏览器或 Node.js 的沙盒中,没有传统意义上的操作系统,甚至文件系统都是虚拟的。
使用 Emscripten 的推荐方式
Emscripten 官方提供了 emcmake 包装脚本,它会自动注入正确的工具链参数,这是最简单可靠的方式:
# 激活 Emscripten 环境后
emcmake cmake -B build/wasm -DCMAKE_BUILD_TYPE=Release
cmake --build build/wasm
手动工具链文件(wasm.toolchain.cmake)
如果你需要更精细的控制(比如在 CI 中避免激活 emsdk 环境),可以这样写:
# wasm.toolchain.cmake
set(CMAKE_SYSTEM_NAME Emscripten)
set(CMAKE_SYSTEM_VERSION 1)
set(EMSDK_ROOT /path/to/emsdk/upstream/emscripten)
set(CMAKE_C_COMPILER ${EMSDK_ROOT}/emcc)
set(CMAKE_CXX_COMPILER ${EMSDK_ROOT}/em++)
set(CMAKE_AR ${EMSDK_ROOT}/emar)
set(CMAKE_RANLIB ${EMSDK_ROOT}/emranlib)
# Wasm 没有常规意义上的动态链接库,默认生成 .js + .wasm 组合
set(CMAKE_EXECUTABLE_SUFFIX ".js")
# 查找模式限制(Emscripten 自带 sysroot)
set(CMAKE_FIND_ROOT_PATH ${EMSDK_ROOT}/system)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
CMakeLists.txt 中的特殊适配
WebAssembly 有许多”施工限制”,需要在 CMakeLists.txt 中做条件判断:
if(EMSCRIPTEN)
# 开启 Embind 或 WebIDL 绑定以支持 JS/C++ 互操作
target_link_options(my_target PRIVATE
"-sEXPORT_ES6=1"
"-sMODULARIZE=1"
"-sENVIRONMENT=web"
)
# 禁用某些不支持的多线程或文件系统特性(视需求而定)
# target_compile_options(my_target PRIVATE "-sUSE_PTHREADS=0")
endif()
平台实战速查表
最后,把本节的核心参数整理成一张”海外施工速查卡”,方便你随时翻阅:
| 目标平台 | SYSTEM_NAME | 关键变量/命令 | 典型生成器 |
|---|---|---|---|
| Android NDK | Android | CMAKE_ANDROID_ARCH_ABI, ANDROID_NDK | Ninja |
| iOS 真机 | iOS | CMAKE_OSX_SYSROOT (iphoneos) | Xcode |
| iOS 模拟器 | iOS | CMAKE_OSX_SYSROOT (iphonesimulator) | Xcode |
| macOS Universal | Darwin | CMAKE_OSX_ARCHITECTURES=”x86_64;arm64″ | Xcode / Ninja |
| 嵌入式 ARM | Linux | CMAKE_SYSROOT, CMAKE_STAGING_PREFIX | Unix Makefiles |
| MinGW-w64 | Windows | CMAKE_RC_COMPILER (windres) | Unix Makefiles / Ninja |
| WebAssembly | Emscripten | emcmake, CMAKE_EXECUTABLE_SUFFIX=”.js” | Unix Makefiles |
小结:因地制宜,但不改图纸核心
交叉编译的”海外工地”千差万别:Android 要区分 ABI,iOS 要区分模拟器与真机,嵌入式 Linux 要操心 sysroot,Wasm 则完全是一套新的运行规则。但万变不离其宗的是 CMake 的核心工作流:
- 写一份干净的工具链文件,把”当地气候”(编译器、sysroot、查找规则)封装起来。
- 在 CMakeLists.txt 中避免硬编码绝对路径,善用
CMAKE_SYSTEM_NAME的条件判断做平台适配。 - 保持源码目录与构建目录分离(Out-of-source),为每个目标平台创建独立的
build/<platform>目录。
掌握了这六大平台的实战要领,你的 CMake “施工队”就真正拥有了全球承建能力。下一节(8.3),我们将把目光投向编译器的高级特性与链接优化,看看如何让这座”海外大楼”不仅盖得起来,还要盖得又快又稳。


没有回复内容