最近在發布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應用程序通常至少包含以下文件:
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下的目錄,這是一個最基礎應用程序的目錄結構,你幾乎不能刪除其中任何文件。
./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
作為應用啟動命令。