Ubuntu18.04下Qt5.15程序的发布(实测裸机成功运行,干货满满,多种方式总有一款适合你)

前言

Qt程序打包发行一直是一个不大不小的问题,在windows下Qt官方提供了windeployqt工具,可以通过简单的cmd命令实现Qt程序的打包发布,绝大部分情况下能正常工作(使用msvc编译器的话有可能需要在目标机上安装vs运行库),而在linux中不知为何官方没有提供类似的工具(可能是因为linux发行版种类很多,碎片化严重,qt官方没有足够的人手针对每个平台进行维护?),这就需要我们自己动手了。
作为linux主要的发行版之一Ubuntu,在其上发布Qt程序方法的文章可以说是汗牛充栋,但是其中大量文章已经过时,在比较新的环境下已经行不通或者需要改进,甚至很多还存在误导读者的现象,而且普遍地只讲方法不讲原理思路,这篇文章在实测的基础上,对前人方法做一个总结和提高,把其中比较关键的难点加以梳理,以馈读者。

现有的方法

根据我的总结,目前qt程序在Ubuntu上打包共有三种方法——脚本法、静态编译法和linuxdeployqt工具法,现分别加以介绍。注意:笔者使用的系统是Ubuntu18.04,编程环境是Qt5.15.2,如果你的环境和笔者不同,具体细节方面可能会略有差别,但本文提供的方法和思路应该能帮到你,所谓授人以鱼不如授人以渔,全文较长,请耐心读下去。

脚本法

这也是关于在Ubuntu上打包程序的最常用方法,首先使用一个脚本生成程序的依赖文件,然后再编写另一个执行脚本,用于启动程序,下面我们实际操作一下——
编写脚本copylib.sh

#!/bin/bash
LibDir=$PWD"/lib"
Target=$1

lib_array=($(ldd $Target | grep -o "/.*" | grep -o "/.*/[^[:space:]]*"))

$(mkdir $LibDir)

for Variable in ${lib_array[@]}
do
    cp "$Variable" $LibDir
done

这个脚本本质上是使用ldd命令查找程序依赖的动态库,再把动态库复制到程序目录, 正常情况直接使用不需要改动,使用时把你在Qt release模式下编译好的可执行文件拷贝到一个单独的文件夹(称为程序目录),把脚本文件copylib.sh也拷贝进来,在终端敲入命令

./copylib.sh yourApp

注意脚本文件需要使用chmod命令加执行权限,脚本文件名字可以随意修改,脚本中/lib是库文件的复制路径,可以替换成其它路径,也有一些文章中使用程序文件所在文件夹,这里不推荐使用其它路径,原因在后文详述。
然后在程序所在文件夹下再新建一个脚本yourApp.sh:

#!/bin/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/$appname "$@"

这个脚本文件必须和程序文件同名,用于在终端中启动程序,有些文章中提供的脚本appname和dirname需要自己填写,这样脚本可以和程序文件不同名。
为什么不直接运行程序?而要编写复杂的脚本?因为在Ubuntu上程序文件启动时不会自动搜索程序所在文件夹(这里和windows上的逻辑不同)

Ubuntu程序搜索依赖库的优先级
编译环境下的动态库路径, 可由环境变量LIBRARY_PATH指定
运行环境下的动态库路径, 由环境变量LD_LIBRARY_PATH指定
配置文件/etc/ld.so.conf中指定的动态库搜索路径
默认的动态库搜索路径/lib
默认的动态库搜索路径/usr/lib

后面三个要修改都需要root权限,而且会使系统越来越臃肿,所以我们通常修改第二级中的环境变量LD_LIBRARY_PATH,达到指定库文件目录的目的。LD_LIBRARY_PATH比较特殊,具有临时性,在终端中修改的话,关掉终端就失效了,我们也可以修改/bashrc文件或者其它一些开机运行的脚本文件,使之一直生效,但是这样可能会对其它程序产生未知的影响,还是那句话,可以做但是没必要,使用脚本在程序运行期间临时改变一下LD_LIBRARY_PATH就好了,安全环保。
好了,以上就是大多数参考文章的全部内容,看起来并不复杂,按照以上方法实战,很遗憾,现实是残酷的,我们会发现坑很多——

坑1

This application failed to start because it could not find or load the Qt platform plugin “xcb”

