个性化阅读
专注于IT技术分析

经典教程:Qmake权威学习指南

本文概述

介绍

qmake是Qt库附带的构建系统工具, 可简化跨不同平台的构建过程。与CMake和Qbs不同, qmake从一开始就是Qt的一部分, 应被视为”本机”工具。不用说, Qt的默认IDE _Qt Creator_是开箱即用的qmake的最佳支持。是的, 你也可以在此处为新项目选择CMake和Qbs构建系统, 但是这些系统集成得不是很好。随着时间的流逝, Qt Creator中CMake的支持可能会得到改善, 这将是发行此指南第二版(专门针对CMake)的一个很好的理由。即使你不打算使用Qt Creator, 也可能仍要考虑将qmake用作第二个构建系统, 以防你构建公共库或插件。几乎所有基于Qt的第三方库或插件都提供qmake文件, 这些文件用于无缝集成到基于qmake的项目中。它们中只有少数提供双重配置, 例如qmake和CMake。如果满足以下条件, 则你可能更喜欢使用qmake:

  • 你正在构建一个基于Qt的跨平台项目
  • 你正在使用Qt Creator IDE及其大多数功能
  • 你正在构建供其他qmake项目使用的独立库/插件

本指南介绍了最有用的qmake功能, 并为每个功能提供了实际示例。 Qt新手可以将本指南用作Qt构建系统的指南。 Qt开发人员可以在开始新项目时将此当作菜谱, 也可以选择性地将某些功能应用到任何现有项目中, 而影响很小。

qmake构建过程的说明

Qmake基本用法

qmake规范以.pro(“项目”)文件编写。这是最简单的.pro文件的示例:

SOURCES = hello.cpp

默认情况下, 这将创建一个Makefile, 该文件将从单个源代码文件hello.cpp构建可执行文件。

要生成二进制文件(在这种情况下是可执行文件), 你需要先运行qmake生成一个Makefile, 然后运行make(或nmake或mingw32-make, 具体取决于你的工具链)来构建目标。

简而言之, qmake规范仅是与可选控制流语句混合的变量定义列表。通常, 每个变量都包含一个字符串列表。控制流语句允许你包括其他qmake规范文件, 控制条件部分, 甚至包括调用函数。

了解变量的语法

学习现有的qmake项目时, 你可能会惊讶于如何引用不同的变量:{VAR}或$$(VAR)…

在遵循规则的同时使用此迷你备忘单:

  • VAR =值将值分配给VAR
  • VAR + =值将值追加到VAR列表
  • VAR-= value从VAR列表中删除值
  • $$ VAR或$$ {VAR}获取qmake运行时的VAR值
  • $(VAR)运行Makefile(而非qmake)时环境VAR的内容
  • qmake(而非Makefile)运行时环境VAR的$$(VAR)内容

通用模板

可以在规范中找到qmake变量的完整列表:http://doc.qt.io/qt-5/qmake-variable-reference.html

让我们回顾一些项目的常用模板:

# Windows application
TEMPLATE = app
CONFIG += windows

# Shared library (.so or .dll)
TEMPLATE = lib
CONFIG += shared

# Static library (.a or .lib) 
TEMPLATE = lib
CONFIG += static

# Console application
TEMPLATE = app
CONFIG += console

只需添加SOURCES + =…和HEADERS + =…列出所有源代码文件, 即可完成。

到目前为止, 我们已经回顾了非常基本的模板。较复杂的项目通常包括几个相互依赖的子项目。让我们看看如何使用qmake进行管理。

子项目

最常见的用例是一个或多个库和测试项目附带的应用程序。考虑以下结构:

/project
../library
..../include
../library-tests
../application

显然, 我们希望能够一次构建所有内容, 例如:

cd project
qmake && make

为了实现这个目标, 我们需要在/ project文件夹下有一个qmake项目文件:

TEMPLATE = subdirs
SUBDIRS = library library-tests application
library-tests.depends = library
application.depends = library

注意:使用CONFIG + =有序是一种不好的做法—最好使用.depends。

该规范指示qmake首先构建一个库子项目, 因为其他目标依赖它。然后, 它可以以任意顺序构建库测试和应用程序, 因为这两者是相关的。

项目目录结构

链接库

