注释

行注释

1
# 这是一个CMakeLists.txt文件

块注释

1
2
3
4
5
#[[
这是一个CMakeLists.txt文件
这是一个CMakeLists.txt文件
这是一个CMakeLists.txt文件
]]

基础设置

最低版本设置

1
cmake_minimum_required(VERSION 3.15)

说明:

  • 指定 CMake 的最低版本要求
  • 一般现代项目建议使用 3.15 或更高版本
  • 若项目依赖较新的特性,可适当提高版本要求

定义工程名称

1
2
3
4
5
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]] # 指定工程版本
[DESCRIPTION <project-description-string>] # 工程描述
[HOMEPAGE_URL <url-string>] # web主页地址
[LANGUAGES <language-name>...]) # 支持的语言等

示例:

1
project(MyApp VERSION 1.0.0 LANGUAGES CXX)

说明:

  • VERSION:工程版本
  • DESCRIPTION:工程描述
  • HOMEPAGE_URL:项目主页
  • LANGUAGES:指定使用的语言,如 C、CXX

设置C++标准

全局设置

1
2
3
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

说明:

  • CMAKE_CXX_STANDARD:设置默认 C++ 标准
  • CMAKE_CXX_STANDARD_REQUIRED ON:强制要求该标准
  • CMAKE_CXX_EXTENSIONS OFF:关闭编译器扩展,尽量使用标准 C++

针对单个目标设置(推荐)

1
2
add_executable(MyApp main.cpp)
target_compile_features(MyApp PRIVATE cxx_std_17)

说明:

  • target_compile_features 是现代 CMake 推荐的目标级写法
  • 该命令必须在目标创建后调用
  • 同样适用于库目标

生成可执行程序

1
add_executable(<可执行程序名> <源文件>)

示例:

1
2
3
4
5
add_executable(MyApp
src/main.cpp
src/app.cpp
src/util.cpp
)

说明:

  • 目标名通常也是默认输出文件名
  • 建议明确列出源文件
  • 不建议初学阶段使用通配符自动收集源码

执行与构建

配置工程

1
cmake -S . -B build

说明:

  • -S .:当前目录是源码目录
  • -B build:将构建文件放到 build 目录
  • 如果 build 目录不存在,CMake 会自动创建

编译工程

1
cmake --build build

说明:

  • 不需要进入 build 目录
  • CMake 会自动调用底层构建工具(如 make、ninja、MSBuild)

指定 Debug / Release

单配置生成器

适用于:

  • Unix Makefiles
  • Ninja
1
2
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
1
2
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build

多配置生成器

适用于:

  • Visual Studio
  • Xcode
  • Ninja Multi-Config
1
2
3
cmake -S . -B build
cmake --build build --config Debug
cmake --build build --config Release

变量

定义变量

1
set(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])

示例:

1
2
set(MY_NAME "hello")
message(STATUS "${MY_NAME}")

说明:

  • VAR:变量名
  • VALUE:变量值
  • CACHE:设置缓存变量,通常可在整个工程中访问

变量的基本特点

  • CMake 中变量本质上按字符串处理
  • 列表本质上是用分号分隔的字符串
  • 变量可以重复赋值,后一次赋值会覆盖前面的值
  • 取变量值使用 ${VAR}

示例:

1
2
3
set(MY_VAR hello)
set(MY_VAR world)
message(STATUS "${MY_VAR}") # 输出 world

常用预定义变量

变量 含义
PROJECT_SOURCE_DIR 当前项目根目录
PROJECT_BINARY_DIR 当前项目构建目录
CMAKE_SOURCE_DIR 顶层源码目录
CMAKE_BINARY_DIR 顶层构建目录
CMAKE_CURRENT_SOURCE_DIR 当前处理的 CMakeLists.txt 所在源码目录
CMAKE_CURRENT_BINARY_DIR 当前处理的 CMakeLists.txt 对应的构建目录
PROJECT_NAME 当前项目名称

目标与属性

现代 CMake 更推荐围绕“目标(target)”进行设置,而不是大量使用全局变量。

常见目标包括:

  • 可执行程序:add_executable(...)
  • 静态库 / 动态库:add_library(...)

设置输出路径

可执行文件输出路径

1
2
3
set_target_properties(MyApp PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)

按配置设置输出路径

1
2
3
4
set_target_properties(MyApp PROPERTIES
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/debug/bin"
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/release/bin"
)

说明:

  • RUNTIME_OUTPUT_DIRECTORY:可执行文件输出目录
  • 推荐输出到构建目录,不建议输出到源码目录

添加头文件目录

1
2
3
4
target_include_directories(MyApp PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/other_include
)
  • PRIVATE:仅当前目标可见
  • PUBLIC:当前目标和依赖此目标的目标都可见
  • INTERFACE:仅依赖此目标的目标可见,当前目标不可见(用于纯头文件库)

