Qt 工程创建与 CMake / qmake 构建规范
面向 Qt 5.14.2 + VS2019 主线的工程侧 手册:讲清 qmake 与 CMake 两套构建系统的取舍、
CMakeLists.txt/.pro骨架怎么写、工程目录结构与文件命名怎么定、第三方库怎么引入、构建与打包脚本怎么组织。环境侧(VS/Qt 安装、Kit 套件、CMake 生成器绑定、排错重置)请看同目录姊妹篇:
《从零到一键绿:Qt 5.14.2 + VS2019 开发环境搭建、Kit 套件配置与构建错误修复完全手册》 ------ 特别是 第四章 CMake 配置、生成器与调试器 和 第五章 Kit 套件配置详解。
本文档与主文档的分工:主文档管**"能不能编译"(Kit 是否绿),本文档管 "编出来是什么形态"**(工程结构、命名、构建脚本、依赖治理);第三篇 《Qt 构建排错实录》 是排错案例库------规律写在本手册,踩坑复现留在实录。
系列导航|Qt 5.14.2 + VS2019 实战三篇② 工程创建与 CMake/qmake 构建规范 (本篇)
目录
- [一、构建系统总览:qmake 还是 CMake](#一、构建系统总览:qmake 还是 CMake)
- 二、版本与构建系统选型推荐
- [三、qmake 工程配置规范(.pro / .pri)](#三、qmake 工程配置规范(.pro / .pri))
- [四、CMake 工程配置规范(CMakeLists.txt)](#四、CMake 工程配置规范(CMakeLists.txt))
- 五、工程目录结构规范
- 六、命名规范(文件/类/资源/目标/宏)
- [七、第三方库与 Qt 模块引入规范](#七、第三方库与 Qt 模块引入规范)
- 八、构建与打包脚本规范
- 九、构建产物与输出目录规范
- 十、常见坑与工程级规避动作
一、构建系统总览:qmake 还是 CMake
Qt 生态里 Windows 桌面工程主流有两套构建系统。两者都是"描述工程如何构建"的元语言,但机制与生态位不同:
| 维度 | qmake (.pro 文件) |
CMake (CMakeLists.txt) |
|---|---|---|
| 出身 | Qt 官方自研 | Kitware 开源,跨语言通用 |
| 支持 Qt 版本 | Qt 4/5 全支持,Qt 6 仍支持但非首推 | Qt 5.15+ 官方主推;Qt 6 唯一推荐 |
| 元编程 | QMake Variables($$、$$[]、${}) |
CMake Variables + Generator Expressions |
| 自动化 | 隐式识别 Q_OBJECT(走 CONFIG += moc) |
需显式 set(CMAKE_AUTOMOC ON) |
| 跨语言/混合工程 | 弱,仅面向 C/C++/Qt | 强,可拉 Rust/Python/Java 混合 |
| IDE 兼容 | Qt Creator 一等公民;VS 需第三方插件 | CLion/VS/Qt Creator 全都原生支持 |
| 依赖管理 | INCLUDEPATH + LIBS += -l... 手工 |
find_package / FetchContent / vcpkg |
| 学习曲线 | 短,Qt 项目开箱即用 | 长,但业界通用;一旦会了走天下 |
| Qt 5.14.2 现状 | 该版本 qmake 是一等公民 | CMake 可用,但 Qt:: 无版本目标尚未提供 |
核心判断:
- Qt 5.14.2 项目仍以 qmake 为主(教程多、生态稳、坑少),除非项目本身要跨语言或与 CMake-only 库耦合。
- Qt 5.15.2 及以上,官方已把 CMake 扶正,新起项目直接 CMake。
- 同一份代码可以同时提供
.pro和CMakeLists.txt(很多老库这么做),但维护成本翻倍------团队应统一。
二、版本与构建系统选型推荐
工程选型不是拍脑袋,按下面矩阵落子:
| 场景 | 推荐 Qt 版本 | 推荐 Kit 家族 | 推荐构建系统 | 备注 |
|---|---|---|---|---|
| 学习/教学/小工具 | Qt 5.14.2 | MinGW 7.3.0 64-bit | qmake | 免装 VS,.pro 十几行起步 |
| 公司老项目长期维护 | 跟随现有 Qt 版本 | MSVC v141 (msvc2017_64) | 跟随现有(多为 qmake) | 别乱迁 CMake,成本大 |
| 新项目、单人或 3--5 人小团队 | Qt 5.15.2 LTS | MSVC v142 (msvc2019_64) | CMake(Qt5 前缀目标) | 生态成熟、迁移 Qt6 平滑 |
| 新项目、跨语言或需 CI/多平台 | Qt 6.2+ LTS | MSVC v142/v143 | CMake(Qt:: 无版本目标) | 官方唯一推荐路线 |
需要生成 VS .sln 交付给非 Qt 团队 |
任意 | MSVC | CMake + Visual Studio 16 2019 Generator |
详见主文档第四章 4.4 |
| 纯嵌入式 / 交叉编译 | 视厂商 SDK | 厂商工具链 | 优先 qmake(多数厂商只给 qmake mkspec) | 有 CMake toolchain 文件再上 CMake |
一句话总结:
- Qt ≤ 5.14 且无强需求 → qmake 起手,别追 CMake。
- Qt ≥ 5.15 或准备迁 Qt 6 → 直接 CMake,一次性把账还了。
- Qt 5.14.2 混合团队 :
.pro主、CMakeLists.txt辅(可选),两边目标一致、目录约定同一份。
三、qmake 工程配置规范(.pro / .pri)
3.1 单模块可执行程序:最小骨架
MyApp.pro(工程根目录):
pro
# ============================================================
# MyApp.pro --- 单模块 GUI 程序
# ============================================================
QT += core gui widgets # 依赖的 Qt 模块
CONFIG += c++17 # C++ 标准
CONFIG -= app_bundle # macOS 不打包 .app(跨平台工程常见)
TEMPLATE = app
TARGET = MyApp # 输出二进制名(Windows 即 MyApp.exe)
DEFINES += APP_VERSION=\\\"1.0.0\\\" # 编译期注入版本宏
INCLUDEPATH += \
$$PWD/src # 内部头文件搜索路径(模块多时再加 $$PWD/modules)
# 源文件按角色分组,方便 IDE 折叠;业务模块(如 network/)放 modules/ 下,见第五章
HEADERS += \
src/mainwindow.h \
src/logger.h
SOURCES += \
src/main.cpp \
src/mainwindow.cpp \
src/logger.cpp
RESOURCES += resources/qml.qrc # Qt 资源(.qrc)
FORMS += src/mainwindow.ui # Qt Designer 生成的 .ui
# 平台差异
win32 {
LIBS += -luser32 # Windows 专有 API 库
RC_FILE = resources/app.rc # 版本信息 / 图标
}
unix:!macx {
LIBS += -lpthread
}
要点:
$$PWD表示"当前 .pro 所在目录",永远用它拼相对路径 ,别写./src------ 后者依赖当前工作目录,容易翻车。CONFIG += c++17在 Qt 5.14+ 有效;更早版本要写CONFIG += c++11。.pro一行末尾的\表示续行,跨平台写法用win32 { ... }/unix { ... }作用域。
3.2 多模块工程:SUBDIRS + .pri 复用
大项目拆多个 .pro,用 TEMPLATE = subdirs 组织顶层:
MyProject.pro(顶层):
pro
TEMPLATE = subdirs
CONFIG += ordered # 按 SUBDIRS 顺序构建
SUBDIRS += \
src/lib/lib.pro \
src/app/app.pro \
tests/tests.pro
# 可选:让 app 依赖 lib(避免并行时链接找不到)
src/app/app.pro.depends = src/lib/lib.pro
src/app/app.pro:
pro
QT += core gui widgets
TEMPLATE = app
TARGET = MyApp
# 引入公共配置片段,别在多个 .pro 里复制粘贴
include($$PWD/../common.pri)
HEADERS += mainwindow.h
SOURCES += main.cpp mainwindow.cpp
# 链接本工程内部的 lib 模块
win32: LIBS += -L$$OUT_PWD/../lib/ -lmylib
unix: LIBS += -L$$OUT_PWD/../lib/ -lmylib
src/common.pri(复用配置片段,无 TEMPLATE):
pri
# 全项目共用的编译/链接默认值
CONFIG += c++17 warn_on
QMAKE_CXXFLAGS += -Wall -Wextra # win32-msvc 下会被忽略,可 win32-g++ 分支
DEFINES += QT_DEPRECATED_WARNINGS
INCLUDEPATH += $$PWD/..
# shadow build 友好的输出目录约定
DESTDIR = $$PWD/../bin
OBJECTS_DIR = $$PWD/../build/obj
MOC_DIR = $$PWD/../build/moc
RCC_DIR = $$PWD/../build/rcc
UI_DIR = $$PWD/../build/ui
要点:
.pri= "片段",被include()进多个.pro,避免重复。- CMake 工程不存在也不需要
.pri,它的等价能力见 4.3 (include()片段 +INTERFACE库)。 $$OUT_PWD指 shadow build 的构建目录;$$PWD指源目录。永远把两者区分开。- qmake 强烈建议开 shadow build(构建产物不落源码目录)------Qt Creator 默认就是
build/<Kit>-<Config>结构。
3.3 qmake 常用 CONFIG/DEFINES 参考
| 变量 | 用途 | 常见写法 |
|---|---|---|
QT |
依赖的 Qt 模块 | QT += core gui widgets sql network |
QT -= gui |
纯命令行程序剥离 GUI | 减小依赖 |
CONFIG |
构建配置 | c++17、release、debug_and_release、warn_on |
DEFINES |
预处理宏 | NDEBUG、APP_VERSION=\"1.0\" |
INCLUDEPATH |
头搜索路径 | 用 $$PWD/ 或 $$OUT_PWD/ |
LIBS |
链接库 | LIBS += -L$$PWD/lib -lfoo |
DESTDIR |
输出目录 | 建议 $$PWD/../bin |
TARGET |
目标名 | 全英文,别用中文或空格 |
四、CMake 工程配置规范(CMakeLists.txt)
4.1 单模块可执行程序:最小骨架
CMake 3.16+ 语法(Qt 5.14.2 兼容,目标名带版本号 Qt5::):
cmake
cmake_minimum_required(VERSION 3.16)
project(MyApp
VERSION 1.0.0
DESCRIPTION "Example Qt application"
LANGUAGES CXX
)
# ============================================================
# 全局设置
# ============================================================
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Qt 自动化三件套(对 Q_OBJECT/.ui/.qrc 生效)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
# Qt5.14 只能使用带版本号的 find_package 与 Qt5::<Mod> 目标
find_package(Qt5 5.14 REQUIRED COMPONENTS Core Gui Widgets)
# ============================================================
# 可执行目标
# ============================================================
add_executable(${PROJECT_NAME} WIN32 # WIN32 = 不弹 CMD 窗口
src/main.cpp
src/mainwindow.cpp
src/mainwindow.h
src/mainwindow.ui # AUTOUIC 会自动处理
resources/app.qrc # AUTORCC 会自动处理
# modules/network/feudphelper.cpp # 业务模块按需追加,见第五章
)
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt5::Core
Qt5::Gui
Qt5::Widgets
)
target_compile_definitions(${PROJECT_NAME} PRIVATE
APP_VERSION="1.0.0"
)
要点:
- Qt 5.14 只能用
Qt5::(带版本号);Qt 5.15+ 才提供Qt::无版本别名。主文档第十章有对照表。 WIN32参数决定生成 GUI 子系统(不弹控制台);命令行工具去掉它。AUTOMOC/AUTOUIC/AUTORCC打开后,.h/.ui/.qrc会由 CMake 自动跑 moc/uic/rcc,不用手写规则。
4.2 多目录工程:现代 CMake 目标化写法
顶层 CMakeLists.txt:
cmake
cmake_minimum_required(VERSION 3.16)
project(MyProject LANGUAGES CXX)
# 全局 Qt 设置只写一次
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)
find_package(Qt5 5.14 REQUIRED COMPONENTS Core Gui Widgets)
# 子目录
add_subdirectory(src/lib) # 产出 libmylib (static)
add_subdirectory(src/app) # 产出 MyApp (executable)
add_subdirectory(tests) # 单元测试(可选)
src/lib/CMakeLists.txt:
cmake
add_library(mylib STATIC
logger.cpp
logger.h
)
target_include_directories(mylib
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include # 依赖方也能看到
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src # 仅内部可见
)
target_link_libraries(mylib
PUBLIC Qt5::Core # 头文件里出现 Qt5Core → PUBLIC
)
src/app/CMakeLists.txt:
cmake
add_executable(MyApp WIN32
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
)
target_link_libraries(MyApp PRIVATE
mylib # 通过 PUBLIC 传染 Qt5::Core
Qt5::Widgets
)
现代 CMake 三条硬规矩(工程级避免踩坑):
- 不写
include_directories()/link_directories(),改用 target 级target_include_directories()/target_link_libraries()。 - PUBLIC / PRIVATE / INTERFACE 语义要选对:PRIVATE 是本目标编译时才需要;PUBLIC 是"我用了你、下游也得用";INTERFACE 是"我只在头文件里暴露给你,我自己不链"。
- 路径一律走
${CMAKE_CURRENT_SOURCE_DIR}或 target 自带的 alias ,别硬编码../相对层级。
4.3 多模块工程:公共配置复用(.pri 的 CMake 等价物)
CMake 没有 .pri,公共配置由两种机制承载,按"作用范围"分工,不能互相替代:
| 机制 | 形态 | 作用范围 | 对位 qmake |
|---|---|---|---|
include() 片段 |
cmake/common_options.cmake |
被 include 之后,当前目录及后续 add_subdirectory() 里的全局变量 |
common.pri |
INTERFACE 库 |
add_library(opt INTERFACE) |
只作用于显式 target_link_libraries(x PRIVATE opt) 的 target |
.pri 里 QMAKE_CXXFLAGS / DEFINES 的组合效果,但更精确 |
(1) 全局片段:cmake/common_options.cmake
cmake
# ============================================================
# cmake/common_options.cmake ------ 公共配置片段(对位 common.pri)
# 只允许放「变量 / option / function」,禁止 add_library / add_executable
# ============================================================
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
# 输出目录统一(对位 .pri 里的 DESTDIR / OBJECTS_DIR / MOC_DIR)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) # exe / dll
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # .lib / .a
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # .so / .dylib
顶层 CMakeLists.txt 里只 include 一次 ,且必须在所有 add_subdirectory() 之前:
cmake
cmake_minimum_required(VERSION 3.16)
project(MyProject LANGUAGES CXX)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/common_options.cmake)
find_package(Qt5 5.14 REQUIRED COMPONENTS Core Gui Widgets)
add_subdirectory(src/lib)
add_subdirectory(src/app)
(2) 选项载体:INTERFACE 库
告警级别、feature 宏这类"只给某几个 target 用"的配置走 INTERFACE 库,靠依赖关系传播,不动全局变量:
cmake
# cmake/project_options.cmake
add_library(myproject_options INTERFACE)
target_compile_features(myproject_options INTERFACE cxx_std_17)
if(MSVC)
target_compile_options(myproject_options INTERFACE /W4 /utf-8)
target_compile_definitions(myproject_options INTERFACE _CRT_SECURE_NO_WARNINGS)
else()
target_compile_options(myproject_options INTERFACE -Wall -Wextra)
endif()
cmake
# src/app/CMakeLists.txt
add_executable(MyApp WIN32 main.cpp mainwindow.cpp)
target_link_libraries(MyApp PRIVATE
myproject_options # 编译选项载体:挂在 PRIVATE 即可生效
mylib
Qt5::Widgets
)
INTERFACE 库不产出任何二进制,这里的 target_link_libraries 真实含义是"把它的 usage requirements(编译选项 / 宏 / 头路径)传给本 target"------ 这是 CMake 挂"配置载体"的惯用手法。
三条红线 (比 .pri 更容易踩):
include()片段里禁止出现add_library/add_executable:片段被两处 include 就会 target 重名报错,与.pri里写TEMPLATE同级硬伤。- 片段不能依赖 include 顺序 :
CMAKE_CXX_STANDARD这类全局变量必须在第一个 target /add_subdirectory()之前生效,否则静默不生效。 - 能用
INTERFACE库就别用全局变量 :全局变量会"传染"到3rdparty/里的 target,把别人的代码也按你的告警级别编译。
4.4 Qt5.14 vs Qt5.15+ 目标名差异
| 项目 | Qt 5.14.2 写法 | Qt 5.15.2+ 写法 |
|---|---|---|
| find_package | find_package(Qt5 5.14 REQUIRED ...) |
find_package(Qt5 REQUIRED ...) 或 find_package(QT NAMES Qt6 Qt5 ...) |
| 目标名 | Qt5::Widgets |
兼容 Qt5::Widgets 与 Qt::Widgets(无版本,便于跨 Qt5/6) |
| 迁移成本 | --- | 建议新工程用 QT_VERSION_MAJOR 变量做前缀:Qt${QT_VERSION_MAJOR}::Widgets |
4.5 CMake 常用变量与命令速查
| 变量/命令 | 作用 | 常见值 |
|---|---|---|
CMAKE_AUTOMOC |
自动跑 moc | ON(Qt Q_OBJECT 必开) |
CMAKE_AUTOUIC |
自动跑 uic | ON |
CMAKE_AUTORCC |
自动跑 rcc | ON |
CMAKE_PREFIX_PATH |
find_package 搜索根 | 例:C:/Qt/5.14.2/msvc2017_64 |
CMAKE_BUILD_TYPE |
单配置 Generator 用 | Debug / Release / RelWithDebInfo |
add_executable(name WIN32 ...) |
生成 GUI 程序 | Windows 上去掉 WIN32 会变控制台程序 |
target_link_libraries(tgt PRIVATE lib) |
目标级链接 | 优先用,别用全局 link_libraries() |
option(MY_OPT "desc" OFF) |
可开关的编译选项 | 用 if(MY_OPT) 分支 |
五、工程目录结构规范
推荐的通用目录布局(qmake / CMake 都适用,目录层级最多两层),拿来即用:
MyProject/
├─ CMakeLists.txt # 或顶层 .pro
├─ README.md
├─ .gitignore # 至少忽略 build/ bin/ *.user
├─ src/ # 应用外壳:入口 + 主窗口,条目少就直接平铺
│ ├─ main.cpp
│ ├─ mainwindow.h/.cpp
│ └─ mainwindow.ui # AUTOUIC 自动处理
├─ modules/ # 业务模块:一模块一子目录,与 src/ 平级
│ ├─ network/ # 例:UDP 收发封装(feudphelper.h/.cpp)
│ └─ protocol/ # 例:帧编解码(feudpprotocol.h/.cpp)
├─ resources/
│ ├─ app.qrc # Qt 资源集合
│ ├─ icons/
│ ├─ images/
│ ├─ translations/ # *.ts 源文件,*.qm 编译产物
│ └─ app.rc # Windows 版本信息与图标
├─ cmake/ # 公共配置片段 / Find*.cmake------仅 CMake 工程需要(见 4.3)
├─ tests/ # 单元/集成测试(一层放完)
├─ scripts/ # 构建/打包/发布脚本(见第八章)
│ ├─ configure.bat / build.bat / package.bat
│ └─ deploy_windeployqt.bat
├─ docs/ # 设计文档、协议文档、排错记录
├─ include/ # 对外公开头(做 SDK / 库交付才需要)
└─ 3rdparty/ # 第三方源码(submodule / 手动拷贝)
硬性原则:
- 源码目录与构建目录严格分离 (shadow build)。所有
.o/.moc/.obj/CMakeCache.txt只进build/,永不进src/。 bin/放最终产物(exe/dll),build/放中间产物;两者都进.gitignore。- 头文件与实现文件同名同目录(
mainwindow.h+mainwindow.cpp),不要搞headers/与sources/分开。 - 一个 CMake target /
.pro只对应一个逻辑模块,别把整个工程塞一个可执行目标。 - 层级不超过两层 :只出现一个子目录的中间层要压平(单模块工程里
src/app/没意义)。 modules/按"业务能力"分包,不按文件类型分 :目录名 = 模块名 = 头文件包含前缀,如modules/network/feudphelper.h对外写#include "network/feudphelper.h"(target_include_directories(... modules))。modules/不强制一个子目录一个 target :模块初期只是源文件分组,直接列进主 target 的源列表;只有当它被两个以上 target 复用、或需要强制切断依赖时,才升格为add_library(network STATIC)。模块目录里不要随手放CMakeLists.txt。
本工程(
QT工具)的现状:src/平铺 +modules/network+modules/protocol,完全符合本布局;但脚本/文档目录写成了Scripts/、doc/,新建工程按本规范统一为scripts/、docs/,历史工程不必为了改名而动。模块拆完后在 CMake 里怎么落地(一个 target 还是子目录 target、公共选项怎么复用),分别看 4.2 / 4.3 ;3.2 / 4.2 里的
src/app+src/lib是"多 target"场景样例,单 target 工程一律按本章平铺。
六、命名规范(文件/类/资源/目标/宏)
命名不统一 = 代码搜索成本翻倍。团队应按下表落定:
6.1 文件与目录
| 对象 | 规范 | 例子 |
|---|---|---|
| 源文件/头文件 | snake_case(全小写 + 下划线) | main_window.h logger.cpp |
| 类名与文件名对齐(首字母大写) | PascalCase → 文件仍 snake_case |
class MainWindow { ... } ↔ main_window.h |
| UI 文件 | 对应窗体类 snake_case + .ui |
main_window.ui |
| 资源(图片/图标) | 前缀按用途分组 | icon_close.svg、bg_header.png |
.qrc 资源集合 |
模块名 + .qrc |
app.qrc、icons.qrc |
| CMakeLists.txt | 固定大写 ,别写 CMakeLists.TXT |
--- |
.pro 工程 |
与目标名一致,PascalCase | MyApp.pro |
| 目录 | snake_case 或 单词小写;modules/ 下目录名即模块名 |
src/、modules/network/、resources/icons/ |
6.2 类、函数、变量
| 对象 | 规范 | 例子 |
|---|---|---|
| 类 / 结构体 | PascalCase |
MainWindow、HttpService |
| 成员变量 | m_ + snake_case |
m_user_name、m_state |
| 全局变量 | g_ + snake_case |
g_app_config |
| 常量/枚举值 | k + PascalCase 或 全大写 |
kMaxRetry、STATUS_OK |
| 函数 | camelCase(Qt 风格)或 snake_case(团队选一个) |
loadFile() / load_file() |
| 槽函数 | on_<信号者>_<信号名> |
on_btnSubmit_clicked() |
| 信号 | 名词/事件过去式 | dataChanged、uploadFinished |
| 命名空间 | PascalCase |
namespace AppCore { ... } |
6.3 Qt 专有对象命名
| 对象 | 规范 | 例子 |
|---|---|---|
objectName(Qt Designer 里设定) |
camelCase,与变量名一致 | mainStackedWidget、btnSubmit |
| 控件前缀 | lbl / btn / edt / cmb / tbl / tree |
lblTitle、btnOK、edtSearch |
| Action(QAction) | act + 动词 |
actExit、actAbout |
| Layout | 描述性 | hboxToolbar、gridFormMain |
| 资源路径(qrc 内前缀) | / <模块名> / <子目录> / |
:/app/icons/close.svg |
6.4 CMake 目标与 qmake 目标命名
| 对象 | 规范 | 例子 |
|---|---|---|
| CMake target | PascalCase 或 snake_case(团队选一个),不加后缀 | add_executable(MyApp ...)、add_library(mylib STATIC ...) |
| 静态库 | 名字直接给,Windows 自动加 lib 前缀看平台 |
target_link_libraries(MyApp PRIVATE mylib) |
| 测试 target | 前缀 test_ 或 UnitTests |
add_executable(test_logger ...) |
qmake TARGET |
与项目名一致,避免空格/中文 | TARGET = MyApp |
| CMake option | MYPROJECT_ 前缀,UPPER_SNAKE |
option(MYPROJECT_WITH_SQLITE "Enable SQLite" ON) |
6.5 宏与预定义
| 宏 | 用途 | 例 |
|---|---|---|
QT_DEBUG / QT_NO_DEBUG |
由 CONFIG 自动切换 | 别手写,让构建系统给 |
| 平台宏 | Q_OS_WIN / Q_OS_LINUX / Q_OS_MACOS |
#ifdef Q_OS_WIN ... #endif |
| 项目自定义 | <PROJECT>_<FEATURE>_ENABLED |
#define MYAPP_WITH_OPENCV 1 |
| 导出宏(做库交付时) | <LIB>_LIB_EXPORT 配合 Q_DECL_EXPORT/IMPORT |
MYLIB_EXPORT |
七、第三方库与 Qt 模块引入规范
7.1 Qt 官方模块
qmake (.pro):一行搞定 ------ QT += sql network widgets charts。
CMake (CMakeLists.txt):
cmake
find_package(Qt5 5.14 REQUIRED COMPONENTS Core Gui Widgets Sql Network)
target_link_libraries(MyApp PRIVATE Qt5::Sql Qt5::Network)
REQUIRED别省,缺模块直接 Configure 失败,好过链接期一堆LNK2019。
7.2 第三方库:三种引入姿势
| 姿势 | 适用 | 优点 | 缺点 |
|---|---|---|---|
| A. 系统/用户目录已装好 | OpenCV、Boost 等大包 | 团队共享、体积小 | 换机器要重装;版本管理弱 |
B. 3rdparty/ 源码 + add_subdirectory |
中小 C/C++ 库(spdlog、fmt) | 版本锁进仓库、可打补丁 | 首次编译慢 |
C. 预编译二进制 + Find*.cmake |
只有 .lib/.dll 的商业 SDK |
无编译依赖 | ABI/位数错配易翻车 |
推荐 :B 为主 + C 兜底(源码可审计,SDK 才用预编译)。
7.3 CMake 侧:预编译库接入
cmake
# cmake/FindFooSdk.cmake
find_path(FOOSDK_INCLUDE_DIR NAMES foo.h
PATHS ${FOOSDK_ROOT}/include NO_DEFAULT_PATH)
find_library(FOOSDK_LIBRARY NAMES foo
PATHS ${FOOSDK_ROOT}/lib/${CMAKE_VS_PLATFORM_TOOLSET_ARCH} NO_DEFAULT_PATH)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(FooSdk REQUIRED_VARS FOOSDK_INCLUDE_DIR FOOSDK_LIBRARY)
if(FooSdk_FOUND AND NOT TARGET FooSdk::FooSdk)
add_library(FooSdk::FooSdk UNKNOWN IMPORTED)
set_target_properties(FooSdk::FooSdk PROPERTIES
IMPORTED_LOCATION "${FOOSDK_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${FOOSDK_INCLUDE_DIR}")
endif()
工程里:
cmake
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
find_package(FooSdk REQUIRED)
target_link_libraries(MyApp PRIVATE FooSdk::FooSdk)
避坑:
- 位数与 ABI 必须与 Kit 一致(
win_msvc2017_64就只能链 64 位 v141/v142 编译的.lib);MinGW Kit 只能用.a或 MinGW 版本重编。 - 引入 SDK 的
.dll需要在部署时一起拷到 exe 目录或 PATH;否则运行时报找不到 xxx.dll。
7.4 qmake 侧:预编译库接入
pro
win32:CONFIG(64bit): {
INCLUDEPATH += $$PWD/3rdparty/foo_sdk/include
LIBS += -L$$PWD/3rdparty/foo_sdk/lib/x64 -lfoo
} else {
message("Unsupported platform/arch for foo_sdk")
}
7.5 用 vcpkg / Conan 托管第三方依赖(进阶)
新起 CMake 工程、依赖多时,推荐用包管理:
powershell
# 一次性设置
vcpkg integrate install
# CMake 配置时
cmake -S . -B build ^
-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/build/vcpkg.toolchain.cmake ^
-DVCPKG_TARGET_TRIPLET=x64-windows
CMakeLists.txt 里就是 find_package(spdlog CONFIG REQUIRED),告别手工 .lib/.dll 拷贝。
八、构建与打包脚本规范
工程根目录 scripts/ 放常用脚本,让"任何机器上手就跑"。脚本要短、只做流程编排,不做构建决策(决策留在 CMakeLists/.pro 里)。
8.1 configure.bat(CMake 工程)
bat
@echo off
chcp 65001 >nul
setlocal
:: 复用主文档 5.1.1 的包装 BAT,锁定 v141 x64 环境
call C:\Qt\Config\vcvars_v141_amd64.bat
cmake -S . -B build ^
-G "Ninja" ^
-DCMAKE_BUILD_TYPE=%1 ^
-DCMAKE_PREFIX_PATH=C:/Qt/5.14.2/msvc2017_64
if errorlevel 1 exit /b 1
echo [OK] Configure done (%1).
用法:scripts\configure.bat Release。默认不填走 Debug。
8.2 build.bat
bat
@echo off
cmake --build build --parallel
if errorlevel 1 exit /b 1
echo [OK] Build finished.
该写法通用于 Ninja / Makefiles;VS 多配置生成器 必须补
--config Release,且聚合目标是ALL_BUILD而不是all,否则报MSB 1009(案例与复现见《Qt 构建排错实录 §1》)。
qmake 版本换成:
bat
@echo off
call C:\Qt\Config\vcvars_v141_amd64.bat
cd build
qmake ..\MyProject.pro
nmake /NOLOGO
8.3 package.bat(用 windeployqt 打运行包)
Qt 应用发布必须把 Qt 的 DLL / plugin / qml 打包。不要手动拷,让 windeployqt 分析依赖:
bat
@echo off
chcp 65001 >nul
setlocal
set BUILD_DIR=%~dp0..\build
set OUT_DIR=%~dp0..\dist\MyApp
set EXE_PATH=%BUILD_DIR%\MyApp.exe
rmdir /s /q "%OUT_DIR%" 2>nul
mkdir "%OUT_DIR%"
copy "%EXE_PATH%" "%OUT_DIR%\"
:: Qt 自带,位于 C:\Qt\5.14.2\msvc2017_64\bin\windeployqt.exe
windeployqt --release --compiler-runtime "%OUT_DIR%\MyApp.exe"
echo [OK] Packaged at %OUT_DIR%
--compiler-runtime会附带 VC++ Redist(MSVC Kit),目标机没装 VS 也能跑;MinGW Kit 需另带libgcc_s_seh-1.dll、libstdc++-6.dll、libwinpthread-1.dll。
8.4 脚本命名与放置约定
| 脚本 | 用途 | 位置 |
|---|---|---|
configure.bat / .sh |
首次或改参数后跑 Configure | scripts/ |
build.bat |
全量构建 | scripts/ |
test.bat |
跑单元测试 | scripts/ |
package.bat |
生成可交付包 | scripts/ |
deploy_<env>.bat |
部署到具体环境 | scripts/ |
qt-msvc-env-check.bat |
Kit 环境诊断 | 主文档第八章内联,可另存到工程根 |
Windows/Linux 各写一份 ,或用 .ps1(PowerShell)跨平台。别把 bash 脚本硬塞 Windows。
九、构建产物与输出目录规范
多配置并行不覆盖是团队工程的基本诉求。推荐:
MyProject/
├─ build/ # 全部为构建产物,git ignore
│ ├─ msvc2017_64-debug/
│ ├─ msvc2017_64-release/
│ └─ mingw73_64-release/
├─ bin/ # 最终产物(可选,或直接从 build 里取)
│ ├─ debug/MyApp.exe
│ └─ release/MyApp.exe
└─ dist/ # 打包发布产物
└─ MyApp-1.0.0-win64/
qmake 里落地 (.pri 通用配置):
pro
CONFIG(debug, debug|release): BUILD_SUFFIX = debug
else: BUILD_SUFFIX = release
# Kit 标签自己传(可选:从命令行 qmake "KIT_TAG=msvc2017_64")
isEmpty(KIT_TAG): KIT_TAG = default
DESTDIR = $$PWD/../bin/$${BUILD_SUFFIX}
OBJECTS_DIR = $$PWD/../build/$${KIT_TAG}-$${BUILD_SUFFIX}/obj
MOC_DIR = $$PWD/../build/$${KIT_TAG}-$${BUILD_SUFFIX}/moc
RCC_DIR = $$PWD/../build/$${KIT_TAG}-$${BUILD_SUFFIX}/rcc
UI_DIR = $$PWD/../build/$${KIT_TAG}-$${BUILD_SUFFIX}/ui
CMake 里落地 (顶层 CMakeLists.txt):
cmake
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_SOURCE_DIR}/bin/debug")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_SOURCE_DIR}/bin/release")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_SOURCE_DIR}/bin/relwithdebinfo")
单配置 Generator(Ninja/Makefiles)用不上
_<CONFIG>后缀,直接:set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin/$<CONFIG>)借助 Generator Expression。
十、常见坑与工程级规避动作
| 坑 | 现象 | 规避 |
|---|---|---|
| Qt 目标名跨版本混用 | Qt 5.14 用 Qt::Widgets 报 target not found |
5.14 一律 Qt5::;跨版本用 QT_VERSION_MAJOR 拼前缀 |
| AUTOMOC 忘开 | 链接报 undefined reference to vtable for XXX |
set(CMAKE_AUTOMOC ON),或 .pro 里 HEADERS += xxx.h 保证 moc 到 |
| CMAKE_PREFIX_PATH 指错套件 | 明明装了 Qt 却 find_package(Qt5) 找不到 |
指向具体套件目录 (如 C:/Qt/5.14.2/msvc2017_64),不是 C:/Qt |
| 改 -D 参数没生效 | 明明改了 -DCMAKE_BUILD_TYPE=Release 编出来还是 Debug |
删 build/CMakeCache.txt 或整个 build 目录再配置 |
相对路径 ./src |
shadow build 下找不到文件 | qmake 用 $$PWD/,CMake 用 ${CMAKE_CURRENT_SOURCE_DIR}/ |
| 硬编码绝对路径 | 换机器全崩 | 依赖变量与 CMAKE_MODULE_PATH,路径集中一处配 |
| 中文/空格目录 | qmake / CMake 都会炸 | 项目路径全英文、无空格;Qt Kit BAT 也放 C:\Qt\Config\ |
3rdparty .lib ABI 错配 |
LNK2019 无法解析的外部符号 |
确认 SDK 是同编译器 + 同位数 + 同运行时(/MD vs /MT) |
| Windows 控制台闪窗 | 双击 exe 弹出黑色 CMD 再进入 GUI | add_executable(MyApp WIN32 ...) / qmake CONFIG += windows |
| 资源没进 exe | 运行时报 :/app/icons/close.svg 找不到 |
.qrc 加进 SOURCES/RESOURCES,或 CMake 打开 AUTORCC |
| windeployqt 漏 DLL | 分发后目标机 缺少 Qt5Core.dll |
windeployqt 加 --dir <out>、--no-translations;MSVC Kit 再加 --compiler-runtime |
| Qt 版本与工具集 ABI 冲突 | MinGW cannot produce code for MSVC2017 |
见主文档第五章 Kit 章节,编译器/Qt 库/调试器三件套同家族同位数 |
附:qmake ↔ CMake 概念对照表
| 概念 | qmake | CMake |
|---|---|---|
| 工程根文件 | MyProject.pro |
顶层 CMakeLists.txt |
| 复用片段 | .pri(include(...)) |
独立 .cmake 模块(include(MyHelper)) |
| 目标类型 | TEMPLATE = app/lib/subdirs |
add_executable / add_library / add_subdirectory |
| 目标名 | TARGET = MyApp |
第一个参数 add_executable(MyApp ...) |
| 依赖 Qt 模块 | QT += widgets |
find_package(Qt5 COMPONENTS Widgets) + target_link_libraries(... Qt5::Widgets) |
| 头搜索路径 | INCLUDEPATH += |
target_include_directories(...) |
| 链接库 | LIBS += -L... -l... |
target_link_libraries(... PRIVATE ...) |
| 预处理宏 | DEFINES += |
target_compile_definitions(... PRIVATE ...) |
| 中间产物目录 | OBJECTS_DIR/MOC_DIR/RCC_DIR/UI_DIR |
CMAKE_<LANG>_OUTPUT_* 或直接用 build 目录 |
| 输出目录 | DESTDIR |
CMAKE_RUNTIME_OUTPUT_DIRECTORY |
| 平台分支 | win32 { ... } |
if(WIN32) ... endif() |
| 影子构建 | Qt Creator 默认;命令行手动 mkdir build && cd build && qmake .. |
cmake -S . -B build |
| moc/uic/rcc | 隐式,QT += 自动处理 |
set(CMAKE_AUTOMOC/AUTOUIC/AUTORCC ON) |
附:与主文档的衔接
- Kit 环境绿勾 → 主文档第五章;本文档只讲工程侧写法。
- CMake Generator 选哪个 / Ninja 与 vcvars 的绑定 → 主文档第四章 4.2--4.5。
- VS2019 装哪些模块 / Windows SDK 与 CDB → 主文档第二章 + 第四章 4.6。
- Qt 5.14 vs 5.15 差异与迁移 → 主文档第十章。
vcvars_v141_amd64.bat从哪来、怎么验证 → 主文档 5.1.1(本文档 8.1 直接引用它)。- 一键诊断 / Kit 配置重置 → 主文档第七、八章(含完整 BAT 源码)。
建议阅读顺序:主文档(配好环境) → 本文档(把工程立起来) → 主文档第 6 章(排错) → 本文档第 10 章(工程级避坑)。