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 对TRANSLATIONS 和EXTRA_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
。
- 取消LIBRARY_NAME的定义,设置LIBRARY_NAME为
$$1
,即函数的第一个参数。 - CONFIG测试函数判断debug或release模式。
- 如果是debug模式,再次进行判断。
- 如果CONFIG没有设置debug_and_release或者是构建过程build_pass,则设置RET变量。对于 mac,LIBRARY_NAME值后面添加_debug赋给RET。对于win,LIBRARY_NAME值后面添加d赋给RET。
- 如果RET为空,则把LIBRARY_NAME值赋给RET。
- 返回RET。
简单来说,该函数实现功能:在debug环境下,在库名后面添加_debug或_d尾缀,来跟release模式进行区分。当然,也有其他方式来实现上述功能,譬如使用join()函数,见CONFIG小节。
自定义替换函数qtLibraryName
。
- 使用qtLibraryTargetName()函数,对输入的第一个参数进行替换,并赋值给RET。
- 如果 是win32 系统。
- 使用split()函数,将前面定义的QTCREATOR_VERSION(即4.6.2),使用'.'进行分隔得到列表,赋值给VERSION_LIST
- 使用first()函数,获取VERSION_LIST的第一个元素(即4),与RET拼接,并赋值给RET。
- 返回RET。
简单来说,该函数实现功能:为了在win32系统中避免出现 dll hell,在win32系统下,在库名后面添加主版本号。
自定义测试函数minQtVersion
。
- 获取三个参数(即,主/次/补丁),并赋值给maj,min,patch。
- maj <= QT_MAJOR_VERSION,min <= QT_MINOR_VERSION和patch <= QT_PATCH_VERSION,则返回true。其他返回false。
简单来说,该函数实现功能:函数参数的版本号小于等于当前Qt的版本号。
自定义替换函数stripSrcDir
。
- 获取绝对路径,absolute_path返回$$OUT_PWD/$$1。
- 获取相对路径,步骤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
}
- 如果设置了QTC_BUILD_TESTS,则赋值给TEST。
- 如果设置了BUILD_TESTS,则给TEST赋值1。
- 如果TEST没有值,且为debug模式,并且没有设置debug_and_release,则在构建过程中,设置TEST为1。
- 如果IDE_LIBRARY_BASENAME为空,则为库赋值基础名为lib。
- 如果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
我们可以发现
- PWD没有发生变化。
- 对比OUT_PWD和_PRO_FILE_PWD_,输出目录和源目录的子文件夹组织架构一样。
下面我们分析pri中的语句
-
设置源目录IDE_SOURCE_TREE。
-
如果构建目录IDE_BUILD_TREE为空。
- 设置sub_dir,并进行替换。可以认为是从_PRO_FILE_PWD_减去PWD,剩下子文件夹相对路径,如bin/和app/。
- 初始化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分支的内容。
-
如果TEMPLATE包含vc.*,其实就是vcapp或vclib,设置vcproj为1,表示是vs工程。
-
设置可执行程序文件名为IDE_ID(默认为qtcreator)。
-
设置输出路径IDE_OUTPUT_PATH默认为IDE_BUILD_TREE。
-
设置IDE相关子文件夹路径,可以发现都是相对于IDE_OUTPUT_PATH的。
-
如果输出路径不是源目录,则设置copydata为1,表示需要拷贝数据。
-
考虑到IDE_BUILD_TREE与IDE_OUTPUT_PATH可能不一样,设置IDE库和插件的链接路径。
-
设置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中的含义:
NAME1中第一个"对,告诉qmake引导里面的是字符串。里面的\"对,是对引号的转义,在makefile中变为"。再里面的\\对,也是转义,在makefile中变为\。在里面的\"同样,最终变为"。最终得到我们想要的字符串。
NAME2使用shell_quote()函数,该函数对参数加引号。
NAME0对比NAME1,少了最外面的"对,这导致NAME0只能定义没有空格的字符串。如果存在空格,这会导致内容发生变化,中间多了个-D。
qmake: DEFINES += NAME0=\"\\\"app1 .0\\\"\" makefile: -DNAME0="\"app1 -D.0\""
此外,对于没有空格的字符串宏定义,我们甚至可以不需要最外层的引号转义。
qmake: DEFINES += NAME0=\\\"app1\\\" makefile: -DNAME0=\"app1\"
分析代码:
- 设置了PLUGIN,LIBEXEC,DATA和DOC相对于BIN的相对路径,譬如PLUGIN的为../lib/qtcreator/plugins。
- 使步骤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平台定义INCLUDEPATH 和LIBS变量时:
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
}
我们依次分析:
-
LIBS中添加qt creator的lib库链接路径,并去除重复项。
-
DEFINES添加字符串宏。看样子vcproj下宏的定义不需要对引号进行转义哈。
-
DEFINES添加其他相关宏。看样子就是用来设置编译选项。
-
unix系统下,首先分别对debug和release模式设置对应的obj文件夹。然后设置qt资源编译输出文件RCC和用户接口文件UI的文件夹。
-
msvc平台下,设置相关编译指令。
-
目标是Qt应用程序或库,并且包含core核心模块或gui图像用户界面模块,则添加concurrent并发模块和widget窗口模块。
-
对于每个pro文件,替换扩展名为qbs,如果同一目录下存在该qbs文件,则添加到dist目标文件列表中。
-
最后,如果插件依赖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()退出。
-
如果QTC_PLUGIN_DEPENDS为空,则退出循环。
-
遍历QTC_PLUGIN_DEPENDS中的每一个依赖dep,
-
对于dep,遍历QTC_PLUGIN_DIRS中的每一个插件文件夹dir,譬如源目录中的src/plugins。如果{dep}子文件夹中存在{dep}_dependencies.pri文件,则找到需要的依赖。
-
如果遍历完毕都没有找到,则报错退出。
-
如果找到了,则include加载之。并从依赖文件中获取QTC_PLUGIN_NAME,添加到LIBS用于链接。
-
-
由于include时,会加入该插件的依赖插件,可能重复。所以需要去除重复项。
-
去除已经解决依赖的插件。
-
回到步骤1,重复。直到每一个依赖都被解决。
这段代码,允许用户在编译时直接通过QTC_PLUGIN_DEPENDS指定插件依赖。
库依赖函数同上。
原创造福大家,共享改变世界
献出一片爱心,温暖作者心灵