添加宏定义

1
target_compile_definitions(MyApp PRIVATE DEBUG)

等价于给当前目标添加:

1
-DDEBUG

按配置添加宏定义

1
2
3
4
target_compile_definitions(MyApp PRIVATE
$<$<CONFIG:Debug>:DEBUG;_DEBUG>
$<$<CONFIG:Release>:NDEBUG>
)

添加编译选项

1
2
3
4
target_compile_options(MyApp PRIVATE
-Wall
-Wextra
)

说明:

  • 用于设置编译器警告、优化等选项
  • 推荐使用目标级命令,而不是全局 add_compile_options

库

生成静态库

1
add_library(<库名> STATIC <源文件>)

示例:

1
2
3
add_library(MyLib STATIC
src/lib.cpp
)

生成动态库

1
add_library(<库名> SHARED <源文件>)

示例:

1
2
3
add_library(MyLib SHARED
src/lib.cpp
)

说明:

  • STATIC:静态库
  • SHARED:动态库 / 共享库
  • 库文件实际命名由平台决定,不同系统格式不同
  • 在 CMake 中链接库时通常直接使用目标名即可

指定库输出路径

1
2
3
4
5
set_target_properties(MyLib PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)

说明:

  • ARCHIVE_OUTPUT_DIRECTORY:静态库输出目录
  • LIBRARY_OUTPUT_DIRECTORY:动态库输出目录(非 Windows)
  • RUNTIME_OUTPUT_DIRECTORY:可执行文件和 Windows 下 DLL 输出目录

链接库

链接项目内构建的库

1
target_link_libraries(MyApp PRIVATE Mylib)

链接外部库:优先使用 find_package

1
2
find_package(SomeLib REQUIRED)
target_link_libraries(MyApp PRIVATE SomeLib::SomeLib)

手动查找库文件

1
2
find_library(CALC_LIB calc HINTS ${CMAKE_CURRENT_SOURCE_DIR}/lib)
target_link_libraries(MyApp PRIVATE ${CALC_LIB})

指定库搜索路径(不推荐作为首选方案)

1
2
target_link_directories(MyApp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/lib)
target_link_libraries(MyApp PRIVATE calc)

说明:

  • 优先级通常为:
  1. find_package
  2. find_library
  3. target_link_directories
  • target_link_directories 只是告诉链接器去哪里找库,不如直接链接目标或完整路径可靠

动态库和静态库的区别

  • 静态库:在链接阶段被打包进可执行文件
  • 动态库:程序运行时加载

CMake 中链接语法相同:

1
target_link_libraries(MyApp PRIVATE MyLib)

但使用动态库时,需要确保运行时能找到该库:

  • Linux:可通过 LD_LIBRARY_PATH
  • macOS:可通过 DYLD_LIBRARY_PATH
  • Windows:DLL 通常需要位于可执行文件同目录,或在 PATH 中

第三方库

优先使用 find_package

对于系统已安装或提供 CMake 配置文件的库,优先使用:

1
2
find_package(SomeLib REQUIRED)
target_link_libraries(MyApp PRIVATE SomeLib::SomeLib)

这是现代 CMake 最推荐的方式。

使用 find_library 查找库文件

如果没有可用的 find_package,可以手动查找:

1
2
find_library(MY_LIB some_lib HINTS /path/to/lib)
target_link_libraries(MyApp PRIVATE ${MY_LIB})

不知道库名时如何排查

方法 1:查官方文档

看库的官方文档是否有类似写法:

1
2
find_package(xxx REQUIRED)
target_link_libraries(myapp PRIVATE xxx::xxx)

方法 2:查看安装目录中的 *Config.cmake

常见文件名:

1
2
3
xxxConfig.cmake
xxx-config.cmake
XxxConfig.cmake

例如:

1
2
3
/lib/cmake/fmt/fmt-config.cmake
/lib/cmake/spdlog/spdlogConfig.cmake
/lib/cmake/OpenCV/OpenCVConfig.cmake

如果存在,通常表示支持:

1
find_package(...)

Linux 下可用以下命令查找:

1
2
find /usr /usr/local -name "*Config.cmake" | grep -i fmt
find /usr /usr/local -iname "*fmt*config.cmake"

方法 3:查看 CMake 报错提示

例如:

1
find_package(fmt REQUIRED)

若报错:

1
2
3
4
Could not find a package configuration file provided by "fmt" with any of the following names:

fmtConfig.cmake
fmt-config.cmake

通常说明:

  • find_package(fmt) 这个包名大概率是对的
  • CMake 正在查找 fmtConfig.cmake 或 fmt-config.cmake
  • 问题多半是没有找到对应路径,而不是包名写错

