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指定插件依赖。

库依赖函数同上。


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

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


posted @ 2020-03-06 19:13  codeForFamily  阅读(2598)  评论(0编辑  收藏  举报