在上面的示例中, 我们有一个需要链接到应用程序的库。在C / C ++中, 这意味着我们需要配置一些其他东西:

  1. 指定-I为#include指令提供搜索路径。
  2. 指定-L为链接程序提供搜索路径。
  3. 指定-l以提供需要链接的库。

因为我们希望所有子项目都是可移动的, 所以我们不能使用绝对或相对路径。例如, 我们不应该这样做:INCLUDEPATH + = ../library/include, 当然我们不能从临时构建文件夹中引用库二进制文件(.a文件)。遵循”关注点分离”的原则, 我们可以很快意识到应用程序项目文件应从库详细信息中抽象出来。相反, 图书馆有责任告诉你在哪里可以找到头文件等。

让我们利用qmake的include()指令解决此问题。在库项目中, 我们将在扩展名为.pri的新文件中添加另一个qmake规范(扩展名可以是任何东西, 但是在这里我代表include)。因此, 该库将具有两个规范:library.pro和library.pri。第一个用于构建库, 第二个用于提供使用项目所需的所有详细信息。

library.pri文件的内容如下:

LIBTARGET = library
BASEDIR   = $${PWD}
INCLUDEPATH *= $${BASEDIR}/include
LIBS += -L$${DESTDIR} -llibrary

BASEDIR指定库项目的文件夹(确切地说, 是当前qmake规范文件的位置, 本例中为library.pri)。你可能会猜到, INCLUDEPATH将被评估为/ project / library / include。 DESTDIR是构建系统放置输出工件(例如.o .a .so .dll或.exe文件)的目录。通常, 这是在你的IDE中配置的, 因此你永远不要对输出文件的位置做任何假设。

在application.pro文件中, 只需添加include(../ library / library.pri)就可以了。

让我们回顾一下在这种情况下如何构建应用程序项目:

  1. 最顶层的project.pro是一个子目录项目。它告诉我们, 必须先构建图书馆项目。因此, qmake进入库的文件夹并使用library.pro进行构建。在此阶段, 将产生library.a并将其放置在DESTDIR文件夹中。
  2. 然后, qmake进入应用程序子文件夹并分析application.pro文件。它找到include(../ library / library.pri)指令, 该指令指示qmake立即读取和解释它。这为INCLUDEPATH和LIBS变量添加了新的定义, 因此现在编译器和链接器知道在哪里搜索包含文件, 库二进制文件以及要链接的库。

我们跳过了库测试项目的构建, 但是它与应用程序项目相同。显然, 我们的测试项目还需要链接应该测试的库。

使用此设置, 你可以轻松地将库项目移至另一个qmake项目并包括它, 从而引用.pri文件。这正是社区分发第三方库的方式。

config.pri

对于复杂的项目来说, 拥有一些共享的配置参数是很常见的, 许多子项目都使用这些参数。为避免重复, 你可以再次使用include()指令并在顶级文件夹中创建config.pri。你可能还具有共享给你的子项目的通用qmake”实用程序”, 类似于我们在本指南中接下来讨论的内容。

将工件复制到DESTDIR

通常, 项目中有一些”其他”文件需要与库或应用程序一起分发。我们只需要能够在构建过程中将所有此类文件复制到DESTDIR中。考虑以下代码段:

defineTest(copyToDestDir) {
    files = $$1

    for(FILE, files) {
        DDIR = $$DESTDIR
		    FILE = $$absolute_path($$FILE)

        # Replace slashes in paths with backslashes for Windows
        win32:FILE ~= s, /, \\, g
        win32:DDIR ~= s, /, \\, g

        QMAKE_POST_LINK += $$QMAKE_COPY $$quote($$FILE) $$quote($$DDIR) $$escape_expand(\\n\\t)
    }

    export(QMAKE_POST_LINK)
}

注意:使用此模式, 你可以定义自己的可重复使用的函数, 这些函数可用于文件。

将此代码放入/project/copyToDestDir.pri中, 以便可以在要求苛刻的子项目中包括()它, 如下所示:

include(../copyToDestDir.pri)

MYFILES += \
    parameters.conf \
    testdata.db

## this is copying all files listed in MYFILES variable
copyToDestDir($$MYFILES)

## this is copying a single file, a required DLL in this example
copyToDestDir($${3RDPARTY}/openssl/bin/crypto.dll)

注意:DISTFILES是出于相同目的而引入的, 但仅在Unix中有效。

