找回密码
 立即注册
首页 业界区 业界 CMAKE学习笔记【上】

CMAKE学习笔记【上】

诞楮 2025-6-9 02:37:02
(零) 变量

一、基本概念

CMake 中的变量是 字符串类型(或列表),没有内置的数据结构如数组、字典等。变量的作用类似于 shell 脚本中的环境变量,但也有自己的作用域机制。
常见预定义变量(举例)


  • PROJECT_NAME:当前项目的名称。
  • CMAKE_CURRENT_SOURCE_DIR:当前处理的源码目录。
  • CMAKE_CURRENT_BINARY_DIR:当前构建的二进制目录。
  • CMAKE_SOURCE_DIR:项目根目录。
  • CMAKE_BINARY_DIR:构建输出根目录。
  • CMAKE_CXX_COMPILER:C++ 编译器路径。
  • CMAKE_BUILD_TYPE:构建类型(Debug/Release)。
二、变量的设置

1. 使用 set() 设置变量
  1. set(VAR_NAME value [CACHE TYPE DOCSTRING [FORCE]])
复制代码
示例:
  1. set(MY_VAR "Hello CMake")
  2. message(STATUS "MY_VAR = ${MY_VAR}")
复制代码
输出:
  1. -- MY_VAR = Hello CMake
复制代码
设置多个值(列表)
  1. set(MY_LIST a b c)
  2. message(STATUS "MY_LIST = ${MY_LIST}")
复制代码
输出:
  1. -- MY_LIST = a;b;c
复制代码
如果你希望用空格分隔显示为字符串:
  1. message(STATUS "MY_LIST = ${MY_LIST}")
  2. # 或者转换为空格分隔
  3. message(STATUS "MY_LIST = ${MY_LIST}")
复制代码
2. 使用 option() 设置布尔变量

用于用户可配置的开关选项,默认值为 ON 或 OFF。
  1. option(USE_MYMATH "Use custom math library" ON)
复制代码
你可以像普通变量一样使用它:
  1. if(USE_MYMATH)
  2.     message(STATUS "Using custom math")
  3. else()
  4.     message(STATUS "Not using custom math")
  5. endif()
复制代码
3. 使用 unset() 删除变量
  1. unset(MY_VAR)
复制代码
可用于清除某些变量的影响。
三、变量的引用

使用 ${VAR_NAME} 来引用变量值。
  1. set(NAME "Alice")
  2. message(STATUS "Hello, ${NAME}!")
复制代码
输出:
  1. -- Hello, Alice!
复制代码
如果变量未定义,则会被替换为空字符串。
四、变量的作用域(Scope)

CMake 的变量作用域不像 C/C++ 那样严格,但它有以下几种行为:
1. 局部作用域(Local Scope)

默认情况下,set() 设置的变量只在当前 CMakeLists.txt 文件中有效,不会传递到父级或子级目录。
  1. # CMakeLists.txt
  2. set(MY_VAR "local")
  3. add_subdirectory(subdir)  # 进入 subdir 目录
复制代码
  1. # subdir/CMakeLists.txt
  2. message(STATUS "MY_VAR = ${MY_VAR}")  # 输出空,因为不在同一个作用域
复制代码
2. PARENT_SCOPE

如果你想把一个变量设置回上一级作用域(比如你在 add_subdirectory() 内部设置变量返回给上级),可以使用:
  1. # subdir/CMakeLists.txt
  2. set(MY_VAR "from child" PARENT_SCOPE)
复制代码
这样上级就能看到这个值了。
3. CACHE 变量(全局可见)

使用 set(... CACHE ...) 定义的变量是全局的,保存在 CMake 缓存中,可以在任何地方访问。
  1. set(MY_CACHE_VAR "global value" CACHE STRING "Description of this variable")
复制代码
这种变量通常用于跨目录通信或配置持久化(例如用户通过 -DMY_VAR=value 传参)。
你也可以使用 cache 类型来覆盖缓存值:
  1. cmake .. -DMY_CACHE_VAR="new value"
复制代码
五、常见问题与技巧

1. 如何查看所有变量?

你可以运行:
  1. cmake --build . --target help
复制代码
或者更直接地:
  1. cmake -LAH ..
