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)
}

注釋說的很明白,一個遞歸解決插件依賴,一個遞歸解決庫依賴。

首先我們來看下依賴文件示例,源目錄src\plugins\cppeditor\cppeditor_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指定插件依賴。

庫依賴函數同上。


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

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



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM