Ein wichtiger Leitfaden für Qmake
Veröffentlicht: 2022-03-11Einführung
qmake ist ein Build-System-Tool, das mit der Qt-Bibliothek geliefert wird und den Build-Prozess auf verschiedenen Plattformen vereinfacht. Im Gegensatz zu CMake und Qbs war qmake von Anfang an Teil von Qt und sollte als „natives“ Tool betrachtet werden. Unnötig zu sagen, dass die Standard-IDE von Qt – Qt Creator – die beste Unterstützung für qmake von Haus aus bietet. Ja, Sie können dort auch CMake- und Qbs-Build-Systeme für ein neues Projekt auswählen, aber diese sind nicht so gut integriert. Es ist wahrscheinlich, dass die CMake-Unterstützung in Qt Creator im Laufe der Zeit verbessert wird, und dies wird ein guter Grund sein, die zweite Ausgabe dieses Handbuchs herauszugeben, die speziell auf CMake ausgerichtet ist. Auch wenn Sie nicht beabsichtigen, Qt Creator zu verwenden, sollten Sie qmake dennoch als zweites Build-System in Betracht ziehen, falls Sie öffentliche Bibliotheken oder Plugins erstellen. Nahezu alle Qt-basierten Bibliotheken oder Plugins von Drittanbietern liefern qmake-Dateien, die zur nahtlosen Integration in qmake-basierte Projekte verwendet werden. Nur wenige von ihnen bieten duale Konfiguration, z. B. qmake und CMake. Möglicherweise bevorzugen Sie die Verwendung von qmake, wenn Folgendes auf Sie zutrifft:
- Sie erstellen ein plattformübergreifendes Qt-basiertes Projekt
- Sie verwenden die Qt Creator IDE und die meisten ihrer Funktionen
- Sie erstellen eine eigenständige Bibliothek/Plugin, die von anderen qmake-Projekten verwendet werden soll
Dieser Leitfaden beschreibt die nützlichsten qmake-Funktionen und bietet Beispiele aus der Praxis für jede von ihnen. Qt-Neulinge können dieses Handbuch als Tutorial für das Build-System von Qt verwenden. Qt-Entwickler können dies als Kochbuch behandeln, wenn sie ein neues Projekt starten, oder einige der Funktionen selektiv auf jedes der bestehenden Projekte mit geringen Auswirkungen anwenden.
Grundlegende Qmake-Nutzung
Die qmake-Spezifikation ist in .pro
(„Projekt“)-Dateien geschrieben. Dies ist ein Beispiel für die einfachste mögliche .pro
-Datei:
SOURCES = hello.cpp
Standardmäßig wird dadurch ein Makefile
erstellt, das eine ausführbare Datei aus der einzelnen Quellcodedatei hello.cpp
.
Um die Binärdatei (in diesem Fall ausführbar) zu erstellen, müssen Sie zuerst qmake ausführen, um ein Makefile zu erstellen, und dann make
(oder nmake
oder mingw32-make
abhängig von Ihrer Toolchain), um das Ziel zu erstellen.
Kurz gesagt, eine qmake-Spezifikation ist nichts anderes als eine Liste von Variablendefinitionen gemischt mit optionalen Kontrollflussanweisungen. Jede Variable enthält im Allgemeinen eine Liste von Zeichenfolgen. Ablaufsteuerungsanweisungen ermöglichen es Ihnen, andere qmake-Spezifikationsdateien einzuschließen, bedingte Abschnitte zu steuern und sogar Funktionen aufzurufen.
Die Syntax von Variablen verstehen
Beim Erlernen vorhandener qmake-Projekte werden Sie vielleicht überrascht sein, wie verschiedene Variablen referenziert werden können: \(VAR,\){VAR} oder $$(VAR) …
Verwenden Sie diesen Mini-Spickzettel, während Sie die Regeln übernehmen:
-
VAR = value
zuweisen -
VAR += value
Wert an VAR-Liste anhängen -
VAR -= value
Wert aus der VAR-Liste entfernen -
$$VAR
oder$${VAR}
Ruft den Wert von VAR ab, wenn qmake läuft -
$(VAR)
Inhalt einer Umgebungs-VAR zu dem Zeitpunkt, zu dem Makefile (nicht qmake) läuft -
$$(VAR)
Inhalt einer Umgebungs-VAR zu dem Zeitpunkt, zu dem qmake (nicht Makefile) läuft
Gemeinsame Vorlagen
Die vollständige Liste der qmake-Variablen finden Sie in der Spezifikation: http://doc.qt.io/qt-5/qmake-variable-reference.html
Sehen wir uns einige gängige Vorlagen für Projekte an:
# 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
Fügen Sie einfach SOURCES + = … und HEADERS += … hinzu , um alle Ihre Quellcodedateien aufzulisten, und Sie sind fertig.
Bisher haben wir sehr einfache Vorlagen überprüft. Komplexere Projekte umfassen in der Regel mehrere Teilprojekte mit Abhängigkeiten zueinander. Mal sehen, wie man das mit qmake handhabt.
Teilprojekte
Der häufigste Anwendungsfall ist eine Anwendung, die mit einer oder mehreren Bibliotheken und Testprojekten ausgeliefert wird. Betrachten Sie die folgende Struktur:
/project ../library ..../include ../library-tests ../application
Natürlich wollen wir in der Lage sein, alles auf einmal zu bauen, wie folgt:
cd project qmake && make
Um dieses Ziel zu erreichen, benötigen wir eine qmake-Projektdatei im Ordner /project
:
TEMPLATE = subdirs SUBDIRS = library library-tests application library-tests.depends = library application.depends = library
HINWEIS: Die Verwendung von CONFIG += ordered
wird als schlechte Praxis angesehen – verwenden Sie stattdessen lieber .depends
.
Diese Spezifikation weist qmake an, zuerst ein Bibliotheksunterprojekt zu erstellen, da andere Ziele davon abhängen. Dann kann es library-tests
und die Anwendung in beliebiger Reihenfolge erstellen, da diese beiden voneinander abhängig sind.
Verknüpfen von Bibliotheken
Im obigen Beispiel haben wir eine Bibliothek, die mit der Anwendung verknüpft werden muss. In C/C++ bedeutet dies, dass wir noch ein paar Dinge konfigurieren müssen:
- Geben Sie
-I
an, um Suchpfade für #include-Direktiven bereitzustellen. - Geben Sie
-L
an, um Suchpfade für den Linker bereitzustellen. - Geben Sie
-l
an, um anzugeben, welche Bibliothek verknüpft werden muss.
Da wir möchten, dass alle Teilprojekte verschiebbar sind, können wir keine absoluten oder relativen Pfade verwenden. Zum Beispiel werden wir dies nicht tun: INCLUDEPATH += ../library/include und natürlich können wir nicht auf eine Bibliotheks-Binärdatei (.a-Datei) aus einem temporären Build-Ordner verweisen. Nach dem Prinzip der „Trennung von Bedenken“ können wir schnell erkennen, dass die Anwendungsprojektdatei von Bibliotheksdetails abstrahieren soll. Stattdessen liegt es in der Verantwortung der Bibliothek, mitzuteilen, wo Header-Dateien usw. zu finden sind.
Lassen Sie uns die Anweisung include()
von qmake nutzen, um dieses Problem zu lösen. Im Bibliotheksprojekt werden wir eine weitere qmake-Spezifikation in einer neuen Datei mit der Erweiterung .pri
(die Erweiterung kann alles sein, aber hier steht i
für include). Die Bibliothek hätte also zwei Spezifikationen: library.pro
und library.pri
. Die erste wird verwendet, um die Bibliothek zu erstellen, die zweite wird verwendet, um alle Details bereitzustellen, die von einem verbrauchenden Projekt benötigt werden.
Der Inhalt der Datei library.pri würde wie folgt aussehen:
LIBTARGET = library BASEDIR = $${PWD} INCLUDEPATH *= $${BASEDIR}/include LIBS += -L$${DESTDIR} -llibrary
BASEDIR
gibt den Ordner des Bibliotheksprojekts an (um genau zu sein, der Speicherort der aktuellen qmake-Spezifikationsdatei, in unserem Fall library.pri
). Wie Sie sich vorstellen können, wird INCLUDEPATH
für /project/library/include
ausgewertet. DESTDIR
ist das Verzeichnis, in dem das Build-System die Ausgabeartefakte platziert, z. B. (.o .a .so .dll- oder .exe-Dateien). Dies wird normalerweise in Ihrer IDE konfiguriert, daher sollten Sie niemals Vermutungen darüber anstellen, wo sich die Ausgabedateien befinden.
Fügen Sie in der Datei application.pro
einfach include(../library/library.pri)
hinzu und Sie sind fertig.
Sehen wir uns an, wie das Anwendungsprojekt in diesem Fall erstellt wird:

- Oberstes
project.pro
ist ein Unterverzeichnisprojekt. Es sagt uns, dass das Bibliotheksprojekt zuerst erstellt werden muss. Also betritt qmake den Ordner der Bibliothek und erstellt ihn mitlibrary.pro
. In diesem Stadium wirdlibrary.a
erstellt und imDESTDIR
Ordner abgelegt. - Dann betritt qmake den Unterordner der Anwendung und parst die Datei
application.pro
. Es findet die Anweisunginclude(../library/library.pri)
, die qmake anweist, sie sofort zu lesen und zu interpretieren. Dadurch werdenINCLUDEPATH
undLIBS
-Variablen neue Definitionen hinzugefügt, sodass der Compiler und der Linker jetzt wissen, wo er nach Include-Dateien, den Bibliotheksbinärdateien und der zu verknüpfenden Bibliothek suchen muss.
Wir haben das Erstellen des Bibliothekstests-Projekts übersprungen, aber es ist identisch mit dem Anwendungsprojekt. Natürlich müsste unser Testprojekt auch die Bibliothek verknüpfen, die es testen soll.
Mit diesem Setup können Sie das Bibliotheksprojekt einfach in ein anderes qmake-Projekt verschieben und es einbinden, wodurch auf die .pri
-Datei verwiesen wird. Genau so werden Bibliotheken von Drittanbietern von der Community verteilt.
config.pri
Bei einem komplexen Projekt ist es sehr üblich, einige gemeinsame Konfigurationsparameter zu haben, die von vielen Teilprojekten verwendet werden. Um Duplikate zu vermeiden, können Sie erneut die include()
Direktive nutzen und config.pri
im Ordner der obersten Ebene erstellen. Möglicherweise haben Sie auch gemeinsame qmake-„Dienstprogramme“, die für Ihre Unterprojekte freigegeben sind, ähnlich dem, was wir als Nächstes in diesem Handbuch besprechen.
Kopieren von Artefakten nach DESTDIR
Häufig haben Projekte einige „andere“ Dateien, die zusammen mit einer Bibliothek oder Anwendung verteilt werden müssen. Wir müssen nur in der Lage sein, alle diese Dateien während des Build-Prozesses in DESTDIR
zu kopieren. Betrachten Sie den folgenden Ausschnitt:
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) }
Hinweis: Mit diesem Muster können Sie Ihre eigenen wiederverwendbaren Funktionen definieren, die mit Dateien arbeiten.
Platzieren Sie diesen Code in /project/copyToDestDir.pri
, damit Sie ihn wie folgt in anspruchsvolle Unterprojekte einfügen 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)
Hinweis: DISTFILES wurde für denselben Zweck eingeführt, funktioniert aber nur unter Unix.
Codegenerierung
Ein großartiges Beispiel für die Codegenerierung als vorgefertigten Schritt ist die Verwendung von Google protobuf in einem C++-Projekt. Mal sehen, wie wir die Protokollausführung in den Build-Prozess protoc
können.
Sie können leicht nach einer geeigneten Lösung googeln, aber Sie müssen sich eines wichtigen Eckfalls bewusst sein. Stellen Sie sich vor, Sie haben zwei Verträge, bei denen A auf B verweist.
A.proto <= B.proto
Wenn wir zuerst Code für A.proto
generieren (um A.pb.h
und A.pb.cxx
zu produzieren) und ihn dem Compiler zuführen würden, wird es einfach fehlschlagen, weil die Abhängigkeit B.pb.h
noch nicht existiert. Um dies zu lösen, müssen wir alle Phasen der Protocode-Generierung durchlaufen, bevor wir den resultierenden Quellcode erstellen.
Ich habe hier ein tolles Snippet für diese Aufgabe gefunden: https://github.com/jmesmon/qmake-protobuf-example/blob/master/protobuf.pri
Es ist ein ziemlich großes Skript, aber Sie sollten bereits wissen, wie man es benutzt:
PROTOS = A.proto B.proto include(protobuf.pri)
Wenn Sie sich protobuf.pri
, bemerken Sie möglicherweise das generische Muster, das leicht auf jede benutzerdefinierte Kompilierung oder Codegenerierung angewendet werden kann:
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
Geltungsbereich und Bedingungen
Oft müssen wir Deklarationen speziell für eine bestimmte Plattform wie Windows oder MacOS definieren. Qmake bietet drei vordefinierte Plattformindikatoren: win32, macx und unix. Hier ist die Syntax:
win32 { # add Windows application icon, not applicable to unix/macx platform RC_ICONS += icon.ico }
Bereiche können verschachtelt werden, können Operatoren verwenden !
, |
und sogar Platzhalter:
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 }
Hinweis: Unix ist auf Mac OS definiert! Wenn Sie für Mac OS (nicht generisches Unix) testen möchten, verwenden Sie die Bedingung unix:!macx
.
In Qt Creator funktionieren die Scope-Bedingungen debug
und release
nicht wie erwartet. Damit diese richtig funktionieren, verwenden Sie das folgende Muster:
CONFIG(debug, debug|release) { LIBS += ... } CONFIG(release, debug|release) { LIBS += ... }
Nützliche Funktionen
Qmake hat eine Reihe von eingebetteten Funktionen, die mehr Automatisierung hinzufügen.
Das erste Beispiel ist die Funktion files()
. Angenommen, Sie haben einen Codegenerierungsschritt, der eine variable Anzahl von Quelldateien erzeugt. So können Sie sie alle in SOURCES
:
SOURCES += $$files(generated/*.c)
Dadurch werden alle Dateien mit der Erweiterung .c
im generated
Unterordner gefunden und zur Variablen SOURCES
hinzugefügt.
Das zweite Beispiel ähnelt dem vorherigen, aber jetzt erzeugt die Codegenerierung eine Textdatei mit Ausgabedateinamen (Dateiliste):
SOURCES += $$cat(generated/filelist, lines)
Dadurch wird nur der Dateiinhalt gelesen und jede Zeile als Eintrag für SOURCES
behandelt.
Hinweis: Die vollständige Liste der eingebetteten Funktionen finden Sie hier: http://doc.qt.io/qt-5/qmake-function-reference.html
Warnungen als Fehler behandeln
Das folgende Snippet verwendet die zuvor beschriebene bedingte Bereichsfunktion:
*g++*: QMAKE_CXXFLAGS += -Werror *msvc*: QMAKE_CXXFLAGS += /WX
Der Grund für diese Komplikation liegt darin, dass MSVC ein anderes Flag zum Aktivieren dieser Option hat.
Git-Version generieren
Das folgende Snippet ist nützlich, wenn Sie eine Präprozessordefinition erstellen müssen, die die aktuelle SW-Version enthält, die Sie von Git erhalten haben:
DEFINES += SW_VERSION=\\\"$$system(git describe --always --abbrev=0)\\\"
Dies funktioniert auf jeder Plattform, solange der Befehl git
verfügbar ist. Wenn Sie Git-Tags verwenden, wird das neueste Tag angezeigt, obwohl die Verzweigung vorangegangen ist. Ändern Sie den Befehl git describe
, um die Ausgabe Ihrer Wahl zu erhalten.
Fazit
Qmake ist ein großartiges Tool, das sich auf die Erstellung Ihrer plattformübergreifenden Qt-basierten Projekte konzentriert. In diesem Leitfaden haben wir die grundlegende Verwendung von Tools und die am häufigsten verwendeten Muster überprüft, die Ihre Projektstruktur flexibel und die Build-Spezifikation leicht lesbar und pflegeleicht halten.
Möchten Sie erfahren, wie Sie Ihre Qt-App besser aussehen lassen? Versuchen Sie: So erhalten Sie abgerundete Eckenformen in C++ mit Bezier-Kurven und QPainter: Eine Schritt-für-Schritt-Anleitung