CMake 系列教程(二):基础命令详解

CMake 系列教程(二):基础命令详解

从单文件到多目标,掌握 CMake 核心命令


一、回顾与准备

上一篇我们完成了一个最简 CMake 项目。现实项目远不止一个 main.cpp------多文件、多目标、头文件搜索路径、链接第三方库......这些都离不开 CMake 的核心命令。

本文所有示例基于 CMake 3.20+,推荐使用现代 CMake 风格。


二、项目定义命令

2.1 cmake_minimum_required

cmake 复制代码
cmake_minimum_required(VERSION 3.20)
  • 必须写在 CMakeLists.txt最开头project() 之前)
  • 设置策略(Policy)行为到对应版本,避免旧版兼容行为
  • 可选加 FATAL_ERROR(3.x 中已无实际区别)

2.2 project

cmake 复制代码
# 完整形式
project(MyApp
    VERSION 1.0.0
    DESCRIPTION "A demo application"
    LANGUAGES CXX C
)

project() 做了很多事:

设置的变量
PROJECT_NAME MyApp
PROJECT_VERSION 1.0.0
PROJECT_DESCRIPTION "A demo application"
CMAKE_PROJECT_NAME MyApp(顶层项目名)
CMAKE_CXX_COMPILER 自动检测的 C++ 编译器

⚠️ LANGUAGES 不写时默认为 C CXX。纯 C 项目记得写 LANGUAGES C,可加速配置。


三、目标定义命令

3.1 add_executable

cmake 复制代码
# 基本用法
add_executable(myapp main.cpp)

# 多个源文件
add_executable(myapp
    main.cpp
    app.cpp
    utils.cpp
)

# 使用 GLOB 自动收集源文件(不推荐,见后文说明)
file(GLOB SOURCES "src/*.cpp")
add_executable(myapp ${SOURCES})

3.2 add_library

cmake 复制代码
# 静态库(默认)
add_library(math STATIC
    math/add.cpp
    math/sub.cpp
)

# 动态库(共享库)
add_library(utils SHARED
    utils/logger.cpp
    utils/config.cpp
)

# 接口库(仅头文件库,无 .cpp 文件)
add_library(fmt INTERFACE)
类型 关键字 产出 说明
静态库 STATIC .a / .lib 编译时链接到可执行文件中
动态库 SHARED .so / .dll 运行时加载
接口库 INTERFACE 无产物 仅传播头文件路径和编译选项

3.3 为什么不用 file(GLOB) 收集源文件?

cmake 复制代码
# ❌ 不推荐
file(GLOB MY_SOURCES "src/*.cpp")
add_executable(myapp ${MY_SOURCES})

原因 :CMake 的配置阶段不会自动重新运行。新增 .cpp 文件后,CMake 不知道需要重新配置,新文件不会被编译 。除非你每次手动 cmake -B build,否则构建会悄悄跳过新文件。

正确做法:显式列出每个源文件。


四、目标属性命令(现代 CMake 核心)

这是现代 CMake 最重要的部分------以目标为中心设置属性,而非全局变量。

4.1 target_include_directories

控制头文件搜索路径:

cmake 复制代码
add_library(math STATIC math/add.cpp math/sub.cpp)

# PRIVATE:仅 math 目标本身可见
target_include_directories(math PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/math
)

# PUBLIC:math 目标可见,链接 math 的目标也可见
target_include_directories(math PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}/math
)

