CMake CMakeLists.txt
指定语言版本
1 set (CMAKE_CXX_STANDARD 11 )
CMAKE_开头的变量都是CMAKE内置变量,CMAKE保留变量
配置编译选项
1 2 3 add_compile_options (-Wall -Wextra -pedantic -Werror)set (CMAKE_CXX_FLAGS "" ${CMAKE_CXX_FLAGS} -pipe -std=c++11 "" )
配置编译类型 类型可设置为:Debug, Release, RelWithDebInfo, MinSizeRel。可以针对不同的编译类型设置不同的编译选项
1 2 3 set (CMAKE_BUILD_TYPE Debug)set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0" )
全局宏定义 通过判断不同的宏定义在命令行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 2 3 4 5 6 7 8 9 add_library (projectB STATIC srcB.cpp includeB/projectB.h)target_include_directories (projectB PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR} /includeB> $<INSTALL_INTERFACE:include > )
2. 在工程A中引用工程B 1 2 3 4 5 add_executable (projectA srcA.cpp)target_link_libraries (projectA PRIVATE projectB)
二、多工程嵌套的完整示例 项目结构 1 2 3 4 5 6 7 8 9 10 11 12 workspace/ ├── projectA/ │ ├── CMakeLists.txt │ ├── src/ │ └── main.cpp └── projectB/ ├── CMakeLists.txt ├── include/ │ └── projectB/ │ └── module.h └── src/ └── module.cpp
工程B配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 cmake_minimum_required (VERSION 3.10 )project (ProjectB)add_library (projectB STATIC src/module.cpp)target_include_directories (projectB PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR} /include > $<INSTALL_INTERFACE:include > ) install (TARGETS projectB EXPORT projectBTargets ARCHIVE DESTINATION lib INCLUDES DESTINATION include ) install (DIRECTORY include /projectB DESTINATION include )
工程A配置 1 2 3 4 5 6 7 8 9 10 cmake_minimum_required (VERSION 3.10 )project (ProjectA)add_subdirectory (../projectB ${CMAKE_BINARY_DIR} /projectB)add_executable (projectA main.cpp)target_link_libraries (projectA PRIVATE projectB)
三、高级用法 1. 使用find_package(安装后引用) 1 2 3 4 5 6 7 8 9 10 install (EXPORT projectBTargets FILE ProjectBConfig.cmake NAMESPACE ProjectB:: DESTINATION lib/cmake/ProjectB ) find_package (ProjectB REQUIRED)target_link_libraries (projectA PRIVATE ProjectB::projectB)
2. 跨工程包含不同目录级别 1 2 3 4 5 6 7 8 target_include_directories (projectB PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR} /include > $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR} /include /projectB/core> $<INSTALL_INTERFACE:include > $<INSTALL_INTERFACE:include /projectB/core> )
3. 条件包含设置 1 2 3 4 5 6 target_include_directories (projectB PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR} /include > PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} /src/private_headers )
四、常见问题解决方案 问题1:找不到头文件 解决方案 :1 2 3 get_target_property (projectB_includes projectB INCLUDE_DIRECTORIES )message (STATUS "ProjectB includes: ${projectB_includes}" )
问题2:安装后路径错误 解决方案 :1 2 3 4 5 target_include_directories (projectB PUBLIC $<INSTALL_INTERFACE:include /projectB> )
问题3:多配置生成器问题 解决方案 :1 2 3 4 5 target_include_directories (projectB PUBLIC $<$<CONFIG:Debug>:${CMAKE_CURRENT_SOURCE_DIR} /include /debug> $<$<NOT :$<CONFIG:Debug>>:${CMAKE_CURRENT_SOURCE_DIR} /include /release> )
五、最佳实践
使用生成器表达式 :$<BUILD_INTERFACE>
和$<INSTALL_INTERFACE>
确保路径正确
命名空间隔离 :为安装的目标添加命名空间避免冲突
明确作用域 :
PUBLIC
:依赖项目需要包含的头文件
PRIVATE
:仅本项目内部使用的头文件
版本兼容性 :为安装的库生成版本文件
1 2 3 4 5 6 7 include (CMakePackageConfigHelpers)write_basic_package_version_file( ProjectBConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion )
通过这种方式,可以清晰地管理多工程间的头文件依赖关系,确保无论是在开发时(同workspace)还是安装后引用,都能正确找到所需的头文件。
Qt中使用多个CMakeLists.txt实现模块化项目管理 在大型Qt项目中,使用多个CMakeLists.txt文件进行模块化管理可以提高项目的可维护性和可扩展性。以下是完整的模块化项目管理方案:
一、基本项目结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 MyProject/ ├── CMakeLists.txt # 根CMake ├── app/ │ ├── CMakeLists.txt # 应用层 │ └── main.cpp ├── core/ │ ├── CMakeLists.txt # 核心模块 │ ├── core.h │ └── core.cpp ├── gui/ │ ├── CMakeLists.txt # GUI模块 │ ├── mainwindow.h │ └── mainwindow.cpp └── tests/ ├── CMakeLists.txt # 测试模块 └── core_test.cpp
二、根CMakeLists.txt配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 cmake_minimum_required (VERSION 3.16 )project (MyProject VERSION 1.0 .0 LANGUAGES CXX)set (CMAKE_AUTOMOC ON )set (CMAKE_AUTOUIC ON )set (CMAKE_AUTORCC ON )find_package (Qt6 REQUIRED COMPONENTS Core Gui Widgets)add_subdirectory (core)add_subdirectory (gui)add_subdirectory (app)add_subdirectory (tests)
三、模块化CMakeLists.txt示例 1. 核心模块 (core/CMakeLists.txt) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 add_library (core_lib STATIC core.cpp core.h ) target_include_directories (core_lib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR} > ) target_link_libraries (core_lib PRIVATE Qt6::Core ) install (TARGETS core_lib DESTINATION lib)install (FILES core.h DESTINATION include /core)
2. GUI模块 (gui/CMakeLists.txt) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 file (GLOB UI_FILES *.ui)file (GLOB RESOURCES *.qrc)add_library (gui_lib STATIC mainwindow.cpp mainwindow.h mainwindow.ui ${RESOURCES} ) target_link_libraries (gui_lib PRIVATE Qt6::Widgets Qt6::Gui PUBLIC core_lib ) target_include_directories (gui_lib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR} > )
3. 应用层 (app/CMakeLists.txt) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 add_executable (MyProject main.cpp ) target_link_libraries (MyProject PRIVATE Qt6::Widgets gui_lib core_lib ) set_target_properties (MyProject PROPERTIES WIN32_EXECUTABLE TRUE MACOSX_BUNDLE TRUE )
四、高级模块化技巧 1. 条件编译模块 1 2 3 4 5 option (BUILD_TESTS "Build tests" ON )if (BUILD_TESTS) add_subdirectory (tests) endif ()
2. 动态库模块 1 2 3 4 5 6 7 8 add_library (my_plugin SHARED plugin.cpp ) target_link_libraries (my_plugin PRIVATE Qt6::Core )
3. 资源文件处理 1 2 3 4 5 6 qt_add_resources(gui_lib PREFIX "/gui" FILES resources.qrc )
4. 翻译文件支持 1 2 3 4 5 6 7 8 set (TS_FILES translations/myproject_zh_CN.ts ) qt_add_translations(MyProject TS_FILES ${TS_FILES} )
五、跨模块依赖管理 1. 创建配置头文件 1 2 3 4 5 6 7 8 9 configure_file ( ${CMAKE_CURRENT_SOURCE_DIR} /core_config.h.in ${CMAKE_CURRENT_BINARY_DIR} /core_config.h ) target_include_directories (core_lib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR} > )
2. 使用target_include_directories 1 2 3 4 5 6 7 target_include_directories (gui_lib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR} > PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} /internal )
3. 导出目标 1 2 3 4 5 6 7 8 9 10 11 12 13 install (TARGETS core_lib EXPORT core_libTargets LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin INCLUDES DESTINATION include ) install (EXPORT core_libTargets FILE core_libTargets.cmake DESTINATION lib/cmake/core_lib )
六、测试模块配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 find_package (Qt6 REQUIRED COMPONENTS Test )add_executable (core_tests core_test.cpp ) target_link_libraries (core_tests PRIVATE Qt6::Test core_lib ) add_test (NAME core_tests COMMAND core_tests)
七、现代CMake最佳实践
使用target_ 命令:优先使用target_include_directories()
而非全局include_directories()
明确作用域 :区分PUBLIC
、PRIVATE
和INTERFACE
依赖
避免全局变量 :使用target_link_libraries()
而非link_libraries()
生成导出头文件 :使用configure_file()
生成版本信息
使用生成器表达式 :如$<BUILD_INTERFACE:...>
和$<INSTALL_INTERFACE:...>
八、完整示例:模块间通信 1. 在core模块定义接口 1 2 3 4 5 6 7 8 9 10 11 #pragma once #include <QObject> class ICore : public QObject { Q_OBJECT public : virtual void initialize () = 0 ; signals: void coreInitialized () ; };
2. 在GUI模块使用接口 1 2 3 4 5 6 7 8 9 #include <core/icore.h> MainWindow::MainWindow (ICore* core, QWidget *parent) : QMainWindow (parent), m_core (core) { connect (m_core, &ICore::coreInitialized, this , &MainWindow::onCoreInitialized); }
3. 在CMake中确保包含路径 1 2 3 4 5 target_include_directories (core_lib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR} > )
通过这种模块化结构,您的Qt项目将获得更好的:
可维护性 :清晰的项目结构
可扩展性 :轻松添加新模块
可测试性 :独立测试各模块
复用性 :模块可独立编译使用
CMake 配置(重载)与构建的区别 CMake 的配置(重载) 和构建 是两个完全不同但相互关联的阶段,理解它们的区别对于有效使用 CMake 至关重要。
定义 配置阶段是 CMake 处理 CMakeLists.txt
文件并生成构建系统文件的过程。
执行命令 1 2 3 cmake -B build_dir -DCMAKE_BUILD_TYPE=Release cmake -S . -B build_dir
主要任务
解析 CMakeLists.txt :读取和分析项目配置
检测系统环境 :检查编译器、工具链、依赖库
生成构建系统文件 :
Unix: 生成 Makefile
Windows: 生成 *.sln
和 *.vcxproj
Ninja: 生成 build.ninja
设置变量和选项 :处理 -D
参数定义的变量
生成自动生成的文件 :如 *_autogen
目录中的文件
输出结果
构建系统文件(Makefile、*.sln 等)
CMakeCache.txt(缓存变量)
CMakeFiles/ 目录
自动生成的文件(如 moc*.cpp、ui *.h 等)
CMake 构建(Build) 定义 构建阶段是使用生成的构建系统文件实际编译源代码和链接目标文件的过程。
执行命令 1 2 3 cmake --build build_dir --config Release cd build_dir && make
主要任务
编译源代码 :将 .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 2 3 4 5 6 7 8 9 10 11 12 cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build --parallel 8 cmake --build build cmake -B build cmake --build build
什么时候需要重新配置? 需要执行 cmake -B build_dir
的情况:
修改了 CMakeLists.txt
文件
添加或删除了源文件
更改了 CMake 变量(如 -D
参数)
系统环境发生变化(编译器路径变更等)
依赖库版本更新
什么时候只需要重新构建? 只需要执行 cmake --build build_dir
的情况:
修改了源代码文件(.cpp, .h)
修改了资源文件
需要重新链接(但配置未变)
实际示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 cmake -B cmake-build-release -DCMAKE_BUILD_TYPE=Release cmake --build cmake-build-release cmake -B cmake-build-release cmake --build cmake-build-release cmake --build cmake-build-release --target clean rm -rf cmake-build-releasecmake -B cmake-build-release -DCMAKE_BUILD_TYPE=Release
常见误区
错误 :每次代码修改都运行 cmake -B build
正确 :只需要运行 cmake --build build
错误 :认为配置很耗时,避免运行
错误 :手动修改生成的 Makefile
正确 :总是通过修改 CMakeLists.txt 来改变配置
理解这两个阶段的区别可以显著提高 CMake 的使用效率和项目的构建速度。
install install
命令用于定义编译完成后如何安装目标文件(如可执行文件、库)、头文件、配置文件 等到指定位置,是构建安装流程的核心指令。该命令不会在构建阶段执行,而是在用户显式调用 cmake --install
时生效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 install (TARGETS <目标名称>... [EXPORT <导出名称>] [RUNTIME DESTINATION <目录>] [LIBRARY DESTINATION <目录>] [ARCHIVE DESTINATION <目录>] [INCLUDES DESTINATION <目录>] ) install (FILES <文件列表>... DESTINATION <目录> ) install (DIRECTORY <目录路径>... DESTINATION <目录> [PATTERN <模式> EXCLUDE] )
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
并执行它)。
内容侧重点:
侧重于
项目结构定义
和
构建目标声明
。
- 项目声明 (`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 命令或策略。