解决方式——复制Qt/5.15.2/gcc_64/plugins下platforms文件夹至程序文件同级目录(也和lib目录同级),里面实测仅有libqxcb.so有用。
xcb是Ubuntu用于生成视窗系统的工具,所有Qt带GUI的程序都必须包含libqxcb.so这个插件。

坑2

**qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "/home/zdit/Desktop/test/./lib/plugins/platforms" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Available platform plugins are: xcb (from /home/zdit/Desktop/test/./lib/plugins/platforms), xcb.
**

好家伙,找到了这个插件,但是不能用,还建议你重装~~
解决方式——复制Qt/5.15.2/gcc_64/lib中的libQt5XcbQpa.so.5.15.2,libQt5XcbQpa.so.5.15,libQt5XcbQpa.so.5,libQt5DBus.so.5.15.2,libQt5DBus.so.5.15,libQt5DBus.so.5六个文件至程序目录/lib,实际上六个文件只有2个是库文件,其余4个是链接,可以把libQt5XcbQpa.so.5.15.2改名成libQt5XcbQpa.so.5这样子,这样库文件会少一点点。
然后发现问题还是没有解决
进入/usr/lib/x86_64-linux-gnu,找到libxcb-xinerama.so.0.0.0和libxcb-xinerama.so.0,复制到程序目录/lib,如果在/usr/lib/x86_64-linux-gnu下面找不到这两个文件,运行

sudo apt-get install libxcb-xinerama0

如果没有意外,这次打包应该成功了,事实上我通过这种方法,成功打包了几个之前用Qt做的GUI程序,把打包好的文件夹复制到一个全新安装的纯净的Ubunt18.04系统上,可以完美运行。
但是在我同样的方法打包带有Qt Data Visualization模块的时候,又遇到了问题

坑3

QXcbIntegration: Cannot create platform offscreen surface, neither GLX nor EGL are enabled
QXcbIntegration: Cannot create platform OpenGL context, neither GLX nor EGL are enabled
QXcbIntegration: Cannot create platform OpenGL context, neither GLX nor EGL are enabled

解决方式——拷贝Qt/5.15.2/gcc_64/plugins下xcbglintegrations文件夹至程序文件夹下的plugins目录。

举一反三

我猜测Qt打包之所以出现这么多问题,根源就是这个Qt特有的插件系统,插件系统为Qt库提供驱动,但不直接被用户程序使用,所以能骗过ldd命令,如果qt用到特殊的模块(比如 Data Visualization)就需要把相关的插件(比如xcbglintegrations,用于OpenGL的支持)手动加入程序目录,而且还可能需要插件库的支持库(比如libqxcb.so需要libQt5XcbQpa.so和libQt5DBus.so),这就变得异常复杂,而且每个人遇到的问题可能都是不同的,除非你把所有插件全部拷贝,Qt5.15插件库600多M,如果用到QML,还需要QML库,这显然不现实。

总结一下解决问题的思路吧——

  1. 使用ldd命令,分析缺失的库,想办法把库补全
  2. 根据提示的错误信息,找到对应的插件动态库,拷贝过来,如果问题依旧,则继续用ldd找到插件的依赖,循环这个步骤
  3. 如果还是不行,请百度谷歌吧

所以这种方法永远可以用,但需要分析问题的能力比较强,正所谓手动挡有自己的乐趣?

进阶1

plugins目录只有放在程序文件同级才能被自动识别,如果非要放到别处或者改名,也不是不可以,就需要在执行脚本中修改临时变量QT_PLUGIN_PATH,比如

QT_PLUGIN_PATH=$dirname/lib/plugins
export QT_PLUGIN_PATH 

同理还有

QT_QPA_PLATFORM_PLUGIN_PATH=$dirname/lib/plugins/platforms
export QT_QPA_PLATFORM_PLUGIN_PATH 

如果用到其它插件,可以去查找对应的宏,这里不再赘述

进阶2

之前讲的方法都是改变临时的运行时搜索路径,所以不得不使用运行脚本启动程序,在windows下被惯坏了的我们怎么看都感觉不舒服,有种脱了裤子放屁的感觉,有没有办法直接运行程序呢?答案是肯定的,我们可以选择在编译时就指定搜索路径(且具有更高的优先级),做法其实很简单,在Qt工程的pro文件中加入一行代码即可:

QMAKE_RPATHDIR = ./lib     #指定执行程序的运行库