# INTERFACE:math 目标本身不可见,仅传播给链接它的目标
# 适用于纯头文件库
target_include_directories(fmt INTERFACE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

三种可见性:

关键字 自己 链接自己的目标 典型场景
PRIVATE 内部实现细节
PUBLIC 对外公开的头文件
INTERFACE 仅头文件库

💡 记忆口诀:PRIVATE 是"我的",PUBLIC 是"大家的",INTERFACE 是"只给别人的"。

链接库,并传播属性:

cmake 复制代码
add_executable(myapp main.cpp)

# 链接 math 库
target_link_libraries(myapp PRIVATE math)

# 链接系统库(如 pthread)
target_link_libraries(myapp PRIVATE pthread)

# 链接第三方库(通过 find_package 找到的,后文详解)
target_link_libraries(myapp PRIVATE fmt::fmt)

关键理解target_link_libraries 不仅链接库文件,还会传播 该库的 PUBLIC/INTERFACE 属性(头文件路径、编译选项等)。

cmake 复制代码
# 假设 math 声明了 PUBLIC include 路径
target_include_directories(math PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/math)

# myapp 链接 math 后,自动获得 math 的 PUBLIC include 路径
target_link_libraries(myapp PRIVATE math)
# ✅ 无需再手动 target_include_directories

4.3 target_compile_options

设置编译选项:

cmake 复制代码
# 开启 C++17,警告全部打开
target_compile_options(myapp PRIVATE
    -std=c++17
    -Wall -Wextra -Wpedantic
)

# 跨平台写法(避免在 MSVC 上传 GCC 选项)
target_compile_options(myapp PRIVATE
    $<$<CXX_COMPILER_ID:GNU,Clang>:-Wall -Wextra>
    $<$<CXX_COMPILER_ID:MSVC>:/W4>
)

💡 $<$<...>:...> 是生成器表达式(Generator Expression),后续文章会详细讲解。

4.4 target_compile_features

更优雅地指定 C++ 标准:

cmake 复制代码
# 等价于 -std=c++17,但跨平台
target_compile_features(myapp PRIVATE cxx_std_17)

这是设置 C++ 标准的推荐方式 ,比全局设置 CMAKE_CXX_STANDARD 更精确。

4.5 属性传播总结

复制代码
                    ┌──────────────────────┐
                    │     math (库目标)      │
                    │                      │
                    │ PUBLIC include: math/ │
                    │ PUBLIC compile: -O2   │
                    └──────────┬───────────┘
                               │
              target_link_libraries(myapp PRIVATE math)
                               │
                               ▼
                    ┌──────────────────────┐
                    │     myapp (可执行)    │
                    │                      │
                    │ ✅ 获得 math/ 路径    │
                    │ ✅ 获得 -O2 选项     │
                    └──────────────────────┘

五、实战:多目标项目

5.1 项目结构

复制代码
calculator/
├── CMakeLists.txt
├── src/
│   ├── main.cpp
│   ├── core/
│   │   ├── calc.h
│   │   └── calc.cpp
│   └── utils/
│       ├── logger.h
│       └── logger.cpp
└── include/
    └── calc/
        └── calc.h      # 对外公开头文件

5.2 CMakeLists.txt

cmake 复制代码
cmake_minimum_required(VERSION 3.20)
project(Calculator VERSION 1.0.0 LANGUAGES CXX)

# --- 库:core ---
add_library(calc STATIC
    src/core/calc.cpp
)
target_include_directories(calc PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}/include      # 对外公开
)
target_include_directories(calc PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/src/core     # 内部使用
)
target_compile_features(calc PUBLIC cxx_std_17)

# --- 库:utils ---
add_library(utils STATIC
    src/utils/logger.cpp
)
target_include_directories(utils PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}/src/utils
)
target_compile_features(utils PRIVATE cxx_std_17)

# --- 可执行文件 ---
add_executable(calculator src/main.cpp)
target_link_libraries(calculator PRIVATE calc utils)
# calculator 自动获得 calc 和 utils 的 PUBLIC 属性

5.3 关键要点

  1. 库目标自己声明自己需要的头文件路径,而非让消费者手动添加
  2. PUBLICPRIVATE 精确控制传播范围
  3. target_compile_features(calc PUBLIC cxx_std_17) --- 库要求 C++17,链接它的目标自动继承此要求
  4. 消费者(calculator)只需 target_link_libraries,无需关心依赖细节

六、构建类型

6.1 四种标准构建类型

类型 优化 调试信息 用途
Debug 开发调试
Release 正式发布
RelWithDebInfo 发布但保留调试信息
MinSizeRel ✅(体积优先) 嵌入式/体积敏感场景

6.2 设置构建类型

bash 复制代码
# 配置时指定
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cmake 复制代码
# CMakeLists.txt 中设置默认值
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE)
endif()

⚠️ 单配置生成器 (Makefile、Ninja)通过 CMAKE_BUILD_TYPE 切换,需要重新配置。

多配置生成器(Visual Studio、Xcode)在构建时指定:

bash 复制代码
cmake -B build -G "Visual Studio 17 2022"
cmake --build build --config Release

