Qt应用打包发布,部署真正的Qt程序Linux&Windows

最近在发布Qt应用时遇到了一些困难,Windows还好,在Linux上面发布遇到了不少的麻烦(实际Linux应该简单才对),经过在网络搜索发现帖子不少,但都比较片面,现把Qt应用程序在Linux&Windows打包部署总结如下。

核心

应用部署的核心是加载库,一个Qt应用程序至少包含以下库:

Windows

Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll

Linux

libQt5Core.so.5、libQt5Gui.so、libQt5Widgets.so


与其他应用程序一样,Qt应用程序也依赖于操作系统来加载这些库文件,这意味着它们必须放置在操作系统可以找到它们的位置。在Windows上,这意味着在.exe文件同一目录中或搜索路径中指定的目录(例如C:\ Windows \ System32)。将DLL复制到未安装Qt的另一台PC上时,我们通常不想弄乱其他计算机上的搜索路径,而是将DLL与.exe文件放在一起,这可能是最好的解决方案。

在Linux上,将.so文件放在可执行文件同一目录中不会像Windows中那样自动加载它们。但大多数Linux系统都预先安装好了Qt,例如在Ubuntu上,通常在/ usr / lib / i686-linux-gnu或/ usr / lib / x86_64-linux-gnu中安装了Qt,因此我们的应用仍然可以运行(但是请注意版本问题,Ubuntu提供的Qt可能太旧了,我们的应用无法启动)。

还有一点在部署Qt应用比较容易出错的是Qt的插件机制,除了上面提到的几个Qt核心库之外,程序想要运行还必须提供一些插件,例如:platforms、sqldrivers、imageformats,这些是插件目录,名字是Qt定的(当然可以修改,但是不建议修改)。其中最重要的是platforms插件目录,里面提供一些平台插件,所有Qt应用程序都需要这些库,对于Windows默认的是qwindows.dll,对于Linux,则为libqxcb.so。这是Qt的QPA(Qt Platform Abstraction层)它负责许多特定于OS的事情,例如将调用转换setWindowState(Qt::WindowMaximized)为Windows / Linux特定的系统调用。

插件设置

上面提到了一些Qt插件,这些插件Qt是如何加载的呢?默认Qt会在可执行文件所在的目录查找并加载这些插件。但是为了我们的程序目录更加简洁,可以自定义插件目录。

设置环境变量QT_PLUGIN_PATH

使用环境变量可能是比较容易的选择,例如:

export QT_PLUGIN_PATH=plugins

Windows使用set

设置上面环境变量,QT将在plugins\platforms寻找qwindows.dll。当然,也可以通过这种方式设置绝对路径。而且,如果想获得大量的插件目录,则可以附加以;分隔的其他路径。(在Linux中是:)。

在代码中指定插件路径:

#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication::addLibraryPath("plugins");//设置插件目录
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

可以在代码中指定插件路径,但是由于Qt在构造QApplication时会加载插件,因此设置此项的机会就是在调用QApplication的构造函数之前进行。上述代码和设置环境变量效果一样。

创建一个qt.conf文件:

这是一种设置插件路径比较的流行方法。

[Paths]
Plugins=plugins

此文件与应用可执行文件位于同一目录中,Qt将读取该文件并将plugins= path路径添加到其插件目录列表中。上面的例子实现效果和之前的一样。

请注意,如果您使用的是Windows:该Plugins=设置中的反斜杠不起作用,应该使用Linux风格的正斜杠。

插件问题排查:

最后,如果仍然遇到插件加载问题,可以通过将环境变量QT_DEBUG_PLUGINS设置为非零值来打开插件加载过程的log

export QT_DEBUG_PLUGINS=1

然后从Terminal/CMD窗口启动应用程序。对于Linux,在终端中会看到类似QFactoryLoader :: QFactoryLoader()checking...这样的行,但是在Windows Qt中,输出会将输出路由到OutputDebugString()API,因此CMD窗口中将不会显示任何内容。使用Visual Studio或Qt Creator可以看到输出,如果在非开发PC上遇到插件问题,这没有太大帮助。另一种选择是下载一个实用程序,并在启动应用程序之前将其打开。

Windows部署

首先使用Release模式构建应用得到可执行文件myapp.exe。使用windeployqt.exe工具可以自动拷贝Qt库和插件到应用程序目录,但是这个工具会拷贝多于程序需要的好多东西。通常都需要手动删除一些程序不需要的库。

Windows部署Qt应用程序通常至少包含以下文件:


windows

platforms目录内包含qwindows.dll,这样简单的应用程序是可以运行的,并且可以打包分发。
这是一个最基础的应用,通常我们的应用会更复杂,比如需要插件sqldrivers、imageformats、translations。比如:

更多插件

所有插件都放在一个目录会显得很乱,现在我们创建一个qt.conf,内容如下:

[Paths]
Plugins=plugins

然后我们新建目录plugins,把所有插件移动到plugins。使我们的应用程序目录变得的更加简洁:


注意:plugins是Qt默认就会查找的目录,所以没有qt.conf也无所谓,但是如果想要指定一个特殊的插件目录名称,则必须使用qt.conf,如:third