复制代码
这会列出所有的缓存变量及其值。
2. 判断变量是否存在或是否为空
  1. if(DEFINED VAR_NAME)
  2.     message(STATUS "${VAR_NAME} is defined.")
  3. endif()
  4. if(NOT VAR_NAME)
  5.     message(STATUS "${VAR_NAME} is empty or not set.")
  6. endif()
复制代码
3. 拼接字符串或路径
  1. set(PATH1 "/usr/local")
  2. set(PATH2 "bin")
  3. set(FULL_PATH "${PATH1}/${PATH2}")
  4. message(STATUS "Full path: ${FULL_PATH}")
复制代码
输出:
  1. -- Full path: /usr/local/bin
复制代码
推荐使用 CMAKE_CURRENT_SOURCE_DIR 等宏拼接路径,避免硬编码。
4. 多个值如何处理?
  1. set(SOURCES main.cpp utils.cpp helper.cpp)
  2. add_executable(myapp ${SOURCES})
复制代码
这是最常见的做法,适用于源文件列表、库名列表等。
六、最佳实践建议

场景推荐做法设置本地变量set(VAR value)设置全局变量set(VAR value CACHE INTERNAL "")用户配置选项option(VAR "desc" ON/OFF)子目录返回变量set(VAR value PARENT_SCOPE)拼接路径使用 ${CMAKE_CURRENT_SOURCE_DIR}/xxx判断变量是否存在if(DEFINED VAR)清理变量unset(VAR)实例总结
  1. # 设置变量
  2. set(MY_LIB mylib)
  3. set(SOURCES main.cpp utils.cpp)
  4. option(BUILD_TESTS "Build unit tests" ON)
  5. # 引用变量
  6. message(STATUS "Building with lib: ${MY_LIB}")
  7. add_executable(app ${SOURCES})
  8. # 控制逻辑
  9. if(BUILD_TESTS)
  10.     enable_testing()
  11.     add_subdirectory(tests)
  12. endif()
复制代码
(一)设置项目名 代码目录和构建目录
  1. cmake_minimum_required(VERSION 3.1)
  2. project(MyProject
  3.     VERSION 1.2.3
  4.     DESCRIPTION "A library for managing non-volatile memory"
  5.     HOMEPAGE_URL "https://example.com/libnvm"
  6.     LANGUAGES CUDA C CXX
  7. )
  8. # 打印 PROJECT_SOURCE_DIR 和 PROJECT_BINARY_DIR
  9. message(STATUS "Source directory: ${PROJECT_SOURCE_DIR}")
  10. message(STATUS "Binary directory: ${PROJECT_BINARY_DIR}")
复制代码
一、LANGUAGES


  • C: CMake 默认会启用的语言之一,适用于C源代码项目。
  • CXXC++: 另一个默认启用的语言,适用于C++源代码项目。
  • CUDA: 从 CMake 3.8 版本开始正式支持 CUDA 作为一等公民,允许直接编写 CUDA 源文件(.cu)并将其集成到构建系统中。
  • ASM: 支持汇编语言。需要注意的是,使用 ASM 需要特定的编译器支持,并且可能需要额外配置。
  • 其他语言
二、代码目录和构建目录

变量名含义是否变化PROJECT_SOURCE_DIR顶级 CMakeLists.txt 所在目录(即源码目录)固定PROJECT_BINARY_DIR你运行 cmake 的目录(即构建目录)根据你运行命令的位置而定假设你的项目结构如下:
  1. /home/user/my_project/
  2. ├── CMakeLists.txt
  3. └── src/
  4.     └── main.cpp
复制代码
情况一:就地构建(不推荐)
  1. cd /home/user/my_project
  2. cmake .
复制代码

  • 此时:

    • PROJECT_SOURCE_DIR = /home/user/my_project
    • PROJECT_BINARY_DIR = /home/user/my_project

⚠️ 这样会导致构建文件和源码混在一起,不利于维护。
情况二:外出构建out-of-source build( 推荐做法)
  1. cd /home/user/my_project/
  2. mkdir build
  3. cd build
  4. cmake ..
复制代码

  • 此时:

    • PROJECT_SOURCE_DIR = /home/user/my_project
    • PROJECT_BINARY_DIR = /home/user/my_project/build

✅ 所有构建产物都会被放在这个目录中,保持源码目录干净整洁。
三、添加子目录到构建系统中