使用FetchContent获取第三方依赖

对于需要源码构建的第三方库,可以使用 FetchContent。

示例:使用 GoogleTest

1
2
3
4
5
6
7
8
9
10
11
include(FetchContent)

FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)

FetchContent_MakeAvailable(googletest)

target_link_libraries(MyTest PRIVATE GTest::gtest_main)

说明:

  • FetchContent 会在配置阶段拉取并引入外部工程
  • 适合测试依赖、小型依赖或需要固定版本的场景

线程库

如果程序使用线程库,推荐写法:

1
2
3
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(MyApp PRIVATE Threads::Threads)

说明:

  • 这是跨平台的推荐方式
  • 不要手动写死 pthread

日志输出

1
message(<关键字> "信息")

常见关键字:

  • STATUS
  • WARNING
  • AUTHOR_WARNING
  • SEND_ERROR
  • FATAL_ERROR

示例:

1
2
message(STATUS "Configuring project...")
message(WARNING "This is a warning")

列表与字符串操作

追加列表

1
list(APPEND SOURCES src/newfile.cpp src/another.cpp)

移除列表元素

1
list(REMOVE_ITEM SOURCES src/main.cpp)

其他常用操作

1
2
list(LENGTH SOURCES len)
list(GET SOURCES 0 first)

使用set拼接

1
set(SOURCES ${SOURCES} src/newfile.cpp)

说明:

  • 这种方式可用,但通常不如 list(APPEND ...) 清晰

条件判断与选项

if 条件判断

1
2
3
4
5
if(WIN32)
message(STATUS "Windows")
elseif(UNIX)
message(STATUS "Unix-like")
endif()

常见条件:

  • WIN32
  • UNIX
  • APPLE
  • MSVC

option 选项开关

1
option(BUILD_TESTS "Build tests" ON)

使用示例:

1
2
3
if(BUILD_TESTS)
add_subdirectory(tests)
endif()

说明:

  • 常用于启用或禁用某些模块
  • 配置时可通过命令行覆盖:
1
cmake -S . -B build -DBUILD_TESTS=OFF

目录嵌套与作用域

大型项目通常会在多个模块目录中分别放置 CMakeLists.txt,再由顶层统一组织。

添加子目录

1
add_subdirectory(<source_dir> [binary_dir] [EXCLUDE_FROM_ALL])

说明:

  • source_dir:子目录路径,里面通常包含 CMakeLists.txt
  • binary_dir:对应的构建目录,一般不需要手动指定
  • EXCLUDE_FROM_ALL:该子目录中的目标不会默认加入父工程的 ALL 目标中,需要显式构建

示例:

1
2
add_subdirectory(lib)
add_subdirectory(app)

变量作用域

在 add_subdirectory() 形成的目录层级中:

  • 父目录设置的普通变量,子目录可以读取
  • 子目录中设置或修改的普通变量,默认不会影响父目录
  • 如需向父目录回传变量,可使用:
1
set(MY_VAR value PARENT_SCOPE)
  • CACHE 变量通常整个工程可见,但应谨慎使用
  • 现代 CMake 更推荐通过目标属性来传递依赖,而不是大量共享变量

安装与测试

安装 install

安装可执行文件

1
install(TARGETS MyApp DESTINATION bin)

安装头文件

1
install(FILES include/myheader.h DESTINATION include)

说明:

  • install() 用于定义安装规则
  • 安装时通常执行:
1
cmake --install build

也可以指定安装目录:

1
cmake --install build --prefix ./install

测试 enable_testing

1
2
3
4
enable_testing()

add_executable(MyTest test.cpp)
add_test(NAME my_test COMMAND MyTest)

说明:

  • enable_testing():启用测试支持
  • add_test():注册测试

执行测试:

1
ctest --test-dir build

一个最小完整示例

项目结构:

1
2
3
4
5
6
MyApp/
├── CMakeLists.txt
├── include/
│ └── hello.h
└── src/
└── main.cpp

顶层 CMakeLists.txt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cmake_minimum_required(VERSION 3.15)
project(MyApp VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_executable(MyApp
src/main.cpp
)

target_include_directories(MyApp PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
)

构建命令:

1
2
cmake -S . -B build
cmake --build build

若为单配置生成器,构建 Release:

1
2
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build

附:推荐的现代 CMake 编写习惯

  1. 优先使用目标级命令:
  • target_include_directories
  • target_link_libraries
  • target_compile_definitions
  • target_compile_options
  1. 优先使用:
  • find_package
  • find_library

尽量少用:

  • link_directories
  1. 构建产物尽量放在构建目录中,不要污染源码目录

  2. 尽量显式列出源文件,避免一开始就依赖通配符

  3. 优先通过“目标之间的依赖关系”组织工程,而不是通过大量全局变量共享配置