CMake(全)

CMake 系统学习指南

第一部分:CMake 基础概念

1.1 什么是 CMake?

CMake 是一个跨平台的构建系统生成器。它不直接构建项目,而是生成平台特定的构建文件:

  • Linux/Unix: 生成 Makefile
  • Windows: 生成 Visual Studio 项目文件
  • macOS: 生成 Xcode 项目文件

1.2 为什么使用 CMake?

  1. 跨平台: 一份 CMakeLists.txt 可以在不同平台生成构建文件
  2. 依赖管理: 自动查找库、头文件路径
  3. 现代 C++ 支持: 支持 C++11/14/17/20 标准
  4. CUDA 支持: 原生支持 CUDA 项目构建
  5. IDE 集成: CLion、VSCode、Visual Studio 都支持

1.3 CMake 构建流程

CMakeLists.txt → CMake 配置 → 生成构建文件 → 编译链接 → 可执行文件
     (源文件)      (cmake)      (Makefile)      (make)      (./app)

两个阶段

  1. 配置阶段: cmake .. - 读取 CMakeLists.txt,生成构建文件
  2. 构建阶段: 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++ 和 CUDA
    DESCRIPTION "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)
# 链接库到可执行文件
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 PROPERTIES
    CXX_STANDARD 17
    CXX_STANDARD_REQUIRED ON
    CXX_EXTENSIONS OFF
)

第四部分:构建目标详解

4.1 可执行文件

add_executable(myapp main.cpp)

# 设置输出目录
set_target_properties(myapp PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin
)

# 设置输出名称
set_target_properties(myapp PROPERTIES
    OUTPUT_NAME "my_application"
)

4.2 库文件

# 创建库
add_library(mylib STATIC lib.cpp)

# 设置库输出目录
set_target_properties(mylib PROPERTIES
    LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib
    ARCHIVE_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(calculator
    PUBLIC 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}")  # original

test_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 PROPERTIES
    CUDA_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(myapp
    PUBLIC     # 对当前目标和依赖者都可见
        ${CMAKE_CURRENT_SOURCE_DIR}/include
    PRIVATE    # 仅对当前目标可见
        ${CMAKE_CURRENT_SOURCE_DIR}/src
    INTERFACE  # 仅对依赖者可见
        ${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 PROPERTIES
    CXX_STANDARD 17
    CXX_STANDARD_REQUIRED ON
    CUDA_STANDARD 17
    CUDA_STANDARD_REQUIRED ON
    CUDA_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.0
    LANGUAGES CXX CUDA
    DESCRIPTION "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 PROPERTIES
    RUNTIME_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_SOURCES
    main.cpp
    utils.cpp
)

set(CUDA_SOURCES
    kernel.cu
    helper.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 PROPERTIES
    RUNTIME_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 myapp
    RUNTIME DESTINATION bin
)

# 安装库文件
install(TARGETS mylib
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
)

# 安装头文件
install(FILES ${PROJECT_SOURCE_DIR}/include/mylib.h
    DESTINATION include
)

# 安装整个目录
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/
    DESTINATION include
)

12.3 自定义目标

# 添加自定义命令
add_custom_command(
    OUTPUT generated.h
    COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/generate.py
    DEPENDS generate.py
    COMMENT "生成头文件"
)

# 添加自定义目标
add_custom_target(generate
    COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/generate.py
    COMMENT "运行生成脚本"
)

# 添加依赖
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 PROPERTIES
    TIMEOUT 10
    LABELS "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 初级阶段

  1. ✅ 理解 CMake 基本概念和构建流程
  2. ✅ 学会编写简单的 CMakeLists.txt
  3. ✅ 掌握 add_executable、add_library、target_link_libraries
  4. ✅ 理解变量和基本语法

15.2 中级阶段

  1. ⬜ 学习 find_package 和依赖管理
  2. ⬜ 掌握条件判断和循环
  3. ⬜ 理解现代 CMake 的 target_* 命令
  4. ⬜ 学会使用 Generator Expressions

15.3 高级阶段

  1. ⬜ 编写自定义 CMake 模块
  2. ⬜ 理解 CMake 内部机制
  3. ⬜ 掌握跨平台构建技巧
  4. ⬜ 学习 CMake 测试和打包

附录:常用资源

官方文档

学习资源

CUDA 相关


练习建议

  1. 从简单开始: 先创建一个最简单的 CMake 项目
  2. 逐步增加复杂度: 添加库、多文件、CUDA 支持
  3. 实践你的项目: 改进现有的 CUDA 项目 CMake 配置
  4. 阅读优秀项目: 查看开源项目的 CMakeLists.txt
posted @ 2026-05-07 11:58  梁文锋之深圳分锋  阅读(36)  评论(0)    收藏  举报