qt creator源碼全方面分析(3-2)
- 2020 年 3 月 6 日
- 筆記
目錄
qtcreator.pri
前面我們介紹了qtcreator.pro,下面我們開始介紹qtcreator.pri,來看看pro中include的pri到底是幹什麼用的。
注意,許多函數/變數/關鍵字的含義,某些基礎用法,在qtcreator.pro中進行了介紹。
判斷重複包含
qtcreator.pri第一部分是
!isEmpty(QTCREATOR_PRI_INCLUDED):error("qtcreator.pri already included") QTCREATOR_PRI_INCLUDED = 1
很明顯,isEmpty()為false,則調用error報錯退出編譯。那麼只能是為true,即要求QTCREATOR_PRI_INCLUDED為空,並在下一行立即定義為1。
那麼這個是在幹什麼呢?我們看變數的名稱就能略窺一二,INCLUDED就是已包含的意思,那麼這裡就是為了避免在其他地方重複包含qtcreator.pri文件,類似於C/C++頭文件中的
# ifndef XXX_H # define XXX_H #endif
定義版本資訊
接下來是
QTCREATOR_VERSION = 4.6.2 QTCREATOR_COMPAT_VERSION = 4.6.0 VERSION = $$QTCREATOR_VERSION QTCREATOR_DISPLAY_VERSION = 4.6.2 QTCREATOR_COPYRIGHT_YEAR = 2018 BINARY_ARTIFACTS_BRANCH = 4.6
VERSION
如果TEMPLATE值為app,則指定應用程式的版本號;如果TEMPLATE值為lib,則指定庫的版本號。
在Windows上,如果未設置RC_FILE和RES_FILE變數,則自動生成.rc文件。 生成的.rc文件將具有FILEVERSION和PRODUCTVERSION條目,並用主,次,修補程式和構建版本號填充。 每個數字的範圍必須在0到65535之間。有關.rc文件生成的更多詳細資訊,請參見Platform Notes。
示例:
win32:VERSION = 1.2.3.4 # major.minor.patch.build else:VERSION = 1.2.3 # major.minor.patch
很明顯,是在定義QtCreator的版本,兼容性版本,版權,以及git分支。
定義IDE名稱
接下來是
isEmpty(IDE_DISPLAY_NAME): IDE_DISPLAY_NAME = Qt Creator isEmpty(IDE_ID): IDE_ID = qtcreator isEmpty(IDE_CASED_ID): IDE_CASED_ID = QtCreator isEmpty(PRODUCT_BUNDLE_IDENTIFIER): PRODUCT_BUNDLE_IDENTIFIER = org.qt-project.$$IDE_ID
我們在qtcreator.pro中已經介紹過isEmpty這種用法。這裡在給相關變數設置默認值。
啟用C++14
接下來是
CONFIG += c++14
CONFIG
指定項目配置和編譯器選項。 這些值由qmake內部識別,並具有特殊含義。
以下CONFIG值控制編譯標誌:
選項 描述 release 該項目將以release模式構建。 如果還指定了debug,則最後那個生效。 debug 該項目將以debug模式構建。 debug_and_release 該項目將同時構建debug和release模式。 debug_and_release_target 默認情況下設置此選項。 如果還設置了debug_and_release,則debug和release版本最終將放置在單獨的debug和release目錄中。 build_all 如果指定了debug_and_release,則默認情況下項目同時構建debug和release模式。 autogen_precompile_source 自動生成一個.cpp文件,其中包含.pro文件中指定的預編譯頭文件。 ordered 當TEMPLATE為subdirs時,此選項指定應按給出的順序處理列出的目錄。
注意:不建議使用此選項。 如SUBDIRS變數文檔中所述指定依賴項。precompile_header 使能支援在項目中使用precompiled headers。 precompile_header_c (MSVC only) 使能支援在C文件中使用precompiled headers。 warn_on 編譯器應儘可能多的輸出警告。 如果還指定了warn_off,則最後那個生效。 warn_off 編譯器應儘可能少的輸出警告。 exceptions 使能異常支援。默認設置該選項。 exceptions_off 禁用異常支援。 rtti 使能RTTI支援。默認情況下,使用編譯器默認值。 rtti_off 禁用RTTI支援。默認情況下,使用編譯器默認值。 stl 使能STL支援。默認情況下,使用編譯器默認值。 stl_off 禁用STL支援。默認情況下,使用編譯器默認值。 thread 使能Thread支援。當CONFIG包含qt(默認設置)時,將使能此功能。 c99 使能C99支援。 如果編譯器不支援C99或無法選擇C標準,則此選項無效。 默認情況下,使用編譯器默認值。 c11 使能C11支援。 如果編譯器不支援C11或無法選擇C標準,則此選項無效。 默認情況下,使用編譯器默認值。 strict_c 禁用對C編譯器擴展的支援。 默認情況下,它們是使能的。 c++11 使能C++11支援。 如果編譯器不支援C++11或無法選擇C++標準,則此選項無效。 默認情況下,使用編譯器默認值。 c++14 使能C++14支援。 如果編譯器不支援C++14或無法選擇C++標準,則此選項無效。 默認情況下,使用編譯器默認值。 c++1z 使能C++17支援。 如果編譯器不支援C++17或無法選擇C++標準,則此選項無效。 默認情況下,使用編譯器默認值。 c++17 同c++1z c++2a 使能C++2a支援。 如果編譯器不支援C++2a或無法選擇C++標準,則此選項無效。 默認情況下,使用編譯器默認值。 c++latest 如果編譯器支援,使能最新C++語言標準的支援。 默認情況下,此選項是禁用的。 strict_c++ 禁用對C++編譯器擴展的支援。 默認情況下,它們是使能的。 depend_includepath 使能將INCLUDEPATH的值附加到DEPENDPATH。 默認設置此選項。 lrelease 對TRANSLATIONS 和EXTRA_TRANSLATIONS中列出的所有文件運行lrelease。 如果未設置embed_translations,則將生成的.qm文件安裝到QM_FILES_INSTALL_PATH中。 使用QMAKE_LRELEASE_FLAGS向lrelease調用添加參數選項。 默認情況下未設置此選項。 embed_translations 將lrelease生成的翻譯內容嵌入QM_FILES_RESOURCE_PREFIX下的可執行文件中。 也需要同時設置lrelease。 默認情況下未設置此選項。 create_libtool 為當前構建的庫創建一個libtool.la文件。 create_pc 為當前構建的庫創建一個pkg-config .pc文件。 no_batch 僅限NMake:關閉NMake批處理規則或推斷規則的生成。 skip_target_version_ext 在Windows上禁止附加自動版本號到DLL文件名。 suppress_vcproj_warnings 禁止VS項目生成器的警告。 windeployqt 鏈接後自動調用windeployqt,並將輸出添加為部署項。 dont_recurse 禁止對當前子項目的qmake遞歸。 no_include_pwd 不要將當前目錄添加到INCLUDEPATHS。 當您使用debug_and_release選項(在Windows下是默認設置)時,項目將被處理三次:一次生成元Makefile,再兩次生成Makefile.Debug和Makefile.Release。
在後面的過程中,將build_pass和相應的debug或release選項附加到CONFIG。 這樣就可以執行特定構建任務。 例如:
build_pass:CONFIG(debug, debug|release) { unix: TARGET = $$join(TARGET,,,_debug) else: TARGET = $$join(TARGET,,,d) }
作為手動編寫構建類型條件的替代方法,除了常規QMAKE_LFLAGS外,某些變數還提供特定構建變數,例如 QMAKE_LFLAGS_RELEASE。 這些應在可用時使用。
元Makefile通過debug和release目標進行子構建調用,並可通過all目標進行聯合構建調用。 使用build_all選項時,聯合構建為默認設置。 否則,CONFIG中最後指定的來自集合(debug,release)的選項會變為默認選項。 在這種情況下,您可以顯式調用all以一次構建兩個配置:
make all
注意:在生成Visual Studio和Xcode項目時,詳細資訊略有不同。
鏈接庫時,qmake依賴基礎平台,來了解該庫應該鏈接的其他庫。 但是,如果是靜態鏈接,qmake不會獲取此資訊,除非使用以下CONFIG選項:
選項 描述 create_prl 此選項使qmake可以跟蹤這些依賴性。 使能此選項後,qmake將創建擴展名為.prl的文件,該文件將保存有關庫的元資訊(有關更多資訊,請參見Library Dependencies)。 link_prl 使能此選項後,qmake將處理該應用程式鏈接的所有庫並找到其元資訊(有關更多資訊,請參見Library Dependencies)。 no_install_prl 此選項禁用創建.prl文件的安裝規則的生成。 注意:構建靜態庫時,需要create_prl選項,而使用靜態庫時,則需要link_prl選項。
以下選項定義應用程式或庫的類型:
選項 描述 qt 目標是Qt應用程式或庫,並且需要Qt庫和頭文件。 Qt庫正確的包含和庫路徑將自動添加到項目中。 這是默認定義的,可以使用l{#qt}{QT}變數進行微調。 x11 目標是X11應用程式或庫。 正確的包含路徑和庫將自動添加到項目中。 testcase 目標是一個自動測試。 一個檢查目標將被添加到生成的Makefile中,以運行測試。 僅在生成Makefile時相關。 insignificant_test 自動測試的退出程式碼將被忽略。 僅當還設置了testcase時才相關。 windows 目標是Win32窗口應用程式(僅適用於TEMPLATE為app)。 正確的包含路徑,編譯器標誌和庫將自動添加到項目中。 console 目標是Win32控制台應用程式(僅適用於TEMPLATE為app)。 正確的包含路徑,編譯器標誌和庫將自動添加到項目中。考慮將選項cmdline用於跨平台應用程式。 cmdline 目標是跨平台的命令行應用程式。 在Windows上,這意味著CONFIG += console。 在macOS上,這意味著CONFIG -= app_bundle。 shared 目標是共享對象/DLL。 正確的包含路徑,編譯器標誌和庫將自動添加到項目中。 請注意,dll也可以在所有平台上使用。 將創建帶有目標平台的適當後綴(.dll或.so)的共享庫文件。 dll 同上。 static 目標是靜態庫(僅lib)。 正確的編譯器標誌將自動添加到項目中。 staticlib 同上。 plugin 目標是插件(僅lib)。 這也會使能dll。 designer 目標是Qt Designer的插件。 no_lflags_merge 確保存儲在LIBS變數中的庫列表在使用前不減少為值唯一(去除了重複的)列表。 這些選項僅在Windows上定義特定功能:
選項 描述 flat 使用vcapp模板時,這會將所有源文件置於源組中,並將頭文件置於頭組中,而不管它們位於哪個目錄中。關閉此選項,將根據文件所在目錄歸類。 默認情況下是打開的。 embed_manifest_dll 將清單文件嵌入到作為庫項目一部分的DLL中。 embed_manifest_exe 將清單文件嵌入到作為應用程式項目一部分的EXE中。 有關嵌入清單文件的選項的更多資訊,請參見Platform Notes。
以下選項僅在macOS上有效:
選項 描述 app_bundle 將可執行文件放入捆綁包(這是默認設置)。 lib_bundle 將庫放入庫包(這是默認設置)。 plugin_bundle 將插件放入插件包中。 Xcode項目生成器不支援此值。 捆綁軟體的構建過程也受QMAKE_BUNDLE_DATA變數內容的影響。
以下選項僅在Linux / Unix平台上有效:
選項 描述 largefile 支援大文件的包含 separate_debug_info 把庫的調試資訊放到單獨的文件中 解析作用域時,將檢查CONFIG變數。 您可以為該變數分配任何內容。
例如:
CONFIG += console newstuff ... newstuff { SOURCES += new.cpp HEADERS += new.h }
自定義函數
接下來是
defineReplace(qtLibraryTargetName) { unset(LIBRARY_NAME) LIBRARY_NAME = $$1 CONFIG(debug, debug|release) { !debug_and_release|build_pass { mac:RET = $$member(LIBRARY_NAME, 0)_debug else:win32:RET = $$member(LIBRARY_NAME, 0)d } } isEmpty(RET):RET = $$LIBRARY_NAME return($$RET) } defineReplace(qtLibraryName) { RET = $$qtLibraryTargetName($$1) win32 { VERSION_LIST = $$split(QTCREATOR_VERSION, .) RET = $$RET$$first(VERSION_LIST) } return($$RET) } defineTest(minQtVersion) { maj = $$1 min = $$2 patch = $$3 isEqual(QT_MAJOR_VERSION, $$maj) { isEqual(QT_MINOR_VERSION, $$min) { isEqual(QT_PATCH_VERSION, $$patch) { return(true) } greaterThan(QT_PATCH_VERSION, $$patch) { return(true) } } greaterThan(QT_MINOR_VERSION, $$min) { return(true) } } greaterThan(QT_MAJOR_VERSION, $$maj) { return(true) } return(false) } # For use in custom compilers which just copy files defineReplace(stripSrcDir) { return($$relative_path($$absolute_path($$1, $$OUT_PWD), $$_PRO_FILE_PWD_)) }
Replace Functions
qmake提供了一些內置函數,以允許處理變數的內容。 這些函數處理提供給它們的參數,並返回一個值或值列表。
要將結果分配給變數,可以將$$運算符與此類函數一起使用
,就像將一個變數的內容分配給另一個一樣:
HEADERS = model.h HEADERS += $$OTHER_HEADERS HEADERS = $$unique(HEADERS)
此類函數應在賦值的右側(即,作為操作數)。
您可以定義自己的函數來處理變數的內容,如下所示:
defineReplace(functionName){ #function code }
以下示例函數將變數名作為唯一參數,使用內置函數eval()從變數中提取值列表,並編譯文件列表:
defineReplace(headersAndSources) { variable = $$1 names = $$eval($$variable) headers = sources = for(name, names) { header = $${name}.h exists($$header) { headers += $$header } source = $${name}.cpp exists($$source) { sources += $$source } } return($$headers $$sources) }
參數$$1
Test Functions
qmake提供了內置函數,可以在編寫作用域時用作條件。 這些函數不返回值,而是指示成功或失敗:
count(options, 2) { message(Both release and debug specified.) }
此類函數應僅在條件表達式中使用。
可以定義自己的函數以提供作用域條件。 以下示例測試列表中的每個文件是否存在,如果全部存在,則返回true,否則返回false:
defineTest(allFiles) { files = $$ARGS for(file, files) { !exists($$file) { return(false) } } return(true) }
參數列表$$ARGS
_PRO_FILE_PWD_
包含正在使用的項目文件的目錄的路徑。(即使該變數出現在 .pri 文件,也是指包含該 .pri 文件的 .pro 文件所在目錄的路徑。)
例如,以下行導致包含項目文件的目錄的位置寫入控制台:
message($$_PRO_FILE_PWD_)
注意:請勿嘗試覆蓋此變數的值。
_PRO_FILE_
包含正在使用的項目文件的路徑。
例如,以下行導致項目文件的位置寫入控制台:
message($$_PRO_FILE_)
注意:請勿嘗試覆蓋此變數的值。
現在我們來分析pri中定義的三個函數。
因為這兩個函數在 Qt Creator 中使用了多次,並且完全可以拷貝複製到其它項目繼續使用。
自定義替換函數qtLibraryTargetName
。
- 取消LIBRARY_NAME的定義,設置LIBRARY_NAME為
$$1
,即函數的第一個參數。 - CONFIG測試函數判斷debug或release模式。
- 如果是debug模式,再次進行判斷。
- 如果CONFIG沒有設置debug_and_release或者是構建過程build_pass,則設置RET變數。對於 mac,LIBRARY_NAME值後面添加_debug賦給RET。對於win,LIBRARY_NAME值後面添加d賦給RET。
- 如果RET為空,則把LIBRARY_NAME值賦給RET。
- 返回RET。
簡單來說,該函數實現功能:在debug環境下,在庫名後面添加_debug或_d尾綴,來跟release模式進行區分。當然,也有其他方式來實現上述功能,譬如使用join()函數,見CONFIG小節。
自定義替換函數qtLibraryName
。
- 使用qtLibraryTargetName()函數,對輸入的第一個參數進行替換,並賦值給RET。
- 如果 是win32 系統。
- 使用split()函數,將前面定義的QTCREATOR_VERSION(即4.6.2),使用’.’進行分隔得到列表,賦值給VERSION_LIST
- 使用first()函數,獲取VERSION_LIST的第一個元素(即4),與RET拼接,並賦值給RET。
- 返回RET。
簡單來說,該函數實現功能:為了在win32系統中避免出現 dll hell,在win32系統下,在庫名後面添加主版本號。
自定義測試函數minQtVersion
。
- 獲取三個參數(即,主/次/修補程式),並賦值給maj,min,patch。
- maj <= QT_MAJOR_VERSION,min <= QT_MINOR_VERSION和patch <= QT_PATCH_VERSION,則返回true。其他返回false。
簡單來說,該函數實現功能:函數參數的版本號小於等於當前Qt的版本號。
自定義替換函數stripSrcDir
。
- 獲取絕對路徑,absolute_path返回$$OUT_PWD/$$1。
- 獲取相對路徑,步驟1中獲取的絕對路徑相對與$$_PRO_FILE_PWD_的相對路徑。
簡單來說,該函數實現功能(見自帶的注釋):用於自定義編譯器拷貝文件。
設置macOS最小版本
接下來是:
darwin:!minQtVersion(5, 7, 0) { # Qt 5.6 still sets deployment target 10.7, which does not work # with all C++11/14 features (e.g. std::future) QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.8 }
如果是基於Darwin作業系統的,並且Qt 的版本低於5.7.0
時,設置應用程式支援的macOs最小版本。可以從注釋看出,10.7不支援C++11/14特性。
設置QTEST模組
接下來是
QTC_BUILD_TESTS = $$(QTC_BUILD_TESTS) !isEmpty(QTC_BUILD_TESTS):TEST = $$QTC_BUILD_TESTS !isEmpty(BUILD_TESTS):TEST = 1 isEmpty(TEST):CONFIG(debug, debug|release) { !debug_and_release|build_pass { TEST = 1 } } isEmpty(IDE_LIBRARY_BASENAME) { IDE_LIBRARY_BASENAME = lib } equals(TEST, 1) { QT +=testlib DEFINES += WITH_TESTS }
- 如果設置了QTC_BUILD_TESTS,則賦值給TEST。
- 如果設置了BUILD_TESTS,則給TEST賦值1。
- 如果TEST沒有值,且為debug模式,並且沒有設置debug_and_release,則在構建過程中,設置TEST為1。
- 如果IDE_LIBRARY_BASENAME為空,則為庫賦值基礎名為lib。
- 如果TEST等於1,則添加QTEST模組功能。
設置源目錄和構建目錄
接下來是
IDE_SOURCE_TREE = $$PWD isEmpty(IDE_BUILD_TREE) { sub_dir = $$_PRO_FILE_PWD_ sub_dir ~= s,^$$re_escape($$PWD),, IDE_BUILD_TREE = $$clean_path($$OUT_PWD) IDE_BUILD_TREE ~= s,$$re_escape($$sub_dir)$,, }
re_escape(string)
對每個string中的特殊正則表達式字元,使用反斜杠轉義,返迴轉義後的字元串。 該函數是QRegExp::escape的包裝。
例如:
s1 = QRegExp::escape("bingo"); // s1 == "bingo" s2 = QRegExp::escape("f(x)"); // s2 == "f\(x\)"
clean_path(path)
處理path,對目錄分隔符進行規範化(轉換為"/"),刪除了多餘的目錄分隔符,並且解析"."和".."(儘可能)。 該函數是QDir::cleanPath的包裝。
另請閱absolute_path(), relative_path(), shell_path(), system_path().
我們在程式碼後面插樁輸出語句
build_pass:message($$PWD) # 當前pri文件所在目錄 build_pass:message($$OUT_PWD) # 生成makefile所在目錄 build_pass:message($$_PRO_FILE_) # 包含當前pri的pro所在路徑 build_pass:message($$_PRO_FILE_PWD_) # 包含當前pri的pro所在目錄
現在,我們來看一下部分輸出。
Project MESSAGE: F:/plugin/qt_creator/qt-creator-opensource-src-4.6.2 Project MESSAGE: F:/plugin/qt_creator/build-qtcreator-Desktop_Qt_5_11_1_MinGW_32bit-Debug-splt-debug-info/bin Project MESSAGE: F:/plugin/qt_creator/qt-creator-opensource-src-4.6.2/bin/bin.pro Project MESSAGE: F:/plugin/qt_creator/qt-creator-opensource-src-4.6.2/bin Project MESSAGE: F:/plugin/qt_creator/qt-creator-opensource-src-4.6.2 Project MESSAGE: F:/plugin/qt_creator/build-qtcreator-Desktop_Qt_5_11_1_MinGW_32bit-Debug-splt-debug-info/src/app Project MESSAGE: F:/plugin/qt_creator/qt-creator-opensource-src-4.6.2/src/app/app.pro Project MESSAGE: F:/plugin/qt_creator/qt-creator-opensource-src-4.6.2/src/app
我們可以發現
- PWD沒有發生變化。
- 對比OUT_PWD和_PRO_FILE_PWD_,輸出目錄和源目錄的子文件夾組織架構一樣。
下面我們分析pri中的語句
- 設置源目錄IDE_SOURCE_TREE。
-
如果構建目錄IDE_BUILD_TREE為空。
- 設置sub_dir,並進行替換。可以認為是從_PRO_FILE_PWD_減去PWD,剩下子文件夾相對路徑,如bin/和app/。
- 初始化IDE_BUILD_TREE,並進行替換。可以認為是從OUT_PWD減去相對路徑,剩下相同的根目錄。
大家可以用我們上面的message輸出結果來簡單的計算下即可。
IDE_SOURCE_TREE為F:/plugin/qt_creator/qt-creator-opensource-src-4.6.2,
IDE_BUILD_TREE為F:/plugin/qt_creator/build-qtcreator-Desktop_Qt_5_11_1_MinGW_32bit-Debug-splt-debug-info。
設置IDE和INSTALLS相關路徑
接下來是
IDE_APP_PATH = $$IDE_BUILD_TREE/bin osx { IDE_APP_TARGET = "$$IDE_DISPLAY_NAME" # check if IDE_BUILD_TREE is actually an existing Qt Creator.app, # for building against a binary package exists($$IDE_BUILD_TREE/Contents/MacOS/$$IDE_APP_TARGET): IDE_APP_BUNDLE = $$IDE_BUILD_TREE else: IDE_APP_BUNDLE = $$IDE_APP_PATH/$${IDE_APP_TARGET}.app # set output path if not set manually isEmpty(IDE_OUTPUT_PATH): IDE_OUTPUT_PATH = $$IDE_APP_BUNDLE/Contents IDE_LIBRARY_PATH = $$IDE_OUTPUT_PATH/Frameworks IDE_PLUGIN_PATH = $$IDE_OUTPUT_PATH/PlugIns IDE_LIBEXEC_PATH = $$IDE_OUTPUT_PATH/Resources IDE_DATA_PATH = $$IDE_OUTPUT_PATH/Resources IDE_DOC_PATH = $$IDE_DATA_PATH/doc IDE_BIN_PATH = $$IDE_OUTPUT_PATH/MacOS copydata = 1 LINK_LIBRARY_PATH = $$IDE_APP_BUNDLE/Contents/Frameworks LINK_PLUGIN_PATH = $$IDE_APP_BUNDLE/Contents/PlugIns INSTALL_LIBRARY_PATH = $$QTC_PREFIX/$${IDE_APP_TARGET}.app/Contents/Frameworks INSTALL_PLUGIN_PATH = $$QTC_PREFIX/$${IDE_APP_TARGET}.app/Contents/PlugIns INSTALL_LIBEXEC_PATH = $$QTC_PREFIX/$${IDE_APP_TARGET}.app/Contents/Resources INSTALL_DATA_PATH = $$QTC_PREFIX/$${IDE_APP_TARGET}.app/Contents/Resources INSTALL_DOC_PATH = $$INSTALL_DATA_PATH/doc INSTALL_BIN_PATH = $$QTC_PREFIX/$${IDE_APP_TARGET}.app/Contents/MacOS INSTALL_APP_PATH = $$QTC_PREFIX/ } else { contains(TEMPLATE, vc.*):vcproj = 1 IDE_APP_TARGET = $$IDE_ID # target output path if not set manually isEmpty(IDE_OUTPUT_PATH): IDE_OUTPUT_PATH = $$IDE_BUILD_TREE IDE_LIBRARY_PATH = $$IDE_OUTPUT_PATH/$$IDE_LIBRARY_BASENAME/qtcreator IDE_PLUGIN_PATH = $$IDE_LIBRARY_PATH/plugins IDE_DATA_PATH = $$IDE_OUTPUT_PATH/share/qtcreator IDE_DOC_PATH = $$IDE_OUTPUT_PATH/share/doc/qtcreator IDE_BIN_PATH = $$IDE_OUTPUT_PATH/bin win32: IDE_LIBEXEC_PATH = $$IDE_OUTPUT_PATH/bin else: IDE_LIBEXEC_PATH = $$IDE_OUTPUT_PATH/libexec/qtcreator !isEqual(IDE_SOURCE_TREE, $$IDE_OUTPUT_PATH):copydata = 1 LINK_LIBRARY_PATH = $$IDE_BUILD_TREE/$$IDE_LIBRARY_BASENAME/qtcreator LINK_PLUGIN_PATH = $$LINK_LIBRARY_PATH/plugins INSTALL_LIBRARY_PATH = $$QTC_PREFIX/$$IDE_LIBRARY_BASENAME/qtcreator INSTALL_PLUGIN_PATH = $$INSTALL_LIBRARY_PATH/plugins win32: INSTALL_LIBEXEC_PATH = $$QTC_PREFIX/bin else: INSTALL_LIBEXEC_PATH = $$QTC_PREFIX/libexec/qtcreator INSTALL_DATA_PATH = $$QTC_PREFIX/share/qtcreator INSTALL_DOC_PATH = $$QTC_PREFIX/share/doc/qtcreator INSTALL_BIN_PATH = $$QTC_PREFIX/bin INSTALL_APP_PATH = $$QTC_PREFIX/bin }
我們可以發現上面的內容大部分是基於IDE_BUILD_TREE和QTC_PREFIX的。
程式碼首先設置了可執行程式的目錄。
接下來,我們重點分析else分支的內容。
-
如果TEMPLATE包含vc.*,其實就是vcapp或vclib,設置vcproj為1,表示是vs工程。
-
設置可執行程式文件名為IDE_ID(默認為qtcreator)。
-
設置輸出路徑IDE_OUTPUT_PATH默認為IDE_BUILD_TREE。
-
設置IDE相關子文件夾路徑,可以發現都是相對於IDE_OUTPUT_PATH的。
-
如果輸出路徑不是源目錄,則設置copydata為1,表示需要拷貝數據。
-
考慮到IDE_BUILD_TREE與IDE_OUTPUT_PATH可能不一樣,設置IDE庫和插件的鏈接路徑。
-
設置INSTALLS用的相關子文件夾路徑。
設置字元串宏
接下來是
RELATIVE_PLUGIN_PATH = $$relative_path($$IDE_PLUGIN_PATH, $$IDE_BIN_PATH) RELATIVE_LIBEXEC_PATH = $$relative_path($$IDE_LIBEXEC_PATH, $$IDE_BIN_PATH) RELATIVE_DATA_PATH = $$relative_path($$IDE_DATA_PATH, $$IDE_BIN_PATH) RELATIVE_DOC_PATH = $$relative_path($$IDE_DOC_PATH, $$IDE_BIN_PATH) DEFINES += $$shell_quote(RELATIVE_PLUGIN_PATH="$$RELATIVE_PLUGIN_PATH") DEFINES += $$shell_quote(RELATIVE_LIBEXEC_PATH="$$RELATIVE_LIBEXEC_PATH") DEFINES += $$shell_quote(RELATIVE_DATA_PATH="$$RELATIVE_DATA_PATH") DEFINES += $$shell_quote(RELATIVE_DOC_PATH="$$RELATIVE_DOC_PATH")
shell_quote
在qmake中的介紹很簡單:為shell對arg加引號,當構建構建項目時。
在linux man page中的介紹:可讓您通過shell傳遞任意字元串,shell不會更改它們。 這使您可以安全地處理帶有
嵌入式空格
或shell globbing字元的命令或文件。
qmake定義字元串宏
有時候,我們想定義字元串宏,並在源程式碼中進行使用。假設你想在qmake中定義字元串宏,這裡有三種途徑
我們先來看一下qmake編譯得到的Makefile.Debug,符合makefile語法的形式
現在我們來介紹下DEFINES中的含義:
NAME1中第一個"對,告訴qmake引導裡面的是字元串。裡面的"對,是對引號的轉義,在makefile中變為"。再裡面的\對,也是轉義,在makefile中變為。在裡面的"同樣,最終變為"。最終得到我們想要的字元串。
NAME2使用shell_quote()函數,該函數對參數加引號。
NAME0對比NAME1,少了最外面的"對,這導致NAME0隻能定義沒有空格的字元串。如果存在空格,這會導致內容發生變化,中間多了個-D。
qmake: DEFINES += NAME0="\"app1 .0\"" makefile: -DNAME0=""app1 -D.0""
此外,對於沒有空格的字元串宏定義,我們甚至可以不需要最外層的引號轉義。
qmake: DEFINES += NAME0=\"app1\" makefile: -DNAME0="app1"
分析程式碼:
- 設置了PLUGIN,LIBEXEC,DATA和DOC相對於BIN的相對路徑,譬如PLUGIN的為../lib/qtcreator/plugins。
- 使步驟1中的變數稱為字元串,添加到DEFINES中,變為宏。
設置INCLUDEPATH
接下來是
INCLUDEPATH += $$IDE_BUILD_TREE/src # for <app/app_version.h> in case of actual build directory $$IDE_SOURCE_TREE/src # for <app/app_version.h> in case of binary package with dev package $$IDE_SOURCE_TREE/src/libs $$IDE_SOURCE_TREE/tools win32:exists($$IDE_SOURCE_TREE/lib/qtcreator) { # for .lib in case of binary package with dev package LIBS *= -L$$IDE_SOURCE_TREE/lib/qtcreator LIBS *= -L$$IDE_SOURCE_TREE/lib/qtcreator/plugins } QTC_PLUGIN_DIRS_FROM_ENVIRONMENT = $$(QTC_PLUGIN_DIRS) QTC_PLUGIN_DIRS += $$split(QTC_PLUGIN_DIRS_FROM_ENVIRONMENT, $$QMAKE_DIRLIST_SEP) QTC_PLUGIN_DIRS += $$IDE_SOURCE_TREE/src/plugins for(dir, QTC_PLUGIN_DIRS) { INCLUDEPATH += $$dir } QTC_LIB_DIRS_FROM_ENVIRONMENT = $$(QTC_LIB_DIRS) QTC_LIB_DIRS += $$split(QTC_LIB_DIRS_FROM_ENVIRONMENT, $$QMAKE_DIRLIST_SEP) QTC_LIB_DIRS += $$IDE_SOURCE_TREE/src/libs for(dir, QTC_LIB_DIRS) { INCLUDEPATH += $$dir } CONFIG += depend_includepath no_include_pwd
INCLUDEPATH
指定編譯項目時應搜索的#include目錄。
例如:
INCLUDEPATH = c:/msdev/include d:/stl/include
要指定包含空格的路徑,請使用Whitespace中所述的技術對路徑添加引號。
win32:INCLUDEPATH += "C:/mylibs/extra headers" unix:INCLUDEPATH += "/home/user/extra headers"
Whitespace
通常,空格在變數賦值是分隔值。 要指定包含空格的值,必須將值用雙引號引起來:
DEST = "Program Files"
引號引起來的文本在變數所保存的值列表中被視為單個條目。使用類似的方法可處理包含空格的路徑,尤其是在為Windows平台定義INCLUDEPATH 和LIBS變數時:
win32:INCLUDEPATH += "C:/mylibs/extra headers" unix:INCLUDEPATH += "/home/user/extra headers"
<app/app_version.h>
我們在源碼中搜索app_version.h,可以在src/app/app.pro中發現定義:
# an hidden functionality in qmake that take a file with '.in' suffix # and creates a copy in the build directory without the suffix in which # variables have been expanded QMAKE_SUBSTITUTES += $$PWD/app_version.h.in
注釋很明白,qmake中的隱藏功能,對於後綴為".in"的文件,在構建目錄中創建一個沒有後綴的副本,並對其中變數進行擴展。
那麼app_version.h.in就變為了app_version.h。現在我們簡單看下該文件
... const char IDE_DISPLAY_NAME[] = "$${IDE_DISPLAY_NAME}"; const char IDE_ID[] = "$${IDE_ID}"; const char IDE_CASED_ID[] = "$${IDE_CASED_ID}"; #define IDE_VERSION $${QTCREATOR_VERSION} ...
上面截取的內容,就是對qtcreator.pri中定義的變數,進行了展開,賦值給同名的char []變數。
其實app_version.h.in中包含了Qt Creator的相關IDE版本資訊。
程式碼首先添加#include搜索路徑。特別說明<app/app_version.h>,為了引用構建目錄中創建的app_version.h,需要在INCLUDEPATH中添加$$IDE_BUILD_TREE/src。
展開的app_version.h的內容如下
接下來的判斷win32系統下,是否在源目錄中存在lib/qtcreator子目錄,存在就過濾重複的鏈接路徑。
接下來是對插件文件夾QTC_PLUGIN_DIRS按照分隔符QMAKE_DIRLIST_SEP進行分隔,得到插件文件夾列表。上述可能為空,所以又添加了源目錄下的src/plugins子目錄。現在QTC_PLUGIN_DIRS至少為src/plugins。
for語句和c++11中的新增的for語句很像,不再展開。意思也很明確,遍歷插件文件夾列表中的每個條目,添加進#include搜索路徑。
對於QTC_LIB_DIRS,同QTC_PLUGIN_DIRS。
CONFIG的depend_includepath和no_include_pwd含義,見上面的CONFIG子小節。
這麼操作後,對於每個包含qtcreator.pri的子項目,可能很方便的統一添加相同的plugins和libs中的頭文件。
設值庫鏈接路徑和編譯選項
接下來是
LIBS *= -L$$LINK_LIBRARY_PATH # Qt Creator libraries exists($$IDE_LIBRARY_PATH): LIBS *= -L$$IDE_LIBRARY_PATH # library path from output path !isEmpty(vcproj) { DEFINES += IDE_LIBRARY_BASENAME="$$IDE_LIBRARY_BASENAME" } else { DEFINES += IDE_LIBRARY_BASENAME=\"$$IDE_LIBRARY_BASENAME\" } DEFINES += QT_CREATOR QT_NO_CAST_TO_ASCII QT_RESTRICTED_CAST_FROM_ASCII QT_DISABLE_DEPRECATED_BEFORE=0x050600 QT_USE_FAST_OPERATOR_PLUS QT_USE_FAST_CONCATENATION unix { CONFIG(debug, debug|release):OBJECTS_DIR = $${OUT_PWD}/.obj/debug-shared CONFIG(release, debug|release):OBJECTS_DIR = $${OUT_PWD}/.obj/release-shared CONFIG(debug, debug|release):MOC_DIR = $${OUT_PWD}/.moc/debug-shared CONFIG(release, debug|release):MOC_DIR = $${OUT_PWD}/.moc/release-shared RCC_DIR = $${OUT_PWD}/.rcc UI_DIR = $${OUT_PWD}/.uic } msvc { #Don't warn about sprintf, fopen etc being 'unsafe' DEFINES += _CRT_SECURE_NO_WARNINGS QMAKE_CXXFLAGS_WARN_ON *= -w44996 # Speed up startup time when debugging with cdb QMAKE_LFLAGS_DEBUG += /INCREMENTAL:NO } qt { contains(QT, core): QT += concurrent contains(QT, gui): QT += widgets } QBSFILE = $$replace(_PRO_FILE_, \.pro$, .qbs) exists($$QBSFILE):DISTFILES += $$QBSFILE !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 }
我們依次分析:
-
LIBS中添加qt creator的lib庫鏈接路徑,並去除重複項。
-
DEFINES添加字元串宏。看樣子vcproj下宏的定義不需要對引號進行轉義哈。
-
DEFINES添加其他相關宏。看樣子就是用來設置編譯選項。
-
unix系統下,首先分別對debug和release模式設置對應的obj文件夾。然後設置qt資源編譯輸出文件RCC和用戶介面文件UI的文件夾。
-
msvc平台下,設置相關編譯指令。
-
目標是Qt應用程式或庫,並且包含core核心模組或gui影像用戶介面模組,則添加concurrent並發模組和widget窗口模組。
-
對於每個pro文件,替換擴展名為qbs,如果同一目錄下存在該qbs文件,則添加到dist目標文件列表中。
-
最後,如果插件依賴QTC_PLUGIN_DEPENDS有值,則LIBS添加插件依賴路徑,並去除重複項。
這麼操作後,對於每個包含qtcreator.pri的子項目,可能很方便的統一添加相同的庫鏈接路徑和編譯選項。
解決插件和庫依賴
接下來是
# 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) } # recursively resolve library deps done_libs = for(ever) { isEmpty(QTC_LIB_DEPENDS): break() done_libs += $$QTC_LIB_DEPENDS for(dep, QTC_LIB_DEPENDS) { dependencies_file = for(dir, QTC_LIB_DIRS) { exists($$dir/$$dep/$${dep}_dependencies.pri) { dependencies_file = $$dir/$$dep/$${dep}_dependencies.pri break() } } isEmpty(dependencies_file): error("Library dependency $$dep not found") include($$dependencies_file) LIBS += -l$$qtLibraryName($$QTC_LIB_NAME) } QTC_LIB_DEPENDS = $$unique(QTC_LIB_DEPENDS) QTC_LIB_DEPENDS -= $$unique(done_libs) }
注釋說的很明白,一個遞歸解決插件依賴,一個遞歸解決庫依賴。
首先我們來看下依賴文件示例,源目錄srcpluginscppeditorcppeditor_dependencies.pri。
QTC_PLUGIN_NAME = CppEditor QTC_LIB_DEPENDS += extensionsystem utils cplusplus QTC_PLUGIN_DEPENDS += texteditor coreplugin cpptools projectexplorer QTC_TEST_DEPENDS += qmakeprojectmanager
內容顯而易見,首先指定插件名稱(同文件夾名,用於查找),然後在依賴項指定其他插件。
首先,我們分析插件依賴。for(ever)顧名思義是死循環,只能通過break()或者error()退出。
-
如果QTC_PLUGIN_DEPENDS為空,則退出循環。
-
遍歷QTC_PLUGIN_DEPENDS中的每一個依賴dep,
-
對於dep,遍歷QTC_PLUGIN_DIRS中的每一個插件文件夾dir,譬如源目錄中的src/plugins。如果{dep}子文件夾中存在{dep}_dependencies.pri文件,則找到需要的依賴。
-
如果遍歷完畢都沒有找到,則報錯退出。
-
如果找到了,則include載入之。並從依賴文件中獲取QTC_PLUGIN_NAME,添加到LIBS用於鏈接。
-
- 由於include時,會加入該插件的依賴插件,可能重複。所以需要去除重複項。
- 去除已經解決依賴的插件。
-
回到步驟1,重複。直到每一個依賴都被解決。
這段程式碼,允許用戶在編譯時直接通過QTC_PLUGIN_DEPENDS指定插件依賴。
庫依賴函數同上。
原創造福大家,共享改變世界
獻出一片愛心,溫暖作者心靈