Qt 工程创建与 CMake / qmake 构建规范

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 实战三篇

① 环境搭建与 Kit 修复指南

② 工程创建与 CMake/qmake 构建规范 (本篇)

③ 构建排错实录:从 MSB 1009 到生成器选型


目录


一、构建系统总览: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 三条硬规矩(工程级避免踩坑):

  1. 不写 include_directories() / link_directories() ,改用 target 级 target_include_directories() / target_link_libraries()。
  2. PUBLIC / PRIVATE / INTERFACE 语义要选对:PRIVATE 是本目标编译时才需要;PUBLIC 是"我用了你、下游也得用";INTERFACE 是"我只在头文件里暴露给你,我自己不链"。
  3. 路径一律走 ${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 更容易踩):

  1. include() 片段里禁止出现 add_library / add_executable :片段被两处 include 就会 target 重名报错,与 .pri 里写 TEMPLATE 同级硬伤。
  2. 片段不能依赖 include 顺序 :CMAKE_CXX_STANDARD 这类全局变量必须在第一个 target / add_subdirectory() 之前生效,否则静默不生效。
  3. 能用 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 章(工程级避坑)。

相关推荐
ShineWinsu1 小时前
对于Qt:QPushButton常见属性的解析
开发语言·c++·qt·api·gui·按钮·qpushbutton
外贸GEO优化1 小时前
外贸独立站的可抓取性:JS 渲染、懒加载与无限滚动到底影响什么
开发语言·javascript·ecmascript
Ivanqhz1 小时前
MoE + LLM 应用
数据库·人工智能·pytorch·python·深度学习·算法·1024程序员节
Lhan.zzZ1 小时前
用 Qt 构建线程安全的通讯静态库:从线程亲和性到完整工程
c++·vscode·qt·tcp/ip·udp
Tsuki_tl1 小时前
【总结】Redis五大数据类型应用场景及基本命令
数据库·redis·后端·缓存·缓存中间件·redis命令·redis 五大数据类型
Gauss松鼠会1 小时前
GaussDB 整体架构解析
运维·服务器·网络·数据库·sql·架构·gaussdb
后台模板学习1 小时前
Go Web开发中任务调度框架的源码解析与高可用实现方案
开发语言·后端·golang
系统集成-鹰翔1 小时前
ERP对接升级提示数据库迁移失败:SQLite逐版本事务到底回滚到哪一步
javascript·数据库·经验分享·后端