Qt Creator 源码学习 03:qtcreator.pro
当我们准备好 Qt Creator 的源代码之后,首先进入到它的目录,来看一下它的源代码目录有什么奥秘。
这里一共有 9 个文件夹和 9 个文件。我们来一一看看它们都是干什么用的。
- .git: 版本控制 git 的隐藏目录,这与 Qt Creator 代码没有关系。
- bin: 生成 Linux 平台 shell 脚本。
- dist: 安装文件配置信息和版本更新记录。
- doc: 生成 doxygen 文档的配置文件。
- qbs: QBS 配置文件。QBS,即 Qt Build Suite,是一种跨平台的编译工具,目的是将高层的项目描述(使用类似 QML 的语言)转换成底层的编译描述(供 make 等工具使用的信息)。它可以简化多平台的编译过程。QBS 与 qmake 类似,区别在于前者适用于任意项目,而后者一般仅供 Qt 项目使用。我们在阅读代码时将关注 qmake,不会深入研究 QBS 的使用。
- scripts: Qt Creator 使用的 perl 以及 python 等脚本。
- share: 源代码中所需要的一些非代码共享文件,例如代码模板等。
- src: Qt Creator 源代码文件。
- tests: Qt Creator 测试代码。
- .gitignore: git 忽略文件配置。
- .gitmodules: git 子模块配置。
- HACKING: Qt Creator 编码规范。
- LICENSE.GPL3-EXCEPT: GPLv3 协议。
- qtcreator.pri: Qt Creator 项目需要使用的通用配置,该文件一般会被 include 到大部分 pro 文件。
- qtcreator.pro: Qt Creator 的 qmake 项目文件。
- qtcreator.qbs: Qt Creator 的 QBS 项目文件。
- qtcreatordata.pri: Qt Creator 数据相关的配置。
- README.md: 有关如何编译 Qt Creator 等相关事宜的一些说明。
阅读源代码,一般可以从main()
着手。但是阅读 Qt 项目的源代码,我们也可以从 pro 文件开始。pro 文件是 Qt 项目组织结构,规定了我们希望该项目如何编译、编译之后要做什么操作等。
下面我们从根目录的 qtcreator.pro 开始。使用 Qt Creator 或者任意文本编辑器打开 qtcreator.pro,开始真正的代码阅读。
1
|
include(qtcreator.pri)
|
第一行是 include qtcreator.pri。前面我们提到过,qtcreator.pri 中定义了很多函数和适用于各个模块的通用操作。pri 文件可以理解为 pro 文件片段,可以使用include
操作符将其引入一个 pro 文件。qmake 会自动处理引用操作,类似于将 pri 文件的全部内容复制到include
语句处。这与 C++ 的#include
指令类似。这里的处理是线性的,也就是 qmake 会从上向下进行解析。因此,如果你在 pri 中定义了一个函数,那么必须在include
语句之后才能正常使用该函数。这是在使用时需要注意的。有关 qtcreator.pri 文件的内容,会在以后的文章中详细介绍。如果你使用 Qt Creator 打开,include
语句会在左侧的项目树中显示一个节点。这种节点不需要物理上的文件夹隔离,只需要include
不同的 pri 文件即可。这样,即便你的所有文件都在同一个目录下,你也可以使用 pri 文件创建出来多个虚拟目录节点。这样的项目结构看起来会清晰很多。
1
2
3
4
5
|
#version check qt
!minQtVersion(5, 6, 0) {
message("Cannot build Qt Creator with Qt version $${QT_VERSION}.")
error("Use at least Qt 5.6.0.")
}
|
接下来的几行用于判断 Qt 的版本。minQtVersion()
是在 qtcreator.pri 中定义的函数。没错!pro 也可以定义自己的函数!这正是 pro 的强大之处。我们会在后面详细介绍如何定义函数。顾名思义,这个函数函数用于判断 Qt 的版本。前面的!
即取非运算符,这与 C++ 一致。当 Qt 的版本低于 5.6.0 时,执行块中的操作。message()
是 qmake 预定义的函数,类似于qDebug()
,可以在控制台输出一段文本。这里我们输出的是“Cannot build Qt Creator with Qt version $${QT_VERSION}.”。字符串最后的$${QT_VERSION}
是占位符,会使用QT_VERSION
变量的内容进行替换。这一操作被称为变量展开(variable expansion)。有关$$
以及相关运算符的使用相当重要。
$$
运算符通常用于展开变量的内容,展开的内容可以用于变量的赋值,也可以用于函数的传参。例如:
1
2
3
|
EVERYTHING = $$SOURCES $$HEADERS
message("The project contains the following files:")
message($$EVERYTHING)
|
上面的代码中,第一行将SOURCES
和HEADERS
的内容赋值给EVERYTHING
;第三行则将EVERYTHING
作为函数参数赋值给message()
函数。如果没有$$
运算符,将只会输出EVERYTHING
字符串。
变量可以保存环境变量。这些变量可以在 qmake 执行时计算出,或者直接包含在 Makefile 中以便构建时使用。如果需要在 qmake 运行时获取环境变量的值,使用$$()
或$${}
运算符。例如:
1
2
|
DESTDIR = $$(PWD)
message(The project will be installed in $$DESTDIR)
|
在上面代码中,PWD
是 qmake 内置的一个环境变量,用于表示当前正在处理的文件所在文件夹的绝对路径。使用$$()
或${}
运算符,会在 qmake 运行时将值赋给DESTDIR
。如果需要在生成 Makefile 时获取环境变量的值,则需要使用$()
运算符。例如:
1
2
3
4
5
6
|
DESTDIR = $$(PWD)
message(The project will be installed in $$DESTDIR)
DESTDIR = $(PWD)
message(The project will be installed in the value of PWD)
message(when the Makefile is processed.)
|
在上面的语句中,PWD
的值在 qmake 处理是就已经获取到了,但是$(PWD)
则会在生成的 Makefile 中赋值给DESTDIR
。这能够保证在处理 Makefile 时环境变量是正确的。
通过上面的解释,我们知道,$${QT_VERSION}
会在 qmake 运行时进行变量展开。
下面再来看另外的代码:
1
2
|
TEMPLATE = subdirs
CONFIG += ordered
|
这是 qmake 典型的配置。TEMPLATE
即代码模板,将告诉 qmake 我们要怎么生成最后的文件。它的可选值分别是:
- app:创建用于构建可执行文件的 Makefile。
- lib:创建用于构建库的 Makefile。
- subdirs:创建依次构建子目录中文件的 Makefile。子目录使用
SUBDIRS
变量指定。 - aux:创建不构建任何东西的 Makefile。如果构建目标不需要编译器,就可以使用这个模板。例如,你的项目使用的是解释型语言,就可以这么做。注意,此时生成的 Makefile 仅适用于基于 Makefile 的生成器,不一定能供 vcxproj 或 Xcode 使用。
- vcapp:仅适用于 Windows 平台,用于生成 VS 应用程序项目。
- vclib:仅适用于 Windows 平台,用于生成 VS 库项目。
我们最常用的是前三种设置。对于大型项目,一般会分成多个源代码文件夹,因此,Qt Creator 使用的是 subdirs。接下来一行,CONFIG += ordered
意思是,按照SUBDIRS
书写顺序来编译。很多时候,我们虽然将源代码分为不同目录,但是这些目录之间是存在依赖关系的。比如,一个基础类库要被其它所有模块使用,在编译时,该类库应该首先被编译。这要求我们按照一定的顺序来添加SUBDIRS
。有关这一点,Qt Creator 是这样做的:
1
2
3
|
SUBDIRS = src share
unix:!macx:!isEmpty(copydata):SUBDIRS += bin
!isEmpty(BUILD_TESTS):SUBDIRS += tests
|
首先,SUBDIRS
只有两个目录:src 和 share。按照顺序,应该是先编译 src,然后编译 share。后面则是一串复杂的判断:对于 Unix 平台(unix
),如果不是 Mac OS(!macx
),并且copydata
不为空(!isEmpty(copydata)
),则需要再增加一个 bin 目录。最后再判断,如果BUILD_TESTS
不为空(!isEmpty(BUILD_TESTS)
),则再增加一个 tests 目录。+=
运算符就像它所展示的那样,用于追加新的值。copydata
和BUILD_TESTS
都是在 qtcreator.pri 中定义的宏。因为我们是在最前面include
了 qtcreator.pri,所以我们可以自由使用在 qtcreator.pri 文件中定义的变量。类似!isEmpty(BUILD_TESTS):SUBDIRS += tests
这样的写法是一种简写,完整的写法应该如下所示:
1
2
3
|
!isEmpty(BUILD_TESTS) {
SUBDIRS += tests
}
|
有关isEmpty()
这样的函数,我们会在下面详细介绍。我们在看这段代码时,可以同 C++ 代码作类比,以便我们理解:
1
2
3
4
5
6
7
8
9
10
11
|
SUBDIRS = src share
if (unix) {
if (!macx) {
if (!isEmpty(copydata)) {
SUBDIRS += bin
}
}
}
if (!isEmpty(BUILD_TESTS)) {
SUBDIRS += tests
}
|
接下来我们遇到的是
1
2
3
4
|
DISTFILES += dist/copyright_template.txt \
README.md \
$$files(dist/changes-*) \
...
|
DESTFILES
知道需要在最终的目标包括的文件。按照 qmake 的文档,这一特性只适用于 UnixMake。这里我们又遇到了熟悉的$$file()
,只不过这里不是变量展开,而是函数调用。
qmake 提供了两类函数:替换函数(replace functions)和测试函数(test fucntion)。替换函数用于处理数据并将处理结果返回;测试函数的返回值只能是bool
值,并且可以用于一些测试的情形。在使用时,替换函数需要添加$$
先导符而测试函数则不需要。
$$file()
正是一个替换函数,接受一个正则表达式作为参数,其返回值是所有符合这个正则表达式的文件名列表。因此,$$file(dist/changes-*)
返回的是在当前目录下的 dist 文件夹中,所有以 changes- 开头的文件,将它们全部添加到了DESTFILES
。另外,这一函数还可以有第二个参数,是一个bool
值,默认是false
,表示是不是要递归寻找文件。
之后我们看到了
1
2
3
|
exists(src/shared/qbs/qbs.pro) {
...
}
|
exists()
则是一个测试函数,顾名思义,该函数用于测试其参数作为文件名,所代表的文件是否存在。注意测试函数的使用:它可以直接作为测试条件,后面跟着一对大括号,如果函数返回值为true
则执行块中的语句。这里我们发现 src/shared/qbs/qbs.pro 并不存在,因此其中的语句并不会执行。
下面是语句
1
2
|
contains(QT_ARCH, i386): ARCHITECTURE = x86
else: ARCHITECTURE = $$QT_ARCH
|
QT_VERSION
是 qmake 内置的一个变量,用于表示 Qt 的架构。很明显,contains()
是一个测试函数,其函数原型是contains(variablename, value)
,当变量variablename
中包含了value
时,测试通过。那么,上面语句即是,如果QT_ARCH
中有i386
,则将ARCHITECTURE
赋值为x86
,否则就是$$QT_ARCH
。注意在使用contains
函数时,QT_ARCH
并没有使用$$
运算符。因为在使用该函数时,第一个参数是变量名,函数会自己取该变量名的实际值。
1
2
3
4
|
macx: PLATFORM = "mac"
else:win32: PLATFORM = "windows"
else:linux-*: PLATFORM = "linux-$${ARCHITECTURE}"
else: PLATFORM = "unknown"
|
定义了一个新的宏PLATFORM
。注意这里使用了前面刚刚定义的ARCHITECTURE
宏。
接下来,
1
2
|
BASENAME = $$(INSTALL_BASENAME)
isEmpty(BASENAME): BASENAME = qt-creator-$${PLATFORM}$(INSTALL_EDITION)-$${QTCREATOR_VERSION}$(INSTALL_POSTFIX)
|
是一种常见的写法。首先,我们定义了BASENAME
宏为$$(INSTALL_BASENAME)
;之后,如果BASENAME
为空的话(使用了测试函数isEmpty()
进行判断),则定义新的BASENAME
的值。这种写法一方面允许我们在编译时通过传入自定义值改变默认设置(也就是说,如果之前定义了INSTALL_BASENAME
,那么就会使用我们定义的值),否则就会生成一个默认值。以后我们会发现,Qt Creator 的 pro 文件中,很多地方都使用了类似的写法。
跳过部分代码,接下来是一大段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
macx {
APPBUNDLE = "$$OUT_PWD/bin/Qt Creator.app"
BINDIST_SOURCE = "$$OUT_PWD/bin/Qt Creator.app"
BINDIST_INSTALLER_SOURCE = $$BINDIST_SOURCE
deployqt.commands = $$PWD/scripts/deployqtHelper_mac.sh \"$${APPBUNDLE}\" \"$$[QT_INSTALL_TRANSLATIONS]\" \"$$[QT_INSTALL_PLUGINS]\" \"$$[QT_INSTALL_IMPORTS]\" \"$$[QT_INSTALL_QML]\"
codesign.commands = codesign --deep -s \"$(SIGNING_IDENTITY)\" $(SIGNING_FLAGS) \"$${APPBUNDLE}\"
dmg.commands = $$PWD/scripts/makedmg.sh $$OUT_PWD/bin $${BASENAME}.dmg
#dmg.depends = deployqt
QMAKE_EXTRA_TARGETS += codesign dmg
} else {
BINDIST_SOURCE = "$(INSTALL_ROOT)$$QTC_PREFIX"
BINDIST_INSTALLER_SOURCE = "$$BINDIST_SOURCE/*"
deployqt.commands = python -u $$PWD/scripts/deployqt.py -i \"$(INSTALL_ROOT)$$QTC_PREFIX\" \"$(QMAKE)\"
deployqt.depends = install
win32 {
deployartifacts.depends = install
deployartifacts.commands = git clone "git://code.qt.io/qt-creator/binary-artifacts.git" -b $$BINARY_ARTIFACTS_BRANCH&& xcopy /s /q /y /i "binary-artifacts\\win32" \"$(INSTALL_ROOT)$$QTC_PREFIX\"&& rmdir /s /q binary-artifacts
QMAKE_EXTRA_TARGETS += deployartifacts
}
}
|
这里使用macx
分为两部分。很明显,如果系统是macx
,则定义宏APPBUNDLE
。我们需要详细解释的是,在定义新的变量时,Qt Creator 所用到的那些宏。首先,$$OUT_PWD
是 qmake 生成的 Makefile 所在的文件夹。
下面我们会看到一个新的语法:$$[]
。这是取 qmake 的属性。qmake 内置了很多属性值,例如:
1
2
3
4
5
6
7
8
9
10
11
12
|
message(Qt version: $$[QT_VERSION])
message(Qt is installed in $$[QT_INSTALL_PREFIX])
message(Qt resources can be found in the following locations:)
message(Documentation: $$[QT_INSTALL_DOCS])
message(Header files: $$[QT_INSTALL_HEADERS])
message(Libraries: $$[QT_INSTALL_LIBS])
message(Binary files (executables): $$[QT_INSTALL_BINS])
message(Plugins: $$[QT_INSTALL_PLUGINS])
message(Data files: $$[QT_INSTALL_DATA])
message(Translation files: $$[QT_INSTALL_TRANSLATIONS])
message(Settings: $$[QT_INSTALL_CONFIGURATION])
message(Examples: $$[QT_INSTALL_EXAMPLES])
|
在运行时,qmake 可以自定义属性:
1
|
qmake -set PROPERTY VALUE
|
然后,我们就可以用下面语句获取这个属性:
1
|
qmake -query PROPERTY
|
在 pro 文件中,则可以使用$$[]
获取这些属性。可以查阅文档找到 qmake 内置了哪些属性。
语句
1
|
deployqt.commands = $$PWD/scripts/deployqtHelper_mac.sh \"$${APPBUNDLE}\" \"$$[QT_INSTALL_TRANSLATIONS]\" \"$$[QT_INSTALL_PLUGINS]\" \"$$[QT_INSTALL_IMPORTS]\" \"$$[QT_INSTALL_QML]\"
|
定义了一个目标 deployqt,这个目标的命令是$$PWD/scripts/deployqtHelper_mac.sh \"$${APPBUNDLE}\" \"$$[QT_INSTALL_TRANSLATIONS]\" \"$$[QT_INSTALL_PLUGINS]\" \"$$[QT_INSTALL_IMPORTS]\" \"$$[QT_INSTALL_QML]\"
。我们可以使用message()
函数输出这条命令。命令的具体实现暂不深究,感兴趣的话可以阅读 scripts/deployqtHelper_mac.sh 文件。接下来的语句是类似的。最后,这些定义的目标被添加到QMAKE_EXTRA_TARGETS
。这才是真正重要的内容。
尽管 qmake 努力成为一个跨平台的构建工具,但是很多时候,我们不得不使用特定平台的语句。例如,一个常见的任务是,在编译完成之后,将预置的配置文件复制到特定目录。这种目标可以通过类似的语法进行定义,然后将定义好的目标添加到QMAKE_EXTRA_TARGETS
。当 qmake 运行完毕后,会接着执行这些目标,直到编译成功。
例如,
1
2
3
4
5
|
mytarget.target = .buildfile
mytarget.commands = touch $$mytarget.target
mytarget.depends = mytarget2
mytarget2.commands = @echo Building $$mytarget.target
|
mytarget
是一个自定义目标;mytarget.target
是这个自定义目标的名字。之后生成的 Makefile 中将会使用这个名字作为 target。mytarget.commands
定义了这个目标的命令:使用touch
命令生成一个文件。mytarget.depends
定义这个目标依赖于mytarget2
,尽管mytarget2
是在后面定义的。最后,我们将这两个目标都添加到QMAKE_EXTRA_TARGETS
:
1
|
QMAKE_EXTRA_TARGETS += mytarget mytarget2
|
最后,我们来看
1
2
3
4
5
6
|
win32 {
deployqt.commands ~= s,/,\\\\,g
bindist.commands ~= s,/,\\\\,g
bindist_installer.commands ~= s,/,\\\\,g
installer.commands ~= s,/,\\\\,g
}
|
有是一个新的语法~=
。~=
运算符将符合正则表达式的内容替换为后面的部分。例如DEFINES ~= s/QT_[DT].+/QT
会将以QT_D
或QT_T
开头的文本替换为QT
。后面s,/,\\\\,g
是替换操作。三个逗号分为四个部分:第一个s
表示输入字符串;第二个/
表示 /;第三个\\\\
表示 \,之所以是四个,是因为 \ 需要转义;第四个g
表示全局替换。合起来的意思就是,将输入字符串中的 / 全部替换为 \。这是适配命令路径中,Unix 的 / 和 Windows 的 \。这一个技巧在编写跨平台代码中非常有用,很多时候我们在 pro 中给出了 Unix 格式的路径,只需要使用简单的语句,例如
1
2
|
PWD_WIN = $${PWD}
PWD_WIN ~= s,/,\\,g
|
就可以转换为合法的 Windows 路径。
本章我们着重学习了 Qt Creator 的主项目文件 qtcreator.pro 的写法。下一节我们将详细介绍 qtcreator.pri 的写法。
https://www.devbean.net/2016/08/qt-creator-source-study-03/
Qt Creator 源码学习 03:qtcreator.pro的更多相关文章
- Qt Creator 源码学习笔记03,大型项目如何管理工程
阅读本文大概需要 6 分钟 一个项目随着功能开发越来越多,项目必然越来越大,工程管理成本也越来越高,后期维护成本更高.如何更好的组织管理工程,是非常重要的 今天我们来学习下 Qt Creator 是如 ...
- Qt Creator 源码学习笔记04,多插件实现原理分析
阅读本文大概需要 8 分钟 插件听上去很高大上,实际上就是一个个动态库,动态库在不同平台下后缀名不一样,比如在 Windows下以.dll结尾,Linux 下以.so结尾 开发插件其实就是开发一个动态 ...
- Qt Creator 源码学习笔记01,初识QTC
阅读本文大概需要 4 分钟 Qt Creator 是一款开源的轻量级 IDE,整个架构代码全部使用 C++/Qt 开发而成,非常适合用来学习C++和Qt 知识,这也是我们更加深入学习Qt最好的方式,学 ...
- Qt Creator 源码学习笔记02,认识框架结构
阅读本文大概需要 6 分钟 在上一篇大概了解了关于Qt Creator 基础知识后[1],本篇先学习下框架基本结构,这样能够清晰的知道这个框架当中包含哪些文件.文件夹.工程文件,这些文件分别代表什么意 ...
- qt creator源码全方面分析(3-3)
目录 qtcreatordata.pri 定义stripStaticBase替换函数 设置自定义编译和安装 QMAKE_EXTRA_COMPILERS Adding Compilers 示例1 示例2 ...
- qt creator源码全方面分析(3-5)
目录 qtcreatorlibrary.pri 使用实例 上半部 下半部 结果 qtcreatorlibrary.pri 上一章节,我们介绍了src.pro,这里乘此机会,把src目录下的所有项目文件 ...
- qt creator源码全方面分析(0)
本人主攻C++和Qt. 上两天刚研究完Qt install framework(IFW)应用程序安装框架. google没发现有正儿八经的官方文档的翻译,我就进行了翻译哈!! 系列文章具体见:http ...
- qt creator源码全方面分析(4-0)
Qt系统 Qt Creator源码是在Qt对象和框架基础下写的,因此,阅读Qt Creator源码,你首先对Qt得有一定的了解. Qt Core Qt Core特征: The Meta-Object ...
- qt creator源码全方面分析(4-2)
目录 global头文件 global.h xx.h global头文件 插件的本质就是动态链接库,对于库,需要导出符号,供用户导入使用.在qt creator的源码中,存在固定的导入导出模式. gl ...
随机推荐
- 破解root用户密码 -rwx权限
破解root用户密码(本地登录) 1.光驱要放入系统光盘 2.重启os 3.修改启动菜单进入1运行级别 4.设置新密码 5.重启os linux的运行级别(默认3或5): 查看默认的运行级别 cat ...
- Centos6.5 安装lnmp环境
最近项目要配置在nginx上,所以搜索了下具体nginx的安装,看到这篇文章简洁明了而且测试成功就借用了,作品出处:http://www.cnblogs.com/xiaoit/p/3991037.ht ...
- vue踩坑-This dependency was not found
* vux in ./node_modules/babel-loader/lib!./node_modules/vue-loader/lib/selector.js?type=script&i ...
- 对比了解Grafana与Kibana的关键差异
对比了解Grafana与Kibana的关键差异 http://www.infoq.com/cn/articles/grafana-vs-kibana-the-key-differences-to-kn ...
- hadoop实验:求气象数据的最低温度
1.下载部分数据.由于实验就仅仅下载2003年的部分气象数据 2.通过zcat *gz > sample.txt命令解压重定向 [hadoop@Master test_data]$ zcat * ...
- Java的线程机制
一.Java中实现多线程的两种方式1) 继承Thread类 Thread类包括了包括和创建线程所需的一切东西. Thread 最重要的方法是 run().编写线程程序时须要覆盖 run() 方法,ru ...
- Kinect 开发 —— 常见手势识别(上)
悬浮按钮 (Hover Button) 悬浮按钮通过将鼠标点击换成悬浮然后等待(hover-and-wait)动作,解决了不小心点击的问题.当光标位于按钮之上时,意味着用户通过将光标悬浮在按钮上一段时 ...
- 在Xampp中添加memache扩展
1.首先下载phpmemcache,地址为: http://up.2cto.com/2012/0522/20120522094758371.rar 解压下的文件,解压后有以下文件: 接着以管理员身份打 ...
- 数据库事务及其EF中如何处理事务
一.基础知识 1) 使用事务级别ReadUnCommited 会产生脏读现像,意味着读取到的为UnCommited(未提交)的数据.怎么理解呢?在使用该隔离级别的事务开始后.更新了数据 ...
- 【Codeforces Round #427 (Div. 2) B】The number on the board
[Link]:http://codeforces.com/contest/835 [Description] 原本有一个数字x,它的各个数码的和原本是>=k的; 现在这个数字x,在不改变位数的情 ...