當咱們準備好 Qt Creator 的源代碼以後,首先進入到它的目錄,來看一下它的源代碼目錄有什麼奧祕。python
這裏一共有 9 個文件夾和 9 個文件。咱們來一一看看它們都是幹什麼用的。linux
閱讀源代碼,通常能夠從main()
着手。可是閱讀 Qt 項目的源代碼,咱們也能夠從 pro 文件開始。pro 文件是 Qt 項目組織結構,規定了咱們但願該項目如何編譯、編譯以後要作什麼操做等。git
下面咱們從根目錄的 qtcreator.pro 開始。使用 Qt Creator 或者任意文本編輯器打開 qtcreator.pro,開始真正的代碼閱讀。github
1
|
include(qtcreator.pri)
|
第一行是 include qtcreator.pri。前面咱們提到過,qtcreator.pri 中定義了不少函數和適用於各個模塊的通用操做。pri 文件能夠理解爲 pro 文件片斷,可使用include
操做符將其引入一個 pro 文件。qmake 會自動處理引用操做,相似於將 pri 文件的所有內容複製到include
語句處。這與 C++ 的#include
指令相似。這裏的處理是線性的,也就是 qmake 會從上向下進行解析。所以,若是你在 pri 中定義了一個函數,那麼必須在include
語句以後才能正常使用該函數。這是在使用時須要注意的。有關 qtcreator.pri 文件的內容,會在之後的文章中詳細介紹。若是你使用 Qt Creator 打開,include
語句會在左側的項目樹中顯示一個節點。這種節點不須要物理上的文件夾隔離,只須要include
不一樣的 pri 文件便可。這樣,即使你的全部文件都在同一個目錄下,你也可使用 pri 文件建立出來多個虛擬目錄節點。這樣的項目結構看起來會清晰不少。正則表達式
1
2
3
4
5
|
#version check qt
!minQtVersion(5, 6, 0) {
message("Cannot build Qt Creator with Qt version $${QT_VERSION}.")
error("Use at least Qt 5.6.0.")
}
|
接下來的幾行用於判斷 Qt 的版本。minQtVersion()
是在 qtcreator.pri 中定義的函數。沒錯!pro 也能夠定義本身的函數!這正是 pro 的強大之處。咱們會在後面詳細介紹如何定義函數。顧名思義,這個函數函數用於判斷 Qt 的版本。前面的!
即取非運算符,這與 C++ 一致。當 Qt 的版本低於 5.6.0 時,執行塊中的操做。message()
是 qmake 預約義的函數,相似於qDebug()
,能夠在控制檯輸出一段文本。這裏咱們輸出的是「Cannot build Qt Creator with Qt version $${QT_VERSION}.」。字符串最後的$${QT_VERSION}
是佔位符,會使用QT_VERSION
變量的內容進行替換。這一操做被稱爲變量展開(variable expansion)。有關$$
以及相關運算符的使用至關重要。shell
$$
運算符一般用於展開變量的內容,展開的內容能夠用於變量的賦值,也能夠用於函數的傳參。例如:windows
1
2
3
|
EVERYTHING = $$SOURCES $$HEADERS
message("The project contains the following files:")
message($$EVERYTHING)
|
上面的代碼中,第一行將SOURCES
和HEADERS
的內容賦值給EVERYTHING
;第三行則將EVERYTHING
做爲函數參數賦值給message()
函數。若是沒有$$
運算符,將只會輸出EVERYTHING
字符串。架構
變量能夠保存環境變量。這些變量能夠在 qmake 執行時計算出,或者直接包含在 Makefile 中以便構建時使用。若是須要在 qmake 運行時獲取環境變量的值,使用$$()
或$${}
運算符。例如:app
1
2
|
DESTDIR = $$(PWD)
message(The project will be installed in $$DESTDIR)
|
在上面代碼中,PWD
是 qmake 內置的一個環境變量,用於表示當前正在處理的文件所在文件夾的絕對路徑。使用$$()
或${}
運算符,會在 qmake 運行時將值賦給DESTDIR
。若是須要在生成 Makefile 時獲取環境變量的值,則須要使用$()
運算符。例如:編輯器
1
2
3
4
5
6
|
DESTDIR = $$(PWD)
message(The project will be installed in $$DESTDIR)
DESTDIR = $(PWD)
message(The project will be installed in the value of PWD)
message(when the Makefile is processed.)
|
在上面的語句中,PWD
的值在 qmake 處理是就已經獲取到了,可是$(PWD)
則會在生成的 Makefile 中賦值給DESTDIR
。這可以保證在處理 Makefile 時環境變量是正確的。
經過上面的解釋,咱們知道,$${QT_VERSION}
會在 qmake 運行時進行變量展開。
下面再來看另外的代碼:
1
2
|
TEMPLATE = subdirs
CONFIG += ordered
|
這是 qmake 典型的配置。TEMPLATE
即代碼模板,將告訴 qmake 咱們要怎麼生成最後的文件。它的可選值分別是:
SUBDIRS
變量指定。咱們最經常使用的是前三種設置。對於大型項目,通常會分紅多個源代碼文件夾,所以,Qt Creator 使用的是 subdirs。接下來一行,CONFIG += ordered
意思是,按照SUBDIRS
書寫順序來編譯。不少時候,咱們雖然將源代碼分爲不一樣目錄,可是這些目錄之間是存在依賴關係的。好比,一個基礎類庫要被其它全部模塊使用,在編譯時,該類庫應該首先被編譯。這要求咱們按照必定的順序來添加SUBDIRS
。有關這一點,Qt Creator 是這樣作的:
1
2
3
|
SUBDIRS = src share
unix:!macx:!isEmpty(copydata):SUBDIRS += bin
!isEmpty(BUILD_TESTS):SUBDIRS += tests
|
首先,SUBDIRS
只有兩個目錄:src 和 share。按照順序,應該是先編譯 src,而後編譯 share。後面則是一串複雜的判斷:對於 Unix 平臺(unix
),若是不是 Mac OS(!macx
),而且copydata
不爲空(!isEmpty(copydata)
),則須要再增長一個 bin 目錄。最後再判斷,若是BUILD_TESTS
不爲空(!isEmpty(BUILD_TESTS)
),則再增長一個 tests 目錄。+=
運算符就像它所展現的那樣,用於追加新的值。copydata
和BUILD_TESTS
都是在 qtcreator.pri 中定義的宏。由於咱們是在最前面include
了 qtcreator.pri,因此咱們能夠自由使用在 qtcreator.pri 文件中定義的變量。相似!isEmpty(BUILD_TESTS):SUBDIRS += tests
這樣的寫法是一種簡寫,完整的寫法應該以下所示:
1
2
3
|
!isEmpty(BUILD_TESTS) {
SUBDIRS += tests
}
|
有關isEmpty()
這樣的函數,咱們會在下面詳細介紹。咱們在看這段代碼時,能夠同 C++ 代碼做類比,以便咱們理解:
1
2
3
4
5
6
7
8
9
10
11
|
SUBDIRS = src share
if (unix) {
if (!macx) {
if (!isEmpty(copydata)) {
SUBDIRS += bin
}
}
}
if (!isEmpty(BUILD_TESTS)) {
SUBDIRS += tests
}
|
接下來咱們遇到的是
1
2
3
4
|
DISTFILES += dist/copyright_template.txt \
README.md \
$$files(dist/changes-*) \
...
|
DESTFILES
知道須要在最終的目標包括的文件。按照 qmake 的文檔,這一特性只適用於 UnixMake。這裏咱們又遇到了熟悉的$$file()
,只不過這裏不是變量展開,而是函數調用。
qmake 提供了兩類函數:替換函數(replace functions)和測試函數(test fucntion)。替換函數用於處理數據並將處理結果返回;測試函數的返回值只能是bool
值,而且能夠用於一些測試的情形。在使用時,替換函數須要添加$$
先導符而測試函數則不須要。
$$file()
正是一個替換函數,接受一個正則表達式做爲參數,其返回值是全部符合這個正則表達式的文件名列表。所以,$$file(dist/changes-*)
返回的是在當前目錄下的 dist 文件夾中,全部以 changes- 開頭的文件,將它們所有添加到了DESTFILES
。另外,這一函數還能夠有第二個參數,是一個bool
值,默認是false
,表示是否是要遞歸尋找文件。
以後咱們看到了
1
2
3
|
exists(src/shared/qbs/qbs.pro) {
...
}
|
exists()
則是一個測試函數,顧名思義,該函數用於測試其參數做爲文件名,所表明的文件是否存在。注意測試函數的使用:它能夠直接做爲測試條件,後面跟着一對大括號,若是函數返回值爲true
則執行塊中的語句。這裏咱們發現 src/shared/qbs/qbs.pro 並不存在,所以其中的語句並不會執行。
下面是語句
1
2
|
contains(QT_ARCH, i386): ARCHITECTURE = x86
else: ARCHITECTURE = $$QT_ARCH
|
QT_VERSION
是 qmake 內置的一個變量,用於表示 Qt 的架構。很明顯,contains()
是一個測試函數,其函數原型是contains(variablename, value)
,當變量variablename
中包含了value
時,測試經過。那麼,上面語句便是,若是QT_ARCH
中有i386
,則將ARCHITECTURE
賦值爲x86
,不然就是$$QT_ARCH
。注意在使用contains
函數時,QT_ARCH
並無使用$$
運算符。由於在使用該函數時,第一個參數是變量名,函數會本身取該變量名的實際值。
1
2
3
4
|
macx: PLATFORM = "mac"
else:win32: PLATFORM = "windows"
else:linux-*: PLATFORM = "linux-$${ARCHITECTURE}"
else: PLATFORM = "unknown"
|
定義了一個新的宏PLATFORM
。注意這裏使用了前面剛剛定義的ARCHITECTURE
宏。
接下來,
1
2
|
BASENAME = $$(INSTALL_BASENAME)
isEmpty(BASENAME): BASENAME = qt-creator-$${PLATFORM}$(INSTALL_EDITION)-$${QTCREATOR_VERSION}$(INSTALL_POSTFIX)
|
是一種常見的寫法。首先,咱們定義了BASENAME
宏爲$$(INSTALL_BASENAME)
;以後,若是BASENAME
爲空的話(使用了測試函數isEmpty()
進行判斷),則定義新的BASENAME
的值。這種寫法一方面容許咱們在編譯時經過傳入自定義值改變默認設置(也就是說,若是以前定義了INSTALL_BASENAME
,那麼就會使用咱們定義的值),不然就會生成一個默認值。之後咱們會發現,Qt Creator 的 pro 文件中,不少地方都使用了相似的寫法。
跳過部分代碼,接下來是一大段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
macx {
APPBUNDLE = "$$OUT_PWD/bin/Qt Creator.app"
BINDIST_SOURCE = "$$OUT_PWD/bin/Qt Creator.app"
BINDIST_INSTALLER_SOURCE = $$BINDIST_SOURCE
deployqt.commands = $$PWD/scripts/deployqtHelper_mac.sh \"$${APPBUNDLE}\" \"$$[QT_INSTALL_TRANSLATIONS]\" \"$$[QT_INSTALL_PLUGINS]\" \"$$[QT_INSTALL_IMPORTS]\" \"$$[QT_INSTALL_QML]\"
codesign.commands = codesign --deep -s \"$(SIGNING_IDENTITY)\" $(SIGNING_FLAGS) \"$${APPBUNDLE}\"
dmg.commands = $$PWD/scripts/makedmg.sh $$OUT_PWD/bin $${BASENAME}.dmg
#dmg.depends = deployqt
QMAKE_EXTRA_TARGETS += codesign dmg
} else {
BINDIST_SOURCE = "$(INSTALL_ROOT)$$QTC_PREFIX"
BINDIST_INSTALLER_SOURCE = "$$BINDIST_SOURCE/*"
deployqt.commands = python -u $$PWD/scripts/deployqt.py -i \"$(INSTALL_ROOT)$$QTC_PREFIX\" \"$(QMAKE)\"
deployqt.depends = install
win32 {
deployartifacts.depends = install
deployartifacts.commands = git clone "git://code.qt.io/qt-creator/binary-artifacts.git" -b $$BINARY_ARTIFACTS_BRANCH&& xcopy /s /q /y /i "binary-artifacts\\win32" \"$(INSTALL_ROOT)$$QTC_PREFIX\"&& rmdir /s /q binary-artifacts
QMAKE_EXTRA_TARGETS += deployartifacts
}
}
|
這裏使用macx
分爲兩部分。很明顯,若是系統是macx
,則定義宏APPBUNDLE
。咱們須要詳細解釋的是,在定義新的變量時,Qt Creator 所用到的那些宏。首先,$$OUT_PWD
是 qmake 生成的 Makefile 所在的文件夾。
下面咱們會看到一個新的語法:$$[]
。這是取 qmake 的屬性。qmake 內置了不少屬性值,例如:
1
2
3
4
5
6
7
8
9
10
11
12
|
message(Qt version: $$[QT_VERSION])
message(Qt is installed in $$[QT_INSTALL_PREFIX])
message(Qt resources can be found in the following locations:)
message(Documentation: $$[QT_INSTALL_DOCS])
message(Header files: $$[QT_INSTALL_HEADERS])
message(Libraries: $$[QT_INSTALL_LIBS])
message(Binary files (executables): $$[QT_INSTALL_BINS])
message(Plugins: $$[QT_INSTALL_PLUGINS])
message(Data files: $$[QT_INSTALL_DATA])
message(Translation files: $$[QT_INSTALL_TRANSLATIONS])
message(Settings: $$[QT_INSTALL_CONFIGURATION])
message(Examples: $$[QT_INSTALL_EXAMPLES])
|
在運行時,qmake 能夠自定義屬性:
1
|
qmake -set PROPERTY VALUE
|
而後,咱們就能夠用下面語句獲取這個屬性:
1
|
qmake -query PROPERTY
|
在 pro 文件中,則可使用$$[]
獲取這些屬性。能夠查閱文檔找到 qmake 內置了哪些屬性。
語句
1
|
deployqt.commands = $$PWD/scripts/deployqtHelper_mac.sh \"$${APPBUNDLE}\" \"$$[QT_INSTALL_TRANSLATIONS]\" \"$$[QT_INSTALL_PLUGINS]\" \"$$[QT_INSTALL_IMPORTS]\" \"$$[QT_INSTALL_QML]\"
|
定義了一個目標 deployqt,這個目標的命令是$$PWD/scripts/deployqtHelper_mac.sh \"$${APPBUNDLE}\" \"$$[QT_INSTALL_TRANSLATIONS]\" \"$$[QT_INSTALL_PLUGINS]\" \"$$[QT_INSTALL_IMPORTS]\" \"$$[QT_INSTALL_QML]\"
。咱們可使用message()
函數輸出這條命令。命令的具體實現暫不深究,感興趣的話能夠閱讀 scripts/deployqtHelper_mac.sh 文件。接下來的語句是相似的。最後,這些定義的目標被添加到QMAKE_EXTRA_TARGETS
。這纔是真正重要的內容。
儘管 qmake 努力成爲一個跨平臺的構建工具,可是不少時候,咱們不得不使用特定平臺的語句。例如,一個常見的任務是,在編譯完成以後,將預置的配置文件複製到特定目錄。這種目標能夠經過相似的語法進行定義,而後將定義好的目標添加到QMAKE_EXTRA_TARGETS
。當 qmake 運行完畢後,會接着執行這些目標,直到編譯成功。
例如,
1
2
3
4
5
|
mytarget.target = .buildfile
mytarget.commands = touch $$mytarget.target
mytarget.depends = mytarget2
mytarget2.commands = @echo Building $$mytarget.target
|
mytarget
是一個自定義目標;mytarget.target
是這個自定義目標的名字。以後生成的 Makefile 中將會使用這個名字做爲 target。mytarget.commands
定義了這個目標的命令:使用touch
命令生成一個文件。mytarget.depends
定義這個目標依賴於mytarget2
,儘管mytarget2
是在後面定義的。最後,咱們將這兩個目標都添加到QMAKE_EXTRA_TARGETS
:
1
|
QMAKE_EXTRA_TARGETS += mytarget mytarget2
|
最後,咱們來看
1
2
3
4
5
6
|
win32 {
deployqt.commands ~= s,/,\\\\,g
bindist.commands ~= s,/,\\\\,g
bindist_installer.commands ~= s,/,\\\\,g
installer.commands ~= s,/,\\\\,g
}
|
有是一個新的語法~=
。~=
運算符將符合正則表達式的內容替換爲後面的部分。例如DEFINES ~= s/QT_[DT].+/QT
會將以QT_D
或QT_T
開頭的文本替換爲QT
。後面s,/,\\\\,g
是替換操做。三個逗號分爲四個部分:第一個s
表示輸入字符串;第二個/
表示 /;第三個\\\\
表示 \,之因此是四個,是由於 \ 須要轉義;第四個g
表示全局替換。合起來的意思就是,將輸入字符串中的 / 所有替換爲 \。這是適配命令路徑中,Unix 的 / 和 Windows 的 \。這一個技巧在編寫跨平臺代碼中很是有用,不少時候咱們在 pro 中給出了 Unix 格式的路徑,只須要使用簡單的語句,例如
1
2
|
PWD_WIN = $${PWD}
PWD_WIN ~= s,/,\\,g
|
就能夠轉換爲合法的 Windows 路徑。
本章咱們着重學習了 Qt Creator 的主項目文件 qtcreator.pro 的寫法。下一節咱們將詳細介紹 qtcreator.pri 的寫法。
https://www.devbean.net/2016/08/qt-creator-source-study-03/