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

GCC 完全指南 / 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


扩展阅读


下一步

18 - Docker 中的 GCC:学习在 Docker 容器中使用 GCC,构建多架构的编译环境。