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的。

  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中定义字符串宏,这里有三种途径

我们先来看一下qmake编译得到的Makefile.Debug,符合makefile语法的形式

现在我们来介绍下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。

展开的app_version.h的内容如下

接下来的判断win32系统下,是否在源目录中存在lib/qtcreator子目录,存在就过滤重复的链接路径。

接下来是对插件文件夹QTC_PLUGIN_DIRS按照分隔符QMAKE_DIRLIST_SEP进行分隔,得到插件文件夹列表。上述可能为空,所以又添加了源目录下的src/plugins子目录。现在QTC_PLUGIN_DIRS至少为src/plugins。

for语句和c++11中的新增的for语句很像,不再展开。意思也很明确,遍历插件文件夹列表中的每个条目,添加进#include搜索路径。

对于QTC_LIB_DIRS,同QTC_PLUGIN_DIRS。

CONFIG的depend_includepath和no_include_pwd含义,见上面的CONFIG子小节。

这么操作后,对于每个包含qtcreator.pri的子项目,可能很方便的统一添加相同的plugins和libs中的头文件。

设值库链接路径和编译选项

接下来是

LIBS *= -L$$LINK_LIBRARY_PATH  # Qt Creator libraries
exists($$IDE_LIBRARY_PATH): LIBS *= -L$$IDE_LIBRARY_PATH # library path from output path !isEmpty(vcproj) {
DEFINES += IDE_LIBRARY_BASENAME=\"$$IDE_LIBRARY_BASENAME\"
} else {
DEFINES += IDE_LIBRARY_BASENAME=\\\"$$IDE_LIBRARY_BASENAME\\\"
} DEFINES += \
QT_CREATOR \
QT_NO_CAST_TO_ASCII \
QT_RESTRICTED_CAST_FROM_ASCII \
QT_DISABLE_DEPRECATED_BEFORE=0x050600 \
QT_USE_FAST_OPERATOR_PLUS \
QT_USE_FAST_CONCATENATION unix {
CONFIG(debug, debug|release):OBJECTS_DIR = $${OUT_PWD}/.obj/debug-shared
CONFIG(release, debug|release):OBJECTS_DIR = $${OUT_PWD}/.obj/release-shared CONFIG(debug, debug|release):MOC_DIR = $${OUT_PWD}/.moc/debug-shared
CONFIG(release, debug|release):MOC_DIR = $${OUT_PWD}/.moc/release-shared RCC_DIR = $${OUT_PWD}/.rcc
UI_DIR = $${OUT_PWD}/.uic
} msvc {
#Don't warn about sprintf, fopen etc being 'unsafe'
DEFINES += _CRT_SECURE_NO_WARNINGS
QMAKE_CXXFLAGS_WARN_ON *= -w44996
# Speed up startup time when debugging with cdb
QMAKE_LFLAGS_DEBUG += /INCREMENTAL:NO
} qt {
contains(QT, core): QT += concurrent
contains(QT, gui): QT += widgets
} QBSFILE = $$replace(_PRO_FILE_, \\.pro$, .qbs)
exists($$QBSFILE):DISTFILES += $$QBSFILE !isEmpty(QTC_PLUGIN_DEPENDS) {
LIBS *= -L$$IDE_PLUGIN_PATH # plugin path from output directory
LIBS *= -L$$LINK_PLUGIN_PATH # when output path is different from Qt Creator build directory
}

我们依次分析:

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

库依赖函数同上。


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

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