add_subdirectory() 是 CMake 中用于组织大型项目的非常重要的命令,它允许你将一个子目录添加到构建系统中,并在该子目录中执行其 CMakeLists.txt 文件。告诉 CMake 进入 src/ 子目录,读取并处理其中的 CMakeLists.txt 文件。  子目录中的目标(如库或可执行文件)会被构建,并被主项目所使用。如果你在子目录中使用了 add_subdirectory(),每个子目录也有自己的 CMAKE_CURRENT_BINARY_DIR,但 PROJECT_BINARY_DIR 始终指向顶层构建目录。子目录中也可以继续调用 add_subdirectory() 添加更深层的模块。
  1. add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
复制代码

  • source_dir: 必须参数,相对于当前 CMakeLists.txt 的源代码子目录路径。
  • binary_dir: 可选参数,指定子目录构建输出的位置(默认是与源码同路径构建)。
  • EXCLUDE_FROM_ALL: 可选参数,表示子目录中的目标不会被默认构建(除非被其他目标依赖)。
使用示例

目录结构
  1. my_project/
  2. ├── CMakeLists.txt           # 主 CMakeLists.txt
  3. ├── main.cpp
  4. └── src/
  5.     ├── CMakeLists.txt       # 子模块
  6.     └── mylib.cpp
复制代码
src/CMakeLists.txt 示例
  1. # 构建一个静态库 libmylib.a
  2. add_library(mylib STATIC mylib.cpp)
  3. # 导出头文件目录
  4. target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
复制代码
主 CMakeLists.txt 示例
  1. cmake_minimum_required(VERSION 3.14)
  2. project(MyProject LANGUAGES CXX)
  3. # 添加子目录 src
  4. add_subdirectory(src)
  5. # 添加主程序
  6. add_executable(my_app main.cpp)
  7. # 链接子目录中生成的库
  8. target_link_libraries(my_app PRIVATE mylib)
复制代码
关键变量说明

在子目录中可以使用的变量:
变量含义CMAKE_CURRENT_SOURCE_DIR当前 CMakeLists.txt 所在的源码目录CMAKE_CURRENT_BINARY_DIR对应的构建目录(即生成中间文件的地方)PROJECT_SOURCE_DIR顶层项目的源码目录(不变)PROJECT_BINARY_DIR顶层项目的构建目录(不变)指定独立构建目录

你可以为子目录指定一个单独的构建路径:
  1. add_subdirectory(src build_src)
复制代码
这会把 src/ 目录下的构建产物放到 build_src/ 路径下,而不是直接放在 src/ 中。
⚠️ 注意:这个功能通常用于外部项目集成,比如结合 ExternalProject_Add 使用。
(二)定义可执行文件

手动添加源文件
add_executable() 是 CMake 中用于定义一个可执行文件目标的命令。它告诉 CMake 你想要从一组源代码文件构建出一个可执行程序,并允许你在后续步骤中为这个目标添加属性,比如链接库、编译选项等。

  • 定义目标:指定要生成的可执行文件名称以及由哪些源文件构成。
  • 组织构建:将相关的源文件组合成一个可执行文件,便于管理和构建。
  1. add_executable(target_name source1 [source2 ...])
复制代码

  • target_name: 可执行文件的目标名称(即输出的可执行文件名)。
  • source1, source2, ...: 构建该可执行文件所需的源代码文件列表。
非递归自动查找源文件
  1. # 作用:查找当前目录(. 表示当前目录)下所有的源文件(默认识别 .c, .cxx, .cpp, .cc 等),并将这些文件路径保存到变量 DIR_NAME 中。
  2. # 不会递归子目录:只查找当前目录,不进入子目录。
  3. # 常用于简单项目:适合小型项目或演示项目,快速将当前目录下所有源文件加入构建系统。
  4. aux_source_directory(. DIR_NAME)
  5. add_executable(target_name ${DIR_NAME})
