zoukankan      html  css  js  c++  java
  • 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指定插件依赖。

    库依赖函数同上。


    原创造福大家,共享改变世界

    献出一片爱心,温暖作者心灵


  • 相关阅读:
    DataGrid行单元格合并显示 (增加交错行颜色设置)
    IListHelper 实现IList到DataSet和DataTable的数据转换
    DataGrid 分页自定义控件
    反射原理的简单例子
    DataTable转换为Excel文件
    经典面试题集锦
    优秀技术网站展播!
    DataGrid行单元格合并显示
    Windows Service Application Overview
    GridView.SortExpression Property
  • 原文地址:https://www.cnblogs.com/codeForFamily/p/qt-creator-ide-source-learn-3-2.html
Copyright © 2011-2022 走看看