第 9 章:工具链与交叉编译
第 9 章:工具链与交叉编译
工具链文件告诉 CMake 使用哪个编译器、链接器以及如何查找系统库。
9.1.1 基本工具链文件
# toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
# C 和 C++ 编译器
set(CMAKE_C_COMPILER gcc)
set(CMAKE_CXX_COMPILER g++)
# 查找路径
set(CMAKE_FIND_ROOT_PATH /usr/local)
# 查找策略
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)
使用方式:
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake
9.1.2 工具链文件的执行时机
工具链文件在 project() 命令之前被执行,因此:
- 不需要
cmake_minimum_required() - 不能使用
add_executable() 等命令 - 主要用于设置编译器和系统变量
9.2 交叉编译
9.2.1 ARM Linux 交叉编译
# arm-linux-toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
# 交叉编译工具链前缀
set(CROSS_COMPILE arm-linux-gnueabihf-)
set(CMAKE_C_COMPILER ${CROSS_COMPILE}gcc)
set(CMAKE_CXX_COMPILER ${CROSS_COMPILE}g++)
set(CMAKE_AR ${CROSS_COMPILE}ar)
set(CMAKE_RANLIB ${CROSS_COMPILE}ranlib)
set(CMAKE_STRIP ${CROSS_COMPILE}strip)
# sysroot(目标系统的根文件系统)
set(CMAKE_SYSROOT /opt/arm-sysroot)
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)
# 安装交叉编译工具链
sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
# 构建
cmake -S . -B build \
-DCMAKE_TOOLCHAIN_FILE=arm-linux-toolchain.cmake \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
9.2.2 aarch64(ARM64)交叉编译
# aarch64-toolchain.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 /usr/aarch64-linux-gnu)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
9.2.3 交叉编译到 Windows(MinGW)
# mingw-toolchain.cmake
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
9.2.4 macOS/iOS 交叉编译
# ios-toolchain.cmake
set(CMAKE_SYSTEM_NAME iOS)
set(CMAKE_OSX_ARCHITECTURES arm64)
set(CMAKE_OSX_DEPLOYMENT_TARGET "14.0")
# SDK 路径
set(CMAKE_OSX_SYSROOT iphoneos)
# 禁用在目标上查找
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
9.2.5 交叉编译总结表
| 目标平台 | 工具链 | 工具前缀 | SYSROOT |
|---|
| ARM Linux | arm-linux-gnueabihf- | arm-linux-gnueabihf- | /opt/arm-sysroot |
| ARM64 Linux | aarch64-linux-gnu- | aarch64-linux-gnu- | /usr/aarch64-linux-gnu |
| Windows (MinGW) | x86_64-w64-mingw32- | x86_64-w64-mingw32- | /usr/x86_64-w64-mingw32 |
| RISC-V | riscv64-linux-gnu- | riscv64-linux-gnu- | /opt/riscv-sysroot |
| 嵌入式裸机 | arm-none-eabi- | arm-none-eabi- | N/A |
| Android | NDK | 见 NDK 文档 | NDK 内置 |
9.3 编译器检测与设置
9.3.1 检测编译器
project(MyApp LANGUAGES CXX)
message("C++ 编译器: ${CMAKE_CXX_COMPILER}")
message("编译器 ID: ${CMAKE_CXX_COMPILER_ID}")
message("编译器版本: ${CMAKE_CXX_COMPILER_VERSION}")
message("编译器路径: ${CMAKE_CXX_COMPILER}")
9.3.2 编译器 ID 对照表
| 编译器 | CMAKE_CXX_COMPILER_ID |
|---|
| GCC | GNU |
| Clang | Clang |
| Apple Clang | AppleClang |
| MSVC | MSVC |
| Intel | Intel / IntelLLVM |
| NVIDIA HPC SDK | NVHPC |
| ARM Compiler | ARMCC / ARMClang |
9.3.3 按编译器设置选项
# 方式一:使用 if 判断
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(myapp PRIVATE -Wall -Wextra -Wpedantic)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(myapp PRIVATE -Wall -Wextra -Wpedantic)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_options(myapp PRIVATE /W4 /WX)
endif()
# 方式二:使用生成器表达式(推荐)
target_compile_options(myapp PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall -Wextra -Wpedantic>
$<$<CXX_COMPILER_ID:MSVC>:/W4 /WX>
)
9.3.4 检测编译器特性
include(CheckCXXCompilerFlag)
# 检查编译器标志
check_cxx_compiler_flag("-fsanitize=address" HAS_ASAN)
check_cxx_compiler_flag("-march=native" HAS_MARCH_NATIVE)
if(HAS_ASAN AND ENABLE_SANITIZERS)
add_compile_options(-fsanitize=address)
add_link_options(-fsanitize=address)
endif()
9.4 语言标准设置
9.4.1 全局设置
# 方式一:使用 CMAKE 变量(旧风格)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# 方式二:使用 target_compile_features(推荐)
target_compile_features(mylib PUBLIC cxx_std_17)
9.4.2 每个目标设置
add_library(mylib src/mylib.cpp)
set_target_properties(mylib PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
)
9.4.3 标准特性级别
| 特性 | 对应标准 | 常用特性 |
|---|
cxx_std_11 | C++11 | auto, lambda, range-for |
cxx_std_14 | C++14 | 泛型 lambda, 返回类型推导 |
cxx_std_17 | C++17 | structured bindings, optional, variant |
cxx_std_20 | C++20 | concepts, ranges, coroutines |
cxx_std_23 | C++23 | 期望的特性 |
9.4.4 C 标准
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
# 或
target_compile_features(mylib PUBLIC c_std_11)
9.4.5 CMAKE_CXX_EXTENSIONS 的区别
| 设置 | GCC/Clang | MSVC |
|---|
ON(默认) | -std=gnu++17 | /std:c++17(无影响) |
OFF | -std=c++17 | /std:c++17 |
⚠️ 建议:设为 OFF,以确保代码的可移植性。
9.5 平台检测
9.5.1 系统检测
# 操作系统
message("系统名: ${CMAKE_SYSTEM_NAME}") # Linux, Darwin, Windows
message("系统版本: ${CMAKE_SYSTEM_VERSION}")
message("处理器: ${CMAKE_SYSTEM_PROCESSOR}") # x86_64, aarch64
# 平台判断
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
message("Linux 平台")
endif()
if(APPLE)
message("macOS/iOS 平台")
endif()
if(WIN32)
message("Windows 平台")
endif()
if(UNIX)
message("Unix 平台(包括 macOS 和 Linux)")
endif()
# Android
if(CMAKE_SYSTEM_NAME STREQUAL "Android")
message("Android: API level ${CMAKE_SYSTEM_VERSION}")
endif()
9.5.2 架构检测
# 检测架构
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
message("x86_64 架构")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
message("ARM64 架构")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm")
message("ARM32 架构")
endif()
9.5.3 包含系统检查模块
include(CheckTypeSize)
check_type_size("void*" SIZEOF_VOID_P)
message("指针大小: ${SIZEOF_VOID_P} 字节")
include(CheckIncludeFileCXX)
check_include_file_cxx("filesystem" HAS_FILESYSTEM)
check_include_file_cxx("optional" HAS_OPTIONAL)
include(CheckCXXSymbolExists)
check_cxx_symbol_exists(getentropy "unistd.h" HAS_GETENTROPY)
include(CheckLibraryExists)
check_library_exists(m sin "" HAS_LIBM)
if(HAS_LIBM)
target_link_libraries(myapp PRIVATE m)
endif()
9.5.4 编译器内建检测变量
| 变量 | 说明 |
|---|
CMAKE_HOST_SYSTEM_NAME | 主机系统名 |
CMAKE_HOST_SYSTEM_PROCESSOR | 主机处理器 |
CMAKE_SIZEOF_VOID_P | 指针大小(4 或 8) |
CMAKE_C_BYTE_ORDER | 字节序(BIG_ENDIAN / LITTLE_ENDIAN) |
9.6 编译标志管理
9.6.1 全局编译标志
# 设置全局标志(所有目标)
add_compile_options(-Wall -Wextra)
# 按语言设置
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-std=c++17>)
add_compile_options($<$<COMPILE_LANGUAGE:C>:-std=c11>)
# 设置全局定义
add_definitions(-DDEBUG_MODE)
add_definitions(-DVERSION="1.0")
9.6.2 链接标志
# 全局链接选项
add_link_options(-fsanitize=address)
# 目标级别
target_link_options(myapp PRIVATE
$<$<CXX_COMPILER_ID:GNU>:-Wl,--as-needed>
)
9.6.3 按配置设置标志
# Debug 特有选项
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -DDEBUG")
# Release 特有选项
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
# RelWithDebInfo
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g -DNDEBUG")
# 推荐方式:使用生成器表达式
target_compile_options(myapp PRIVATE
$<$<CONFIG:Debug>:-g -O0>
$<$<CONFIG:Release>:-O3 -DNDEBUG>
)
9.6.4 预编译头(PCH)
# CMake 3.16+ 支持预编译头
target_precompile_headers(mylib PRIVATE
<vector>
<string>
<memory>
<algorithm>
)
# 或使用自定义头文件
target_precompile_headers(mylib PRIVATE
"src/pch.h"
)
9.7 CMake 工具链预设
{
"version": 6,
"configurePresets": [
{
"name": "arm-linux",
"toolchainFile": "${sourceDir}/cmake/arm-linux-toolchain.cmake",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-arm"
},
{
"name": "native-debug",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
}
]
}
cmake --list-presets
cmake --preset arm-linux
cmake --build --preset arm-linux
9.8 业务场景
场景:嵌入式固件构建
# stm32-toolchain.cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR cortex-m4)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
set(CMAKE_SIZE arm-none-eabi-size)
# 芯片特定标志
set(CPU_FLAGS "-mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16")
set(CMAKE_C_FLAGS_INIT "${CPU_FLAGS} -fdata-sections -ffunction-sections")
set(CMAKE_CXX_FLAGS_INIT "${CPU_FLAGS} -fdata-sections -ffunction-sections -fno-rtti -fno-exceptions")
set(CMAKE_EXE_LINKER_FLAGS_INIT "${CPU_FLAGS} -Wl,--gc-sections -T${CMAKE_SOURCE_DIR}/linker.ld")
# 禁用编译器检查(交叉编译无法运行目标二进制)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
9.9 注意事项
| 问题 | 说明 |
|---|
| CMAKE_SYSTEM_NAME 必须设置 | 交叉编译时必须显式设置 |
| CMAKE_TRY_COMPILE_TARGET_TYPE | 交叉编译时设为 STATIC_LIBRARY 避免运行测试 |
| sysroot 路径 | 确保 sysroot 包含目标系统的头文件和库 |
| pkg-config | 交叉编译时可能需要设置 PKG_CONFIG_PATH |
| 运行时检测 | 交叉编译时 execute_process() 运行的是主机程序 |
9.10 扩展阅读
上一章:第 8 章 — 命令与控制流 | 下一章:第 10 章 — 测试与 CTest →