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. Java复习(二)类与对象的基本概念

    2.1面向对象的程序设计方法概述 对象 程序中: 一切皆是对象 都具有标识,属性和行为 通过一个或多个变量来保存其状态 通过方法实现他的行为 类 将属性及行为相同或相似的对象归为一类 类可以看成是对象 ...

  2. day04-函数,装饰器初成

    面试的时候,经常被问过装饰器,所以掌握好装饰器非常重要. 一.装饰器形成的过程:1.最简单的装饰器.2.被装饰的函数有返回值.3.被装饰的函数有一个参数.4.被装饰的函数有多个位置参数.5.被装饰的函 ...

  3. PAT甲级——1050 String Subtraction

    1050 String Subtraction Given two strings S​1​​ and S​2​​, S=S​1​​−S​2​​ is defined to be the remain ...

  4. jsp 页面内容导出到Excel中

    日常使用网络资源时经常需要把网页中的内容下载到本地,并且导出到Excel中,现在介绍一种非常简单的方式实现网络资源的下载.只需要讲jsp的最上面加上一句话 <% response.reset() ...

  5. 项目引入nacos 日志不显示问题

    禁用nacos的日志即可解决 idea当中 添加vm options参数即可 -Dnacos.logging.default.config.enabled=false 打包后的启动命令  java - ...

  6. [LC] 76. Minimum Window Substring

    Given a string S and a string T, find the minimum window in S which will contain all the characters ...

  7. CocoaPods 安装和使用 - darcy_tang 的博客

    最近换了新机器,重新搭建了开发环境,其中当然包括 CocoaPods. 装完顺便更新下 CocoaPods 安装文档. 正文 安装 CocoaPods 是用 ruby 实现的,要想使用它首先需要有 r ...

  8. numpy中的ndarray与pandas中的series、dataframe的转换

    一个ndarray是一个多维同类数据容器.每一个数组有一个dtype属性,用来描述数组的数据类型. Series是一种一维数组型对象,包含了一个值序列,并且包含了数据标签----索引(index). ...

  9. js - 常用的继承

    零.序言 参考资料:JavaScript常用八种继承方案: 注:1.此篇笔记是站在上述资料的肩膀上的一篇小结: 2.阅读之前建议温习一下 js 中的 prototype 和 constructor:( ...

  10. zabbix配置文件详解--服务(server)端、客户(agent)端、代理(proxy)端

    在zabbix服务(server)端.客户(agent)端.代理(proxy)端分别对应着一个配置文件,即:zabbix_server.conf,zabbix_agentd.conf,zabbix_p ...