qt creator源码全方面分析(3-2)的更多相关文章

  1. qt creator源码全方面分析(3-3)

    目录 qtcreatordata.pri 定义stripStaticBase替换函数 设置自定义编译和安装 QMAKE_EXTRA_COMPILERS Adding Compilers 示例1 示例2 ...

  2. qt creator源码全方面分析(3-5)

    目录 qtcreatorlibrary.pri 使用实例 上半部 下半部 结果 qtcreatorlibrary.pri 上一章节,我们介绍了src.pro,这里乘此机会,把src目录下的所有项目文件 ...

  3. qt creator源码全方面分析(0)

    本人主攻C++和Qt. 上两天刚研究完Qt install framework(IFW)应用程序安装框架. google没发现有正儿八经的官方文档的翻译,我就进行了翻译哈!! 系列文章具体见:http ...

  4. qt creator源码全方面分析(4-0)

    Qt系统 Qt Creator源码是在Qt对象和框架基础下写的,因此,阅读Qt Creator源码,你首先对Qt得有一定的了解. Qt Core Qt Core特征: The Meta-Object ...

  5. qt creator源码全方面分析(4-2)

    目录 global头文件 global.h xx.h global头文件 插件的本质就是动态链接库,对于库,需要导出符号,供用户导入使用.在qt creator的源码中,存在固定的导入导出模式. gl ...

  6. qt creator源码全方面分析(4-5)

    目录 Qt中的字符串 QLatinString 详细介绍 源码 小结 QStringLiteral(str) 详细介绍 源码 小结 Qt中的字符串 Qt中处理字符串最常用的肯定是QString,但是在 ...

  7. qt creator源码全方面分析(4-6)

    目录 Qt插件基础 Qt插件基础 我们知道Qt Creator源码是基于插件架构的,那么我们先来介绍下插件基础知识. 相关内容如下: How to Create Qt Plugins [ - Defi ...

  8. qt creator源码全方面分析(2-7)

    目录 Completing Code 补全代码片段 编辑代码片段 添加和编辑片段 删除片段 重置片段 补全Nim代码 Completing Code 在编写代码时,Qt Creator建议使用属性,I ...

  9. qt creator源码全方面分析(2-10-1)

    目录 Getting and Building Qt Creator 获取Qt 获取和构建Qt Creator Getting and Building Qt Creator 待办事项:应该对此进行扩 ...

随机推荐

  1. c# winForm 将窗体状态栏StatusStrip 分成左中右三部分 右边显示当前时间

    实现效果:通过StatusStrip显示窗体状态栏同时将状态栏分成三部分居左边显示相关文字信息中间空白显示居右边显示时间信息 1.创建窗体及添加StatusStrip  默认StatusStrip名称 ...

  2. 用C语言实现的轴对称变换

    #include<stdio.h> main() { int i,p,n,k,f,c,h,g,w; ][]; ;i<=;i++) { ;p<=;p++) { a[i][p]=i ...

  3. 81)PHP,session面试题总结

    (1)session和cookie的比较: (2)session是否可以持久化? (3)

  4. linux环境下卸载mysql

    第一种使用yum安装的mysql,使用如下命令进行卸载(不能确定使用何种方式安装的mysql情况下,按后续步骤一一进行处理即可): # yum remove mysql mysql-server my ...

  5. 吴裕雄--天生自然python学习笔记:pandas模块用 dataframe.loc 通过行、列标题读取数据

    用 df.va lue s 读取数据的前提是必须知道学生及科目的位置,非常麻烦 . 而 df.loc 可直接通过行.列标题读取数据,使用起来更为方便 . 使用 df.loc 的语法为: 行标题或列标题 ...

  6. sql 日期表示

    今年时间 昨天,等于0表示今天 =month(时间对应列名) 上月

  7. ZooTracer:打破传统追踪软件的束缚

    编者按:自今年2月24日起,用户可以免费从官网下载Zootracer试用.这是由来自微软剑桥研究院的Joppa和他的同事研发的桌面工具,可追踪任意画质的视频中任意移动物体行踪,是对对视频画质拍摄要求高 ...

  8. HihoCode-1323-回文字符串

    参考博客: https://blog.csdn.net/mitsuha_/article/details/76690634 https://blog.csdn.net/u014142379/artic ...

  9. svn使用2(转)

    首先打开VisualSVN Server Manager,如图: 可以在窗口的右边看到版本库的一些信息,比如状态,日志,用户认证,版本库等.要建立版本库,需要右键单击左边窗口的Repositores, ...

  10. [LC] 318. Maximum Product of Word Lengths

    Given a string array words, find the maximum value of length(word[i]) * length(word[j]) where the tw ...