【C++】使用 CMake 在 Windows 上自动化发布 C++/Qt 应用程序

server/2025/3/4 16:39:02/

对于使用 MinGW 编译 C++/Qt 项目的开发者来说,发布程序时常常面临目标机器缺少必要运行时库(DLL)的情况。传统方法需要手动收集依赖文件,不仅繁琐,还容易遗漏。本文将展示如何利用 CMake 构建系统,结合 Qt 官方部署工具,实现从编译到发布的自动化流程,既能生成静态链接的单一 exe 文件,也能自动打包 MinGW 动态库,极大提高发布效率。

CMake 基础配置

最小化项目结构

一个典型的 CMake 项目结构如下:

ProjectRoot/
├── CMakeLists.txt    # 构建规则核心文件
├── main.cpp          # 程序入口文件
└── build/            # 构建产物目录(建议加入 .gitignore)

基础 CMakeLists.txt 配置

在项目根目录下创建 CMakeLists.txt,内容如下:

cmake_minimum_required(VERSION 3.10)
project(MyApp LANGUAGES CXX)set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)add_executable(${PROJECT_NAME} main.cpp)

构建命令优化

建议使用以下现代化构建命令:

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release

这种方式不仅能生成 Release 版本,还能在生成目录和编译配置上做到分离,便于后续扩展和维护。

规范化输出路径

为了方便管理构建产物,我们统一配置二进制文件、库文件的输出目录:

# 统一设置输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)# 针对不同构建类型设置输出路径
foreach(config DEBUG RELEASE RELWITHDEBINFO MINSIZEREL)string(TOUPPER ${config} config_upper)set_target_properties(${PROJECT_NAME} PROPERTIESRUNTIME_OUTPUT_DIRECTORY_${config_upper} ${CMAKE_BINARY_DIR}/bin/${config})
endforeach()

MinGW 的动态链接与静态链接

静态链接策略

如果希望生成一个单一的 exe 文件,可以采用完全静态链接的方式。对于 MinGW 平台,可以在 CMakeLists.txt 中添加如下配置:

if(MINGW)# 完全静态链接set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")# 对标准库及其他可选组件进行静态化add_link_options(-static-libgcc-static-libstdc++-Wl,-Bstatic -lstdc++ -lpthread)
endif()

这样可以有效避免目标机器因缺少动态链接库而运行失败。

自动化收集 MinGW 运行时库

如果你选择动态链接或项目中含有依赖 MinGW 提供的动态库(如 Qt 部分插件),可以在 CMake 中自动收集并拷贝所需 DLL 文件。示例如下:

if(MINGW AND NOT BUILD_STATIC)# 获取编译器所在目录,并定位到 bin 目录get_filename_component(MINGW_BIN_PATH ${CMAKE_CXX_COMPILER} DIRECTORY)set(MINGW_BIN_PATH "${MINGW_BIN_PATH}/../bin")# 定义必须拷贝的运行时 DLL 文件set(ESSENTIAL_DLLSlibgcc_s_seh-1.dlllibstdc++-6.dlllibwinpthread-1.dlllibssp-0.dll)# 构建拷贝命令,将 DLL 拷贝至目标可执行文件所在目录foreach(dll ${ESSENTIAL_DLLS})add_custom_command(TARGET ${PROJECT_NAME} POST_BUILDCOMMAND ${CMAKE_COMMAND} -E copy_if_different"${MINGW_BIN_PATH}/${dll}"$<TARGET_FILE_DIR:${PROJECT_NAME}>COMMENT "Copying ${dll}")endforeach()
endif()

通过这种方式,构建完成后,必要的 DLL 文件将自动被拷贝到可执行文件目录中,简化发布步骤。

Qt 应用程序的自动化部署

Qt 模块化配置

首先需要设置 Qt 的安装路径,推荐使用参数传递或环境变量方式来指定。示例代码如下:

