GoDm@'s Blog

Cmake简易使用文档

版权信息

warning

本文章为博主原创文章。遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。


*AI生成内容


这是一份为有一定基础的 C/C++ 开发者准备的现代 CMake 实战指南。相比于早期 CMake 满天飞的全局变量(如 include_directories),现代 CMake(3.0+)的核心哲学是基于目标(Target-based)

理解并坚持“基于目标”的原则,能极大提升软件架构的清晰度,尤其是在开发包含多个模块或硬件抽象层的复杂项目时。

1. 现代 CMake 的核心:Target 与属性

在现代 CMake 中,一切皆为 Target(可执行文件或库)。你不需要全局设置包含路径或编译选项,而是将它们绑定到特定的 Target 上。

核心命令只有这几个家族:

关键概念:PRIVATE, PUBLIC, INTERFACE。:

当你为一个 Target 指定包含路径、编译选项或链接库时,必须使用这三个关键字来声明作用域:

示例:假设我们正在编写一个传感器硬件抽象层(sensor_hal)库,并且它内部调用了底层的内核级字符串处理函数(比如内核版的 string.h),但不对外暴露这些底层细节。

# 创建一个静态库 Target
add_library(sensor_hal STATIC hal_core.c hal_utils.c)

# PUBLIC: 使用 sensor_hal 的上层应用也需要找到这些头文件
target_include_directories(sensor_hal PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

# PRIVATE: 只有 sensor_hal 内部编译时需要这些特定的编译选项(如独立环境)
target_compile_options(sensor_hal PRIVATE -Wall -Wextra -ffreestanding)

2. 标准的单模块项目模板

适用于快速编写测试、脚本管理工具或小型服务端程序。

# 1. 指定最低版本要求 (建议 3.15 以上,支持更多现代特性)
cmake_minimum_required(VERSION 3.15)

# 2. 定义项目名称、版本和语言
project(SmartTerminal VERSION 1.0.0 LANGUAGES C CXX)

# 3. 设置全局 C/C++ 标准 (少数推荐的全局变量之一)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 4. 收集源文件 (推荐明确列出,而不是用 file(GLOB) 避免缓存问题)
set(SOURCES
    src/main.c
    src/utils.c
)

# 5. 定义可执行文件 Target
add_executable(${PROJECT_NAME} ${SOURCES})

# 6. 指定头文件目录
# ${CMAKE_CURRENT_SOURCE_DIR} 指向当前 CMakeLists.txt 所在目录
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)

# 7. 添加特定宏定义 (如开启调试日志)
target_compile_definitions(${PROJECT_NAME} PRIVATE ENABLE_DEBUG_LOG=1)

3. 多模块架构与库的管理

对于工业级项目(如网关、复杂控制系统),通常会划分为核心业务、硬件抽象层(HAL)、第三方组件等。这就需要用到 add_subdirectory()

拒绝 file GLOB。很多旧教程会教你使用 file(GLOB SRC_FILES "*.c") 来批量添加源码。这是一个非常危险的反模式,因为当你新增或删除 .c 文件时,CMake 往往无法察觉,导致不会重新生成构建树。最佳实践是使用分治法、显式列出源码

目录结构示例:

├── CMakeLists.txt         # 顶层 CMake
├── app/
│   ├── CMakeLists.txt     # 业务层 CMake
│   └── main.c
└── components/
    └── hal/
        ├── CMakeLists.txt # 硬件驱动层 CMake
        ├── include/hal_gpio.h
        └── src/hal_gpio.c

底层模块 (components/hal/CMakeLists.txt):

# 编译为一个静态库
add_library(hal STATIC src/hal_gpio.c)

