cmake
CMake
CMakeLists.txt
- 指定语言版本
1 | set(CMAKE_CXX_STANDARD 11) |
CMAKE_开头的变量都是CMAKE内置变量,CMAKE保留变量
- 配置编译选项
1 | add_compile_options(-Wall -Wextra -pedantic -Werror) |
- 配置编译类型
类型可设置为:Debug, Release, RelWithDebInfo, MinSizeRel。可以针对不同的编译类型设置不同的编译选项
1 | set(CMAKE_BUILD_TYPE Debug) |
- 全局宏定义
通过判断不同的宏定义在命令行cmake -DDEBUG=1 -DREAL_COOL_ENGINEER=0实现.cpp源代码中宏的流程控制
1 | add_definitions(-DDEBUG -DREAL_COOL_ENGINEER) |
- 添加include目录
1 | include_directories(src/include) |
多工程嵌套时使用target_include_directories管理头文件包含
当工程A需要使用工程B的头文件时,target_include_directories是CMake中管理这种依赖关系的推荐方式。以下是详细解决方案:
一、基本配置方法
1. 在工程B中设置头文件可见性
1 | # 工程B的CMakeLists.txt |
2. 在工程A中引用工程B
1 | # 工程A的CMakeLists.txt |
二、多工程嵌套的完整示例
项目结构
1 | workspace/ |
工程B配置
1 | # projectB/CMakeLists.txt |
工程A配置
1 | # projectA/CMakeLists.txt |
三、高级用法
1. 使用find_package(安装后引用)
1 | # 工程B配置安装导出 |
2. 跨工程包含不同目录级别
1 | # 工程B设置多层包含目录 |
3. 条件包含设置
1 | target_include_directories(projectB |
四、常见问题解决方案
问题1:找不到头文件
解决方案:
1 | # 确保工程B正确设置了PUBLIC包含路径 |
问题2:安装后路径错误
解决方案:
1 | # 使用相对路径确保可移植性 |
问题3:多配置生成器问题
解决方案:
1 | target_include_directories(projectB |
五、最佳实践
- 使用生成器表达式:
$<BUILD_INTERFACE>和$<INSTALL_INTERFACE>确保路径正确 - 命名空间隔离:为安装的目标添加命名空间避免冲突
- 明确作用域:
PUBLIC:依赖项目需要包含的头文件PRIVATE:仅本项目内部使用的头文件
- 版本兼容性:为安装的库生成版本文件
1 | # 工程B的版本文件 |
通过这种方式,可以清晰地管理多工程间的头文件依赖关系,确保无论是在开发时(同workspace)还是安装后引用,都能正确找到所需的头文件。
Qt中使用多个CMakeLists.txt实现模块化项目管理
在大型Qt项目中,使用多个CMakeLists.txt文件进行模块化管理可以提高项目的可维护性和可扩展性。以下是完整的模块化项目管理方案:
一、基本项目结构
1 | MyProject/ |
二、根CMakeLists.txt配置
1 | cmake_minimum_required(VERSION 3.16) |
三、模块化CMakeLists.txt示例
1. 核心模块 (core/CMakeLists.txt)
1 | # 创建核心库 |
2. GUI模块 (gui/CMakeLists.txt)
1 | # 查找UI文件 |
3. 应用层 (app/CMakeLists.txt)
1 | # 创建可执行文件 |
四、高级模块化技巧
1. 条件编译模块
1 | # 根CMakeLists.txt中 |
2. 动态库模块
1 | # 在模块CMakeLists.txt中 |
3. 资源文件处理
1 | # 在模块中添加资源 |
4. 翻译文件支持
1 | # 查找翻译文件 |
五、跨模块依赖管理
1. 创建配置头文件
1 | # 在core/CMakeLists.txt中 |
2. 使用target_include_directories
1 | # 在gui/CMakeLists.txt中 |
3. 导出目标
1 | # 在core/CMakeLists.txt中 |
六、测试模块配置
1 | # tests/CMakeLists.txt |
七、现代CMake最佳实践
- **使用target_**命令:优先使用
target_include_directories()而非全局include_directories() - 明确作用域:区分
PUBLIC、PRIVATE和INTERFACE依赖 - 避免全局变量:使用
target_link_libraries()而非link_libraries() - 生成导出头文件:使用
configure_file()生成版本信息 - 使用生成器表达式:如
$<BUILD_INTERFACE:...>和$<INSTALL_INTERFACE:...>
八、完整示例:模块间通信
1. 在core模块定义接口
1 | // core/icore.h |
2. 在GUI模块使用接口
1 | // gui/mainwindow.cpp |
3. 在CMake中确保包含路径
1 | # core/CMakeLists.txt |
通过这种模块化结构,您的Qt项目将获得更好的:
- 可维护性:清晰的项目结构
- 可扩展性:轻松添加新模块
- 可测试性:独立测试各模块
- 复用性:模块可独立编译使用
CMake 配置(重载)与构建的区别
CMake 的配置(重载)和构建是两个完全不同但相互关联的阶段,理解它们的区别对于有效使用 CMake 至关重要。
CMake 配置(Configure / 重载)
定义
配置阶段是 CMake 处理 CMakeLists.txt 文件并生成构建系统文件的过程。
执行命令
1 | cmake -B build_dir -DCMAKE_BUILD_TYPE=Release # 配置 |
主要任务
- 解析 CMakeLists.txt:读取和分析项目配置
- 检测系统环境:检查编译器、工具链、依赖库
- 生成构建系统文件:
- Unix: 生成
Makefile - Windows: 生成
*.sln和*.vcxproj - Ninja: 生成
build.ninja
- Unix: 生成
- 设置变量和选项:处理
-D参数定义的变量 - 生成自动生成的文件:如
*_autogen目录中的文件
输出结果
- 构建系统文件(Makefile、*.sln 等)
- CMakeCache.txt(缓存变量)
- CMakeFiles/ 目录
- 自动生成的文件(如 moc_.cpp、ui_.h 等)
CMake 构建(Build)
定义
构建阶段是使用生成的构建系统文件实际编译源代码和链接目标文件的过程。
执行命令
1 | cmake --build build_dir --config Release # 构建 |
主要任务
- 编译源代码:将
.cpp文件编译为.o对象文件 - 运行代码生成器:执行 moc(Qt)、protoc(Protocol Buffers)等
- 链接目标文件:将对象文件链接成可执行文件或库
- 处理依赖关系:确保正确的构建顺序
输出结果
- 可执行文件(bin/)
- 库文件(lib/)
- 对象文件(*.o)
- 最终的应用程序
关键区别对比
| 特性 | CMake 配置(重载) | CMake 构建 |
|---|---|---|
| 目的 | 生成构建系统 | 编译和链接代码 |
| 输入 | CMakeLists.txt | 源代码文件 |
| 输出 | Makefile/MSBuild 文件 | 可执行文件/库 |
| 执行频率 | 修改配置时执行 | 修改代码时执行 |
| 耗时 | 相对较短 | 可能很长(取决于代码量) |
| 命令 | cmake -B build_dir |
cmake --build build_dir |
| 依赖 | 需要 CMake | 需要编译器 |
典型工作流程
1 | # 1. 首次配置(生成构建系统) |
什么时候需要重新配置?
需要执行 cmake -B build_dir 的情况:
- 修改了
CMakeLists.txt文件 - 添加或删除了源文件
- 更改了 CMake 变量(如
-D参数) - 系统环境发生变化(编译器路径变更等)
- 依赖库版本更新
什么时候只需要重新构建?
只需要执行 cmake --build build_dir 的情况:
- 修改了源代码文件(.cpp, .h)
- 修改了资源文件
- 需要重新链接(但配置未变)
实际示例
1 | # 初始设置 |
常见误区
-
错误:每次代码修改都运行
cmake -B build- 正确:只需要运行
cmake --build build
- 正确:只需要运行
-
错误:认为配置很耗时,避免运行
- 正确:配置相对较快,确保构建系统正确很重要
-
错误:手动修改生成的 Makefile
- 正确:总是通过修改 CMakeLists.txt 来改变配置
理解这两个阶段的区别可以显著提高 CMake 的使用效率和项目的构建速度。
install
install 命令用于定义编译完成后如何安装目标文件(如可执行文件、库)、头文件、配置文件等到指定位置,是构建安装流程的核心指令。该命令不会在构建阶段执行,而是在用户显式调用 cmake --install 时生效。
1 | install(TARGETS <目标名称>... # 安装构建目标(如可执行文件、库) |
cmake文件与CMakeLists.txt区别
核心区别:
-
角色与定位:
CMakeLists.txt: 这是 项目构建的入口点和主控文件。它是 CMake 处理一个项目(或项目中的一个目录)的起点和核心。每个包含源代码或需要构建逻辑的目录必须有一个CMakeLists.txt文件。它定义了项目的整体结构、目标(可执行文件、库)、编译选项、链接依赖关系以及包含哪些子目录。.cmake文件: 这些是 模块化、可重用的脚本片段或功能包。它们本身通常不是 CMake 的起点。它们包含函数、宏、变量定义、查找特定库或工具的复杂逻辑(Find<Package>.cmake)、设置特定功能的代码等。它们被设计成可以被include()指令加载到CMakeLists.txt文件或其他.cmake文件中。
-
命名与位置:
CMakeLists.txt: 严格命名。必须叫CMakeLists.txt(注意大小写和下划线)。通常位于项目的根目录以及每个需要参与构建的子目录中。.cmake文件: 灵活命名。可以是任何有效的文件名(通常具有描述性),只要以.cmake结尾即可(例如:ConfigureCompiler.cmake,VersionHelpers.cmake,FindOpenSSL.cmake)。它们可以放在项目的任何位置(通常组织在cmake/目录下),也可以放在系统的 CMake 模块路径中(如/usr/share/cmake/Modules/)。
-
执行方式:
CMakeLists.txt: CMake 工具 (cmake命令) 直接处理这些文件。当你运行cmake <path-to-source>时,你指向的就是包含顶层CMakeLists.txt的目录。CMake 会处理该文件,并递归处理其中add_subdirectory()指令指定的子目录中的CMakeLists.txt。.cmake文件: 它们需要通过include()指令显式加载到CMakeLists.txt文件(或另一个.cmake文件)中才会被执行。例如:include(cmake/MyHelpers.cmake)或find_package(OpenSSL REQUIRED)(后者会在模块路径中查找FindOpenSSL.cmake并执行它)。
-
内容侧重点:
-
CMakeLists.txt:侧重于
项目结构定义
和
构建目标声明
。
- 项目声明 (
project()) - 添加可执行文件 (
add_executable()) - 添加库 (
add_library()) - 链接库 (
target_link_libraries()) - 设置编译/链接选项 (
target_compile_options(),target_link_options()) - 包含目录 (
target_include_directories()) - 添加子目录 (
add_subdirectory()) - 条件分支 (
if(),elseif(),else()) - 循环 (
foreach(),while()) - 加载
.cmake文件 (include(),find_package())
- 项目声明 (
-
.cmake文件:侧重于
封装可重用逻辑
。
- 定义函数 (
function() ... endfunction()) - 定义宏 (
macro() ... endmacro()) - 定义变量(尤其是供外部使用的变量)
- 封装复杂的查找逻辑(
find_path(),find_library(),find_program()等),通常用于Find<Package>.cmake脚本。 - 封装特定平台或编译器的设置。
- 提供工具函数(如字符串处理、文件操作辅助函数)。
- 实现自定义的 CMake 命令或策略。
- 定义函数 (
-
cmake_安装导入静态/动态库的三种方式(find_package INSTALL 使用绝对路径)
1 使用find_pakcage导入库

1.1 模块模式


如下,一个例子,如何管理 需要安装的三个文件:头文件、库、Findxxx.cmake文件?
将上述三者安装在指定的目录下,方便管理
- 头文件: ${PROJECT_SOURCE_DIR}/install/include
- 库文件: ${PROJECT_SOURCE_DIR}/install/lib
- Findxxx.cmake: ${PROJECT_SOURCE_DIR}/install/cmake


1.2 配置模式
CMAKE_SOURCE_DIR 工程的根目录 (= CMakeList.txt 所在目录)??



2 使用安装的方式导入库

备注: 安装/导入 动态库 使用 SHARED
3 使用绝对路径导入库(不推荐)

4. 使用 catkin 直接构建cmake文件


- hello_cv_2_add_static_lib 为自定义生成的库
- hello_cv_5_target_link_outer_lib 为使用上述库的一个程序



生成自定义库和添加自定义库到其他项目
- 自定义项目名Dialog, 输出库名dialogExport, 命名空间dialogExport
- install export安装的导出目录不可以是项目源码目录Dialog/和编译生成目录build/, 由CMAKE_INSTALL_PREFIX定义安装位置
- 安装后的目录结构
1 | |- ${CMAKE_INSTALL_PREFIX}/ |
- target_link_libraries(MainWindow PUBLIC Qt${QT_VERSION_MAJOR}::Widgets dialogExport::Dialog) #使用项目名Dialog而不是输出包名dialogExport,注意还需要加命名空间名dialogExport
出现问题
- AutoUic error ------------- “SRC:/dialog/SRC/dialogopen.cpp” includes the uic file “ui_dialogopen.h”, but the user interface file “dialogopen.ui” could not be found in the following directories “SRC:/dialog/SRC”
CMAKE_AUTOUIC_SEARCH_PATHS 需要在 add_executable() 或 add_library() 之前设置。
- cmake添加目标文件的头文件时找不到目标文件
target_include_directories放到add_library或add_executable之后。
- moc编译不了QT_Object类
把含有QT_Object头文件放到add_library或add_executable中
- CMake Error in thirdPart/Message/CMakeLists.txt: Target “Message” INTERFCE_INCLUDE_DERECTORIES property contains path: “/home/code/CMakeLearning/10Install/02output/thirdPart/Message/include” which is prefixed in the source directory.
产生该错误的原因是:target_include_directories命令和第二个install命令产生了冲突。
-
target_include_directories命令其实就是给Message库设置了属性INTERFCE_INCLUDE_DERECTORIES。即Message库在构建和安装时配置信息中都会包含target_include_directories命令中的路径。 -
而在第二个
install中,MessageTarget为该库导出的对象文件,该对象文件会包含Message的配置相关信息。而再指定该对象文件的安装DESITNATION会和target_include_directories中的设置冲突。而安装该导出文件有不能不指定DESTINATION,当然直接注释第二个install也行。 -
(1)使用生成器表达式,分开定义构建时和安装时的头文件路径。
-
(2) 下边代码路径不要包含build路径和源代码路径
1
2
3
4
5
6
7
8
9
10
11install(TARGETS ${PROJECT}
EXPORT ${LIBNAME}Targets
#安装可执行文件, 例如.exe
RUNTIME DESTINATION bin#"${INSTALL_EXAMPLEDIR}"
#安装macos应用程序包bundle, *.app
BUNDLE DESTINATION bin#"${INSTALL_EXAMPLEDIR}"
#安装动态库.dll, .so, .dylib
LIBRARY DESTINATION lib#"${INSTALL_EXAMPLEDIR}"
ARCHIVE DESTINATION lib
INCLUDES DESTINATION include
)
生成器表达式
基本语法结构
cmake
1 | $<TARGET_FILE:target_name> |
-
$<...>:生成器表达式的语法包装 -
TARGET_FILE:生成器表达式的关键字,表示获取目标文件的完整路径 -
target_name:CMake 目标名称
具体到我们的例子
cmake
1 | "$<TARGET_FILE:Python3::Python>" |
这表示:获取 Python3::Python 这个目标对应的可执行文件或库文件的完整路径。
类似的相关生成器表达式
cmake
1 | # 获取目标文件的完整路径 |
为什么使用生成器表达式?
传统方式的问题:
cmake
1 | # 传统方式 - 在配置时获取路径 |
生成器表达式的优势:
cmake
1 | # 现代方式 - 在生成时解析路径 |



