閱讀本文大概需要 6 分鍾
一個項目隨着功能開發越來越多,項目必然越來越大,工程管理成本也越來越高,后期維護成本更高。如何更好的組織管理工程,是非常重要的
今天我們來學習下 Qt Creator
是如何組織管理這么龐大的一個項目工程的
QMake 多工程管理方法
我們知道 Qt
采用 qmake
語法進行組織管理工程結構,想要更好的學習管理一個工程需要你了解基本的qmake
語法
在Qt
當中,一般以xx.pro
結尾的文件是某個工程文件,我們只要打開該文件即可打開該文件管理的工程
單工程基本用法
比如我們新建一個MainWindow
工程,那么自動會生成如下結構的工程文件
其中untitled.pro
文件內容如下所示
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
TARGET = TestDemo
TEMPLATE = app
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
FORMS += \
mainwindow.ui
該文件描述了這個工程一些基本信息
- QT += 表示需要包含哪些模塊
- greaterThan 可以判斷 Qt 的一些版本進而做一些版本之間差異的事情
- TEMPLATE 表示該工程編譯完最終會連接生成一個app,即會生成
xx.exe
可執行文件 - TARGET 表示該工程最終生成的文件名字,如果沒有配置默認取該工程名字
那么如果我們想編譯生成一個動態庫或者靜態庫該怎么辦?關鍵語句TEMPLATE
來進行控制
TEMPLATE = lib
此時編譯該工程默認會生成動態庫
TEMPLATE = lib
CONFIG += staticlib
此時編譯該工程又會生成靜態庫,所以關鍵地方就在上面兩句配置
多工程用法
多工程項目中一般是某些核心模塊編譯成庫(靜態庫、動態庫),然后在依賴的地方進行引入即可
比如我們有一個字符串處理工具模塊StringUtil
,該模塊最終編譯完會生成一個動態庫StringUtil.dll
,然后我們其中一個模塊需要使用到該模塊,那么該工程怎么使用呢?
首先是工程pro
文件需要導入該動態庫,這樣才能加載進來參與編譯,否則會提示某些函數未定義的錯誤
LIBS += -L$$PWD/../../ -lStringUtil
插件依賴管理緣由
上述代碼正常情況下是沒有任何問題的,但是,但是,但是
凡是重要的事情肯定要說三遍,你以為這樣寫就完事了,那么說明你的這個庫被依賴的工程比較少,如果這個基礎庫在 n 個工程下面要使用呢?
到這里相比有些人就說了,哪個工程要使用直接復制上述代碼過去不就行了,這樣做從功能上來看是沒有問題,可以正常使用和運行,但是你想過沒有,未來的那一天因為特殊原因這個庫有變化(名字、路徑等等),你是不是得修改這 n 個地方使用該庫的地方呀
程序員碰見重復的代碼肯定是不可以忍受的,肯定要想辦法封裝一下,將修改減少到最小,那么怎么實現比較好呢?
核心思想就是把依賴導入相關流程使用循環搞定,工程初始化時(也就是qmake)自動根據某個規則加載你的所有依賴即可
管理方案
當你閱讀 Qt Creator
源碼的時候就可以看到比較有意思的寫法,每個插件或者動態庫都有自己的依賴配置pri
文件,該文件記錄了這個庫或插件依賴那些庫、那些插件
每個插件的依賴配置文件命名類似這樣:xxx_dependencies.pri
,我們拿歡迎界面插件來舉個栗子分析下
打開該文件 welcome_dependencies.pri
。,查看文件內容
# 插件名字
QTC_PLUGIN_NAME = Welcome
# 插件依賴的庫
QTC_LIB_DEPENDS += \
extensionsystem \
utils
# 插件依賴的插件
QTC_PLUGIN_DEPENDS += \
coreplugin
QTC_PLUGIN_NAME
表示了當前生成動態庫或者插件的名字QTC_LIB_DEPENDS
表示當前庫依賴的庫文件名稱,多個庫依次追加即可QTC_PLUGIN_DEPENDS
表示當前插件依賴的插件名稱,比如welcome
插件依賴核心Coreplugin
插件
那么這些定義的文件是怎么加載進來的?又是怎么起作用的呢?源碼面前了無秘密,我們打開qtcreator.pri
文件來一探究竟,重點關注 244 行到 277 行之間的內容,可以看到如下內容:
!isEmpty(QTC_PLUGIN_DEPENDS) {
LIBS *= -L$$IDE_PLUGIN_PATH # plugin path from output directory
LIBS *= -L$$LINK_PLUGIN_PATH # when output path is different from Qt Creator build directory
}
# recursively resolve plugin deps
done_plugins =
for(ever) {
isEmpty(QTC_PLUGIN_DEPENDS): \
break()
done_plugins += $$QTC_PLUGIN_DEPENDS
for(dep, QTC_PLUGIN_DEPENDS) {
dependencies_file =
for(dir, QTC_PLUGIN_DIRS) {
exists($$dir/$$dep/$${dep}_dependencies.pri) {
dependencies_file = $$dir/$$dep/$${dep}_dependencies.pri
break()
}
}
isEmpty(dependencies_file): \
error("Plugin dependency $$dep not found")
include($$dependencies_file)
LIBS += -l$$qtLibraryName($$QTC_PLUGIN_NAME)
}
QTC_PLUGIN_DEPENDS = $$unique(QTC_PLUGIN_DEPENDS)
QTC_PLUGIN_DEPENDS -= $$unique(done_plugins)
}
上述代碼核心思想就是循環獲取依賴庫文件,然后進行引入
第一句 for(ever)
是一個無限循環,相當於是死循環while(1)
,等到 break
語句才會退出
第二個循環獲取$$QTC_PLUGIN_DEPENDS
值挨個進行遍歷,這個循環是為了檢測引入每個依賴插件
第三個循環,首先會判斷對應的依賴描述文件是否存在,如果不存在則會輸出錯誤信息給與提醒,后面會include
進來該文件,最后使用我們熟悉的LIBS+=
進行進入庫文件
循環最后面兩句非常重要,這兩句起到停止循環作用,-=
每次會從QTC_PLUGIN_DEPENS
中去重done_plugins
變量對應的插件,最后直到QTC_PLUGIN_DEPENDS
為空退出最外邊的循環
上面就是插件依賴處理流程,動態庫依賴處理流程原理也類似,比如下面所示
done_libs =
for(ever) {
isEmpty(QTC_LIB_DEPENDS): \
break()
done_libs += $$QTC_LIB_DEPENDS
for(dep, QTC_LIB_DEPENDS) {
include($$PWD/src/libs/$$dep/$${dep}_dependencies.pri)
LIBS += -l$$qtLibraryName($$QTC_LIB_NAME)
}
QTC_LIB_DEPENDS = $$unique(QTC_LIB_DEPENDS)
QTC_LIB_DEPENDS -= $$unique(done_libs)
}
可以看到整個過程幾行代碼就可以解決整個項目工程之間的依賴問題,后面開發其它插件和模塊只需要按照這個規則編寫對應xx__dependencies.pri
文件即可,后續的依賴加載會自動處理,可以減少很多工作量以及出錯問題
小試牛刀
我們來驗證下,編寫一個工具集模塊Misc
,該模塊編譯后生成一個動態庫,為了盡可能的簡單,工程結構如下所示
文件Misc_dependencies.pri
列舉了該庫的依賴信息
QTC_LIB_NAME = Misc
demo 全部工程源碼下載可以訪問這里[https://github.com/kevinlq/Qt-Creator-Opensource-Study]
多工程管理
上面提到了多工程依賴庫的一些管理方法,下面來看看Qt Creator
工程中一些其它管理技巧
善於定義變量
工程中難免會有很多重復的配置,比如:
- 某些文件編譯時生成的臨時文件路徑、編譯后動態庫、靜態庫、插件的路徑;
- 動態庫、靜態庫命名怎么區分;
Debug
和Release
版本下每個庫生成后的名字怎么區分- 程序版本號怎么在工程配置,然后代碼中直接使用
其實稍微大一點的項目,會面臨很多基礎的問題,解決這些問題要善於定義變量
這句話怎么理解呢?我們來看一些基本的例子就明白了
Debug 和 Release 區分
有時候我們不同編譯模式下生成的庫是不一樣的,調用第三方庫的時候也要注意這一點,那么就要求程序在不同模式下編譯后生成的庫路徑放到不同路徑中
CONFIG(debug, debug|release):{
DIR_COMPILEMODE = Debug
}
else:CONFIG(release, debug|release):{
DIR_COMPILEMODE = Release
}
DESTDIR = $$PWD/$$DIR_COMPILEMODE
上面配置會在當前目錄下對應編譯模式下生成對應庫
文件生成路徑定義
項目中一般都會定義文件的輸出路徑,比如我有一個動態庫要輸出指定目錄,那么對應的 pro
文件該怎么寫呢?
IDE_APP_NAME = QTC
isEmpty(IDE_BASE_BIN_PATH): IDE_BASE_BIN_PATH = $$QTC_PREFIX/$$IDE_APP_NAME
IDE_LIBRARY_PATH = $$IDE_BASE_BIN_PATH/bin
IDE_PLUGIN_PATH = $$IDE_BASE_BIN_PATH/$$IDE_LIBRARY_BASENAME/$$IDE_APP_TARGET/plugins
IDE_DATA_PATH = $$IDE_BASE_BIN_PATH/share/$$IDE_APP_TARGET
IDE_DOC_PATH = $$IDE_BASE_BIN_PATH/share/doc/$$IDE_APP_TARGET
IDE_BIN_PATH = $$IDE_BASE_BIN_PATH/bin
上述配置命令,在我們程序編譯后最終生成的路徑格式如下所示:
bin
│ └─Win32
│ ├─Debug
│ │ └─QTCLearn03
│ │ └─bin
│ │ libMiscd.a
│ │ libPluginsd.a
│ │ Miscd.dll
│ │ Pluginsd.dll
│ │ QTCLearn03.exe
│ │
│ └─Release
│ ├─QTC
│ │ └─bin
│ └─QTCLearn03
│ └─bin
自動根據當前是 Debug
還是 Release
模式生成到對應目錄,對程序不會造成干擾,而且每個模塊插件可以單獨設置其路徑,這樣做的好處就是分離清晰明確,便於管理和維護
針對插件和動態庫可以分別處於不同的目錄,以依葫蘆畫瓢即可完成
PS: 如果想要完整的
pro
配置模板可以私信我,真的很好用,拿來就可以直接用。
事實上,上述代碼你也可以學習完Qt Creator
后自己也可以整理出來。
Qt Creator源碼工程結構
源碼雖然看起來很多很復雜,不過大概可以分為三個部分,libs
、plugins
,App
。如上圖我重點標紅的內容,我在上一篇學習筆記當中介紹過這三個部分分別是干什么的,詳細可以看上篇文章
libs
工程封裝了一些外部使用的方法和函數,以動態庫的方式呈現,調用時引入動態庫加入頭文件即可。具體是怎么加入的呢?閱讀源碼你發現其它子工程並沒有直接引入,關鍵點還是上面提到的依賴管理方法
每個子工程都有自己的依賴配置文件,比如aggregation_dependencies.pri
,這個文件必須要有,否則編譯時會報錯,提醒你缺少對應的依賴文件
比如核心插件管理庫依賴配置 extensionsystem_dependencies.pri
QTC_LIB_NAME = ExtensionSystem
QTC_LIB_DEPENDS += \
aggregation \
utils
你能很清晰的看出來這個庫依賴兩個庫,那么在編譯它時這兩個庫必須先編譯
plugins
是所有插件功能的實現部分,包含核心插件以及剩余的擴展插件
插件也是一個個的動態庫,只不過每個插件都是繼承自同一個接口或者說叫純虛類,各自實現一些必要的初始化函數,這樣才能統一管理和訪問控制
工程管理原理還是一樣的手法,每個插件都擁有一個配置文件,比如核心插件 coreplugin_dependencies.pri
QTC_PLUGIN_NAME = Core
QTC_LIB_DEPENDS += \
aggregation \
extensionsystem \
utils
可以看出來該插件依賴三個動態庫,那么如果插件要依賴呢,怎么寫?也非常簡單,只需要添加依賴的插件名字即可,比如「書簽」插件依賴配置文件
QTC_PLUGIN_NAME = Bookmarks
QTC_LIB_DEPENDS += \
extensionsystem \
utils
QTC_PLUGIN_DEPENDS += \
projectexplorer \
coreplugin \
texteditor
可以看出多了一項QTC_PLUGIN_DEPENDS
,需要依賴那些插件只需要往后追加即可
這里說到書簽插件,是一個非常好用的功能,平時編寫調試代碼梳理流程非常有用,比如閱讀到某個關鍵函數,發現這個函數又調用了其它文件的方法,跳過去后發現又調用其它功能函數,每次跳轉此時太多,想要返回初始位置查看就不方便,有了書簽隨時點擊書簽就可以跳轉回去了
PS:建議大家使用最新版本的Qt Creator
,書簽會一直緩存,直到你手動刪除了,有時候標記了書簽,但是下班后電腦關機了第二天打開后發現書簽還在,直接開始干活,效率非常高
app
工程是程序主入口,會顯示加載三個動態庫,各個插件的調用是在main.cpp
函數里面動態加載調用的
當然了,實際配置時你還要考慮各各個平台下的一些兼容性,比如 mac
、linux
等平台,如果你的軟件不涉及這些平台那么就不用考慮了
總結
通過閱讀開源的框架和項目,可以增加我們的見識,平時工作或者學習當中可能不注意或者想不到的一些技巧方法和編程思想在閱讀源碼的過程中都可以看到,時間久了各方面都會有很大的提升
下一篇我們來學習看看Qt Creator
核心插件管理機制是如何實現的,也是學習的重點