强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

CMake 从入门到精通:完整教程 / 第 9 章:工具链与交叉编译

第 9 章:工具链与交叉编译

9.1 工具链文件(Toolchain File)

工具链文件告诉 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 Linuxarm-linux-gnueabihf-arm-linux-gnueabihf-/opt/arm-sysroot
ARM64 Linuxaarch64-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-Vriscv64-linux-gnu-riscv64-linux-gnu-/opt/riscv-sysroot
嵌入式裸机arm-none-eabi-arm-none-eabi-N/A
AndroidNDK见 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
GCCGNU
ClangClang
Apple ClangAppleClang
MSVCMSVC
IntelIntel / IntelLLVM
NVIDIA HPC SDKNVHPC
ARM CompilerARMCC / 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_11C++11auto, lambda, range-for
cxx_std_14C++14泛型 lambda, 返回类型推导
cxx_std_17C++17structured bindings, optional, variant
cxx_std_20C++20concepts, ranges, coroutines
cxx_std_23C++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/ClangMSVC
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 →