赞
踩
还有一些说明文档。如:ReadMe、License等
在 Windows 中,要解决可执行文件过大的问题,可以使用像 UPX 这样的加壳工具。在对可执行文件压缩之后,会在启动时将其解压缩到 RAM 中。
而在 Linux 中,可以通过 strip 命令来去除目标文件中的调试信息、符号信息,以减小程序的大小。但要注意一点,strip 只能用于可执行文件和动态库(.so),不能用于静态库(.a)。
Qt Installer Framework
https://doc.qt.io/qtinstallerframework/
简称 Qt IFW,由 Qt 官方提供,以前仅用于 Qt 本身,但现在已经发布了,用于创建通用的安装程序。
InstallBuilder
http://installbuilder.bitrock.com/
是一个功能强大、易于使用的跨平台安装程序创建工具。它一直都在积极维护,但仅供商业使用。
InstallJammer
https://sourceforge.net/projects/installjammer/
是一个跨平台 GUI 安装程序和生成器,虽然是开源的,但 2011 年之后就不再维护了,比较遗憾!
NSIS
https://nsis.sourceforge.io/Main_Page
如果你喜欢开源软件,则一定知道 NSIS(全称:Nullsoft Scriptable Install System)。它是一个专业的工具,可用于创建从非常简单到非常复杂的安装程序。虽然它很小,但功能却很丰富,非常适合 Internet 分发。
正如其名,NSIS 是基于脚本的,它能够让我们创建处理任何情况所需的复杂逻辑。幸运的是,它包含了许多插件和预定义脚本,以帮助初学者入门。
Inno Setup
http://www.jrsoftware.org/isinfo.php
是一个免费的安装制作软件,小巧、简便、精美是其最大特点,支持 pascal 脚本,能快速制作出标准 Windows 2000 风格的安装界面,足以完成一般安装任务。
该软件用 Delphi 写成,其官网同时也提供源程序免费下载。它虽不能与 Installshield 这类“恐龙级”的安装制作软件相比,但也算是当之无愧的后起之秀。
Advanced Installer
https://www.advancedinstaller.com/
有一个免费版本,但也有其他几个版本,这些版本的价格取决于安装程序的复杂程度。如果你正在寻找更专业的东西,其中还包括一些支持选项,那么 Advanced Installer 是一个不错的选择。
用它创建 MSI 文件包非常方便,只需添加文件、修改名称、添加按钮即可,无需任何脚本方面的知识,并且生成的安装文件保证符合 Windows 最佳操作建议。
InstallShield
https://www.flexerasoftware.com/install/products/installshield.html
是一款“恐龙级”的安装包制作工具,是 Flexera Software 的当家产品。这不仅是因为它拥有 20 多年的研发历史,而且它也是全球著名软件公司的“皇家御用”打包软件,比如 Adobe、Corel、Autodesk 等公司。
然而这款软件过于专业,并不像 NSIS、Inno Setup 等那样容易入门,所以想学习必须下很大功夫。这也是全球领先的 Windows 安装开发解决方案,现在已经成为 Windows Installer 和 InstallScript 安装方面的行业标准。
WIX Toolset
https://wixtoolset.org/
是一个免费的打包工具,通常要与 Visual Studio(2012 或更高版本)一起使用。之所以最后提到它,是因为它需要经过大量的学习。虽然可用它创建一些非常复杂的安装程序,但要编写大量的代码并经常使用命令行。
对于 Linux 系统来说,要查看可执行文件依赖的库以及缺少的函数符号,可以使用 ldd -r 指令。
构建源码包
对于开源项目来说,这是最简单的方法。
但一定要记得,在使用 tar 命令对目录树进行压缩之前,需要先运行 make distclean 来清理构建环境。
创建本地分发包
这要考虑系统的发行版,而主流的包管理系统有两个:rpm 和 deb。rpm 格式起源于 RedHat,但也被其他发行版使用,例如:CentOS、SuSE 和 Fedora。deb 格式是由 Debian 项目开发的,适用于 Debian/Ubuntu 及其衍生版。
rpm 可参考 RPM HOWTO(http://www.tldp.org/HOWTO/RPM-HOWTO/),尤其是关于构建的部分 - Building RPMs。
deb 可参考Debian 新维护者手册(https://www.debian.org/doc/manuals/maint-guide/)。
创建独立的应用程序包
若要将 Qt 程序作为一个独立的包部署到 Linux 中,需要将其以及所需的组件捆绑在一起,像 Qt 库、Qt 插件(尤其是 platforms 插件)。
推荐一个 Linux 部署工具 - linuxdeployqt(https://github.com/probonopd/linuxdeployqt),它能够自动执行上述的流程,并提供 AppImage(https://appimage.org/)。
Tips:和 Windows 不同的是,Linux 下可执行文件及其依赖库放在同一目录一般是无法正常运行的。常用方式是写一个 Shell 脚本,并用 LD_LIBRARY_PATH 指定依赖库所在目录,然后通过运行这个脚本来间接地启动程序。
@startuml
:1.构建realease版本应用程序;
:2.用打包工具添加依赖项;
:3.添加其他帮助文档到目录结构;
:4.测试程序打包是否成功;
@enduml
过程说明:
过程2中. 常用的打包工具
Tips:
windeployqt 并没有考虑程序依赖的第三方库(例如:OpenCV)。此外,如果使用了 MSVC 编译器,它也不会将相应的 C/C++ 运行时库拷贝进去。这时,需用Dependency Walker检查程序所链接的库文件。
对于特定的编译器,其依赖的库文件如下:
VC++ 14.0 (2015) | MinGW |
---|---|
C 运行时:vccorlib140.dll | libwinpthread-1.dll |
vcruntime140.dll | libgcc_s_dw2-1.dll |
C++ 运行时:msvcp140.dll | libstdc+±6.dll |
Qt Installer Framework 简称 Qt IFW,是由 Qt 官方提供的安装程序制作框架。
Qt IFW 下载页
bin:提供了一些基本的工具,比如打包要用的 binarycreator。
doc:包含了相应的帮助文档,有助于更好的掌握 Qt IFW。
examples:有各种各样的示例,方便我们学习研究。
Licenses:许可协议。
Qt IFW 创建安装程序有一定的步骤:
Name: 被添加到页面和简介文本中的应用程序名称
Version: 程序版本号
Title: 标题栏上的安装程序名称
Publisher: 软件的发布者(如 Windows 控制面板中所示)
StartMenuDir: Windows 开始菜单中产品的默认程序组名称
TargetDir: 程序安装的目标路径
其他参数可查看:https://doc.qt.io/qtinstallerframework/ifw-globalconfig.html
QtIFW框架中安装程序只需要处理一个组件,提供安装程序时安装组件可选,如:com.qtobject.ifw。 在 com.qtobject.ifw/meta 中配置package.xml。
DisplayName: 组件名称
Description: 组件描述信息
Version: 组件版本号
ReleaseDate: 组件发布日期
Default: 如果在安装程序中预先选择了组件,则为 true。
Script: js文件名,如本例installscriqt.qs,用于在加载时执行一些安装操作。
其他参数可查看: https://doc.qt.io/qtinstallerframework/ifw-component-description.html#package-information-file-syntax
安装组件效果如下:
在com.qtobject.ifw/data中放入需打包的文件(.exe、.dll 等)。
要生成安装程序,需要借助 QtIFW 提供的 binarycreator.exe 参考链接
打开 CMD或 PowerShell,并进入包目录 ,然后输入
binarycreator -c config\config.xml -p packages MySoftwareInstaller.exe -v
QtIFW 在生成安装程序时,会用自带的 archivegen 工具将这些文件压缩成 7zip 格式;然后在安装时,再从压缩包中将它们提取出来。
好处:保证程序的安全性,压缩程序的大小。
最好使用一台没有qt开发环境的电脑测试安装程序。除了在windows系统上运行,有条件的话也可以尝试在不同的系统中生成安装程序,测试运行。
上一节简单介绍QtIFW离线安装程序,接下来介绍QtIFW如何在线安装程序。主要区别是在于存储库的创建和配置过程。
Url: 存储库的地址,指向列出可用组件列表的 Updates.xml 文件。如上图:本地Url也可以,注意格式前面要加file:///
Enabled: 若为 0,则禁用存储库。若为 1,则启用存储库。
Username: 对于需要身份验证的存储库,用来表示用户名。
Password: 对于需要身份验证的存储库,用来表示密码。
DisplayName: 设置要显示的字符串,而非 Url。
Tips:密码是以纯文本形式保存的,因此不建议在这里输入。此处如果未设置身份认证信息,将会在运行时通过对话框获取,用户可以在运行时处理这些设置。
在安装程序的根目录选中maintenancetool.exe(Qt 中的维护工具,用于添加/更新/删除组件)可以在线更新组件,如下图可以看见组件版本号从1.0.0增加到2.0.0,选中即可更新版本。
在设置中的资料档案库可以看见之前配置存储库的用户和密码,选中该存储库可以用下方的按钮“条件测试”检测存储库是否可用。
QtIFW不支持离线升级,程序新版本目录不能直接覆盖旧版本目录安装,要先卸载。QtIFW 提供覆盖安装的方法简化这一流程。
//我们可以通过模拟用户单击与 UI 交互,来实现最终的卸载功能 // 卸载脚本:如果程序已安装,则会调用 maintenance 工具,自动进行卸载。 function Controller() { gui.clickButton(buttons.NextButton); gui.clickButton(buttons.NextButton); // 连接信号槽 installer.uninstallationFinished.connect(this, this.uninstallationFinished); } // 当卸载完成时,触发 Controller.prototype.uninstallationFinished = function() { gui.clickButton(buttons.NextButton); } // 与完成页面上的部件交互 Controller.prototype.FinishedPageCallback = function() { gui.clickButton(buttons.FinishButton); }
然后用之前的安装程序测试;执行 maintenancetool.exe --script=uninstallscript.qs 命令卸载旧版本。
要完成这一步,则需要为安装程序添加自定义 UI。首先,要在 meta 目录下添加一个 targetwidget.ui 界面文件(QtDesigner可以编写ui),然后,还需要在 package.xml 文件中用 UserInterfaces元素标记它:
<?xml version="1.0" encoding="UTF-8"?>
<Package>
<Name>MySoftware</Name>
<Version>1.0.0</Version>
<Title>MySoftware Installer</Title>
<Publisher>Twy</Publisher>
<StartMenuDir>MySoftware</StartMenuDir>
<TargetDir>@HomeDir@/MySoftware</TargetDir>
<UserInterfaces>
<UserInterface>targetwidget.ui</UserInterface>
</UserInterfaces>
</Package>
将交互部分添加到安装脚本 installscript.qs 中。注意程序各文件的路径。
var targetDirectoryPage = null; // 构造函数 function Component() { installer.gainAdminRights(); component.loaded.connect(this, this.installerLoaded); } // 实用函数,类似于 QString QDir::toNativeSeparators() var Dir = new function () { this.toNativeSparator = function (path) { if (installer.value("os") == "win") return path.replace(/\//g, '\\'); return path; } }; // 添加桌面和开始菜单快捷方式 Component.prototype.createOperations = function() { component.createOperations(); component.addOperation("CreateShortcut", "@TargetDir@/bin/MySoftware.exe", "@DesktopDir@/MySoftware.lnk", "workingDirectory=@TargetDir@"); component.addOperation("CreateShortcut", "@TargetDir@/bin/MySoftware.exe", "@StartMenuDir@/MySoftware.lnk", "workingDirectory=@TargetDir@"); } // 加载组件后立即调用 Component.prototype.installerLoaded = function() { installer.setDefaultPageVisible(QInstaller.TargetDirectory, false); installer.addWizardPage(component, "TargetWidget", QInstaller.TargetDirectory); targetDirectoryPage = gui.pageWidgetByObjectName("DynamicTargetWidget"); targetDirectoryPage.windowTitle = "选择安装目录"; targetDirectoryPage.description.setText("请选择程序的安装位置:"); targetDirectoryPage.targetDirectory.textChanged.connect(this, this.targetDirectoryChanged); targetDirectoryPage.targetDirectory.setText(Dir.toNativeSparator(installer.value("TargetDir"))); targetDirectoryPage.targetChooser.released.connect(this, this.targetChooserClicked); gui.pageById(QInstaller.ComponentSelection).entered.connect(this, this.componentSelectionPageEntered); } // 当点击选择安装位置按钮时调用 Component.prototype.targetChooserClicked = function() { var dir = QFileDialog.getExistingDirectory("", targetDirectoryPage.targetDirectory.text); if (dir != "") { targetDirectoryPage.targetDirectory.setText(Dir.toNativeSparator(dir)); } } // 当安装位置发生改变时调用 Component.prototype.targetDirectoryChanged = function() { var dir = targetDirectoryPage.targetDirectory.text; if (installer.fileExists(dir) && installer.fileExists(dir + "/bin/MySoftware.exe")) { targetDirectoryPage.warning.setText("<p style=\"color: red\">检测到程序已安装,继续将会被覆盖。</p>"); } else { targetDirectoryPage.warning.setText(""); } installer.setValue("TargetDir", dir); } // 当进入【选择组件】页面时调用 Component.prototype.componentSelectionPageEntered = function() { var dir = installer.value("TargetDir"); if (installer.fileExists(dir) && installer.fileExists(dir + "/maintenancetool.exe")) { installer.execute(dir + "/maintenancetool.exe", "--script=" + dir + "/script/uninstallscript.qs"); } }
要实现自动更新,必须要有一个在线存储库respoitory。在 QtIFW 中,如果要实现这样的功能,可以利用 maintenancetool.exe及其两个重要选项:
--checkupdates:检测更新,并返回一个 XML。
--updater:以更新模式启动应用程序。
为了获取更新相关的信息,我们新建一个 bat 脚本,并通过以下命令将输出重定向到 checkUpdate.txt 文件中:
@echo off
maintenancetool --checkupdates > checkUpdate.txt
当有可用的更新时,maintenancetool 会返回一个 XML,通过bat 脚本将输出信息重定向到 checkUpdate.txt 文件中:其中包含了新版本的名称、大小、以及版本号等内容。倘若没有任何更新,将不会返回任何内容。
一旦检测到有新版本存在,只需要以更新模式启动 maintenancetool 就行maintenancetool --updater,这样以来,默认就会选择【Update components】选项:剩下的具体要更新哪些组件,就交由用户选择。
为了启动外部程序,先简单介绍下 QProcess 类,它有两种启动方式:
一体式:start(),外部程序启动后,将随主程序的退出而退出。
分离式:startDetached(),外部程序启动后,当主程序退出时并不退出,而是继续运行。
参考 https://github.com/Skycoder42/QtAutoUpdater。自写Demo,
//在使用 --checkupdates 检测更新时,并不会运行 GUI,而是仅输出更新信息 //以下内容放入main.cpp中的一个函数中 QString program("../maintenancetool.exe"); QStringList checkArgs; checkArgs << "--checkupdates"; // 检测更新 QProcess process; process.start(program, checkArgs); // 等待检测完成 if (!process.waitForFinished()) { qDebug() << "Error checking for updates."; return; } // 读取输出内容 QByteArray data = process.readAllStandardOutput(); // 没有输出意味着没有可用的更新 if (data.isEmpty()) { qDebug() << "No updates available."; return; } // 倘若需要特定的更新信息,应该解析输出的 XML。 //当检测到有可用的更新之后,以更新模式启动 maintenancetool // 以分离式启动 QStringList updaterArgs; updaterArgs << "--updater"; bool success = QProcess::startDetached(program, updaterArgs); if (!success) { qDebug() << "Program startup failed."; return; } //需要注意的是,这里需要以分离式启动,因为程序需要关闭以进行更新。 //在启动成功之后,最后记得关闭程序: // 关闭程序 qApp->closeAllWindows();
主要是界面的UI美化。参考样式可在github上搜索“Qt Frameless"。
在config.xml中添加QSS文件美化UI用StyleSheet等元素标记。
<WizardStyle>Classic</WizardStyle>
<StyleSheet>style.qss</StyleSheet>
<TitleColor>#b1b1b1</TitleColor>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。