复制代码
递归查找源文件
file(GLOB_RECURSE ...) 不会自动检测新增的文件,在 IDE 中(如 CLion、Qt Creator),可能需要重新运行 CMake 才能识别新加入的文件。
  1. # 递归查找 src 目录下所有 .cpp 文件(包括子目录)这里的 GLOB_RECURSE 表示递归搜索。
  2. file(GLOB_RECURSE SRC_LIST
  3.     "${PROJECT_SOURCE_DIR}/*.cpp"
  4.     "${PROJECT_SOURCE_DIR}/*.cc"
  5.     "${PROJECT_SOURCE_DIR}/*.cxx"
  6. )
  7. # 添加可执行程序
  8. add_executable(myapp ${SRC_LIST})
复制代码
示例

假设有一个简单的项目结构如下:
  1. my_project/
  2. ├── CMakeLists.txt
  3. └── main.cpp
复制代码
其中 main.cpp 包含了项目的入口代码。
CMakeLists.txt 示例
  1. cmake_minimum_required(VERSION 3.10)
  2. project(MyExecutable LANGUAGES CXX)
  3. # 添加可执行文件目标
  4. add_executable(my_app main.cpp)
复制代码
在这个例子中,my_app 将是最终生成的可执行文件的名字,而 main.cpp 则是它的源文件。
使用场景与扩展

1. 多个源文件

如果你的项目包含多个源文件,可以像下面这样列出所有源文件:
  1. add_executable(my_app main.cpp utils.cpp network.cpp)
复制代码
或者,使用通配符简化输入(虽然不推荐,因为这可能会导致不必要的重新构建):
  1. file(GLOB SOURCES "src/*.cpp")
  2. add_executable(my_app ${SOURCES})
复制代码
2. 链接外部库

你可以通过 target_link_libraries() 来链接你的可执行文件到其他库:
  1. find_package(SomeLibrary REQUIRED)
  2. add_executable(my_app main.cpp)
  3. target_link_libraries(my_app PRIVATE Some::Library)
复制代码
3. 设置编译选项

你可以通过 target_compile_options() 或者 target_compile_definitions() 等命令来设置特定于目标的编译器选项或宏定义:
  1. add_executable(my_app main.cpp)
  2. target_compile_options(my_app PRIVATE -Wall -Wextra)
  3. target_compile_definitions(my_app PRIVATE DEBUG=1)
复制代码
4. 设置包含目录

为了包含头文件路径,可以使用 target_include_directories():
  1. add_executable(my_app main.cpp)
  2. target_include_directories(my_app PRIVATE ${PROJECT_SOURCE_DIR}/include)
复制代码
注意事项


  • 目标名称唯一性:在一个 CMake 项目中,所有的目标名称必须是唯一的。
  • 避免重复定义:不要在同一个 CMakeLists.txt 文件或不同的子目录中重复定义相同的目标名称。
  • 现代 CMake 实践:尽量使用目标级别的命令(如 target_link_libraries, target_include_directories)而不是全局命令(如 include_directories, link_libraries),以提高封装性和减少冲突的可能性。
(三)头文件搜索路径和指定链接库

一、目标级命令 vs 全局作用域命令

目标级命令全局作用域命令target_link_libraries(...)link_libraries(...), link_directories(...)target_include_directories(...)include_directories(...)target_compile_options(...)add_compile_options(...)target_compile_definitions(...)add_definitions(...)二、逐个讲解每个命令的作用和用法

1. target_include_directories(...)

作用 & 示例

为某个目标添加头文件搜索路径。在哪里搜索头文件。
  1. target_include_directories(myapp PRIVATE ${PROJECT_SOURCE_DIR}/include)
复制代码

  • PRIVATE: 表示这些头文件路径只对 myapp 生效。
  • 支持 PUBLIC 和 INTERFACE 模式,用于控制是否将路径暴露给依赖者。
旧式写法(不推荐):
  1. include_directories(${PROJECT_SOURCE_DIR}/include)
  2. add_executable(myapp main.cpp)
复制代码
⚠️ 缺点:include_directories(...) 是全局生效的,会影响所有后续定义的目标,即使它们并不需要这些路径。
2. target_link_libraries(...)

作用 & 示例

指定某个目标(如可执行文件或库)在链接阶段需要使用的其他库。
  1. add_executable(myapp main.cpp)
  2. target_link_libraries(myapp PRIVATE Some::Library)
复制代码

  • PRIVATE: 表示这个依赖只用于 myapp,不会传递给使用 myapp 的目标。
  • Some:ibrary: 是一个“导入目标”(imported target),通常通过 find_package() 获得。