Linux部署

几乎所有的Linux发行版都会预装Qt,所以如果我们的应用是为特定发行版编译的,我们几乎是不需要进行任何配置,直接打包分发可执行文件即可(如果你是这种情况,就不需要看下面内容了)。

但是如果目标Linux没有安装Qt或者版本比较老,那我们的应用程序就可能不会运行了。这也是我遇到的问题,我的应用使用Qt5.12.5开发并编译,但目前非常主流的Ubuntu 18.04 lts最高只可使用Qt5.9.5

那就没办法了么?是不是可以升级Linux系统的Qt安装呢?网上有相关文章,需要破坏系统基础配置,对于只想运行一个应用程序,就需要破坏系统环境配置,这是得不偿失的。能不能像Windows那样拷贝Qt5.12.5的动态库到其他安装了低版本的Qt,甚至没有安装Qt的Linux上面运行呢,答案是肯定的。下面内容就是说明如何完全体部署Qt应用到Linux。

修改连接库(不推荐)

就是把系统Qt库链接到新版本的Qt库,网上有类似方法,这种方式非常不推荐,原因是Linux发行版在开发时对所有库进行了预设,强行修改可能会造成一个应用程序好用了,十个应用程序崩溃了的情况。

带动态库一起打包发布(推荐)

首先同样使用Release模式构建应用,得到可执行文件myapp。GitHub上面有一个linuxdeployqt工具可以帮助构建.AppImage格式的Linux可执行程序,但是它的宗旨是构建可在所有平台运行的Qt程序,因此就会有同样的问题,如果我们编译环境过于新,也就无法使用这个工具了。

自己动手,丰衣足食

网上好多文章使用ldd命令来查看我们的应用依赖了哪些库,并且还有脚本可以自动复制这些库到应用目录,这通常是不可行的。因为ldd列表出来的大部分库并不是我们需要的,有些甚至是我们构建系统独有的库,并且我们并不能很好的确定该删除哪些库,还有就是ldd只能检查我们的应用程序直接依赖的哪些库,并不能查出依赖库的间接依赖,这些只有Qt自己知道,比如:libqxcb.so依赖libQt5DBus.so.5。

链接库

.so文件放在可执行文件旁边并不会像Windows加载DLL那样自动被系统加载,Linux有几个可以配置加载动态库的方法,我这里使用设置环境变量LD_LIBRARY_PATH的方法,其他方法通常都需要root权限,并且破坏了系统原有运行模式,所以对于部署一个独立运行的应用程序使用LD_LIBRARY_PATH是最好的方法。

以下是我的应用程序在Linux下的目录,这是一个最基础应用程序的目录结构,你几乎不能删除其中任何文件。


linux

./bin 可执行文件目录
./bin/qt.conf qt特殊配置文件
./bin/myapp 可执行文件
./plugins 插件目录
./plugins/platforms
./plugins/platforms/libqxcb.so
./lib 动态库加载目录
./lib/libQt5Gui.so.5
./lib/libQt5Widgets.so.5
./lib/libQt5XcbQpa.so.5
./lib/libQt5DBus.so.5
./lib/libicudata.so.56
./lib/libicui18n.so.56
./lib/libQt5Core.so.5
./lib/libicuuc.so.56
./myapp.sh 应用启动脚本

启动脚本

因为我们的程序包含了所需要的库,所以需要让操作系统来加载我们自己的库,基于此我们需要使用脚本的方式启动我们的应用程序,来代替直接运行可执行文件。使用LD_LIBRARY_PATH来指定动态库加载目录,这里需要指定到了lib目录。

myapp.sh内容如下:

#!/bin/sh
appname=`basename $0 | sed s,\.sh$,,`

dirname=`dirname $0`
tmp="${dirname#?}"

if [ "${dirname%$tmp}" != "/" ]; then
dirname=$PWD/$dirname
fi
LD_LIBRARY_PATH=$dirname/lib
export LD_LIBRARY_PATH
$dirname/bin/$appname "$@"

如果你是用以上脚本来启动应用程序,只需要修改脚本名称即可。

bin目录

bin目录存放可执行文件和qt.conf
qt.conf内容如下:

[Paths]
Prefix = ../
Plugins = plugins

Prefix指定程序工作目录,相对于可执行文件路径。默认为.当前目录,这里我为了是目录更加整洁,指定为..上级目录。
Plugins指定插件加载目录,相对于Prefix目录。上面这种指定方式,Qt就会在我的plugins目录下寻找插件了,例如plugins/platforms/libqxcb.so

打包发布

以上目录结构和各种依赖即是一个Qt发布Linux环境的最小集了,可以使用以上目录针对各种不同Linux发行版进行构建打包,或者直接压缩进行分发。这样应用就可以在装有低版本Qt甚至没有安装Qt的环境上面运行了。

你还可以编写自己的.desktop文件使用myapp.sh作为应用启动命令。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,999评论 4 368
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,102评论 1 302
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,709评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,439评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,846评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,881评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,062评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,783评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,517评论 1 248
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,762评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,241评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,568评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,236评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,145评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,941评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,965评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,802评论 2 275