七、子目录与多 CMakeLists.txt

7.1 add_subdirectory

当项目变大,可以拆分为多个子目录,每个子目录有自己的 CMakeLists.txt

复制代码
project/
├── CMakeLists.txt          # 顶层
├── src/
│   ├── CMakeLists.txt      # 定义可执行目标
│   └── main.cpp
└── libs/
    ├── math/
    │   ├── CMakeLists.txt  # 定义 math 库目标
    │   └── math.cpp
    └── utils/
        ├── CMakeLists.txt  # 定义 utils 库目标
        └── logger.cpp

顶层 CMakeLists.txt

cmake 复制代码
cmake_minimum_required(VERSION 3.20)
project(MyProject LANGUAGES CXX)

add_subdirectory(libs/math)
add_subdirectory(libs/utils)
add_subdirectory(src)

libs/math/CMakeLists.txt

cmake 复制代码
add_library(math STATIC math.cpp)
target_include_directories(math PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_features(math PUBLIC cxx_std_17)

src/CMakeLists.txt

cmake 复制代码
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE math utils)

7.2 重要的目录变量

变量 含义
CMAKE_CURRENT_SOURCE_DIR 当前 CMakeLists.txt 所在目录(源码侧)
CMAKE_CURRENT_BINARY_DIR 当前 CMakeLists.txt 对应的构建目录
CMAKE_SOURCE_DIR 最顶层 CMakeLists.txt 所在目录
CMAKE_BINARY_DIR 最顶层的构建目录
PROJECT_SOURCE_DIR 最近一次 project() 调用所在目录

💡 最佳实践 :优先使用 CMAKE_CURRENT_SOURCE_DIRPROJECT_SOURCE_DIR,避免使用 CMAKE_SOURCE_DIR(后者在项目被其他项目通过 add_subdirectory 引入时会指错位置)。


八、常用辅助命令速查

命令 用途 示例
message 输出信息 message(STATUS "Configuring...")
set 设置变量 set(SOURCES a.cpp b.cpp)
list 列表操作 list(APPEND SOURCES c.cpp)
option 定义开关 option(ENABLE_TESTS "Build tests" ON)
configure_file 生成配置头文件 configure_file(config.h.in config.h)
add_custom_command 自定义构建步骤 代码生成、预处理等
install 安装规则 安装到系统路径

小结

核心命令 作用 关键点
add_executable 定义可执行目标 显式列出源文件
add_library 定义库目标 STATIC / SHARED / INTERFACE
target_include_directories 头文件搜索路径 PRIVATE / PUBLIC / INTERFACE
target_link_libraries 链接库 + 传播属性 优先用 PRIVATE
target_compile_features 设置 C++ 标准 比全局变量更精确
add_subdirectory 添加子目录 模块化管理

现代 CMake 的核心思想 :所有属性挂载在目标上,通过 target_* 命令设置,通过链接关系自动传播。减少全局变量,让依赖关系显式、可追溯。


📖 下一期预告:《CMake 系列教程(三):变量、条件与控制流》------ 讲解 CMake 的变量机制、条件判断、循环、函数与宏,让你的构建脚本具备复杂逻辑处理能力。

相关推荐
南境十里·墨染春水5 小时前
C++ 工厂模式:从入门到进阶,彻底掌握对象创建的艺术
开发语言·c++·算法
JosieBook7 小时前
【数据库】时序预测能力的分级进化:TimechoAI如何让每一类用户都能精准预见未来
java·开发语言·数据库
加号37 小时前
【C#】 文件与目录管理:创建、删除操作的技术解析
开发语言·c#
diving deep7 小时前
脚本速览-python
开发语言·python
一生了无挂8 小时前
Java处理JSON技巧教学(从基础到高阶实战全覆盖)
java·开发语言·json
swordbob8 小时前
Spring 单例 Bean 是线程安全的吗?
java·开发语言
一拳一个呆瓜8 小时前
【STL】_SCL_SECURE_NO_WARNINGS
c++·stl
小小编程路9 小时前
C++ 异常 完整讲解
开发语言·c++
AI科技星9 小时前
数术工坊 · 第四卷 橡皮泥江湖(拓扑学)【完整定稿】
c语言·开发语言·汇编·electron·概率论·拓扑学