关于CMake的基础概念和CMake项目构建流程
一、CMake 基础语法
1. 基础语法规则
-
命令格式:不区分大小写(惯例用大写),格式为
COMMAND(参数1 参数2 ...),参数含空格需用双引号包裹。
例:PROJECT(MyApp)与project(myapp)等效。 -
变量:
- 定义:
set(变量名 值),如set(SRC main.cpp)。 - 引用:
${变量名},如add_executable(app ${SRC})。 - 类型:
- 普通变量:当前作用域有效(函数/目录内)。
- 缓存变量:
set(变量名 值 CACHE 类型 "描述"),存入CMakeCache.txt跨构建保留(如CMAKE_BUILD_TYPE)。 - 环境变量:
$ENV{变量名}引用,set(ENV{变量名} 新值)修改。
- 预定义核心变量:
变量名 含义 PROJECT_SOURCE_DIR项目根目录(根 CMakeLists.txt路径)CMAKE_BINARY_DIR构建目录(执行 cmake命令的目录)CMAKE_CXX_STANDARDC++ 标准版本(如 17、20)
- 定义:
-
列表(List):空格分隔的字符串集合,如
set(LIBS math utils)。
常用操作:list(APPEND 列表 元素)(追加)、list(LENGTH 列表 变量)(长度)。 -
注释:单行用
#,无多行注释(需每行加#)。
二、核心构建命令
1. 项目配置基础
-
cmake_minimum_required(VERSION 版本):指定最低 CMake 版本(必须在project前),如cmake_minimum_required(VERSION 3.10)。 -
project(项目名 [LANGUAGES C CXX ...]):定义项目名称及支持语言(默认 C/C++),自动生成PROJECT_NAME、PROJECT_VERSION等变量。
2. 目标(Target)构建
目标是 CMake 核心,代表可执行文件、库等产物。
-
可执行文件:
add_executable(目标名 源文件...)
例:add_executable(app main.cpp src/func.cpp)。 -
库文件:
add_library(目标名 [STATIC|SHARED|MODULE] 源文件...)STATIC:静态库(.a/.lib,编译时链接)。SHARED:动态库(.so/.dll,运行时加载)。MODULE:模块库(非链接用,运行时动态加载)。
例:add_library(math STATIC src/math.cpp)。
3. 目标属性与依赖配置
现代 CMake(3.0+)推荐"目标导向"配置,替代全局命令(如 include_directories),明确作用域:
-
target_include_directories(目标 作用域 目录...):指定头文件目录PUBLIC:当前目标及依赖它的目标可见。PRIVATE:仅当前目标可见。INTERFACE:仅依赖它的目标可见。
例:target_include_directories(app PUBLIC include)。
-
target_link_libraries(目标 依赖...):链接库(自定义库或系统库)
例:target_link_libraries(app PUBLIC math pthread)(链接自定义库math和系统线程库)。 -
target_compile_features(目标 作用域 cxx_std_17):指定 C++ 标准(自动配置编译选项)。 -
target_compile_options(目标 作用域 -Wall -O2):添加编译选项(警告、优化等)。
三、项目组织与模块化(多目录工程)
1. 典型目录结构
1 | project_root/ |
2. 父子目录协作
-
根目录
CMakeLists.txt:定义全局配置,通过add_subdirectory加载子目录。1
2
3
4
5
6
7
8
9
10cmake_minimum_required(VERSION 3.15)
project(MyProject LANGUAGES CXX)
# 全局路径变量
set(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
# 加载子目录(自动解析子目录的 CMakeLists.txt)
add_subdirectory(src/math)
add_subdirectory(src/utils)
add_subdirectory(apps) -
子目录
src/math/CMakeLists.txt:定义模块库,暴露头文件路径。1
2
3add_library(math STATIC math.cpp)
# 头文件对依赖该库的目标可见(PUBLIC)
target_include_directories(math PUBLIC ${PROJECT_ROOT}/include) -
应用目录
apps/CMakeLists.txt:链接子模块库。1
2add_executable(app main.cpp)
target_link_libraries(app PUBLIC math utils) # 依赖 math 和 utils 库
3. 关键技巧
-
输出路径控制:通过
set_target_properties统一输出目录,避免污染源码。1
2set_target_properties(app PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_ROOT}/bin)
set_target_properties(math PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_ROOT}/lib) -
变量作用域:子目录变量默认局部,修改父目录变量需加
PARENT_SCOPE:1
set(PARENT_VAR "value" PARENT_SCOPE) # 子目录修改父目录变量
四、流程控制(条件与循环)
1. 条件判断(if-else-endif)
根据平台、配置等动态调整构建规则:
1 | if(条件) |
常用条件:
-
平台:
if(WIN32)、if(UNIX)、if(APPLE)(macOS)。 -
变量:
if(DEFINED VAR)(是否定义)、if(VAR STREQUAL "value")(字符串相等)。 -
文件:
if(EXISTS 路径)(路径存在)、if(IS_DIRECTORY 路径)(是否目录)。
示例:平台差异化编译
1 | if(WIN32) |
2. 循环(foreach 与 while)
-
foreach:遍历列表或范围1
2
3
4
5
6
7
8
9
10# 遍历列表
set(SRCS a.cpp b.cpp c.cpp)
foreach(src IN LISTS SRCS)
message(STATUS "Source: ${src}")
endforeach()
# 遍历数值范围(0到10,步长2)
foreach(i RANGE 0 10 2)
message(STATUS "Index: ${i}")
endforeach() -
while:条件循环(适合动态计数场景)1
2
3
4
5set(COUNT 0)
while(COUNT LESS 5)
message(STATUS "Count: ${COUNT}")
math(EXPR COUNT "${COUNT} + 1") # 计数+1
endwhile()
五、依赖管理与外部库
1. 查找系统库(find_package)
用于引入外部库(如 Boost、OpenCV):
1 | find_package(库名 [版本] [REQUIRED]) # REQUIRED:找不到则报错 |
找到库后:
-
现代库会生成目标
库名::库名,直接链接:1
2find_package(OpenCV 4 REQUIRED)
target_link_libraries(app PUBLIC OpenCV::OpenCV) # 自动包含头文件和链接库 -
传统库需手动处理变量:
1
2
3find_package(Boost 1.70 REQUIRED COMPONENTS filesystem)
target_include_directories(app PUBLIC ${Boost_INCLUDE_DIRS})
target_link_libraries(app PUBLIC ${Boost_LIBRARIES})
2. 自定义模块
若外部库无默认查找模块,可自定义 FindXXX.cmake 并放入模块目录:
1 | # 添加自定义模块目录 |
六、安装与打包
1. 安装规则(install)
定义目标、文件的安装路径(默认前缀:Unix 为 /usr/local,Windows 为 C:/Program Files):
1 | # 安装可执行文件到 ${CMAKE_INSTALL_PREFIX}/bin |
修改安装前缀:
1 | cmake .. -DCMAKE_INSTALL_PREFIX=/path/to/install # 生成时指定 |
2. 生成安装包(CPack)
在根 CMakeLists.txt 末尾添加:
1 | include(CPack) |
生成包:
1 | cd build && cpack # 在构建目录执行 |
七、最佳实践与避坑指南
-
优先目标导向命令:用
target_include_directories替代include_directories,用target_link_libraries替代link_libraries,明确依赖范围。 -
分离构建目录:采用"out-of-source"构建,避免污染源码:
1
mkdir build && cd build && cmake .. # 构建目录与源码分离
-
调试变量:用
message(STATUS "VAR: ${VAR}")打印变量值,排查配置问题。 -
跨平台路径:用
PATH_EQUAL比较路径(自动处理/与\差异):1
if("/a/b" PATH_EQUAL "/a//b") # 结果为真
-
动态库查找:
- Linux:设置
LD_LIBRARY_PATH或CMAKE_INSTALL_RPATH。 - Windows:将
.dll复制到可执行文件目录或添加到PATH。
- Linux:设置
八、完整构建流程
-
编写
CMakeLists.txt:按模块拆分,定义目标与依赖。 -
生成构建文件:
1
2mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release # Release 模式(默认 Debug) -
编译:
1
2cmake --build . -j8 # 8线程编译(通用)
# 或针对 Makefile:make -j8 -
安装(可选):
1
cmake --install . # 安装到指定前缀