很久之前,Heresy 曾經寫過一篇《不使用 qmake 建置 Qt Designer 產生的程式:手動建置》,記錄了當時還在 Qt4 時代的、Qt 的 moc 等前置流程的處理方式。
當時 Heresy 是決定自己寫一個 Visual Studio 的 Custom build rule 來解決 Visual Studio 下建置的流程,後來也算是完成了一個可以用的版本;所以其實相關的專案,後來也都是透過這個方法來建置了。
不過後來發現,這個自製的方案雖然算是可以正常運作,但是其實使用上還是有一些小問題;所以在切換到 Qt5 之後,Heresy 這邊的新專案都是改用 Qt 官方提供的 Qt Visual Studio Tools(連結、官方文件)來建立 Qt 的專案。
而前一陣子,Heresy 一來是想把部分還在使用 Qt4 + 自行建置的 build rule 的專案一口氣升級到 Qt6,所以花了好些時間來測試、研究。這邊算是稍微記錄一下這次升級的時候,研究的一些相關的東西。
Qt 專案版本
首先,Qt VS Tools 在 Heresy 沒注意到的時候,似乎改了很多東西?以往 Heresy 使用 Qt VS Tools 建立的專案,裡面的版本設定是「Qt4VSv1.0」,而現在新建的則是「QtVS_v304」,感覺版本跳滿多的(中間有沒有其他版本個人不清楚就是了)。
而舊版的 Qt 專案,也可以透過專案右鍵選單的 Qt 子選單中,直接選擇升級(不過 Heresy 這邊有的沒有升級的選項,不知道為什麼?)。
(用自己的 build rule 建立的專案當然就不可能升級,只能重建了。)
在 Qt Visual Studio Tools 中,可以設定多個 Qt 的版本,並快速地切換,算是滿方便的~
而在建置專案的時候,系統就會去根據這邊設定的路徑,來找 Qt 相關的檔案。
在升級使用新版的 Qt 專案後,整個 build rule 應該是切換到新版去了,連同 Visual Studio 中的專案設定介面也都大改了。
以往的 Qt 相關的設定大多在另一個獨立視窗裡,現在則是好好地整合在 Visual Studio 的專案屬性裡了。
改成這樣的好處,應該就是修改設的時候的一致性更好了啦~
另外,雖然一般人應該是不用去調整,但是這邊可以設定的選項好像變更多了?不過這邊也可以看出,Heresy 當時自己撰寫的 build rule 忽略了許多東西。
Qt VS Tools 的建置流程
之前自定義的流程
Heresy 之前自己弄得 Qt build rule 建置方案,基本上是假設一個 QtWidget 的類別會有 a.ui、a.hxx、a.cpp 這三個檔案;在這樣的狀況下,建置的流程大致上會是:
- 把 a.ui 透過 uic 編譯成 ui_a.hpp
- 把 a.hxx 透過 moc 編譯成 moc_a.hpp
- 這邊會 include ui_a.hpp
- 使用 cl 編譯 a.cpp
- 這邊會 include moc_a.hpp
實際上的概念,就是把 a.ui 和 a.hxx 都透過 Qt 的工具處理好後,統一由 a.cpp 來包起來、一起編譯。
不過在使用上的時候,要注意的就是檔案到底是要引用 a.hxx 還是 moc_a.hpp 了;因為如果有多個 .cpp 引用了 moc_a.hpp 下去編譯,就會造成連接階段的重複定義了。
而如果還有 .qrc 這樣的資源檔的話,要由哪個 .cpp 來引用也會是個問題。
Qt VS Tools 的建置流程
Qt VS Tools 定義的建置流程,大致上會像下面的樣子:
- 把 a.ui 透過 uic 編譯成 ui_a.h
- 把 a.h 透過 moc 編譯成 moc_a.cpp
- 這邊會 include ui_a.h
- 在編譯時會繼續使用 cl 來編譯 moc_a.cpp
- 並同時去編譯有使用到的檔案,包括 a.cpp(有其他檔案也會)
- 使用 cl 編譯 a.cpp
- 這邊會 include a.h
他這樣的架構,基本上就是把透過 moc 或 rcc 產生出來的檔案,都獨立去建置;只有 uic 產生的檔案因為是圖形介面元件的類別定義,所以還是需要被引用的。
他這樣的架構確實比較好。因為之前 Heresy 自己的作法,要確保只有一個 .cpp 會 include moc_a.hpp,否則就會造成 linking 階段的重複定義(moc_a.hpp 的內容)。
不過如果是要開發函式庫的話,個人會建議不要在 a.h 裡面直接引用 ui_a.h、而是採用 forward declaration 的形式,把相關的呼叫、連同 ui_a.h 的引用都放到 a.cpp 會比較好;否則在要由外部引用 a.h 的時候,會因為要去找 ui_a.h 而產生很大的問題。
Qt VS Tools 中的版本資訊
在現行的 Qt Visual Studio Tools 中,可以設定多個 Qt 的版本,並快速地切換,算是滿方便的~
而在建置專案的時候,系統就會去根據這邊設定的路徑,來找 Qt 相關的檔案。
甚至,考量到現在 Visual Studio 支援 Linux 建置(WSL 或 SSH),這邊的 Qt 平台也可以設定成 Linux 用的。
而由於 Heresy 一開始是想修改既有的 build rule 來讀取這邊的設定,所以就研究一下他到底怎麼玩的。
基本上,Qt VS Tools 是把這些版本資訊儲存在 Windows Registry 裡,他的路徑基本上是:
HKEY_CURRENT_USERSoftwareDigiaVersions
而它的內容則如下:
基本上會有「DefaultQtVersion」、儲存預設的版本;「VersionNames」則是用單一字串來記錄所有版本。
而在各版本下,基本上就是透過「InstallDir」這個字串,來儲存對應的路徑。
如果想在自己的 build rule 裡面、去取得這樣的資料的話,則可以在 .props 裡面,定義:
<PropertyGroup> <QtVersionsRegKey>HKEY_CURRENT_USERSoftwareDigiaVersions</QtVersionsRegKey> <QtVer>$([MSBuild]::GetRegistryValue('$(QtVersionsRegKey)','DefaultQtVersion'))</QtVer> <QTDIR>$([MSBuild]::GetRegistryValue('$(QtVersionsRegKey)$(QtVer)','InstallDir'))</QTDIR> <QT_INCLUDE>$(QTDIR)include</QT_INCLUDE> <QT_LIB>$(QTDIR)lib</QT_LIB> </PropertyGroup>
如此一來,QtVer 就會是預設的 Qt 版本,而 QTDIR 就會是該版本 Qt SDK 的路徑了。
這樣做技術上是可以的,也就是說可以自己寫一組 custom build rule、去抓 Qt VS Tools 的設定路徑;雖然說這樣做可以自己可以在某種程度上、更有彈性地控制整個 Qt 的建制架構,但是實務上,會需要這樣的機會應該不大就是了。
使用 MSBuild Rule 建置
由於 Heresy 這邊現在已經固定有在使用 GitLab CI/CD 來做建置,所以其實也需要研究使用 Visual Studio Build Tools 這種僅有文字介面、無法安裝 Qt VS Tools 的建置環境。
在使用舊版的 Qt VS Tools 專案的時候,基本上只需要設定「QTDIR」和「QtMsBuild」這兩個環境變數,個別去指到 Qt SDK 的安裝路徑、以及 QT VS Tools 的 build rule 所在路徑(預設是在「%LOCALAPPDATA%QtMsBuild」)就可以了。(參考)
不過在切換到新版後,光這兩個環境變數似乎就不夠用了。
Heresy 的做法,是盡量去符合 Qt VS Tools 的 build rules 的需求;所以後來是先在 Windows Registry 裡,加入相對應的版本資訊:
Windows Registry Editor Version 5.00 [HKEY_CURRENT_USERSOFTWAREDigia] [HKEY_CURRENT_USERSOFTWAREDigiaVersions] "DefaultQtVersion"="QtSDK" "VersionNames"="QtSDK" [HKEY_CURRENT_USERSOFTWAREDigiaVersionsQtSDK] "InstallDir"="C:\Qt\QtSDK"
然後再加入下面三個環境變數:
ENV QtInstallRegKey HKEY_CURRENT_USERSOFTWAREDigiaVersions ENV QtInstall QtSDK ENV QtInstallDir C:QtSDK
其中第一個 QtInstallRegKey 是告訴系統 Qt 版本的 registry 路徑,而 QtInstall 則是要使用的 Qt SDK 版本。
不過,雖然理論上之後應該會自動推論出 QtInstallDir 才對,但是由於不知道為什麼都沒辦法成功,所以後來還是連 QtInstallDir 都手動指定到對應的路徑了…
總之,這樣在加上之前的「QTDIR」和「QtMsBuild」,基本上就可以透過 MSBuild 來在命令提示字元的情況下,建置 Qt VS Tools 的專案了。
最後,一些其他的雜談:
-
Q 4 到 Qt5 的變化應該算是不小,相較之下 Qt 5 到 Qt 6 的變化感覺就比較小了。
雖然還是有 API 改變,但是相對影響不大。 -
Qt4 到 Qt5 除了部分類別換模組(例如從 QtGui 搬到 QtWidgets)外,像是 Qt 的 Plugin 系統,更是整個改掉了,舊版寫法會直接回報錯誤。
-
QtOpenGL 的部分,在 Qt 4 可以動的程式,改成 Qt 5 之後就會直接和 glew 相衝,完全編譯不過;解決方法基本上就是完全拆開兩邊的程式,確保不會在同一個檔案被編譯到。
本來 Heresy 這邊是想直上 Qt 6 的,不過後來是決定先轉到 Qt5、確認沒問題之後再改到 Qt6 了。