閱讀本文大概需要 6 分鐘
一個項目隨著功能開發(fā)越來越多,項目必然越來越大宋列,工程管理成本也越來越高昭抒,后期維護成本更高。如何更好的組織管理工程炼杖,是非常重要的
今天我們來學習下 Qt Creator
是如何組織管理這么龐大的一個項目工程的
QMake 多工程管理方法
我們知道 Qt
采用 qmake
語法進行組織管理工程結(jié)構(gòu)灭返,想要更好的學習管理一個工程需要你了解基本的qmake
語法
在Qt
當中,一般以xx.pro
結(jié)尾的文件是某個工程文件坤邪,我們只要打開該文件即可打開該文件管理的工程
單工程基本用法
比如我們新建一個MainWindow
工程熙含,那么自動會生成如下結(jié)構(gòu)的工程文件
其中untitled.pro
文件內(nèi)容如下所示
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
可執(zhí)行文件 - TARGET 表示該工程最終生成的文件名字艇纺,如果沒有配置默認取該工程名字
那么如果我們想編譯生成一個動態(tài)庫或者靜態(tài)庫該怎么辦怎静?關(guān)鍵語句TEMPLATE
來進行控制
TEMPLATE = lib
此時編譯該工程默認會生成動態(tài)庫
TEMPLATE = lib
CONFIG += staticlib
此時編譯該工程又會生成靜態(tài)庫,所以關(guān)鍵地方就在上面兩句配置
多工程用法
多工程項目中一般是某些核心模塊編譯成庫(靜態(tài)庫黔衡、動態(tài)庫)蚓聘,然后在依賴的地方進行引入即可
比如我們有一個字符串處理工具模塊StringUtil
,該模塊最終編譯完會生成一個動態(tài)庫StringUtil.dll
盟劫,然后我們其中一個模塊需要使用到該模塊夜牡,那么該工程怎么使用呢?
首先是工程pro
文件需要導入該動態(tài)庫侣签,這樣才能加載進來參與編譯塘装,否則會提示某些函數(shù)未定義的錯誤
LIBS += -L$$PWD/../../ -lStringUtil
插件依賴管理緣由
上述代碼正常情況下是沒有任何問題的,但是影所,但是蹦肴,但是
凡是重要的事情肯定要說三遍,你以為這樣寫就完事了猴娩,那么說明你的這個庫被依賴的工程比較少阴幌,如果這個基礎(chǔ)庫在 n 個工程下面要使用呢?
到這里相比有些人就說了胀溺,哪個工程要使用直接復制上述代碼過去不就行了裂七,這樣做從功能上來看是沒有問題,可以正常使用和運行仓坞,但是你想過沒有背零,未來的那一天因為特殊原因這個庫有變化(名字、路徑等等)无埃,你是不是得修改這 n 個地方使用該庫的地方呀
程序員碰見重復的代碼肯定是不可以忍受的徙瓶,肯定要想辦法封裝一下毛雇,將修改減少到最小,那么怎么實現(xiàn)比較好呢侦镇?
核心思想就是把依賴導入相關(guān)流程使用循環(huán)搞定灵疮,工程初始化時(也就是qmake)自動根據(jù)某個規(guī)則加載你的所有依賴即可
管理方案
當你閱讀 Qt Creator
源碼的時候就可以看到比較有意思的寫法,每個插件或者動態(tài)庫都有自己的依賴配置pri
文件壳繁,該文件記錄了這個庫或插件依賴那些庫震捣、那些插件
每個插件的依賴配置文件命名類似這樣:xxx_dependencies.pri
,我們拿歡迎界面插件來舉個栗子分析下
打開該文件 welcome_dependencies.pri
闹炉。蒿赢,查看文件內(nèi)容
# 插件名字
QTC_PLUGIN_NAME = Welcome
# 插件依賴的庫
QTC_LIB_DEPENDS += \
extensionsystem \
utils
# 插件依賴的插件
QTC_PLUGIN_DEPENDS += \
coreplugin
-
QTC_PLUGIN_NAME
表示了當前生成動態(tài)庫或者插件的名字 -
QTC_LIB_DEPENDS
表示當前庫依賴的庫文件名稱,多個庫依次追加即可 -
QTC_PLUGIN_DEPENDS
表示當前插件依賴的插件名稱渣触,比如welcome
插件依賴核心Coreplugin
插件
那么這些定義的文件是怎么加載進來的羡棵?又是怎么起作用的呢?源碼面前了無秘密嗅钻,我們打開qtcreator.pri
文件來一探究竟皂冰,重點關(guān)注 244 行到 277 行之間的內(nèi)容,可以看到如下內(nèi)容:
!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)
}
上述代碼核心思想就是循環(huán)獲取依賴庫文件养篓,然后進行引入
第一句 for(ever)
是一個無限循環(huán)秃流,相當于是死循環(huán)while(1)
,等到 break
語句才會退出
第二個循環(huán)獲取$$QTC_PLUGIN_DEPENDS
值挨個進行遍歷觉至,這個循環(huán)是為了檢測引入每個依賴插件
第三個循環(huán)剔应,首先會判斷對應(yīng)的依賴描述文件是否存在睡腿,如果不存在則會輸出錯誤信息給與提醒语御,后面會include
進來該文件,最后使用我們熟悉的LIBS+=
進行進入庫文件
循環(huán)最后面兩句非常重要席怪,這兩句起到停止循環(huán)作用应闯,-=
每次會從QTC_PLUGIN_DEPENS
中去重done_plugins
變量對應(yīng)的插件,最后直到QTC_PLUGIN_DEPENDS
為空退出最外邊的循環(huán)
上面就是插件依賴處理流程挂捻,動態(tài)庫依賴處理流程原理也類似碉纺,比如下面所示
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)
}
可以看到整個過程幾行代碼就可以解決整個項目工程之間的依賴問題,后面開發(fā)其它插件和模塊只需要按照這個規(guī)則編寫對應(yīng)xx__dependencies.pri
文件即可刻撒,后續(xù)的依賴加載會自動處理骨田,可以減少很多工作量以及出錯問題
小試牛刀
我們來驗證下,編寫一個工具集模塊Misc
声怔,該模塊編譯后生成一個動態(tài)庫态贤,為了盡可能的簡單,工程結(jié)構(gòu)如下所示
文件Misc_dependencies.pri
列舉了該庫的依賴信息
QTC_LIB_NAME = Misc
demo 全部工程源碼下載可以訪問這里[https://github.com/kevinlq/Qt-Creator-Opensource-Study]
多工程管理
上面提到了多工程依賴庫的一些管理方法醋火,下面來看看Qt Creator
工程中一些其它管理技巧
善于定義變量
工程中難免會有很多重復的配置,比如:
- 某些文件編譯時生成的臨時文件路徑、編譯后動態(tài)庫拂封、靜態(tài)庫豺瘤、插件的路徑;
- 動態(tài)庫瘩欺、靜態(tài)庫命名怎么區(qū)分;
-
Debug
和Release
版本下每個庫生成后的名字怎么區(qū)分 - 程序版本號怎么在工程配置,然后代碼中直接使用
其實稍微大一點的項目怎栽,會面臨很多基礎(chǔ)的問題,解決這些問題要善于定義變量
這句話怎么理解呢宿饱?我們來看一些基本的例子就明白了
Debug 和 Release 區(qū)分
有時候我們不同編譯模式下生成的庫是不一樣的婚瓜,調(diào)用第三方庫的時候也要注意這一點,那么就要求程序在不同模式下編譯后生成的庫路徑放到不同路徑中
CONFIG(debug, debug|release):{
DIR_COMPILEMODE = Debug
}
else:CONFIG(release, debug|release):{
DIR_COMPILEMODE = Release
}
DESTDIR = $$PWD/$$DIR_COMPILEMODE
上面配置會在當前目錄下對應(yīng)編譯模式下生成對應(yīng)庫
文件生成路徑定義
項目中一般都會定義文件的輸出路徑刑棵,比如我有一個動態(tài)庫要輸出指定目錄巴刻,那么對應(yīng)的 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
自動根據(jù)當前是 Debug
還是 Release
模式生成到對應(yīng)目錄胡陪,對程序不會造成干擾,而且每個模塊插件可以單獨設(shè)置其路徑碍舍,這樣做的好處就是分離清晰明確柠座,便于管理和維護
針對插件和動態(tài)庫可以分別處于不同的目錄,以依葫蘆畫瓢即可完成
PS: 如果想要完整的
pro
配置模板可以私信我片橡,真的很好用妈经,拿來就可以直接用。
事實上捧书,上述代碼你也可以學習完Qt Creator
后自己也可以整理出來吹泡。
Qt Creator源碼工程結(jié)構(gòu)
源碼雖然看起來很多很復雜,不過大概可以分為三個部分经瓷,libs
爆哑、plugins
,App
舆吮。如上圖我重點標紅的內(nèi)容揭朝,我在上一篇學習筆記當中介紹過這三個部分分別是干什么的,詳細可以看上篇文章
Qt Creator 源碼學習筆記02色冀,認識框架結(jié)構(gòu)結(jié)構(gòu)
libs
工程封裝了一些外部使用的方法和函數(shù)潭袱,以動態(tài)庫的方式呈現(xiàn),調(diào)用時引入動態(tài)庫加入頭文件即可锋恬。具體是怎么加入的呢屯换?閱讀源碼你發(fā)現(xiàn)其它子工程并沒有直接引入,關(guān)鍵點還是上面提到的依賴管理方法
每個子工程都有自己的依賴配置文件伶氢,比如aggregation_dependencies.pri
,這個文件必須要有趟径,否則編譯時會報錯瘪吏,提醒你缺少對應(yīng)的依賴文件
比如核心插件管理庫依賴配置 extensionsystem_dependencies.pri
QTC_LIB_NAME = ExtensionSystem
QTC_LIB_DEPENDS += \
aggregation \
utils
你能很清晰的看出來這個庫依賴兩個庫,那么在編譯它時這兩個庫必須先編譯
plugins
是所有插件功能的實現(xiàn)部分蜗巧,包含核心插件以及剩余的擴展插件
插件也是一個個的動態(tài)庫掌眠,只不過每個插件都是繼承自同一個接口或者說叫純虛類,各自實現(xiàn)一些必要的初始化函數(shù)幕屹,這樣才能統(tǒng)一管理和訪問控制
工程管理原理還是一樣的手法蓝丙,每個插件都擁有一個配置文件,比如核心插件 coreplugin_dependencies.pri
QTC_PLUGIN_NAME = Core
QTC_LIB_DEPENDS += \
aggregation \
extensionsystem \
utils
可以看出來該插件依賴三個動態(tài)庫望拖,那么如果插件要依賴呢渺尘,怎么寫?也非常簡單说敏,只需要添加依賴的插件名字即可鸥跟,比如「書簽」插件依賴配置文件
QTC_PLUGIN_NAME = Bookmarks
QTC_LIB_DEPENDS += \
extensionsystem \
utils
QTC_PLUGIN_DEPENDS += \
projectexplorer \
coreplugin \
texteditor
可以看出多了一項QTC_PLUGIN_DEPENDS
,需要依賴那些插件只需要往后追加即可
這里說到書簽插件盔沫,是一個非常好用的功能医咨,平時編寫調(diào)試代碼梳理流程非常有用,比如閱讀到某個關(guān)鍵函數(shù)架诞,發(fā)現(xiàn)這個函數(shù)又調(diào)用了其它文件的方法拟淮,跳過去后發(fā)現(xiàn)又調(diào)用其它功能函數(shù),每次跳轉(zhuǎn)此時太多谴忧,想要返回初始位置查看就不方便很泊,有了書簽隨時點擊書簽就可以跳轉(zhuǎn)回去了
PS:建議大家使用最新版本的Qt Creator
,書簽會一直緩存沾谓,直到你手動刪除了委造,有時候標記了書簽,但是下班后電腦關(guān)機了第二天打開后發(fā)現(xiàn)書簽還在搏屑,直接開始干活争涌,效率非常高
app
工程是程序主入口粉楚,會顯示加載三個動態(tài)庫辣恋,各個插件的調(diào)用是在main.cpp
函數(shù)里面動態(tài)加載調(diào)用的
當然了,實際配置時你還要考慮各各個平臺下的一些兼容性模软,比如 mac
伟骨、linux
等平臺,如果你的軟件不涉及這些平臺那么就不用考慮了
總結(jié)
通過閱讀開源的框架和項目燃异,可以增加我們的見識携狭,平時工作或者學習當中可能不注意或者想不到的一些技巧方法和編程思想在閱讀源碼的過程中都可以看到,時間久了各方面都會有很大的提升
下一篇我們來學習看看Qt Creator
核心插件管理機制是如何實現(xiàn)的回俐,也是學習的重點