Qt Creator 源碼學習筆記03湖蜕,大型項目如何管理工程

閱讀本文大概需要 6 分鐘

一個項目隨著功能開發(fā)越來越多,項目必然越來越大宋列,工程管理成本也越來越高昭抒,后期維護成本更高。如何更好的組織管理工程炼杖,是非常重要的

今天我們來學習下 Qt Creator 是如何組織管理這么龐大的一個項目工程的

QMake 多工程管理方法

我們知道 Qt 采用 qmake語法進行組織管理工程結(jié)構(gòu)灭返,想要更好的學習管理一個工程需要你了解基本的qmake語法

Qt當中,一般以xx.pro結(jié)尾的文件是某個工程文件坤邪,我們只要打開該文件即可打開該文件管理的工程

單工程基本用法

比如我們新建一個MainWindow工程熙含,那么自動會生成如下結(jié)構(gòu)的工程文件

image

其中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)如下所示

image
image

文件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ū)分;
  • DebugRelease 版本下每個庫生成后的名字怎么區(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)

image

源碼雖然看起來很多很復雜,不過大概可以分為三個部分经瓷,libs 爆哑、pluginsApp舆吮。如上圖我重點標紅的內(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)的回俐,也是學習的重點


相關(guān)閱讀

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市单默,隨后出現(xiàn)的幾起案子碘举,更是在濱河造成了極大的恐慌,老刑警劉巖搁廓,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件引颈,死亡現(xiàn)場離奇詭異,居然都是意外死亡境蜕,警方通過查閱死者的電腦和手機蝙场,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粱年,“玉大人售滤,你說我怎么就攤上這事√ㄊ” “怎么了趴泌?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拉庶。 經(jīng)常有香客問我嗜憔,道長,這世上最難降的妖魔是什么氏仗? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任吉捶,我火速辦了婚禮,結(jié)果婚禮上皆尔,老公的妹妹穿的比我還像新娘呐舔。我一直安慰自己,他們只是感情好慷蠕,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布珊拼。 她就那樣靜靜地躺著,像睡著了一般流炕。 火紅的嫁衣襯著肌膚如雪澎现。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天每辟,我揣著相機與錄音剑辫,去河邊找鬼。 笑死渠欺,一個胖子當著我的面吹牛妹蔽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼胳岂,長吁一口氣:“原來是場噩夢啊……” “哼编整!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乳丰,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤闹击,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后成艘,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赏半,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年淆两,在試婚紗的時候發(fā)現(xiàn)自己被綠了断箫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡秋冰,死狀恐怖仲义,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情剑勾,我是刑警寧澤埃撵,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站虽另,受9級特大地震影響暂刘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捂刺,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一谣拣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧族展,春花似錦森缠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至恰画,卻和暖如春宾茂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锣尉。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工刻炒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人自沧。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拇厢。 傳聞我的和親對象是個殘疾皇子爱谁,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容