这实际上是指定了gcc编译器的-rpath参数,使得我们的可执行程序拥有了主动搜索./lib目录的能力,这样上面的打包过程可以省略第二个脚本了,目标机上直接终端输入./你的程序名,即可启动程序,是不是舒服多了。
多说一句,编译时就用QMAKE_RPATHDIR指定搜索路径是更好的写法,因为在一些系统中LD_LIBRARY_PATH是无法生效的,不过很遗憾,国内众多讲解打包发布的文章没有发现主推这种方法的,这里我起个头,强力推荐!

静态编译法

笔者在windows上一直习惯使用静态编译的Qt版本,至少有2个优点:

  1. 发布方便,只需要一个可执行文件,拷贝过去就能执行
  2. 体积小,有朋友说这不对啊,静态编译只会增大可执行文件,怎么会减小体积呢?那时没有计算动态编译发布时附带的dll库,如果计算进来整体大小还是静态编译更小一些。

同样在我看来也有两个缺点:

  1. Qt官方不提供编译好的静态库,用户只能自己从Qt源码编译,而这个过程不但漫长(笔者的电脑大约需要2个小时),而且颇有难度(需要配置一套复杂的参数,不同版本参数可能不同,而且在linux下还需要下载一堆奇奇怪怪的前置软件包),难倒了一大堆初学者
  2. 作商用发布时可能面临版权麻烦,因为Qt虽然开源,但是大多组件的开源协议是LGPL,如果你的软件中使用了Qt,且静态发布,那么就犯规了,具体条款比较复杂,这里不再赘述。

静态编译实战

windows下的静态版Qt一直非常好用,那么在Ubuntu下面是否同样好用呢?笔者亲自实践了一下,查阅了一些资料,针对Qt5.15.2版本做了静态编译,编写配置脚本autoConfigure.sh

#! /bin/bash

QT_INSTALL_PATH="-prefix /usr/local/Qt/5.15.2_static"     #Qt安装路径(自己修改)
QT_COMPLIER+="-platform linux-g++-64"  #编译器

CONFIG_PARAM+="-static "               #静态编译
CONFIG_PARAM+="-release "             #编译release
CONFIG_PARAM+="-recheck-all "
CONFIG_PARAM+="-nomake examples "    #不编译examples
CONFIG_PARAM+="-nomake tests "        #不编译tests

CONFIG_PARAM+="-xcb "				#Qt5.15前的版本这里应该是-qt-xcb(坑)

#选择Qt版本(开源, 商业), 并自动确认许可认证
CONFIG_PARAM+="-opensource "         #编译开源版本, -commercial商业版本
CONFIG_PARAM+="-confirm-license "      #自动确认许可认证

echo "./configure $CONFIG_PARAM $QT_COMPLIER $QT_INSTALL_PATH"
./configure $CONFIG_PARAM $QT_COMPLIER $QT_INSTALL_PATH

运行autoConfigure.sh,可能报xcb错误,我这里比较粗爆,直接

sudo apt-get install "^libxcb.*"

编译检查顺利通过,之后make,等待1-2小时之后,make install,安装完毕。
其实静态编译这里坑也不少,但因为静态编译不是本文讲解的重点,读者如果遇到其它问题,请自行百度解决。
之后打开Qt Creator,设置好kits,建立一个最简单的GUI程序,选择静态版本的kits,release模式编译,顺利通过,打开工程编译文件夹,终端里直接运行,成功!难道在Ubuntu下静态编译发布程序和windows下一样简单?
赶紧复制程序至纯净版的Ubuntu18.04,终端运行,失败!果然没那么简单
根据错误提示,一步步排查问题,经过两个多小时的摸索终于搞定,过程省略,直接给出结论:
要想让静态编译的Qt程序直接运行在裸机中,解决方法有两种

  1. 在裸机中下载安装运行库,之后就能“直接”运行了
sudo apt-get install libxcb-xinerama0
sudo apt-get install "^libxcb.*"
sudo apt-get install libjasper-dev #有的系统可能无法直接安装,还要涉及到换源等操作
  1. 在开发环境下,用第一部分介绍的“脚本法”打包静态程序,把需要的运行库找全,一起复制到新机中

静态编译发布程序总结

上面两种方法可以看出都不完美,第一种方法对程序安装者要求较高,第二种方法和动态编译类似,可是又失去了静态编译的优势。究其根源还是linux各版本之间碎片化太严重,Qt编译起来都困难重重,就难以指望沙滩上堆出来的城堡能到处移动了。
而且Qt官方文档中讲解了一个静态发布的例子,其中提到