旧式写法(不推荐):
  1. link_directories(/usr/local/lib)
  2. link_libraries(Some::Library)
  3. add_executable(myapp main.cpp)
复制代码
⚠️ 缺点:link_directories 和 link_libraries 是全局生效的,影响所有后续目标,容易造成混乱。
3. target_compile_options(...)

作用 & 示例

为某个目标设置编译器选项(如 -Wall, -O3, /W4 等)。
  1. target_compile_options(myapp PRIVATE -Wall -Wextra -O3)
复制代码

  • 只对 myapp 这个目标生效。
  • 支持跨平台编译器选项判断:
  1. target_compile_options(myapp PRIVATE
  2.     $<$<CXX_COMPILER_ID:GNU>:--pedantic>
  3.     $<$<CXX_COMPILER_ID:MSVC>:/W4>
  4. )
复制代码
旧式写法(不推荐):
  1. add_compile_options(-Wall -Wextra)
  2. add_executable(myapp main.cpp)
复制代码
⚠️ 缺点:add_compile_options(...) 是全局生效的,影响所有目标。
4. target_compile_definitions(...)

作用 & 示例

为某个目标定义宏定义(预处理器宏),相当于 -DNAME=VALUE。
  1. target_compile_definitions(myapp PRIVATE DEBUG=1 USE_FEATURE_X)
复制代码

  • 仅对 myapp 生效。
  • 不会污染其他目标。
旧式写法(不推荐):
  1. add_definitions(-DDEBUG=1 -DUSE_FEATURE_X)
  2. add_executable(myapp main.cpp)
复制代码
⚠️ 缺点:add_definitions(...) 是全局生效的,影响所有目标。
三、进阶技巧:使用 INTERFACE 库封装接口

你可以创建一个纯接口的库,把公共配置集中起来:
  1. add_library(mylib INTERFACE)
  2. target_include_directories(mylib INTERFACE ${PROJECT_SOURCE_DIR}/include)
  3. target_compile_definitions(mylib INTERFACE DEBUG=1)
  4. target_link_libraries(mylib INTERFACE Some::Library)
  5. # 使用该接口库
  6. add_executable(myapp main.cpp)
  7. target_link_libraries(myapp PRIVATE mylib)
复制代码
(四)动态库和静态库

在 CMake 中,你可以通过 add_library() 命令来创建静态库或动态库(共享库)。这两个类型的库在编译、链接和最终部署时有不同的行为和用途。

  • 静态库(Static Library)

    • 文件扩展名通常为 .a(Unix/Linux)或 .lib(Windows)。
    • 在编译时,静态库的代码会被直接复制到生成的可执行文件中。
    • 优点:部署简单,因为所有依赖都已包含在可执行文件内;启动速度快,因为没有额外的加载过程。
    • 缺点:可能导致可执行文件体积较大;如果多个程序使用同一个静态库,则每个程序都会携带一份副本,浪费磁盘空间和内存。

  • 动态库(Shared Library)

    • 文件扩展名为 .so(Unix/Linux)、.dylib(macOS)或 .dll(Windows)。
    • 在编译时仅记录对库的引用,在运行时才真正加载这些库。
    • 优点:多个程序可以共享同一份库的副本,节省磁盘空间和内存;便于更新,无需重新编译依赖它的程序。
    • 缺点:需要确保在运行时能够找到并加载相应的库文件;可能会导致启动时间增加,因为需要加载外部库。

创建静态库和动态库

在 CMake 中,使用 add_library() 来定义库,并通过指定库类型来决定是创建静态库还是动态库。如果没有指定 STATIC 或 SHARED,CMake 将根据平台默认设置选择一种类型。但是为了明确性和控制力,建议总是显式地指定库类型。
创建静态库STATIC
  1. add_library(my_static_lib STATIC source1.cpp source2.cpp)
复制代码

  • STATIC: 表示创建一个静态库。
创建动态库SHARED
  1. add_library(my_shared_lib SHARED source1.cpp source2.cpp)
复制代码

  • SHARED: 表示创建一个动态库。
使用库

无论是静态库还是动态库,都可以通过 target_link_libraries() 来将它们链接到你的可执行文件或其他库中。
示例

