文章目录
-
- [0 背景](#0 背景)
-
- [0.1 其他](#0.1 其他)
- [1 使用cmake脚本](#1 使用cmake脚本)
-
- [1.1 创建项目](#1.1 创建项目)
- [1.2 简化cmakefile文件](#1.2 简化cmakefile文件)
- [1.3 创建cmake文件](#1.3 创建cmake文件)
- 附录
0 背景
因为Qt6抛弃了qmake,全面转向了CMake的怀抱。因此新项目开发,也需要使用到CMake来进行C++的项目管理。为了兼容win7和旧mac os系统,因此还是用Qt5.15.2(Qt最后一个5版本)来进行项目开发。
本文讲述使用CMake进行项目管理和模块化。
0.1 其他
这里顺带提及一下qt5与qt6的区别:

详细说就是:
- 1,构建系统;
- Qt5:使用
.pro文件和qmake; - Qt6:使用
cmake;
- Qt5:使用
- 2,图形架构(最本质);
- Qt5:图形渲染主要依赖
OpenGL(在不同平台上的性能和兼容性(尤其是 Windows 上的ANGLE)一直是个痛点); - Qt6:使用
RHI(Rendering Hardware Interface,抽象层)。能根据运行环境自动选择最优的图形 API------在 Windows 上用Direct3D 12,在 macOS 上用Metal,在 Linux/Android 上用Vulkan。
- Qt5:图形渲染主要依赖
- 3, C++ 标准;
- Qt5:最低支持 C++11 / C++14。
- Qt6 : 强制要求编译器支持 C++17,移除了约 15% 的过时 API(如
Qt Script)。
- 4,应式编程;
- Qt5 :需要手动写
if (oldValue != newValue)然后emit signal; - Qt6: 引入了
QProperty<T>。它实现了属性的自动绑定和通知。即,当修改一个属性时,所有依赖它的其他属性或 UI 元素会自动更新,无需手动触发信号槽。
- Qt5 :需要手动写
- 5, QML;
- Qt6: JavaScript 引擎升级(支持
ECMAScript 7),增强了类型检查,Qt Multimedia模块完全重写。
- Qt6: JavaScript 引擎升级(支持
1 使用cmake脚本
CMake脚本文件(后缀名.cmake)功能与pri文件相似,以定义变量、设置编译选项等。之前写过一篇使用pri文件的文章,可以参考一下。
这是原始的项目结构:

这是使用.cmake后的项目结构:

1.1 创建项目
- 1, 新建一个项目;

- 2,构建系统选择
Cmake for Qt 5 and 6;

如果选择下面的Camke,就支持Qt6,而不是兼容Qt5。

- 3,构建完成后,将会生成如下的
CMakeList.txt文件;
cpp
cmake_minimum_required(VERSION 3.16)
project(TestCmake VERSION 0.1 LANGUAGES CXX)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(TestCmake
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET TestCmake APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(TestCmake SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(TestCmake
${PROJECT_SOURCES}
)
endif()
endif()
target_link_libraries(TestCmake PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.TestCmake)
endif()
set_target_properties(TestCmake PROPERTIES
${BUNDLE_ID_OPTION}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
include(GNUInstallDirs)
install(TARGETS TestCmake
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(TestCmake)
endif()
项目结构如下所示:

1.2 简化cmakefile文件
- 1,设置cmake的版本;
cpp
# 设置cmake的最低版本
cmake_minimum_required(VERSION 3.16)
# 定义项目名称、项目版本、变成语言
project(TestNewFramework VERSION 0.1 LANGUAGES CXX)
- 2,设置camke可以自动识别并处理 Qt 的核心特性(信号槽、UI 文件、资源文件);
自动处理信号与槽:如果类继承自 QObject 并使用了 Q_OBJECT 宏(为了使用信号槽、属性等特性),编译器需要先通过 moc 工具生成一个额外的 C++ 文件(例如 moc_myclass.cpp),然后才能编译
cpp
# 自动处理信号与槽
set(CMAKE_AUTOMOC ON)
自动处理界面文件:当使用 Qt Designer 画了一个界面(.ui 文件),编译时需要将其转换为对应的 C++ 头文件(ui_*.h),你的代码才能通过 ui->setupUi(this) 来加载界面。
cpp
# 自动处理界面文件
set(CMAKE_AUTOUIC ON)
自动处理资源文件:当把图片、翻译文件等打包进 .qrc 资源文件时,这些资源实际上是被编译进了二进制文件中。这需要 rcc 工具将 .qrc 转换为一个 C++ 源文件(通常包含在 qrc_*.cpp 中)。
cpp
# 自动处理资源文件
set(CMAKE_AUTORCC ON)
- 3,强制指定C++的版本,并阻止编译器扩展;
指定 C++ 语言标准版本为C++17:
cpp
# 指定 C++ 语言标准版本
set(CMAKE_CXX_STANDARD 17)
强制要求指定的标准必须被满足。如果不支持C++17,CMake 配置阶段会直接报错并终止,不会尝试降级编译。
cpp
# 强制要求指定的标准必须被满足。如果不支持C++17,CMake 配置阶段会直接报错并终止,不会尝试降级编译
set(CMAKE_CXX_STANDARD_REQUIRED ON)
保证代码的跨平台可移植性。 禁止编译器使用 GNU 扩展(如 GCC 的 gnu++17)或其他非标准的编译器扩展,强制使用严格的 ISO C++ 标准(即 c++17)。
cpp
# 保证代码的跨平台可移植性
set(CMAKE_CXX_EXTENSIONS OFF)
- 4,寻找Qt版本,并加载具体的 Qt 版本配置;
尝试按照Qt6、Qt5的顺序去寻找配置文件,如果找到了,则会记录具体的版本,例如Qt6、Qt5。代码执行成功后,CMake 会自动生成一个变量 QT_VERSION_MAJOR,如果找到了 Qt6,QT_VERSION_MAJOR 的值就是 6。
cpp
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
根据上面的结果,精确加载具体的 Qt 版本配置。
cpp
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
没有只写成find_package(Qt6 ...) 或 find_package(Qt5 ...),是为了兼容团队成员中既有人装了Qt6,又有人装了Qt5。
- 5,定义一个变量,用来集中存储项目所需的源文件列表;
cpp
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
)
- 6,调用编译器(如
g++, cl.exe)将源代码编译成目标文件(.o 或 .obj),
然后调用链接器(Linker),将这些目标文件和依赖的库(如Qt5Core, Qt5Widgets)链接在一起,生成一个完整的、操作系统可以直接加载的二进制文件exe;
cpp
add_executable(TestCmake
${PROJECT_SOURCES}
)
如果使用Qt6,可以使用下面的代码来编译(智能化"编译:自动处理 Qt6 的跨平台细节(如 RHI 图形后端、资源嵌入)):
cpp
qt_add_executable(TestCmake
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# 完成最终的链接,比add_executable多一步
qt_finalize_executable(TestCmake)
如果是Android程序,则使用如下的代码(将源代码编译成一个动态链接库【Android App 是由 Java 驱动的,C++ 代码只是作为"本地方法"提供支持,它必须被打包成 .so 动态库,等待 Java 代码在运行时通过 System.loadLibrary (JNI),将其加载进内存使用。】):
cpp
add_library(TestCmake SHARED
${PROJECT_SOURCES}
)
- 7,连接目标与依赖库(将 Qt 的 Widgets 模块链接到你的可执行程序 TestCmake 中,且该依赖仅在内部使用);
cpp
target_link_libraries(TestCmake PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
具体:
TestCmake(项目名称);
PRIVATE(链接作用域:私有依赖 ,另一个库要调用TestCmake的功能时,不需要知道Widgets的存在,能减少不必要的依赖传递);
Qt $ {QT_VERSION_MAJOR}::Widgets(库的目标名);
- 8, 为构建目标(如可执行文件、库)设置特定的属性(发布专业、规范的软件时,必不可少);
设置BUNDLE_ID_OPTION变量:
在 Qt 6.1.0 之前的版本中,MACOSX_BUNDLE_GUI_IDENTIFIER 是一个有效的 CMake 属性,用于设置 macOS 应用的唯一标识符(Bundle ID)。
从 Qt 6.1.0 开始,这个属性被移除或更改了(通常建议直接在 Info.plist 文件中设置,或者使用新的机制)。如果在 6.1.0+ 的版本中仍然传递这个属性,CMake 可能会报错或发出警告。
目的:这段代码确保了你的 CMakeLists.txt 既能兼容旧版 Qt(< 6.1.0),也能在新版 Qt(>= 6.1.0)中正常编译,不报错。
cpp
if(${QT_VERSION} VERSION_LESS 6.1.0)
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.TestCmake)
endif()
设置macOS相关属性:
${BUNDLE_ID_OPTION}: macOS 应用的唯一标识符(例如 com.company.myapp),这在应用分发和沙盒机制中非常重要;
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}: 设置应用的构建版本号(Bundle Version),通常显示在 macOS 的"显示简介"中,例如0.1.0;
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}: 设置应用的用户显示版本号(Bundle versions string, short),用户在界面上看到的版本号;
MACOSX_BUNDLE TRUE: 告诉 CMake 这是一个 macOS Bundle 应用(即生成 .app 文件夹),而不是一个命令行工具。
设置Windows相关属性:
WIN32_EXECUTABLE TRUE: 告诉 Windows 链接器,这是一个 GUI 应用程序,而不是控制台(Console)应用程序(当运行生成的.exe文件时,不会弹出黑色的 CMD 命令行窗口)。
合起来就是:
cpp
set_target_properties(TestCmake PROPERTIES
${BUNDLE_ID_OPTION}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
- 9,定义项目的安装规则(告诉构建系统(如 CMake),当你执行安装命令(例如 cmake --install 或 make install)时,应该把编译生成的文件复制到系统的哪个目录下)【主要用于 Linux、macOS 等类 Unix 系统以及动态链接库】;
引入 CMake 的标准模块,定义一套符合GNU 标准的安装路径变量,会自动根据当前操作系统和环境,设置好标准的目录名称。($ {CMAKE_INSTALL_BINDIR} 会被设置为 bin(存放可执行程序);
$ {CMAKE_INSTALL_LIBDIR} 会被设置为 lib 或 lib64(存放库文件);$ {CMAKE_INSTALL_DATAROOTDIR} 会被设置为 share(存放共享数据)。)
cpp
include(GNUInstallDirs)
针对不同类型的构建产物,指定了不同的安装目的地(BUNDLE DESTINATION .:用于macOS (或 iOS) 平台,将这个 .app 包(macOS 上的应用程序,通常是一个文件夹)直接安装到安装前缀(CMAKE_INSTALL_PREFIX)的根目录下;LIBRARY DESTINATION $ {CMAKE_INSTALL_LIBDIR}: 指定动态库文件的安装位置,方便操作系统加载;RUNTIME DESTINATION $ {CMAKE_INSTALL_BINDIR} 的作用是指定可执行文件(以及 Windows 平台的动态库)在安装时的存放位置。);
cpp
install(TARGETS TestCmake
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
- 10,把前面修改的部分汇总一下,得到如下的CMakeList文件;
cpp
# 设置cmake的最低版本
cmake_minimum_required(VERSION 3.16)
# 定义项目名称、项目版本、变成语言
project(TestCmake VERSION 0.1 LANGUAGES CXX)
# 自动处理界面文件
set(CMAKE_AUTOUIC ON)
# 自动处理信号与槽
set(CMAKE_AUTOMOC ON)
# 自动处理资源文件
set(CMAKE_AUTORCC ON)
# 指定 C++ 语言标准版本为C++17
set(CMAKE_CXX_STANDARD 17)
# 强制要求指定的标准必须被满足。如果不支持C++17,CMake 配置阶段会直接报错并终止,不会尝试降级编译
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 保证代码的跨平台可移植性。 禁止编译器使用 GNU 扩展(如 GCC 的 gnu++17)或其他非标准的编译器扩展,强制使用严格的 ISO C++ 标准(即 c++17)。
set(CMAKE_CXX_EXTENSIONS OFF)
# 寻找Qt版本,并精确加载具体的 Qt 版本配置。
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
# 定义一个变量,用来集中存储项目所需的源文件列表。
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
)
# 调用编译器(如 `g++, cl.exe`)将源代码编译成目标文件`(.o 或 .obj)`,
# 然后调用链接器将这些目标文件和依赖库链接成一个完整的、操作系统可以直接加载的二进制文件`exe`
add_executable(TestCmake
${PROJECT_SOURCES}
)
# 把库链接到目标执行程序上
target_link_libraries(TestCmake PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
# 为构建目标设置特定的属性(发布专业、规范的软件时,必不可少)
set_target_properties(TestCmake PROPERTIES
WIN32_EXECUTABLE TRUE
)
1.3 创建cmake文件
- 1,创建form文件夹,把UI相关的文件移到文件夹内;
原始的文件夹:

完成操作后:

把main函数代码改为如下内容:
cpp
// #include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// MainWindow w;
// w.show();
return a.exec();
}
修改CMakeList.txt中的部分内容为:
cpp
# 定义一个变量,用来集中存储项目所需的源文件列表。
set(PROJECT_SOURCES
main.cpp
# mainwindow.cpp
# mainwindow.h
# mainwindow.ui
)
重写构建后,得到如下项目结构:

- 2,在根目录下创建
form.cmake文件;

在pro文件中,添加如下代码:
cpp
# 包含子项目配置
include(form.cmake)
重写构建后,得到如下的项目结构:

- 3,在
form.cmake中编写如下内容;
cpp
# comm.cmake(类似.pri文件)
set(FORM_SOURCS
form/mainwindow.cpp
form/mainwindow.h
form/mainwindow.ui
)
# 设置一个自定义变量来保存当前路径
set(FORM_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/form/)
- 4,修改
CMakeList.txt文件,重写构建;
修改CMakeList.txt的部分内容为:
cpp
add_executable(TestCmake
${PROJECT_SOURCES}
${FORM_SOURCS}
)
重写构建得到如下的项目结构:

- 5,编写main函数;
cpp
#include "form/mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
- 6,如果引入
form中的文件,不想带form/前缀时,可以在CMakeList.txt中添加如下内容;
cpp
# 核心功能【用于include】:为指定目标添加头文件搜索路径,确保编译器能够找到项目所需的头文件。
target_include_directories( TestCmake PRIVATE ${FORM_INCLUDE_DIRS})
完整的CMakeList.txt文件内容为:
cpp
# 设置cmake的最低版本
cmake_minimum_required(VERSION 3.16)
# 定义项目名称、项目版本、变成语言
project(TestCmake VERSION 0.1 LANGUAGES CXX)
# 自动处理界面文件
set(CMAKE_AUTOUIC ON)
# 自动处理信号与槽
set(CMAKE_AUTOMOC ON)
# 自动处理资源文件
set(CMAKE_AUTORCC ON)
# 指定 C++ 语言标准版本为C++17
set(CMAKE_CXX_STANDARD 17)
# 强制要求指定的标准必须被满足。如果不支持C++17,CMake 配置阶段会直接报错并终止,不会尝试降级编译
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 保证代码的跨平台可移植性。 禁止编译器使用 GNU 扩展(如 GCC 的 gnu++17)或其他非标准的编译器扩展,强制使用严格的 ISO C++ 标准(即 c++17)。
set(CMAKE_CXX_EXTENSIONS OFF)
# 寻找Qt版本,并精确加载具体的 Qt 版本配置。
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
# 定义一个变量,用来集中存储项目所需的源文件列表。
set(PROJECT_SOURCES
main.cpp
# mainwindow.cpp
# mainwindow.h
# mainwindow.ui
)
# 包含子项目配置
include(form.cmake)
# 调用编译器(如 `g++, cl.exe`)将源代码编译成目标文件`(.o 或 .obj)`,
# 然后调用链接器将这些目标文件和依赖库链接成一个完整的、操作系统可以直接加载的二进制文件`exe`
add_executable(TestCmake
${PROJECT_SOURCES}
${FORM_SOURCS}
)
# 把库链接到目标执行程序上
target_link_libraries(TestCmake PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
# 核心功能【用于include】:为指定目标添加头文件搜索路径,确保编译器能够找到项目所需的头文件。
target_include_directories( TestCmake PRIVATE ${FORM_INCLUDE_DIRS})
# 为构建目标设置特定的属性(发布专业、规范的软件时,必不可少)
set_target_properties(TestCmake PROPERTIES
WIN32_EXECUTABLE TRUE
)
然后main函数可以如下的编写:
cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}