Qmake 重要指南

已發表: 2022-03-11

介紹

qmake是 Qt 庫附帶的構建系統工具,可簡化跨平台的構建過程。 與CMakeQbs不同,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 (或nmakemingw32-make取決於您的工具鏈)來構建目標。

簡而言之,一個 qmake 規範只不過是一個混合了可選控制流語句的變量定義列表。 通常,每個變量都包含一個字符串列表。 控制流語句允許您包含其他 qmake 規範文件、控制條件部分,甚至調用函數。

理解變量的語法

在學習現有的 qmake 項目時,您可能會驚訝於如何引用不同的變量: \(VAR,\){VAR} 或 $$(VAR) ...

在採用規則時使用此迷你備忘單:

  • VAR = value value 給 VAR 賦值
  • VAR += value將值附加到 VAR 列表
  • VAR -= value從 VAR 列表中刪除值
  • $$VAR$${VAR}在 qmake 運行時獲取 VAR 的值
  • $(VAR) Makefile(不是 qmake)運行時環境 VAR 的內容
  • $$(VAR) qmake(不是 Makefile)運行時環境 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 += ordered被認為是一種不好的做法——最好使用.depends代替。

該規範指示 qmake 首先構建一個庫子項目,因為其他目標依賴於它。 然後它可以以任意順序構建library-tests和應用程序,因為這兩者是依賴的。

項目目錄結構

鏈接庫

在上面的示例中,我們有一個需要鏈接到應用程序的庫。 在 C/C++ 中,這意味著我們需要配置更多的東西:

  1. 指定-I以提供#include 指令的搜索路徑。
  2. 指定-L以提供鏈接器的搜索路徑。
  3. 指定-l以提​​供需要鏈接的庫。

因為我們希望所有子項目都是可移動的,所以我們不能使用絕對或相對路徑。 例如,我們不應該這樣做: INCLUDEPATH += ../library/include ,當然我們不能從臨時構建文件夾中引用庫二進製文件(.a 文件)。 遵循“關注點分離”的原則,我們可以很快意識到應用項目文件應該從庫細節中抽像出來。 相反,庫有責任告訴在哪裡可以找到頭文件等。

讓我們利用 qmake 的include()指令來解決這個問題。 在庫項目中,我們將在擴展名為.pri的新文件中添加另一個 qmake 規範(擴展名可以是任何東西,但這裡i代表包含)。 因此,該庫將有兩個規範: library.prolibrary.pri 。 第一個用於構建庫,第二個用於提供消費項目所需的所有詳細信息。

library.pri 文件的內容如下:

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

BASEDIR指定庫項目的文件夾(準確地說,當前 qmake 規範文件的位置,在我們的例子中是library.pri )。 正如您可能猜到的, INCLUDEPATH將被評估為/project/library/includeDESTDIR是構建系統放置輸出工件的目錄,例如(.o .a .so .dll 或 .exe 文件)。 這通常在您的 IDE 中配置,因此您永遠不應該對輸出文件的位置做出任何假設。

application.pro文件中添加include(../library/library.pri)就完成了。

讓我們回顧一下在這種情況下如何構建應用程序項目:

  1. 最頂層的project.pro是一個子目錄項目。 它告訴我們需要先構建庫項目。 所以 qmake 進入庫的文件夾並使用library.pro構建它。 在這個階段, library.a被生成並放入DESTDIR文件夾。
  2. 然後qmake進入application子文件夾,解析application.pro文件。 它找到了include(../library/library.pri)指令,該指令指示 qmake 立即讀取和解釋它。 這為INCLUDEPATHLIBS變量添加了新定義,因此現在編譯器和鏈接器知道在哪裡搜索包含文件、庫二進製文件以及要鏈接的庫。

我們跳過了 library-tests 項目的構建,但它與應用程序項目相同。 顯然,我們的測試項目還需要鏈接它應該測試的庫。

通過此設置,您可以輕鬆地將庫項目移動到另一個 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()到要求苛刻的子項目中,如下所示:

 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 時。 讓我們看看如何將protoc執行注入到構建過程中。

您可以輕鬆地在 Google 上找到合適的解決方案,但您需要了解一個重要的極端案例。 假設你有兩個合約,其中 A 引用 B。

 A.proto <= B.proto

如果我們首先為A.proto生成代碼(生成A.pb.hA.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 中, debugrelease的範圍條件沒有按預期工作。 要使這些正常工作,請使用以下模式:

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

有用的功能

Qmake 有許多嵌入式功能,增加了更多的自動化。

第一個示例是files()函數。 假設您有一個生成可變數量源文件的代碼生成步驟。 以下是如何將它們全部包含在SOURCES中:

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

這將在generated的子文件夾中找到所有擴展名為.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 應用程序看起來更好嗎? 嘗試:如何在 C++ 中使用貝塞爾曲線和 QPainter 獲得圓角形狀:分步指南