­

qt creator源碼全方面分析(3-2)

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 TRANSLATIONSEXTRA_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

  1. 取消LIBRARY_NAME的定義,設置LIBRARY_NAME為 $$1,即函數的第一個參數。
  2. CONFIG測試函數判斷debug或release模式。
    1. 如果是debug模式,再次進行判斷。
    2. 如果CONFIG沒有設置debug_and_release或者是構建過程build_pass,則設置RET變數。對於 mac,LIBRARY_NAME值後面添加_debug賦給RET。對於win,LIBRARY_NAME值後面添加d賦給RET。
  3. 如果RET為空,則把LIBRARY_NAME值賦給RET。
  4. 返回RET。

簡單來說,該函數實現功能:在debug環境下,在庫名後面添加_debug或_d尾綴,來跟release模式進行區分。當然,也有其他方式來實現上述功能,譬如使用join()函數,見CONFIG小節。

自定義替換函數qtLibraryName

  1. 使用qtLibraryTargetName()函數,對輸入的第一個參數進行替換,並賦值給RET。
  2. 如果 是win32 系統。
  3. 使用split()函數,將前面定義的QTCREATOR_VERSION(即4.6.2),使用’.’進行分隔得到列表,賦值給VERSION_LIST
  4. 使用first()函數,獲取VERSION_LIST的第一個元素(即4),與RET拼接,並賦值給RET。
  5. 返回RET。

簡單來說,該函數實現功能:為了在win32系統中避免出現 dll hell,在win32系統下,在庫名後面添加主版本號。

自定義測試函數minQtVersion

  1. 獲取三個參數(即,主/次/修補程式),並賦值給maj,min,patch。
  2. maj <= QT_MAJOR_VERSION,min <= QT_MINOR_VERSION和patch <= QT_PATCH_VERSION,則返回true。其他返回false。

簡單來說,該函數實現功能:函數參數的版本號小於等於當前Qt的版本號。

自定義替換函數stripSrcDir

  1. 獲取絕對路徑,absolute_path返回$$OUT_PWD/$$1。
  2. 獲取相對路徑,步驟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  }
  1. 如果設置了QTC_BUILD_TESTS,則賦值給TEST。
  2. 如果設置了BUILD_TESTS,則給TEST賦值1。
  3. 如果TEST沒有值,且為debug模式,並且沒有設置debug_and_release,則在構建過程中,設置TEST為1。
  4. 如果IDE_LIBRARY_BASENAME為空,則為庫賦值基礎名為lib。
  5. 如果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

我們可以發現

  1. PWD沒有發生變化。
  2. 對比OUT_PWD和_PRO_FILE_PWD_,輸出目錄和源目錄的子文件夾組織架構一樣。

下面我們分析pri中的語句

  1. 設置源目錄IDE_SOURCE_TREE。
  2. 如果構建目錄IDE_BUILD_TREE為空。

    1. 設置sub_dir,並進行替換。可以認為是從_PRO_FILE_PWD_減去PWD,剩下子文件夾相對路徑,如bin/和app/。
    2. 初始化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分支的內容。

  1. 如果TEMPLATE包含vc.*,其實就是vcapp或vclib,設置vcproj為1,表示是vs工程。

  2. 設置可執行程式文件名為IDE_ID(默認為qtcreator)。

  3. 設置輸出路徑IDE_OUTPUT_PATH默認為IDE_BUILD_TREE。

  4. 設置IDE相關子文件夾路徑,可以發現都是相對於IDE_OUTPUT_PATH的。

    image-20200229193843851

  5. 如果輸出路徑不是源目錄,則設置copydata為1,表示需要拷貝數據。

  6. 考慮到IDE_BUILD_TREE與IDE_OUTPUT_PATH可能不一樣,設置IDE庫和插件的鏈接路徑。

  7. 設置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中定義字元串宏,這裡有三種途徑

image-20200229215423993

我們先來看一下qmake編譯得到的Makefile.Debug,符合makefile語法的形式

image-20200229215353865

現在我們來介紹下DEFINES中的含義:

  1. NAME1中第一個"對,告訴qmake引導裡面的是字元串。裡面的"對,是對引號的轉義,在makefile中變為"。再裡面的\對,也是轉義,在makefile中變為。在裡面的"同樣,最終變為"。最終得到我們想要的字元串。

  2. NAME2使用shell_quote()函數,該函數對參數加引號。

  3. NAME0對比NAME1,少了最外面的"對,這導致NAME0隻能定義沒有空格的字元串。如果存在空格,這會導致內容發生變化,中間多了個-D。

    qmake: DEFINES += NAME0="\"app1 .0\""  makefile: -DNAME0=""app1 -D.0""

    此外,對於沒有空格的字元串宏定義,我們甚至可以不需要最外層的引號轉義。

    qmake: DEFINES += NAME0=\"app1\"  makefile: -DNAME0="app1"

分析程式碼:

  1. 設置了PLUGIN,LIBEXEC,DATA和DOC相對於BIN的相對路徑,譬如PLUGIN的為../lib/qtcreator/plugins。
  2. 使步驟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平台定義INCLUDEPATHLIBS變數時:

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。

image-20200301092319099

展開的app_version.h的內容如下

image-20200301092609307

接下來的判斷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  }

我們依次分析:

  1. LIBS中添加qt creator的lib庫鏈接路徑,並去除重複項。

  2. DEFINES添加字元串宏。看樣子vcproj下宏的定義不需要對引號進行轉義哈。

  3. DEFINES添加其他相關宏。看樣子就是用來設置編譯選項。

  4. unix系統下,首先分別對debug和release模式設置對應的obj文件夾。然後設置qt資源編譯輸出文件RCC和用戶介面文件UI的文件夾。

  5. msvc平台下,設置相關編譯指令。

  6. 目標是Qt應用程式或庫,並且包含core核心模組或gui影像用戶介面模組,則添加concurrent並發模組和widget窗口模組。

  7. 對於每個pro文件,替換擴展名為qbs,如果同一目錄下存在該qbs文件,則添加到dist目標文件列表中。

  8. 最後,如果插件依賴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()退出。

  1. 如果QTC_PLUGIN_DEPENDS為空,則退出循環。

  2. 遍歷QTC_PLUGIN_DEPENDS中的每一個依賴dep,

    1. 對於dep,遍歷QTC_PLUGIN_DIRS中的每一個插件文件夾dir,譬如源目錄中的src/plugins。如果{dep}子文件夾中存在{dep}_dependencies.pri文件,則找到需要的依賴。

    2. 如果遍歷完畢都沒有找到,則報錯退出。

    3. 如果找到了,則include載入之。並從依賴文件中獲取QTC_PLUGIN_NAME,添加到LIBS用於鏈接。

  3. 由於include時,會加入該插件的依賴插件,可能重複。所以需要去除重複項。
  4. 去除已經解決依賴的插件。
  5. 回到步驟1,重複。直到每一個依賴都被解決。

這段程式碼,允許用戶在編譯時直接通過QTC_PLUGIN_DEPENDS指定插件依賴。

庫依賴函數同上。


原創造福大家,共享改變世界

獻出一片愛心,溫暖作者心靈