Linux 编译免安装 Qt 程序
需求说明
需要给客户编译一个 Linux 程序,主要有以下的特点:
- 因为工业上的客户一般不是太懂技术,所以需要一个免安装的程序,不需要额外安装 Qt 环境。
- 本身程序又依赖一些其他的库,所以需要把这些依赖库也打包进去。
本来以为很快,给自己预定时间是两个小时,结果周五晚上开始,周六一天都在做,周日简单想了一会,到了周一又搞了一下,才终于搞完。
期间问豆包实在把我问郁闷了,然后又是 Deepseek,结果还是一直原地打转,一点进展也没有。后来周日想了一会,才算梳理了思路,用了 Deepseek 专业版,总算好了。
第一个问题:GLIBC_2.38 NOT FOUND
遇到的第一个问题,如上所述。原因是编译的机器比较新,所以相当于编译出来的程序是依赖于比较新的 glibc 的。而用的机器比较老,所以导致找不到高版本 glibc 的符号。
所以就是用旧的电脑去编译就行了,我之前是担心会不会这种旧是无止境的,比如即使我用了 Ubuntu 20.04,万一客户用的电脑是 Ubuntu 18.04,那怎么办?但是想想还是算了,就先这样吧,不要钻牛角尖,不要奢求完美。AI 也说了,Ubuntu 20.04 就够了,没有必要考虑的太远。
第二个问题:寻找依赖的 lib
依赖的 lib 主要是 Qt 的 lib,以及一些其他的 lib。一开始豆包给我的脚本,就只是把 Qt 的一些 lib 拷贝进去,结果很明显,到新的机器上运行的时候会报错。
于是就是想着用 ldd 命令来寻找依赖的 lib,但是发现这种方法很难完美:
- 有些库是不应该打包进去的(比如 libc.so),但我又不知道哪些库是这样的,所以全部打包。然后就出问题了,因为新的机器上运行就应该依赖它自己系统的 libc,否则会有问题。
- 有些第三方库没有办法通过 ldd 命令找到,可以用循环的方式,但这就麻烦了。
使用 linuxqtdeploy 打包
周六就一直折腾这个,而且当时用的 Deepseek 又很傻,它最后验证是否成功拷贝 lib 的时候,总是认为没成功,实际上我看目录里面是有这些 lib 的,折腾这个都折腾好久,后来我就干脆不让他最后验证有没有拷贝成功了。
后来发现 linuxqtdeploy 这个工具,可以很方便的打包 Qt 程序,更重要的是可以很方便的打包依赖的 lib,他会搜索依赖的 lib 并打包进去。最后得到的目录结构是:
MyAPP/
bin/
MyAPP
qt.conf
lib/
share/
plugins/
translations/
APPRun
其中 share 目录一般是 docs 一些资源数据,我是直接删除;plugins 目录是 Qt 用到的东西,必须要有;translations 目录是 Qt 中如语言切换用的文件,我不需要所以直接删除。
而 APPRun 是用来运行程序的脚本,但是我不需要,我是希望越直观越好,我更希望直接把 MyAPP 提出来,这样更直观。此时用 ldd 命令查看 MyAPP,可以看到他里面的依赖的 lib 都已经换成了当前目录的 lib 了,比如:
ldd MyAPP
libQt5Core.so.5 => /home/allen/MyAPP/bin/../lib/libQt5Core.so.5 (0x00007f0a01b00000)
如果直接提出来会有问题,所以其实正确做法更简单了:把编译出来的二进制文件 MyAPP 直接放在当前目录就可以了。然后写一个运行脚本:
export LD_LIBRARY_PATH=./lib
./MyAPP
现在的目录结构如下,以后只要替换 MyAPP 文件,就可以运行了。
MyAPP/
lib/
plugins/
MyAPP
run.sh
第三个问题:qt 依赖
但拿到下一个机器的时候,会报 Qt 的错误:
Warning: Ignoring XDG_SESSION_TYPE=wayland on Gnome. Use QT_QPA_PLATFORM=wayland to run on Wayland anyway.
qt.qpa.plugin: Could not find the Qt platform plugin "xcb" in ""
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
这个 AI 就回答的很好了:你遇到的错误原因是:Qt 找不到平台插件,即使你把插件目录放在了正确的位置,Qt 默认也不会自动去那里找。
方法是新建一个 qt.conf 文件,内容如下,总之就是告诉 Qt 插件和库都在哪个位置。
[Paths]
Prefix = .
Plugins = plugins
Libraries = lib
最后的目录结构,以后只需要每次编译完,替换 MyAPP 文件,就可以运行了。
MyAPP/
lib/
plugins/
MyAPP
qt.conf
run.sh