# 任何链接了 'hal' 的 Target,都会自动获得 include 目录的搜索路径!
# 这就是 Modern CMake 的魅力,不需要在上层重复写 include_directories
target_include_directories(hal PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

顶层 (CMakeLists.txt):

cmake_minimum_required(VERSION 3.15)
project(IoTGateway LANGUAGES C)

# 引入子目录
add_subdirectory(components/hal)
add_subdirectory(app)

应用层 (app/CMakeLists.txt):

add_executable(gateway_app main.c)

# 链接底层库,自动获取其 PUBLIC 暴露的头文件路径和宏定义
target_link_libraries(gateway_app PRIVATE hal)

4. 引入外部依赖

日常开发中经常需要引入外部库(如多线程、或者像 LVGL 这样的 GUI 库)。

方式 A:系统已安装的库 (find_package)

适用于标准库或通过包管理器安装的库。

# 寻找系统的线程库
find_package(Threads REQUIRED)

add_executable(my_app main.c)
target_link_libraries(my_app PRIVATE Threads::Threads)

方式 B:源码级拉取 (FetchContent)

这是现代 CMake 管理第三方源码依赖的利器,非常适合嵌入式或没有包管理器的环境。

include(FetchContent)

# 自动从 Git 拉取依赖并加入构建树
FetchContent_Declare(
  cJSON
  GIT_REPOSITORY https://github.com/DaveGamble/cJSON.git
  GIT_TAG        v1.7.15
)
FetchContent_MakeAvailable(cJSON)

add_executable(my_app main.c)
# 直接链接拉取下来的 target
target_link_libraries(my_app PRIVATE cjson) 

5. 交叉编译基础 (Cross-compiling)

如果在 WSL2 或普通 Linux 环境下为特定的 MCU 或嵌入式板子编译代码,不要在 CMakeLists.txt 里硬编码编译器路径,而是使用 Toolchain File (工具链文件)

1. 创建一个 arm-gcc-toolchain.cmake:

set(CMAKE_SYSTEM_NAME Generic) # Generic 通常用于裸机/RTOS,如果是嵌入式 Linux 则写 Linux
set(CMAKE_SYSTEM_PROCESSOR arm)

# 指定编译器
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)

# 指定一些硬件相关的强制编译选项 (例如 Cortex-M4)
set(CMAKE_C_FLAGS "-mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16" CACHE INTERNAL "C Compiler options")

2. 在命令行使用该工具链配置项目:

# 在 build 目录下执行
cmake -DCMAKE_TOOLCHAIN_FILE=../arm-gcc-toolchain.cmake -DCMAKE_BUILD_TYPE=Release ..
make -j4

6. 日常高频命令汇总备忘

7. 高频预定义变量 (Predefined Variables)

这部分相当于 CMake 给我们提供的系统级环境变量,直接决定了构建路径和编译参数。

7.1. 路径与目录相关(模块化开发必备)

在多目录项目中,极容易把这几个路径变量搞混:

7.2. 构建与编译控制

7.3. 系统与平台判定(交叉编译必备)

当你的代码需要在不同平台(比如 Linux 服务器环境和 ARM 嵌入式设备)之间切换时:

# 实战示例:跨平台链接特定库
if(UNIX AND NOT APPLE)
    target_link_libraries(my_app PRIVATE pthread)
elseif(WIN32)
    target_link_libraries(my_app PRIVATE wsock32)
endif()

共计约2.1k字。于2026/03/14首次发布,最后更新于2026/03/14。

本文章为博主原创文章。遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

AI辅助创作:本文部分内容由 Gemini 3.1 Pro 生成,最终版本由作者审核与修改。了解该AI模型

  1. 1. 现代 CMake 的核心:Target 与属性
  2. 2. 标准的单模块项目模板
  3. 3. 多模块架构与库的管理
  4. 4. 引入外部依赖
    1. 方式 A:系统已安装的库 (find_package)
    2. 方式 B:源码级拉取 (FetchContent)
  5. 5. 交叉编译基础 (Cross-compiling)
  6. 6. 日常高频命令汇总备忘
  7. 7. 高频预定义变量 (Predefined Variables)
    1. 7.1. 路径与目录相关(模块化开发必备)
    2. 7.2. 构建与编译控制
    3. 7.3. 系统与平台判定(交叉编译必备)