一、CMake 基础概念
1.1 什么是 CMake?
CMake 是一个跨平台的构建系统生成器,它不直接构建项目,而是生成特定平台的构建文件(如 Makefile、Visual Studio 解决方案等)。
1.2 CMakeLists.txt 的作用
-
定义项目的构建规则
-
管理源文件和依赖关系
-
配置编译和链接选项
-
支持跨平台构建
二、常用命令速查
| 命令 | 用途 |
|---|---|
| add_executable | 添加可执行文件 |
| add_library | 添加库文件 |
| target_link_libraries | 链接库 |
| target_include_directories | 添加头文件路径 |
| find_package | 查找外部包 |
| add_subdirectory | 添加子目录 |
| set | 设置变量 |
| message | 打印信息 |
三、CMakeLists.txt 核心语法
3.1 命令调用
基本语法
命令(参数1 参数2 ...)
command(arg1 arg2 arg3)
example:
cmake
add_executable(my_app main.cpp)
带引号的参数(支持空格)
command("argument with spaces")
example:
cmake
set(DESCRIPTION "This is my project")
message("Hello World")
多行字符串
多行字符串集中等价写法
-
方式1:使用多行括号参数
cmakecommand([=[ This is a multi-line string with "quotes" ]=]) -
方式2:使用普通字符串 + 转义换行(不推荐)
cmakecommand(" This is a multi-line\n string with "quotes"") -
方式3:使用 ${VAR} 或 set 存储多行文本
cmake
set(MY_TEXT [[
This is a multi-line
string with "quotes"
]])
command("${MY_TEXT}")
括号参数规则
[=[和]=]之间的内容完全保留原样(包括换行、缩进、引号)- 中间可以写任意内容,无需转义
=的数量必须匹配(可以是 0 到任意多个)
cmake
# 0 个等号(最常用)
command([[
multi-line
text
]])
# 2 个等号
command([==[
can contain ]=] without closing
]==])
实际 CMake 示例
# 生成文件内容
file(WRITE output.txt [=[
Hello world
This line has "quotes"
And ${THIS_IS_NOT_VARIABLE} (no expansion)
]=])
# 执行命令时传递多行字符串
execute_process(
COMMAND echo [=[
line1
line2
]=]
)
注意:CMake 的括号参数是原始字符串,不会展开 ${变量}(除非你用普通引号字符串)。
3.2 变量
cmake
# 设置变量
set(MY_VARIABLE "value")
set(SOURCES main.cpp util.cpp math.cpp)
# 使用变量(${} 语法)
message(STATUS "Value: ${MY_VARIABLE}")
# 列表操作
set(LIST a b c)
list(APPEND LIST d e)
list(REMOVE_ITEM LIST b)
list(LENGTH LIST len)
# 环境变量
set(ENV{PATH} "/new/path:$ENV{PATH}")
message("PATH: $ENV{PATH}")
3.3 条件判断
cmake
# if 语法
if(expression)
# 真分支
elseif(another_expression)
# 其他分支
else()
# 假分支
endif()
# 常用表达式
if(VAR) # 变量非空且不为 0/NO/OFF/FALSE
if(NOT VAR) # 取反
if(VAR1 AND VAR2) # 与
if(VAR1 OR VAR2) # 或
if(DEFINED VAR) # 变量已定义
if(COMMAND command_name) # 命令存在
if(TARGET target_name) # 目标存在
if(EXISTS path) # 文件或目录存在
if(IS_DIRECTORY path) # 是目录
if(CMAKE_SYSTEM_NAME STREQUAL "Linux") # 字符串比较
if(VERSION GREATER 3.10) # 版本比较
3.4 循环
cmake
# foreach 循环
foreach(var IN ITEMS a b c d)
message("Item: ${var}")
endforeach()
# 范围循环
foreach(i RANGE 10) # 0-10
foreach(i RANGE 1 10 2) # 1 3 5 7 9
endforeach()
# 列表循环
set(FILES file1.cpp file2.cpp)
foreach(file ${FILES})
message("Processing: ${file}")
endforeach()
# while 循环
set(counter 0)
while(counter LESS 5)
message("Counter: ${counter}")
math(EXPR counter "${counter} + 1")
endwhile()
四、项目组织
4.1 目录结构示例
project/
├── CMakeLists.txt # 根 CMakeLists.txt
├── include/ # 公共头文件
│ └── project/
│ └── api.h
├── src/ # 源文件目录
│ ├── CMakeLists.txt
│ ├── main.cpp
│ └── module/
│ ├── CMakeLists.txt
│ └── impl.cpp
├── tests/ # 测试目录
│ ├── CMakeLists.txt
│ └── test_main.cpp
├── libs/ # 第三方库
│ └── external_lib/
└── cmake/ # CMake 模块
└── FindMyLib.cmake
4.2 根 CMakeLists.txt 模板
cmake
# 版本要求
cmake_minimum_required(VERSION 3.15)
# 项目定义
project(MyProject
VERSION 1.0.0
DESCRIPTION "My awesome project"
LANGUAGES CXX C
HOMEPAGE_URL "https://example.com"
)
# 设置策略
cmake_policy(SET CMP0077 NEW)
# 构建类型
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING
"Build type" FORCE)
endif()
# C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# 编译选项
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
endif()
# 输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# 包含 CMake 模块路径
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# 添加子目录
add_subdirectory(src)
if(BUILD_TESTING)
enable_testing()
add_subdirectory(tests)
endif()
# 安装配置
include(GNUInstallDirs)
install(TARGETS my_target
EXPORT MyProjectTargets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
五、目标详解
5.1 目标类型
cmake
# 可执行文件
add_executable(app main.cpp)
# 静态库
add_library(mylib STATIC lib.cpp)
# 动态库/共享库
add_library(myshared SHARED lib.cpp)
# 对象库(不链接,只编译)
add_library(myobj OBJECT obj.cpp)
# 接口库(纯头文件库)
add_library(header_only INTERFACE)
target_include_directories(header_only INTERFACE include/)
# 导入库(已存在的库)
add_library(external_lib UNKNOWN IMPORTED)
set_target_properties(external_lib PROPERTIES
IMPORTED_LOCATION "/path/to/lib.so"
)
5.2 目标属性
cmake
# 设置属性
set_target_properties(target_name PROPERTIES
CXX_STANDARD 17
OUTPUT_NAME "custom_name"
VERSION 1.2.3
SOVERSION 1
POSITION_INDEPENDENT_CODE ON
)
# 获取属性
get_target_property(prop target_name PROPERTY_NAME)
# 目标包含目录
target_include_directories(target
PRIVATE src # 仅目标自身使用
PUBLIC include # 目标和使用者都使用
INTERFACE api # 仅使用者使用
)
# 目标编译定义
target_compile_definitions(target
PRIVATE DEBUG_MODE=1
PUBLIC USE_FEATURE
)
# 目标编译选项
target_compile_options(target
PRIVATE -Wall -Wextra
PUBLIC -pthread
)
# 目标链接库
target_link_libraries(target
PRIVATE my_internal_lib
PUBLIC external_lib
INTERFACE header_only_lib
)
# 链接选项
target_link_options(target
PRIVATE -static-libstdc++
)
六、依赖管理
6.1 find_package
cmake
# 查找包
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)
# 使用包
if(Boost_FOUND)
target_link_libraries(myapp ${Boost_LIBRARIES})
target_include_directories(myapp PRIVATE ${Boost_INCLUDE_DIRS})
endif()
# 现代 CMake 方式(使用导入目标)
find_package(OpenCV REQUIRED)
target_link_libraries(myapp PRIVATE opencv::core opencv::imgproc)
6.2 FetchContent(CMake 3.11+)
cmake
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.11.0
)
FetchContent_MakeAvailable(googletest)
target_link_libraries(my_test PRIVATE gtest_main)
6.3 ExternalProject
cmake
include(ExternalProject)
ExternalProject_Add(
project_zlib
URL https://zlib.net/zlib-1.2.11.tar.gz
CONFIGURE_COMMAND ./configure --prefix=${CMAKE_BINARY_DIR}/external
BUILD_COMMAND make
INSTALL_COMMAND make install
)
七、高级特性
7.1 生成器表达式
cmake
# 条件编译
target_compile_definitions(app PRIVATE
$<$<CONFIG:Debug>:DEBUG_MODE>
$<$<PLATFORM_ID:Windows>:_WIN32>
)
# 包含目录
target_include_directories(lib PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
# 链接库条件
target_link_libraries(app PRIVATE
$<$<CXX_COMPILER_ID:GNU>:stdc++fs>
$<$<CXX_COMPILER_ID:Clang>:c++fs>
)
7.2 自定义命令和目标
cmake
# 添加自定义命令
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.h
COMMAND ${CMAKE_COMMAND} -E echo "Generating file..."
COMMAND python generate.py ${CMAKE_CURRENT_SOURCE_DIR}/input.txt
DEPENDS generate.py input.txt
COMMENT "Generating header file"
)
# 添加自定义目标
add_custom_target(generate_files ALL
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated.h
)
# 依赖关系
add_dependencies(app generate_files)
7.3 配置文件生成
cmake
# 创建 config.h.in
configure_file(
${CMAKE_SOURCE_DIR}/config.h.in
${CMAKE_BINARY_DIR}/config.h
)
# config.h.in 内容
#cmakedefine HAVE_FEATURE
#cmakedefine VERSION "@PROJECT_VERSION@"
7.4 导出和安装
cmake
# 导出目标
export(TARGETS mylib
FILE ${CMAKE_BINARY_DIR}/MyLibTargets.cmake
NAMESPACE MyLib::
)
# 安装导出
install(EXPORT MyLibTargets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyLib
NAMESPACE MyLib::
)
# 创建包配置
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
configure_package_config_file(
${CMAKE_SOURCE_DIR}/cmake/MyLibConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyLib
)
八、调试和诊断
8.1 消息输出
cmake
# 消息级别
message(STATUS "Info message") # 正常信息
message(WARNING "Warning") # 警告
message(AUTHOR_WARNING "Dev warning") # 开发警告
message(SEND_ERROR "Error") # 错误但不终止
message(FATAL_ERROR "Fatal") # 致命错误,终止
8.2 变量调试
cmake
# 打印所有变量
get_cmake_property(vars VARIABLES)
foreach(var ${vars})
message("${var}=${${var}}")
endforeach()
# 打印缓存变量
get_cmake_property(cache_vars CACHE_VARIABLES)
foreach(var ${cache_vars})
message("${var}=${${var}}")
endforeach()
九、最佳实践
9.1 现代 CMake 建议
- 使用目标导向:优先使用
target_*命令而非全局*_DIRECTORIES - 明确可见性:始终指定
PRIVATE/PUBLIC/INTERFACE - 避免全局变量:尽量减少
include_directories、link_directories - 使用生成器表达式:处理条件依赖和平台差异
- 模块化:使用
add_subdirectory组织代码
9.2 性能优化
cmake
# 避免使用 file(GLOB)
# 不推荐
file(GLOB SOURCES "src/*.cpp")
# 推荐:显式列出
set(SOURCES
src/main.cpp
src/feature1.cpp
src/feature2.cpp
)
# 使用 Ninja 生成器
# cmake -G Ninja -B build
9.3 常见陷阱
cmake
# 错误:变量作用域
set(VAR "global")
function(myfunc)
set(VAR "local") # 局部变量
endfunction()
# 正确:使用 PARENT_SCOPE
function(myfunc)
set(VAR "local" PARENT_SCOPE)
endfunction()
# 缓存变量
set(MY_CACHE "value" CACHE STRING "Description")
十、完整实战示例
cmake
# 根 CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(Calculator VERSION 1.0.0)
# 构建配置
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# 选项
option(BUILD_TESTS "Build tests" ON)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
# 包含子目录
add_subdirectory(src)
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
# 安装
include(GNUInstallDirs)
install(TARGETS calculator
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
cmake
# src/CMakeLists.txt
add_library(calc_lib STATIC
calculator.cpp
operations.cpp
)
target_include_directories(calc_lib
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
)
target_compile_definitions(calc_lib
PRIVATE $<$<CONFIG:Debug>:DEBUG>
)
add_executable(calculator main.cpp)
target_link_libraries(calculator PRIVATE calc_lib)
# 版本信息
set_target_properties(calc_lib PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION 1
)
十一、构建命令
11.1 配置项目(生成构建系统)
bash
# 基础用法(推荐)
cmake -B build -S .
# 指定生成器
cmake -B build -G "Ninja"
# 指定构建类型(单配置生成器)
cmake -B build -DCMAKE_BUILD_TYPE=Release
# 多配置生成器(如VS)
cmake -B build -DCMAKE_CONFIGURATION_TYPES="Debug;Release"
# 设置安装路径
cmake -B build -DCMAKE_INSTALL_PREFIX=/usr/local
# 传递自定义变量
cmake -B build -DMY_VARIABLE=ON
# 查看所有配置选项
cmake -B build -LAH
11.2 构建项目
bash
# 基础构建
cmake --build build
# 指定构建配置
cmake --build build --config Release
# 并行构建(指定线程数)
cmake --build build --parallel 8
# 或简写
cmake --build build -j 8
# 指定目标
cmake --build build --target my_target
# 清理(清空构建产物)
cmake --build build --target clean
# 显示详细编译命令
cmake --build build --verbose
# 或
cmake --build build -v