Since we cannot deploy plugins using the static linking approach, the executable we have prepared so far is incomplete. The application will run, but the functionality will be disabled due to the missing plugins. To deploy plugin-based applications we should use the shared library approach.

说的是这个例子含有插件,而静待编译法是无法发布含有插件的程序的,如果你的程序含有插件,只能选择动态发布。具体参考官方文档
笔者尝试静态发布带Qt Data Visualization的程序,果然尝试了多种方法,都无法成功,看来官方文档所言不虚。
总的来说在Ubuntu上用静态编译的方法来发布程序,是一件吃力不讨好的事情。

linuxdeployqt方法

除了上面两种方法,还有没有别的方法呢?那是一定要有的,win系统下官方出了个自动拷贝依赖文件的工具windeployqt,而linux系统下也有个类似的工具,叫linuxdeployqt,可惜不是官方出品, git地址
一般个人出品的工具质量是参差不齐的,那么这个linuxdeployqt能否像windeployqt那样好用呢?笔者也进行了测试

linuxdeployqt方法实战

下载linuxdeployqt-6-x86_64.AppImage版本,改权限,尝试运行,提示系统版本过高,什么鬼,现在是2021年了,Ubuntu20.04都出来了,竟然说18.04版本过高!
无奈,打开尘封已久的Ubuntu16.04虚拟机,把linuxdeployqt程序拷贝进去,改名放入/usr/bin目录,然后编译好一个qt例子程序,执行

linuxdeployqt untitled -appimage

等待片刻,生成了如下文件:
在这里插入图片描述
把文件夹拷贝至Ubuntu18.04的纯净系统,直接运行成功!而且可以双击运行,无限接近win系统的体验。
可惜如此好用的工具无法在Ubuntu18.04中使用,不过经过一番研究,有一些文章提到可以通过修改源代码并重新编译,使得linuxdeployqt能够在更高的系统中运行参考文章
注释掉tools/linuxdeployqt/main.cpp中的一段代码

		// openSUSE Leap 15.0 uses glibc 2.26 and is used on OBS
        /*if (strverscmp (glcv, "2.27") >= 0) {
            qInfo() << "ERROR: The host system is too new.";
            qInfo() << "Please run on a system with a glibc version no newer than what comes with the oldest";
            qInfo() << "currently still-supported mainstream distribution (xenial), which is glibc 2.23.";
            qInfo() << "This is so that the resulting bundle will work on most still-supported Linux distributions.";
            qInfo() << "For more information, please see";
            qInfo() << "https://github.com/probonopd/linuxdeployqt/issues/340";
            return 1;
        }*/

然后

qmake linuxdeployqt.pro
make

生成的linuxdeployqt放入/usr/local/bin,随时可用。
我编译了qt自带的bars例子,这是一个带3d显示的例子没,用之前的脚本法费了好大力气发布成功,现在使用linuxdeployqt重新发布

linuxdeployqt bars -appimage

注意这里如果不加-appimage会报错,打包失败,原因未知,加了-appimage参数,还是会报几个错,但是能够生成AppRun文件,整个文件夹拷贝至裸机中测试,一次成功!
在这里插入图片描述

linuxdeployqt方法小结

1.官方下载只适用于特定的低版本,对于高版本需要修改源码重新编译。
2.重新编译后使用需要设置好运行库或者按照本文前两种方式完成打包发行使用,这里更推荐使用静态编译的方式
3.经过笔者试验,linuxdeployqt生成的发行包具有更广泛的适用性,在Ubuntu18.04生成的发行包可以运行在Ubuntu20.04系统之上,而本文介绍的前两种方式不行。

总结

下面表格尝试对比了三种方法,可以说都不是非常完美,全都需要大量的手动操作,脚本法是完全手动挡,linuxdeployqt法是半自动挡,静态编译法则在小部分情况下非常好用。

打包方式打包前准备工作难易程度打包时难易程度适用范围
脚本法简单非常难广泛
静态编译法非常难简单仅能编译不带插件程序
linuxdeployqt工具简单广泛

相对来说使用linuxdeployqt工具算是一个不错的方案,但还是不算完美,还是希望Qt官方能负起责任,针对主流linux发行版提供出真正的linuxdeployqt工具,更少的bug,更频繁的维护。

  • 8
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值