# 设置 Qt 安装路径(优先级:参数 > 环境变量 > 默认路径)
if(NOT DEFINED QT_PATH)if(DEFINED ENV{QT_DIR})set(QT_PATH $ENV{QT_DIR})else()set(QT_PATH "C:/Qt/6.8.2/mingw_64")endif()
endif()list(APPEND CMAKE_PREFIX_PATH ${QT_PATH})# 查找必需的 Qt 模块
find_package(Qt6 REQUIRED COMPONENTSCoreGuiWidgetsOpenGLMultimediaNetworkConcurrent
)

集成 windeployqt 部署工具

Qt 提供的 windeployqt 工具可以自动收集 Qt 应用所需的所有运行时依赖。将其集成到 CMake 中,可以参考下面的配置:

find_program(WINDEPLOYQT windeployqt HINTS "${QT_PATH}/bin")if(WINDEPLOYQT)# 如有需要,可配置 QML 资源路径set(QML_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/qml")add_custom_command(TARGET ${PROJECT_NAME} POST_BUILDCOMMAND ${WINDEPLOYQT} --verbose 1--no-compiler-runtime--no-system-d3d-compiler--no-angle--no-opengl-sw--qmldir "${QML_ROOT}""$<TARGET_FILE_DIR:${PROJECT_NAME}>"COMMENT "Deploying Qt runtime dependencies...")
else()message(WARNING "windeployqt not found! Qt dependencies will be missing.")
endif()

这样在构建完成后,windeployqt 将自动分析并部署 Qt 所需的依赖,简化了打包工作。

资源文件处理

如果你的项目中包含资源文件(例如图像、配置文件等),也可以使用 CMake 自动将这些文件拷贝到输出目录:

# 自动查找资源目录下所有文件
file(GLOB_RECURSE RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/resources/*"
)foreach(res_file ${RESOURCE_FILES})file(RELATIVE_PATH rel_path ${CMAKE_CURRENT_SOURCE_DIR}/resources ${res_file})add_custom_command(TARGET ${PROJECT_NAME} POST_BUILDCOMMAND ${CMAKE_COMMAND} -E copy${res_file}"$<TARGET_FILE_DIR:${PROJECT_NAME}>/resources/${rel_path}")
endforeach()

完整的 QT 配置示例

下面给出一个综合示例,展示如何将上述各部分内容整合到一个完整的 CMakeLists.txt 中:

# 指定 CMake 的最低版本要求  
cmake_minimum_required(VERSION 3.20)  # 定义项目名称和使用的编程语言  
project(MyQtApp LANGUAGES CXX)  # 全局 C++ 标准设置  
# 设置 C++ 标准为 C++17,并要求必须使用该标准  
set(CMAKE_CXX_STANDARD 17)  
set(CMAKE_CXX_STANDARD_REQUIRED ON)  # 输出目录配置  
# 设置可执行文件、静态库和动态库的输出目录  
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)  # 可执行文件输出到 bin 目录  
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)  # 静态库输出到 lib 目录  
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)  # 动态库输出到 lib 目录  # Qt 配置  
# 设置 Qt 的安装路径  
set(QT_PATH "C:/Qt/6.8.2/mingw_64")  
# 将 Qt 的路径添加到 CMake 的前缀路径中,以便找到 Qt 库  
list(APPEND CMAKE_PREFIX_PATH ${QT_PATH})  
# 查找所需的 Qt 组件,确保 Core、Gui 和 Widgets 组件可用  
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)  # 添加可执行文件及链接 Qt 库  
# 创建一个可执行文件,源文件为 src/main.cpp  
add_executable(${PROJECT_NAME} src/main.cpp)  
# 将 Qt 的核心库、图形库和小部件库链接到可执行文件  
target_link_libraries(${PROJECT_NAME} Qt6::Core Qt6::Gui Qt6::Widgets)  # Qt 部署:调用 windeployqt 自动打包依赖  
# 查找 windeployqt 工具  
find_program(WINDEPLOYQT windeployqt)  
if(WINDEPLOYQT)  # 在构建后调用 windeployqt 来打包 Qt 依赖文件  add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD  COMMAND ${WINDEPLOYQT}   --qmldir ${CMAKE_CURRENT_SOURCE_DIR}/qml  # 指定 QML 目录  $<TARGET_FILE_DIR:${PROJECT_NAME}>  # 指定可执行文件目录  )  
endif()  

构建与打包脚本

为了进一步简化整个流程,可以编写一个简单的批处理脚本(例如 package.bat),实现一键构建和打包:

@echo off
set BUILD_DIR=build
set OUTPUT_DIR=packagecmake -B %BUILD_DIR% -DCMAKE_BUILD_TYPE=Release
cmake --build %BUILD_DIR% --config Releasexcopy /E /Y %BUILD_DIR%\bin\Release %OUTPUT_DIR%

运行该脚本后,将自动生成 Release 版本,并将生成的 exe 与相关依赖拷贝到 package 目录下,便于分发。

构建即发布的未来

通过本文介绍的技术方案,开发者可以将繁琐的多步发布流程简化为单一构建命令。无论是静态链接生成单一 exe,还是自动收集动态库、部署 Qt 依赖,这套方案都能满足小型项目乃至企业级持续交付的需求。建议开发者根据实际项目需求灵活调整配置,平衡文件体积、依赖管理与构建效率,迈向自动化构建与发布的未来。


http://www.ppmy.cn/server/172375.html

相关文章

【弹框组件封装】展示、打印、下载XX表(Base64格式图片)

目录 打印、下载弹框组件组件使用弹框展示 打印、下载弹框组件 /components/PrintDialog.vue <!-- 打印、下载弹框 --> <template><el-dialog:title"title"top"3vh"append-to-body:visible.sync"dialogVisible":close-on-click…

信息发布小程序功能方案详解

信息发布小程序功能方案详解&#xff0c;php开发语言&#xff0c;可以源码搭建、二开&#xff0c;或者定制开发。 一 用户端&#xff1a;小程序核心功能模块 1.信息发布小程序&#xff0c;信息分类&#xff0c;分类的图标和名称是自定义设置的 2.查看信息&#xff1a;查看信…

Leetcode 49: 字母异位词分组

Leetcode 49: 字母异位词分组 这是一道经典的哈希表与字符串操作相关的题目&#xff0c;考察快速分组和使用数据结构的能力。所谓字母异位词&#xff0c;是指由相同的字母通过重新排列形成的不同单词。题目要求将一组字符串按照字母异位词分组。 问题描述 给定一个字符串数组…

【Go】十八、http 调用服务的编写

http接口框架的搭建 这个http接口框架的搭建参考之前的全量搭建&#xff0c;这里是快速搭建的模式&#xff1a; 直接对已有的http模块进行复制修改&#xff0c;主要修改点在于 proto部分与api、router 部分&#xff0c;剩余的要针对进行修改模块名称。 接口的具体编写 在 a…

使用Fuse-DFS挂载文件存储 HDFS-后端存储ceph

1. 编译环境准备 yum install cmake3 ln -s /usr/bin/cmake3 /usr/bin/cmake yum install gcc-c安装挂载依赖 yum -y install fuse fuse-devel fuse-libs执行以下命令&#xff0c;载入FUSE模块 modprobe fuse2. 下载源码包 hadoop-3.3.4-src.tar.gz解压后执行以下命令 打开…

Freertos卡在while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )

今天用CubeMX创建freertos点了一个灯让他闪&#xff0c;他竟然不闪&#xff0c;我giao&#xff0c;然后调试发现他一直卡在while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )这句话&#xff0c;后来搜了好多都不行&#xff0c;最后&#xff0c;改了这个 就ok了

重学SpringBoot3-Spring Retry实践

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞??收藏评论 重学SpringBoot3-Spring Retry实践 1. 简介2. 环境准备3. 使用方式 3.1 注解方式 基础使用自定义重试策略失败恢复机制重试和失败恢复效果注意事项 3.2 编程式使用3.3 监听重试过程 监…

安卓基础组件Looper - 02 native层面的剖析

文章目录 native使用使用总结创建Looper构造函数创建(不推荐)使用举例源代码 Looper::prepare 获取Looper可忽略初始化Looper主动休眠 pollAll主动唤醒 wake 发送消息 sendMessage轮询消息 native使用 Android Native Looper 机制 - 掘金 (juejin.cn) /system/core/libutils/…