17 - CMake 集成
17 - CMake 集成
学习在 CMake 项目中配置 GCC 编译器标志、工具链文件和生成器表达式。
17.1 CMake 与 GCC 基础
基本 CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(MyProject VERSION 1.0.0 LANGUAGES C CXX)
# C/C++ 标准
set(CMAKE_C_STANDARD 17)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 检查编译器
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
message(STATUS "Using GCC ${CMAKE_C_COMPILER_VERSION}")
endif()
# 添加可执行文件
add_executable(hello main.c greet.c)
# 链接库
target_link_libraries(hello PRIVATE m pthread)
CMake 变量
# 编译器信息变量
message(STATUS "C Compiler: ${CMAKE_C_COMPILER}") # /usr/bin/gcc
message(STATUS "C++ Compiler: ${CMAKE_CXX_COMPILER}") # /usr/bin/g++
message(STATUS "Compiler ID: ${CMAKE_C_COMPILER_ID}") # GNU
message(STATUS "Compiler Version: ${CMAKE_C_COMPILER_VERSION}") # 13.2.0
# 系统信息
message(STATUS "System: ${CMAKE_SYSTEM_NAME}") # Linux
message(STATUS "Processor: ${CMAKE_SYSTEM_PROCESSOR}") # x86_64
17.2 编译器标志配置
设置编译器标志
# 全局标志(不推荐用于新项目)
set(CMAKE_C_FLAGS "-Wall -Wextra")
set(CMAKE_C_FLAGS_DEBUG "-g -O0")
set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG")
# 现代 CMake 方式:target 级别
add_executable(hello main.c)
target_compile_options(hello PRIVATE
-Wall
-Wextra
-Wpedantic
)
# 使用生成器表达式
target_compile_options(hello PRIVATE
$<$<CONFIG:Debug>:-g -O0 -fsanitize=address>
$<$<CONFIG:Release>:-O2 -DNDEBUG>
)
条件编译标志
# 根据编译器添加选项
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
target_compile_options(hello PRIVATE
-Wall -Wextra -Wpedantic
-Wshadow -Wconversion
-Wformat=2
)
if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL "10")
target_compile_options(hello PRIVATE -fanalyzer)
endif()
elseif(CMAKE_C_COMPILER_ID STREQUAL "Clang")
target_compile_options(hello PRIVATE
-Wall -Wextra -Wpedantic
-Weverything # Clang 特有
)
endif()
17.3 工具链文件
工具链文件(Toolchain File)用于交叉编译配置。
ARM64 交叉编译工具链文件
# cmake/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++)
# sysroot
set(CMAKE_SYSROOT /path/to/aarch64-sysroot)
set(CMAKE_FIND_ROOT_PATH /path/to/aarch64-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) # CMake 包
使用工具链文件
# 配置时指定工具链文件
cmake -B build -DCMAKE_TOOLCHAIN_FILE=cmake/aarch64-toolchain.cmake
# 构建
cmake --build build
# 检查生成的文件
file build/hello
# hello: ELF 64-bit LSB executable, ARM aarch64 ...
ESP32 工具链文件
# cmake/esp32-toolchain.cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR xtensa)
set(CMAKE_C_COMPILER xtensa-esp32-elf-gcc)
set(CMAKE_CXX_COMPILER xtensa-esp32-elf-g++)
set(CMAKE_C_FLAGS_INIT "-mlongcalls")
set(CMAKE_CXX_FLAGS_INIT "-mlongcalls")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
17.4 生成器表达式
生成器表达式在构建系统生成时求值,用于配置相关的选择。
# 基本语法
$<条件:真值>
$<条件:真值:假值>
$<IF:条件,真值,假值>
# 配置相关
$<CONFIG:Debug> # Debug 配置时为 1
$<CONFIG:Release> # Release 配置时为 1
# 编译器相关
$<C_COMPILER_ID:GNU> # GCC 时为 1
$<CXX_COMPILER_ID:GNU> # G++ 时为 1
$<C_COMPILER_VERSION:>=10> # 版本 >= 10 时为 1
# 平台相关
$<PLATFORM_ID:Linux> # Linux 时为 1
# 组合
$<AND:$<C_COMPILER_ID:GNU>,$<CONFIG:Debug>> # GCC + Debug
$<OR:$<C_COMPILER_ID:GNU>,$<C_COMPILER_ID:Clang>> # GCC 或 Clang
实用示例
add_executable(hello main.c)
# 根据配置设置选项
target_compile_options(hello PRIVATE
$<$<CONFIG:Debug>:-g3 -O0 -fsanitize=address,undefined>
$<$<CONFIG:Release>:-O2 -DNDEBUG -Werror>
$<$<CONFIG:RelWithDebInfo>:-O2 -g -DNDEBUG>
)
# 根据编译器设置选项
target_compile_options(hello PRIVATE
$<$<C_COMPILER_ID:GNU>:-Wall -Wextra>
$<$<C_COMPILER_ID:Clang>:-Wall -Wextra -Weverything>
$<$<C_COMPILER_ID:MSVC>:/W4 /WX>
)
# 条件链接
target_link_libraries(hello PRIVATE
$<$<CONFIG:Debug>:-fsanitize=address -fsanitize=undefined>
$<$<PLATFORM_ID:Linux>:rt>
m
)
17.5 查找包和库
find_package
# 查找系统安装的包
find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED)
find_package(ZLIB REQUIRED)
find_package(OpenSSL COMPONENTS Crypto SSL)
# 使用查找结果
target_link_libraries(hello PRIVATE
Threads::Threads
ZLIB::ZLIB
OpenSSL::Crypto
OpenSSL::SSL
)
# 可选依赖
find_package(CURL)
if(CURL_FOUND)
target_link_libraries(hello PRIVATE CURL::libcurl)
target_compile_definitions(hello PRIVATE HAVE_CURL=1)
endif()
pkg_check_modules
find_package(PkgConfig REQUIRED)
# 使用 pkg-config 查找库
pkg_check_modules(GTK3 REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(OPENSSL REQUIRED IMPORTED_TARGET openssl)
target_link_libraries(hello PRIVATE
PkgConfig::GTK3
PkgConfig::OPENSSL
)
FetchContent(下载依赖)
include(FetchContent)
FetchContent_Declare(
json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.2
)
FetchContent_MakeAvailable(json)
target_link_libraries(hello PRIVATE nlohmann_json::nlohmann_json)
17.6 安装规则
# 安装可执行文件
install(TARGETS hello
RUNTIME DESTINATION bin
)
# 安装库
install(TARGETS mylib
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
)
# 安装头文件
install(FILES include/mylib.h
DESTINATION include
)
# 安装 CMake 配置文件
install(EXPORT mylib-targets
FILE mylibTargets.cmake
NAMESPACE mylib::
DESTINATION lib/cmake/mylib
)
17.7 Sanitizer 集成
option(ENABLE_ASAN "Enable AddressSanitizer" OFF)
option(ENABLE_TSAN "Enable ThreadSanitizer" OFF)
option(ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer" OFF)
if(ENABLE_ASAN)
add_compile_options(-fsanitize=address -fno-omit-frame-pointer)
add_link_options(-fsanitize=address)
endif()
if(ENABLE_TSAN)
add_compile_options(-fsanitize=thread)
add_link_options(-fsanitize=thread)
endif()
if(ENABLE_UBSAN)
add_compile_options(-fsanitize=undefined)
add_link_options(-fsanitize=undefined)
endif()
# 使用
cmake -B build -DENABLE_ASAN=ON
cmake --build build
17.8 预设文件(CMakePresets.json)
{
"version": 3,
"configurePresets": [
{
"name": "default",
"displayName": "Default Config",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_C_COMPILER": "gcc-13",
"CMAKE_CXX_COMPILER": "g++-13"
}
},
{
"name": "release",
"displayName": "Release",
"inherits": "default",
"binaryDir": "${sourceDir}/build-release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "arm64",
"displayName": "ARM64 Cross Compile",
"toolchainFile": "${sourceDir}/cmake/aarch64-toolchain.cmake",
"binaryDir": "${sourceDir}/build-arm64"
},
{
"name": "asan",
"displayName": "Debug with ASan",
"inherits": "default",
"binaryDir": "${sourceDir}/build-asan",
"cacheVariables": {
"ENABLE_ASAN": "ON",
"ENABLE_UBSAN": "ON"
}
}
],
"buildPresets": [
{
"name": "default",
"configurePreset": "default"
},
{
"name": "release",
"configurePreset": "release"
}
]
}
# 使用预设
cmake --preset default
cmake --build --preset default
cmake --preset release
cmake --build --preset release
cmake --preset asan
cmake --build --preset asan
要点回顾
| 要点 | 核心内容 |
|---|---|
| CMake 基础 | add_executable, target_compile_options, target_link_libraries |
| 生成器表达式 | $<$<CONFIG:Debug>:...> 配置相关选择 |
| 工具链文件 | -DCMAKE_TOOLCHAIN_FILE=... 用于交叉编译 |
| find_package | 查找系统安装的库 |
| FetchContent | 下载并构建依赖 |
| 预设 | CMakePresets.json 统一构建配置 |
注意事项
优先使用 target 级别命令:
target_compile_options优于add_compile_options,前者只影响特定 target。
工具链文件中的路径要绝对: 交叉编译工具链文件中的路径应使用绝对路径或基于
CMAKE_CURRENT_LIST_DIR的相对路径。
Sanitizer 不能混合: ASan 和 TSan 不能同时启用,需要在 CMake 预设中分别配置。
Generator 选择: 推荐使用 Ninja 作为生成器,比 Make 更快。
cmake -G Ninja -B build。
扩展阅读
- CMake 官方文档 — 完整参考
- Modern CMake — 现代 CMake 最佳实践
- CMake Cookbook — CMake 实战
- cmake-generator-expressions — 生成器表达式参考
下一步
→ 18 - Docker 中的 GCC:学习在 Docker 容器中使用 GCC,构建多架构的编译环境。