代码生成

当C ++项目使用Google protobuf时, 将代码生成作为预建步骤的一个很好的例子。让我们看看如何将协议执行注入构建过程。

你可以轻松地通过Google找到合适的解决方案, 但你需要了解一个重要的案例。假设你有两个合同, 其中A引用B。

A.proto <= B.proto

如果我们首先为A.proto生成代码(以生成A.pb.h和A.pb.cxx)并将其提供给编译器, 则它将失败, 因为依赖项B.pb.h尚不存在。为了解决这个问题, 我们需要在生成结果源代码之前通过所有原始代码生成阶段。

我在这里找到了一个很好的代码片段:https://github.com/jmesmon/qmake-protobuf-example/blob/master/protobuf.pri

这是一个相当大的脚本, 但是你应该已经知道如何使用它:

PROTOS = A.proto B.proto
include(protobuf.pri)

查看protobuf.pri时, 你可能会注意到可以轻松应用于任何自定义编译或代码生成的通用模式:

my_custom_compiler.name = my custom compiler name
my_custom_compiler.input = input variable (list)
my_custom_compiler.output = output file path + pattern
my_custom_compiler.commands = custom compilation command
my_custom_compiler.variable_out = output variable (list)
QMAKE_EXTRA_COMPILERS += my_custom_compiler

范围和条件

通常, 我们需要专门为给定平台(例如Windows或MacOS)定义声明。 Qmake提供了三个预定义的平台指示器:win32, macx和unix。语法如下:

win32 {
    # add Windows application icon, not applicable to unix/macx platform
    RC_ICONS += icon.ico
}

范围可以嵌套, 可以使用运算符!, |甚至通配符:

macx:debug {
    # include only on Mac and only for debug build
    HEADERS += debugging.h
}

win32|macx {
    HEADERS += windows_or_macx.h
}

win32-msvc* {
    # same as win32-msvc|win32-mscv.net
}

注意:Unix是在Mac OS上定义的!如果要测试Mac OS(非通用Unix), 请使用unix:!macx条件。

在Qt Creator中, 范围条件调试和释放无法按预期方式工作。要使它们正常工作, 请使用以下模式:

CONFIG(debug, debug|release) {
    LIBS += ...
}

CONFIG(release, debug|release) {
    LIBS += ...
}

有用的功能

Qmake具有许多嵌入式功能, 可以提高自动化程度。

第一个示例是files()函数。假设你有一个代码生成步骤, 该步骤生成可变数量的源文件。你可以通过以下方法将它们全部包含在SOURCES中:

SOURCES += $$files(generated/*.c)

这将在生成的子文件夹中找到所有扩展名为.c的文件, 并将它们添加到SOURCES变量中。

第二个示例与前面的示例相似, 但是现在代码生成生成了一个文本文件, 其中包含输出文件名(文件列表):

SOURCES += $$cat(generated/filelist, lines)

这只会读取文件内容, 并将每一行都视为SOURCES的条目。

注意:嵌入式功能的完整列表可在以下位置找到:http://doc.qt.io/qt-5/qmake-function-reference.html

将警告视为错误

以下代码段使用前面描述的条件范围功能:

*g++*: QMAKE_CXXFLAGS += -Werror
*msvc*: QMAKE_CXXFLAGS += /WX

这种复杂的原因是因为MSVC具有不同的标志来启用此选项。

生成Git版本

当需要创建包含从Git获取的当前SW版本的预处理器定义时, 以下代码段很有用:

DEFINES += SW_VERSION=\\\"$$system(git describe --always --abbrev=0)\\\"

只要git命令可用, 它就可以在任何平台上使用。如果你使用Git标签, 那么即使分支继续进行, 这也会偷看最新的标签。修改git describe命令以获取你选择的输出。

总结

Qmake是一个出色的工具, 专注于构建基于Qt的跨平台项目。在本指南中, 我们回顾了基本的工具用法和最常用的模式, 这些模式将使你的项目结构保持灵活, 并易于阅读和维护构建规范。

想学习如何使你的Qt应用看起来更好吗?尝试:如何使用贝塞尔曲线和QPainter在C ++中获得圆角形状:分步指南

赞(0)
未经允许不得转载:srcmini » 经典教程:Qmake权威学习指南

评论 抢沙发

评论前必须登录!