qtcanpool的工程管理来源于qtcreator源码,并进行了优化和改进,使之变为更通用的工程管理模板。
工程管理,不对,工程管理模板,这个有什么意义?
在开发QT程序的时候,大概会有下面几种组织形式:
下面看看QT程序的工程是如何演进的。
功能:实现一个窗口类HelloCanpool,其包含一个QPushButton,Button文本显示为Hello Canpool。HelloCanpool的实例作为QMainWindow的中心窗体,效果图如下:
如何创建工程可以参考:qtcreator 手册
主要代码如下:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000
SOURCES += \
hellocanpool.cpp \
main.cpp \
mainwindow.cpp
HEADERS += \
hellocanpool.h \
mainwindow.h
#include "hellocanpool.h"
#include
#include
HelloCanpool::HelloCanpool(QWidget *parent)
: QWidget(parent)
{
QPushButton *btn = new QPushButton("Hello Canpool");
QHBoxLayout *lay = new QHBoxLayout();
lay->addWidget(btn);
setLayout(lay);
}
#include "mainwindow.h"
#include "hellocanpool.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
HelloCanpool *hc = new HelloCanpool();
setCentralWidget(hc);
resize(300, 100);
}
备注:更多的配置和使用可以参考 qmake手册
将可执行程序改名为HelloCanpool,并输出到bin目录下:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
DESTDIR = bin
TARGET = HelloCanpool
CONFIG += c++11
这些就是一个可执行程序大致的开发内容了,之后可以不断的丰富这个可执行程序的功能。
一个可执行程序如果采用qmake构建方式,那么pro文件就是它的配置管理文件,通过DESTDIR、TARGET、TEMPLATE等完全可以管理好这个项目,这还需要什么工程管理模板吗?
思考一下:一个可执行程序可能存在哪些问题?
把所谓的模块封装为库,提供开放接口的头文件,那么是不是可以更好的解决上面的问题呢?
功能:将上一个可执行程序的窗口类HelloCanpool封装成hello库,提供给可执行程序使用。
这里Type选择Shared Library,Qt module选择Widgets,类名和文件名保持不变。
主要代码如下:
QT += widgets
TEMPLATE = lib
DEFINES += HELLO_LIBRARY
CONFIG += c++11
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
hello.cpp
HEADERS += \
hello_global.h \
hello.h
# Default rules for deployment.
unix {
target.path = /usr/lib
}
!isEmpty(target.path): INSTALLS += target
#ifndef HELLO_GLOBAL_H
#define HELLO_GLOBAL_H
#include
#if defined(HELLO_LIBRARY)
# define HELLO_EXPORT Q_DECL_EXPORT
#else
# define HELLO_EXPORT Q_DECL_IMPORT
#endif
#endif // HELLO_GLOBAL_H
#ifndef HELLO_H
#define HELLO_H
#include "hello_global.h"
class HELLO_EXPORT Hello
{
public:
Hello();
};
#endif // HELLO_H
看破
编译输出动态库和静态库如下:
延伸
新建一个静态库如下,静态库是通过CONFIG += staticlib配置,没有导出导入的概念,因为静态库最终会集成到可执行程序中。
将类HelloCanpool从可执行程序中移到hello库中,主要修改如下:
hellocanpool.h中包含头文件hello_global.h,并在类定义前添加HELLO_EXPORT,将HelloCanpool定义为导出类,供应用程序使用。
上面已经生成了动态库hello.dll和静态库libhello.a,该如何使用呢?
在hellocanpool目录下新建子目录hello,然后拷贝hello.dll、hello_global.h、hellocanpool.h、libhello.a几个文件到hello目录,并修改mainwindow.cpp(hellocanpool.h前面增加hello路径)如下:
这里使用了HelloCanpool类,但是类的定义/实现却在库中,直接使用自然会报未定义和链接错误。
在hellocanpool.pro中配置LIBS如下,再次编译即可通过。
看破
结论:
经过上面反复对静态库和动态库的测试,总结如下:
备注:如果发布软件时,动态库需要拷贝到可执行程序所在的目录
使用hello库的时候,是在hellocanpool中新建子目录hello,需要拷贝开放头文件和相应的库。一般源码修改频繁,每次修改库后都拷贝头文件和库,不是一个好的处理方案。
hellocanpool和hello两个工程在同一目录,是否可以直接用hello目录,而不用频繁拷贝呢?
将hello的库文件(libhello.a和hello.dll)拷贝到hello目录,删除hellocanpool的子目录hello,同时修改hellocanpool工程的hello路径,如下:
目前开放头文件不需要拷贝了,但是库文件还需要手动拷贝到hello目录,可以设置hello的库文件输出目录,这样就不需要每次都拷贝了,如下:
说明
上面的源码和输出库的目录结构没有进行规划(比如源码放在src目录,输出的库放在lib目录),因为程序功能相对较简单,如果功能复杂,建议组织好目录结构体(可以使用pri来管理),然后更改相应的DESTDIR,INCLUDEPATH、LIBS等路径信息。
目前,可执行程序hellocanpool和库hello是两个独立的工程,两个工程存在下面一些问题:
现代的很多IDE都支持单工程多应用管理,就是一个工程/项目,支持多个应用/库,而且应用和库之间的依赖关系可以明确指定。当编译某个应用时,同时会自动编译依赖的库。
qmake通过TEMPLATE来指定工程是应用还是库,那么还有没有别的模板呢?如下所示:
TEMPLATE = subdirs可以使用单工程多应用模板,再来看看SUBDIRS变量如何使用?
SUBDIRS用来指定多个应用,.depends用来设置依赖关系。
下面将采用subdirs的模板来重新管理hellocanpool和hello。
功能:新建一个subdirs模板工程mycanpool,添加可执行程序hellocanpool和库hello。
新建mycanpool目录和mycanpool.pro文件,并将hellocanpool和hello移到mycanpool目录,并做如下修改:
重新修改DESTDIR和LIBS的路径,编译输出如下:
至此,工程的演进过程就介绍完了,最后这种方式还有一些地方值得思考的:
这些问题,随着以后不断的完善,将会形成一套方便的工程管理。作者在阅读qtcreator的源码时,发现了这个工程管理,然后进行了优化和改进,整理出了一套通用的工程管理模板,这就是qtcanpool的工程管理。
接下来,将正式开始介绍qtcanpool的工程管理,下图中各种pri文件就是整个工程管理的核心。
整个qtcanpool主要由几大部分构成,如下表:
部件 | 说明 |
---|---|
libs | 功能独立的库模块 |
plugins | 插件模块,基于QtCreator的插件系统 |
tools | 工具模块,最终也是生成可执行程序,开发一个软件可能同时会伴随一些辅助的小工具,即多应用 |
modules | 一些功能独立但又不具备库的规模的模块 |
工程管理到底管的是什么呢?其实,就是管理前面介绍的几大部件,即如何管理libs库、plugins插件、tools工具等。
管理文件 | 说明 |
---|---|
qtproject.pri | 整个工程的管理文件 |
library.pri | libs库的管理文件 |
plugin.pri | plugins插件的管理文件 |
tool.pri | tools工具的管理文件 |
这几个文件分别对应QtCreator的qtcreator.pri、qtcreatorlibrary.pri、qtcreatorplugin.pri、qtcreatortool.pri,只是做了一些细微的处理。
qtproject.pri是整个工程的基础管理文件,定义了工程版本,通用函数,应用输出路径,共用头文件路径,库路径,库头文件路径,库链接等。
isEmpty(QTPROJECT_DIR): QTPROJECT_DIR = $$PWD
isEmpty(QTPROJECT_OUT_PWD): QTPROJECT_OUT_PWD = $$OUT_PWD
isEmpty(QTPROJECT_PRO_FILE_PWD): QTPROJECT_PRO_FILE_PWD = $$_PRO_FILE_PWD_
isEmpty(QTPROJECT_PRO_FILE): QTPROJECT_PRO_FILE = $$_PRO_FILE_
defineReplace(qtLibraryName) {
RET = $$qtLibraryTargetName($$1)
win32 {
VERSION_LIST = $$split(QTPROJECT_VERSION, .)
RET = $$RET$$first(VERSION_LIST)
}
return($$RET)
}
# eg: $$qtLibraryNameVersion(qcanpool, 1)
defineReplace(qtLibraryNameVersion) {
RET = $$qtLibraryTargetName($$1)
win32 {
exists($$2) {
VERSION_LIST = $$split(QTPROJECT_VERSION, .)
RET = $$RET$$first(VERSION_LIST)
} else {
RET = $$RET$$2
}
}
return($$RET)
}
# config IDE_SOURCE_TREE
IDE_SOURCE_TREE = $$QTPROJECT_DIR
isEmpty(IDE_BUILD_TREE) {
sub_dir = $$QTPROJECT_PRO_FILE_PWD
sub_dir ~= s,^$$re_escape($$IDE_SOURCE_TREE),,
greaterThan(QT_MAJOR_VERSION, 4) {
IDE_BUILD_TREE = $$clean_path($$QTPROJECT_OUT_PWD) # qt5
} else {
IDE_BUILD_TREE = $$QTPROJECT_OUT_PWD # qt4
}
IDE_BUILD_TREE ~= s,$$re_escape($$sub_dir)$,,
}
IDE_APP_PATH = $$IDE_BUILD_TREE/bin
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/qtproject) {
# for .lib in case of binary package with dev package
LIBS *= -L$$IDE_SOURCE_TREE/lib/qtproject
LIBS *= -L$$IDE_SOURCE_TREE/lib/qtproject/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
!isEqual($$IDE_SOURCE_TREE, $$QTCANPOOL_ROOT) {
QTC_PLUGIN_DIRS += $$QTCANPOOL_ROOT/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
!isEqual($$IDE_SOURCE_TREE, $$QTCANPOOL_ROOT) {
QTC_LIB_DIRS += $$QTCANPOOL_ROOT/src/libs
}
for(dir, QTC_LIB_DIRS) {
INCLUDEPATH += $$dir
}
# 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)
}
library.pri是所有库的管理文件,统一设置了库的名称,输出目录等信息。
isEmpty(QTLIBRARY_PRO_FILE_PWD): QTLIBRARY_PRO_FILE_PWD = $$_PRO_FILE_PWD_
include($$replace(QTLIBRARY_PRO_FILE_PWD, ([^/]+$), \\1/\\1_dependencies.pri))
TARGET = $$QTC_LIB_NAME
include(../qtproject.pri)
# use precompiled header for libraries by default
isEmpty(PRECOMPILED_HEADER):PRECOMPILED_HEADER = $$PWD/shared/qtproject_pch.h
win32 {
DLLDESTDIR = $$IDE_APP_PATH
}
DESTDIR = $$IDE_LIBRARY_PATH
osx {
QMAKE_LFLAGS_SONAME = -Wl,-install_name,@rpath/
QMAKE_LFLAGS += -compatibility_version $$QTCREATOR_COMPAT_VERSION
}
RPATH_BASE = $$IDE_LIBRARY_PATH
include(rpath.pri)
TARGET = $$qtLibraryTargetName($$TARGET)
TEMPLATE = lib
CONFIG += shared dll
contains(QT_CONFIG, reduce_exports):CONFIG += hide_symbols
win32 {
dlltarget.path = $$INSTALL_BIN_PATH
INSTALLS += dlltarget
} else {
target.path = $$INSTALL_LIBRARY_PATH
INSTALLS += target
}
以应用fancydemo和库qcanpool为例进行介绍。
库的依赖关系和名字等写在xxx_dependencies.pri文件中,qcanpool_dependencies.pri如下:
QTC_LIB_NAME = qcanpool
qcanpoool库名字为qcanpool,暂时没有依赖别的库,所以没有依赖关系,依赖关系通过QTC_LIB_DEPENDS += xxx指明。
每个库都是一个工程,在libs.pro的SUBDIRS中添加即可:
在SUBDIRS中添加库工程后,会自动解析其依赖关系。
fancydemo是一个可执行应用程序,在demos.pro的SUBDIRS中添加即可:
库的引用通过函数qtLibraryName进行解析,因为库文件名会加调试或版本等信息,用函数可以屏蔽这些信息,只要关注库名字即可。
应用输出目录引用qtproject.pri的IDE_APP_PATH即可。
作者没使用过插件开发,所以没有讲解插件库,如果有需要,可以自行尝试。
其它pri,lib、plugin、module等请自行解锁,欢迎评论交流!