CMake 系统学习指南
第一部分:CMake 基础概念
1.1 什么是 CMake?
CMake 是一个跨平台的构建系统生成器。它不直接构建项目,而是生成平台特定的构建文件:
- Linux/Unix: 生成 Makefile
- Windows: 生成 Visual Studio 项目文件
- macOS: 生成 Xcode 项目文件
1.2 为什么使用 CMake?
- 跨平台: 一份 CMakeLists.txt 可以在不同平台生成构建文件
- 依赖管理: 自动查找库、头文件路径
- 现代 C++ 支持: 支持 C++11/14/17/20 标准
- CUDA 支持: 原生支持 CUDA 项目构建
- IDE 集成: CLion、VSCode、Visual Studio 都支持
1.3 CMake 构建流程
CMakeLists.txt → CMake 配置 → 生成构建文件 → 编译链接 → 可执行文件(源文件) (cmake) (Makefile) (make) (./app)
两个阶段:
- 配置阶段:
cmake ..- 读取 CMakeLists.txt,生成构建文件 - 构建阶段:
make- 编译链接生成目标
第二部分:CMake 基础语法
2.1 最简单的 CMakeLists.txt
# 指定 CMake 最低版本
cmake_minimum_required(VERSION 3.10)# 项目名称
project(MyProject)# 生成可执行文件
add_executable(myapp main.cpp)
2.2 常用命令详解
2.2.1 cmake_minimum_required
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.10...3.20) # 支持 3.10 到 3.20
作用: 指定运行此 CMakeLists.txt 所需的最低 CMake 版本。
2.2.2 project
project(MyProject) # 最简形式
project(MyProject VERSION 1.0.0) # 指定版本
project(MyProject LANGUAGES CXX) # 指定语言
project(MyProject VERSION 1.0.0 LANGUAGES CXX CUDA # 支持 C++ 和 CUDADESCRIPTION "A CUDA learning project"
)
project() 会设置以下变量:
PROJECT_NAME: 项目名称PROJECT_VERSION: 项目版本PROJECT_SOURCE_DIR: 项目源码目录PROJECT_BINARY_DIR: 项目构建目录
2.2.3 add_executable
# 基本用法
add_executable(myapp main.cpp)# 多个源文件
add_executable(myapp main.cpp utils.cpp helper.cpp
)# 使用变量
set(SOURCES main.cpp utils.cpp helper.cpp)
add_executable(myapp ${SOURCES})
2.2.4 add_library
# 静态库 (libmylib.a 或 mylib.lib)
add_library(mylib STATIC lib.cpp)# 动态库 (libmylib.so 或 mylib.dll)
add_library(mylib SHARED lib.cpp)# 默认 (通常为静态库)
add_library(mylib lib.cpp)
2.2.5 target_link_libraries
# 链接库到可执行文件
add_executable(myapp main.cpp)
add_library(mylib lib.cpp)
target_link_libraries(myapp mylib)# 链接系统库
target_link_libraries(myapp pthread)
target_link_libraries(myapp cuda)
第三部分:CMake 变量
3.1 变量定义和使用
# 定义变量
set(MY_VAR "Hello")
set(SOURCES main.cpp utils.cpp)# 使用变量
message(STATUS "MY_VAR = ${MY_VAR}")
add_executable(myapp ${SOURCES})# 追加变量
set(SOURCES ${SOURCES} helper.cpp)
list(APPEND SOURCES extra.cpp) # 推荐
3.2 CMake 内置变量
# 项目相关
PROJECT_NAME # 项目名称
PROJECT_SOURCE_DIR # 项目源码目录
PROJECT_BINARY_DIR # 项目构建目录# 构建类型
CMAKE_BUILD_TYPE # Debug, Release, RelWithDebInfo, MinSizeRel# 编译器相关
CMAKE_CXX_COMPILER # C++ 编译器路径
CMAKE_CUDA_COMPILER # CUDA 编译器路径# 标准相关
CMAKE_CXX_STANDARD # C++ 标准版本 (11, 14, 17, 20)
CMAKE_CXX_STANDARD_REQUIRED # 强制使用指定标准 (ON/OFF)
CMAKE_CXX_EXTENSIONS # 启用编译器扩展 (ON/OFF)# 安装路径
CMAKE_INSTALL_PREFIX # 安装前缀 (/usr/local)
3.3 设置 C++ 标准
# 方法 1: 使用变量
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)# 方法 2: 使用 target (推荐)
add_executable(myapp main.cpp)
set_target_properties(myapp PROPERTIESCXX_STANDARD 17CXX_STANDARD_REQUIRED ONCXX_EXTENSIONS OFF
)
第四部分:构建目标详解
4.1 可执行文件
add_executable(myapp main.cpp)# 设置输出目录
set_target_properties(myapp PROPERTIESRUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin
)# 设置输出名称
set_target_properties(myapp PROPERTIESOUTPUT_NAME "my_application"
)
4.2 库文件
# 创建库
add_library(mylib STATIC lib.cpp)# 设置库输出目录
set_target_properties(mylib PROPERTIESLIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/libARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib
)# 创建头文件库 (header-only library)
add_library(mylib INTERFACE)
target_include_directories(mylib INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
4.3 链接库
# 创建库
add_library(mathlib STATIC math.cpp)# 创建可执行文件并链接库
add_executable(calculator main.cpp)
target_link_libraries(calculator mathlib)# 链接可见性
target_link_libraries(calculatorPUBLIC mathlib # 传递给依赖此目标的其他目标PRIVATE threadlib # 仅用于此目标INTERFACE apilib # 仅用于依赖此目标的其他目标
)
第五部分:条件判断和循环
5.1 条件判断
# if-else-endif
if(CMAKE_BUILD_TYPE STREQUAL "Debug")message(STATUS "Debug 模式")add_definitions(-DDEBUG)
else()message(STATUS "Release 模式")
endif()# 检查变量
if(ENABLE_CUDA)enable_language(CUDA)
endif()# 检查操作系统
if(WIN32)message(STATUS "Windows 平台")
elseif(UNIX)message(STATUS "Unix/Linux 平台")
elseif(APPLE)message(STATUS "macOS 平台")
endif()# 逻辑运算
if(ENABLE_CUDA AND CUDA_FOUND)message(STATUS "CUDA 已启用且找到")
endif()if(USE_OPENMP OR USE_TBB)message(STATUS "并行库已启用")
endif()
5.2 循环
# foreach 循环
set(SOURCES a.cpp b.cpp c.cpp)
foreach(SRC ${SOURCES})message(STATUS "源文件: ${SRC}")
endforeach()# 数字循环
foreach(i RANGE 1 5)message(STATUS "数字: ${i}")
endforeach()# while 循环
set(COUNT 0)
while(COUNT LESS 5)message(STATUS "计数: ${COUNT}")math(EXPR COUNT "${COUNT} + 1")
endwhile()
第六部分:函数和宏
6.1 函数
# 定义函数
function(add_my_executable NAME)message(STATUS "创建可执行文件: ${NAME}")add_executable(${NAME} ${ARGN}) # ARGN 包含额外参数
endfunction()# 使用函数
add_my_executable(myapp main.cpp utils.cpp)
6.2 宏
# 定义宏
macro(print_var VAR_NAME)message(STATUS "${VAR_NAME} = ${${VAR_NAME}}")
endmacro()# 使用宏
set(MY_VALUE 42)
print_var(MY_VALUE) # 输出: MY_VALUE = 42
6.3 函数 vs 宏
区别:
- 函数: 有自己的作用域,内部变量不影响外部
- 宏: 在调用者的作用域中展开,内部变量会影响外部
function(test_func)set(VAR "function") # 不影响外部
endfunction()macro(test_macro)set(VAR "macro") # 会影响外部
endmacro()set(VAR "original")
test_func()
message(STATUS "After function: ${VAR}") # originaltest_macro()
message(STATUS "After macro: ${VAR}") # macro
第七部分:查找包和依赖管理
7.1 find_package
# 查找包
find_package(CUDA REQUIRED)
find_package(OpenCV REQUIRED)
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)# 检查是否找到
if(CUDA_FOUND)message(STATUS "CUDA 版本: ${CUDA_VERSION}")message(STATUS "CUDA 编译器: ${CUDA_COMPILER}")
endif()# 使用找到的包
target_link_libraries(myapp ${CUDA_LIBRARIES})
target_include_directories(myapp ${CUDA_INCLUDE_DIRS})
7.2 find_library 和 find_path
# 查找库文件
find_library(MATH_LIB m) # 查找 libm.so 或 libm.a
if(MATH_LIB)target_link_libraries(myapp ${MATH_LIB})
endif()# 查找头文件路径
find_path(MATH_INCLUDE_DIR math.h)
if(MATH_INCLUDE_DIR)target_include_directories(myapp ${MATH_INCLUDE_DIR})
endif()
7.3 包配置模式
# 使用 Config 模式(推荐)
find_package(CUDA REQUIRED)# 创建导入目标
add_executable(myapp main.cpp)
target_link_libraries(myapp CUDA::cudart) # 现代 CMake 方式
第八部分:CMake 与 CUDA 集成
8.1 启用 CUDA 支持
cmake_minimum_required(VERSION 3.10)
project(MyCUDAProject LANGUAGES CXX CUDA)# 或者动态启用
project(MyProject LANGUAGES CXX)
enable_language(CUDA)
8.2 CUDA 源文件编译
# 方法 1: 直接添加 .cu 文件
add_executable(myapp main.cpp kernel.cu)# 方法 2: 使用变量分离
set(CPP_SOURCES main.cpp utils.cpp)
set(CUDA_SOURCES kernel.cu helper.cu)
add_executable(myapp ${CPP_SOURCES} ${CUDA_SOURCES})
8.3 CUDA 编译选项
# 设置 CUDA 架构
set(CMAKE_CUDA_ARCHITECTURES 75 86) # SM 75 (Turing), SM 86 (Ampere)# 或者针对特定目标设置
set_target_properties(myapp PROPERTIESCUDA_ARCHITECTURES "75;86"
)# 设置 CUDA 标准
set(CMAKE_CUDA_STANDARD 17)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)# CUDA 编译器标志
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -O3")
set(CMAKE_CUDA_FLAGS_DEBUG "-G -g") # Debug 模式
set(CMAKE_CUDA_FLAGS_RELEASE "-O3") # Release 模式
8.4 CUDA 库链接
# 链接 CUDA 运行时库
target_link_libraries(myapp CUDA::cudart)# 链接 CUDA 库
target_link_libraries(myapp CUDA::cublas CUDA::cufft)# 链接 cuDNN(需要先找到)
find_package(CUDNN REQUIRED)
target_link_libraries(myapp CUDA::cudnn)
8.5 分离 CUDA 和 C++ 代码
# 创建 CUDA 库
add_library(cudakernel STATIC kernel.cu)
target_include_directories(cudakernel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(cudakernel CUDA::cudart)# 创建 C++ 可执行文件
add_executable(myapp main.cpp)
target_link_libraries(myapp cudakernel)
第九部分:现代 CMake 最佳实践
9.1 使用 target_* 命令
传统方式(不推荐):
include_directories(/usr/include)
link_directories(/usr/lib)
add_definitions(-DDEBUG)
现代方式(推荐):
target_include_directories(myapp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_directories(myapp PRIVATE /usr/lib)
target_compile_definitions(myapp PRIVATE DEBUG)
9.2 可见性修饰符
target_include_directories(myappPUBLIC # 对当前目标和依赖者都可见${CMAKE_CURRENT_SOURCE_DIR}/includePRIVATE # 仅对当前目标可见${CMAKE_CURRENT_SOURCE_DIR}/srcINTERFACE # 仅对依赖者可见${CMAKE_CURRENT_SOURCE_DIR}/api
)
9.3 Generator Expressions
# 根据构建类型设置不同选项
target_compile_options(myapp PRIVATE$<$<CONFIG:Debug>:-g -O0>$<$<CONFIG:Release>:-O3>
)# 根据编译器设置选项
target_compile_options(myapp PRIVATE$<$<CXX_COMPILER_ID:GNU>:-Wall>$<$<CXX_COMPILER_ID:Clang>:-Weverything>
)# 平台特定设置
target_compile_definitions(myapp PRIVATE$<$<PLATFORM_ID:Windows>:WINDOWS_BUILD>$<$<PLATFORM_ID:Linux>:LINUX_BUILD>
)
9.4 属性设置
# 设置目标属性
set_target_properties(myapp PROPERTIESCXX_STANDARD 17CXX_STANDARD_REQUIRED ONCUDA_STANDARD 17CUDA_STANDARD_REQUIRED ONCUDA_ARCHITECTURES "75;86"OUTPUT_NAME "my_application"RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin
)# 获取属性
get_target_property(OUTPUT_NAME myapp OUTPUT_NAME)
message(STATUS "输出名称: ${OUTPUT_NAME}")
第十部分:实际项目示例
10.1 你的 CUDA 项目改进版
cmake_minimum_required(VERSION 3.18)
project(HelloWorld VERSION 1.0.0LANGUAGES CXX CUDADESCRIPTION "CUDA Hello World 示例"
)# 设置 C++ 和 CUDA 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CUDA_STANDARD 17)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)# 设置 CUDA 架构(根据你的 GPU 选择)
# SM 75: Turing (RTX 20xx, GTX 16xx)
# SM 80: Ampere (RTX 30xx, A100)
# SM 86: Ampere (RTX 30xx mobile)
# SM 89: Ada Lovelace (RTX 40xx)
set(CMAKE_CUDA_ARCHITECTURES 75 86)# 编译选项
if(MSVC)add_compile_options(/W4)
else()add_compile_options(-Wall -Wextra -Wpedantic)
endif()# 创建可执行文件
add_executable(hello_world hello_world.cu)# 链接 CUDA 运行时
target_link_libraries(hello_world CUDA::cudart)# 设置输出目录
set_target_properties(hello_world PROPERTIESRUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin
)# 打印配置信息
message(STATUS "项目名称: ${PROJECT_NAME}")
message(STATUS "项目版本: ${PROJECT_VERSION}")
message(STATUS "C++ 标准: ${CMAKE_CXX_STANDARD}")
message(STATUS "CUDA 标准: ${CMAKE_CUDA_STANDARD}")
message(STATUS "CUDA 架构: ${CMAKE_CUDA_ARCHITECTURES}")
10.2 多文件 CUDA 项目
cmake_minimum_required(VERSION 3.18)
project(MyCUDAApp LANGUAGES CXX CUDA)# 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CUDA_STANDARD 17)
set(CMAKE_CUDA_ARCHITECTURES 75 86)# 源文件
set(CPP_SOURCESmain.cpputils.cpp
)set(CUDA_SOURCESkernel.cuhelper.cu
)# 创建 CUDA 库
add_library(cudakernel STATIC ${CUDA_SOURCES})
target_include_directories(cudakernel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(cudakernel PUBLIC CUDA::cudart)# 创建可执行文件
add_executable(myapp ${CPP_SOURCES})
target_link_libraries(myapp PRIVATE cudakernel)# 输出目录
set_target_properties(myapp PROPERTIESRUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin
)
第十一部分:常用命令速查
11.1 文件操作
# 读取文件
file(READ "config.txt" CONFIG_CONTENT)# 写入文件
file(WRITE "output.txt" "Hello, CMake!")# 查找文件
file(GLOB SOURCES "src/*.cpp")
file(GLOB_RECURSE SOURCES "src/**/*.cpp") # 递归查找# 创建目录
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/output)# 复制文件
file(COPY ${PROJECT_SOURCE_DIR}/data DESTINATION ${PROJECT_BINARY_DIR})
11.2 字符串操作
# 字符串拼接
set(FULL_NAME "${FIRST_NAME} ${LAST_NAME}")# 字符串替换
string(REPLACE "old" "new" NEW_STRING ${OLD_STRING})# 字符串长度
string(LENGTH ${MY_STRING} STRING_LENGTH)# 子字符串
string(SUBSTRING ${MY_STRING} 0 5 SUB_STR)# 转换为大写/小写
string(TOUPPER ${MY_STRING} UPPER_STRING)
string(TOLOWER ${MY_STRING} LOWER_STRING)
11.3 列表操作
# 创建列表
set(MY_LIST a b c d)# 追加元素
list(APPEND MY_LIST e f)# 插入元素
list(INSERT MY_LIST 2 x) # 在索引 2 处插入 x# 删除元素
list(REMOVE_ITEM MY_LIST b)
list(REMOVE_AT MY_LIST 0) # 删除索引 0 的元素# 列表长度
list(LENGTH MY_LIST LIST_LENGTH)# 获取元素
list(GET MY_LIST 0 FIRST_ITEM)# 连接列表
string(JOIN " " JOINED_STRING ${MY_LIST})
11.4 消息输出
# 不同级别的消息
message(STATUS "普通信息")
message(WARNING "警告信息")
message(SEND_ERROR "错误信息,继续处理")
message(FATAL_ERROR "致命错误,停止处理")# 调试输出
message(STATUS "变量值: ${MY_VAR}")
message(STATUS "列表: ${MY_LIST}")
第十二部分:构建和安装
12.1 构建类型
# 设置默认构建类型
if(NOT CMAKE_BUILD_TYPE)set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()# 不同构建类型的编译选项
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g -DNDEBUG")
set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG")
12.2 安装规则
# 安装可执行文件
install(TARGETS myappRUNTIME DESTINATION bin
)# 安装库文件
install(TARGETS mylibLIBRARY DESTINATION libARCHIVE DESTINATION lib
)# 安装头文件
install(FILES ${PROJECT_SOURCE_DIR}/include/mylib.hDESTINATION include
)# 安装整个目录
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/DESTINATION include
)
12.3 自定义目标
# 添加自定义命令
add_custom_command(OUTPUT generated.hCOMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/generate.pyDEPENDS generate.pyCOMMENT "生成头文件"
)# 添加自定义目标
add_custom_target(generateCOMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/generate.pyCOMMENT "运行生成脚本"
)# 添加依赖
add_dependencies(myapp generate)
第十三部分:调试和故障排除
13.1 查看变量
# 打印所有缓存变量
get_cmake_property(_variableNames VARIABLES)
foreach(_variableName ${_variableNames})message(STATUS "${_variableName}=${${_variableName}}")
endforeach()# 打印特定变量
message(STATUS "CMAKE_CXX_COMPILER = ${CMAKE_CXX_COMPILER}")
message(STATUS "CMAKE_CUDA_COMPILER = ${CMAKE_CUDA_COMPILER}")
13.2 查看目标属性
# 获取目标属性
get_target_property(INCLUDE_DIRS myapp INCLUDE_DIRECTORIES)
message(STATUS "包含目录: ${INCLUDE_DIRS}")get_target_property(LINK_LIBS myapp LINK_LIBRARIES)
message(STATUS "链接库: ${LINK_LIBS}")
13.3 调试构建过程
# 详细输出
cmake .. -DCMAKE_VERBOSE_MAKEFILE=ON# 或者使用 make
make VERBOSE=1# 查看编译命令
ninja -v # 如果使用 Ninja
第十四部分:进阶主题
14.1 CMake 模块
# 包含模块
include(CheckCXXCompilerFlag)# 检查编译器标志
check_cxx_compiler_flag("-std=c++17" COMPILER_SUPPORTS_CXX17)
if(COMPILER_SUPPORTS_CXX17)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
endif()# 自定义模块
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
include(MyCustomModule)
14.2 测试支持
enable_testing()add_executable(test_myapp test.cpp)
target_link_libraries(test_myapp mylib)add_test(NAME MyTest COMMAND test_myapp)# 设置测试属性
set_tests_properties(MyTest PROPERTIESTIMEOUT 10LABELS "unit"
)
14.3 打包
# 设置 CPack
set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "My Application")# 包含 CPack
include(CPack)
第十五部分:学习路径建议
15.1 初级阶段
- ✅ 理解 CMake 基本概念和构建流程
- ✅ 学会编写简单的 CMakeLists.txt
- ✅ 掌握 add_executable、add_library、target_link_libraries
- ✅ 理解变量和基本语法
15.2 中级阶段
- ⬜ 学习 find_package 和依赖管理
- ⬜ 掌握条件判断和循环
- ⬜ 理解现代 CMake 的 target_* 命令
- ⬜ 学会使用 Generator Expressions
15.3 高级阶段
- ⬜ 编写自定义 CMake 模块
- ⬜ 理解 CMake 内部机制
- ⬜ 掌握跨平台构建技巧
- ⬜ 学习 CMake 测试和打包
附录:常用资源
官方文档
- CMake 官方文档: https://cmake.org/documentation/
- CMake 教程: https://cmake.org/cmake/help/latest/guide/tutorial/
学习资源
- Modern CMake: https://cliutils.gitlab.io/modern-cmake/
- CMake 最佳实践: https://github.com/cpp-best-practices/cppbestpractices
CUDA 相关
- CMake CUDA 支持: https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#cuda
- NVIDIA CMake 示例: https://github.com/NVIDIA/cuda-samples
练习建议
- 从简单开始: 先创建一个最简单的 CMake 项目
- 逐步增加复杂度: 添加库、多文件、CUDA 支持
- 实践你的项目: 改进现有的 CUDA 项目 CMake 配置
- 阅读优秀项目: 查看开源项目的 CMakeLists.txt