假设你有一个项目结构如下:
  1. my_project/
  2. ├── CMakeLists.txt
  3. └── src/
  4.     ├── main.cpp
  5.     └── mylib.cpp
  6.     └── mylib.h
复制代码
CMakeLists.txt
  1. cmake_minimum_required(VERSION 3.10)
  2. project(MyProject LANGUAGES CXX)
  3. # 添加子目录 src
  4. add_subdirectory(src)
  5. # 添加主程序
  6. add_executable(main_app main.cpp)
  7. # 链接库
  8. target_link_libraries(main_app PRIVATE mylib) # 假设 mylib 是上面添加的库
复制代码
src/CMakeLists.txt
  1. # 创建静态库
  2. add_library(my_static_lib STATIC mylib.cpp)
  3. # 创建动态库
  4. add_library(my_shared_lib SHARED mylib.cpp)
  5. # 导出静态库头文件目录
  6. target_include_directories(my_static_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
  7. # 导出动态库头文件目录
  8. target_include_directories(my_shared_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
  9. # 在 Windows 上构建 .dll 动态库时,默认情况下,CMake 不会自动导出任何符号(与 Linux/macOS 上的 .so/.dylib 行为不同)。你需要明确告诉编译器哪些符号应该被导出,供外部程序调用。Linux、macOS 上不需要加这一行代码
  10. set_target_properties(my_shared_lib PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
  11. # 安装库文件
  12. install(TARGETS my_static_lib my_shared_lib
  13.         ARCHIVE DESTINATION lib
  14.         LIBRARY DESTINATION lib
  15.         RUNTIME DESTINATION bin
  16.         INCLUDES DESTINATION include)
  17. # 安装头文件(可选,如果 PUBLIC 没有包含全部头文件)
  18. install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/mylib.h
  19.         DESTINATION include)
  20. # 可选:导出配置文件以支持 find_package()
  21. install(EXPORT MyLibTargets
  22.         FILE MyLibTargets.cmake
  23.         NAMESPACE MyLib::
  24.         DESTINATION lib/cmake/MyLib)
复制代码
特别注意事项


  • Windows 上的 DLLs:在 Windows 平台上,当创建动态库时,通常还需要提供一个“导入库”(.lib 文件),这个文件包含了对外部函数的引用信息。CMake 会自动生成这个导入库。
  • 版本号与 ABI 兼容性:对于共享库,特别是在 Linux 系统上,考虑给库加上版本号以管理 ABI(应用程序二进制接口)兼容性问题。可以通过 set_target_properties() 设置属性来实现这一点。
  • 安装规则:如果你计划分发你的库,可能需要定义安装规则,以便于正确地安装头文件和库文件到系统目录。
例如:
  1. install(TARGETS my_shared_lib DESTINATION lib)
  2. install(FILES mylib.h DESTINATION include)
复制代码
这将把你的共享库安装到系统的 lib 目录下,头文件安装到 include 目录下。
(五)查找和加载外部依赖库

find_package() 是 CMake 中用于 查找和加载外部依赖库(第三方库或系统库) 的核心命令之一。它帮助你自动检测系统中是否安装了某个库,并设置好相应的变量(如头文件路径、库路径、版本号等),以便后续使用这些库进行编译和链接。
一、find_package 的作用

简单来说,它的主要功能是:

  • 查找指定的库是否存在于系统中
  • 设置相关变量:

    • _FOUND: 是否找到该库
    • _INCLUDE_DIRS 或 _INCLUDE_DIR: 头文件路径
    • _LIBRARIES 或 _LIBRARY: 库文件路径
    • _VERSION: 版本信息(如果支持)

  • 自动导入目标(现代用法):可以直接 target_link_libraries(my_target PRIVATE Some:ibrary)
二、基本语法
  1. find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
  2.              [REQUIRED] [[COMPONENTS] [components...]]
  3.              [OPTIONAL_COMPONENTS components...]
  4.              [NO_POLICY_SCOPE])
复制代码
参数说明:
参数含义要查找的包名,比如 Threads, OpenCV, CUDA, Protobuf 等[version]可选,指定最低版本要求EXACT可选,要求精确匹配版本QUIET可选,禁止输出错误信息MODULE可选,强制使用模块模式(Module mode)而不是配置模式(Config mode)REQUIRED可选,如果找不到包则报错并停止构建COMPONENTS可选,指定需要查找的子组件(适用于有多个组件的库,如 Qt5Core, Qt5Gui 等)三、典型使用示例

示例 1:查找线程库(Threads)
  1. find_package(Threads REQUIRED)
  2. target_link_libraries(my_target PRIVATE Threads::Threads)
复制代码

  • find_package(Threads REQUIRED):查找线程库
  • Threads::Threads:现代 CMake 推荐的导入方式,表示找到的线程库目标
示例 2:查找 CUDA
  1. find_package(CUDA 11.7 REQUIRED)
  2. if (CUDA_FOUND)
  3.     message(STATUS "Found CUDA ${CUDA_VERSION}")
  4. endif()
复制代码
示例 3:查找 OpenCV 并链接
  1. find_package(OpenCV REQUIRED COMPONENTS core imgproc highgui)
  2. include_directories(${OpenCV_INCLUDE_DIRS})
  3. add_executable(my_app main.cpp)
  4. target_link_libraries(my_app PRIVATE ${OpenCV_LIBS})
复制代码
示例 4:查找可选库(非必须)
  1. find_package(ZLIB QUIET)
  2. if (ZLIB_FOUND)
  3.     target_compile_definitions(my_target PRIVATE HAVE_ZLIB)
  4.     target_link_libraries(my_target PRIVATE ZLIB::ZLIB)
  5. else()
  6.     message(WARNING "ZLIB not found, some features will be disabled.")
  7. endif()
复制代码
四、两种查找模式

CMake 支持两种查找机制:
1. Module Mode(模块模式)


  • 使用内置的 Find.cmake 文件来查找库
  • 通常用于标准库或常见第三方库(如 Threads、OpenGL、SDL 等)
  • 示例:FindOpenCV.cmake
2. Config Mode(配置模式)


  • 使用库自带的 Config.cmake 文件
  • 更现代、更灵活的方式,推荐优先使用
  • 一般在你安装了某个库之后会自动生成这些文件(如通过 make install 或 vcpkg, conan 安装)
你可以通过环境变量 CMAKE_PREFIX_PATH 来告诉 CMake 去哪些路径下查找这些配置文件。
五、如何查看 find_package 找到了什么?

你可以在 CMakeLists.txt 中加入以下代码来打印相关信息:
  1. message(STATUS "<PackageName>_FOUND: ${PackageName}_FOUND")
  2. message(STATUS "<PackageName>_INCLUDE_DIRS: ${PackageName}_INCLUDE_DIRS")
  3. message(STATUS "<PackageName>_LIBRARIES: ${PackageName}_LIBRARIES")
  4. message(STATUS "<PackageName>_VERSION: ${PackageName}_VERSION")
复制代码
或者在运行 cmake 时加上 --debug-find(某些版本支持)查看详细查找过程。
六、一些常见的可以 find_package 的库

包名用途Threads多线程支持CUDANVIDIA GPU 编程支持OpenMPOpenMP 并行编程支持OpenCV计算机视觉库Eigen3C++ 矩阵运算库BoostC++ 扩展库Python3Python 支持PkgConfig使用 pkg-config 查找其他库Doxygen文档生成工具GitGit 工具支持(六)配置文件configure_file

configure_file 是 CMake 中的一个命令,用于在构建过程的配置阶段生成新的文件或更新现有文件。这个命令通常用来处理模板文件,比如将一些项目配置或者版本信息嵌入到源代码中,或者根据构建环境的不同生成不同的配置文件。*.in 模板文件一般是用于在 cmake 执行阶段(准确来说,应该是配置阶段)将其嵌入的 CMake 变量、列表展开,并生成目标文件的过程中的。这一步通常是由 configure_file() 完成,会在【06】变量参与 C++ 的编译 中进行介绍。*.in 的后缀是约定俗成的,当然也可以使用其他形式的后缀。
基本用法
  1. configure_file(<input> <output>
  2.                [NO_SOURCE_PERMISSIONS | FILE_PERMISSIONS <permissions>...]
  3.                [COPYONLY] [ESCAPE_QUOTES] [@ONLY])
复制代码

  • : 模板文件的位置。可以使用相对路径或绝对路径。
  • 文件中可以包含 CMake 变量,它们会在执行 configure_file 时被实际值替换。
  • : 目标文件的位置和名称。如果目标文件已经存在,则会被覆盖。
示例

假设你有一个名为 config.h.in 的输入文件,内容如下:
  1. #define PROJECT_SOURCE_DIR "@CMAKE_SOURCE_DIR@"
  2. #define PROJECT_VERSION "@PROJECT_VERSION@"
复制代码
然后,在你的 CMakeLists.txt 文件中,你可以这样使用 configure_file:
  1. cmake_minimum_required(VERSION 3.10)
  2. project(MyProject VERSION 1.0)
  3. configure_file(
  4.   ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
  5.   ${CMAKE_CURRENT_BINARY_DIR}/config.h
  6. )
复制代码
在这个例子中,@CMAKE_SOURCE_DIR@ 和 @PROJECT_VERSION@ 这样的标记会被对应的 CMake 变量值所替换,并生成一个新的 config.h 文件到构建目录下
参数解释


  • NO_SOURCE_PERMISSIONS: 不复制原始文件的权限设置给输出文件,默认情况下会复制。
  • FILE_PERMISSIONS: 设置输出文件的权限。
  • COPYONLY: 如果指定了此选项,configure_file 将不会替换任何变量,只会简单地复制文件。
  • ESCAPE_QUOTES: 转义输出中的引号(对于某些特定情况可能需要)。
  • @ONLY: 仅替换以 @ 开头和结尾的变量,忽略 ${} 格式的变量引用。
configure_file 是一个非常有用的工具,尤其是在你需要基于构建环境动态生成配置文件的时候。通过它,你可以轻松实现从简单的文本替换到复杂的配置文件生成等多种任务。
举例说明1:在代码中使用

背景

假设你正在开发一个C++项目,并希望在编译时根据项目的设置动态生成配置头文件(如 config.h),以便于在代码中访问一些构建时确定的信息,比如项目版本号、源码目录等。
步骤 1: 创建模板文件

首先,在你的项目目录下创建一个名为 config.h.in 的模板文件。这个文件将包含一些占位符,这些占位符会在构建时被实际值替换。
例如,config.h.in 文件内容如下:
  1. #pragma once#define PROJECT_SOURCE_DIR "@CMAKE_SOURCE_DIR@"
  2. #define PROJECT_VERSION "@PROJECT_VERSION@"
复制代码
这里的 @CMAKE_SOURCE_DIR@ 和 @PROJECT_VERSION@ 是占位符,它们会被 CMake 中定义的实际值替换。
步骤 2: 修改 CMakeLists.txt

接下来,你需要修改或者创建 CMakeLists.txt 文件,告诉 CMake 在构建过程中如何处理这个模板文件。
  1. cmake_minimum_required(VERSION 3.10)
  2. # 定义项目名称和版本
  3. project(MyProject VERSION 1.0)
  4. # 使用 configure_file 命令处理模板文件
  5. configure_file(
  6.   ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
  7.   ${CMAKE_CURRENT_BINARY_DIR}/config.h
  8. )
复制代码
这里我们使用了 configure_file 命令来指定输入文件(config.h.in)和输出文件(config.h)。${CMAKE_CURRENT_SOURCE_DIR} 和 ${CMAKE_CURRENT_BINARY_DIR} 分别表示源代码目录和构建目录。
步骤 3: 构建项目

现在,当你运行 CMake 来构建项目时,它会自动处理 config.h.in 文件,并生成一个新的 config.h 文件到构建目录下。
例如,如果你的源代码目录结构如下:
  1. MyProject/
  2. ├── CMakeLists.txt
  3. └── config.h.in
复制代码
并且你在一个单独的构建目录中执行 CMake:
  1. mkdir build
  2. cd build
  3. cmake ..
复制代码
CMake 将会在 build/ 目录下生成 config.h 文件,其内容类似于:
  1. #pragma once
  2. #define PROJECT_SOURCE_DIR "/path/to/your/source/directory"
  3. #define PROJECT_VERSION "1.0"
复制代码
这样,你就可以在代码中包含这个自动生成的 config.h 文件,并访问其中定义的宏了:
[code]#include "config.h"void printConfig() {    std::cout
您需要登录后才可以回帖 登录 | 立即注册