PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Lektion 4: Arbeiten mit Makefiles



anda_skoa
11-02-2003, 16:14
Lektion 4: Arbeiten mit Makefiles

Wenn wir, wie in Lektion 3, ein Projekt auf mehrere Dateien aufgeteilt haben, brauchen wir ein Linkerkommando und pro CPP Datei ein Compilerkommando.

Außerdem müssen wir uns darum kümmern, welche Dateien neu kompiliert werden müssen, wenn wir eine Datei ändern.
Die Änderung einer CPP Datei erfordert nur, dass diese neu kompiliert wird, eine Änderung eines Headers
erfordert potentiell die Neukompilierung jeder CPP Datei, die diesen Header direkt oder indirekt (über einen anderen Header) inkludiert.

Abhilfe für diese Problematik schaffen uns Makefiles.

Ein Makefile ist eine Steuerdatei für das make Programm.
Makefiles bestehen aus einer Listen von Regeln, die die Reihenfolge der auszuführenden Befehle angibt.

Eine Regel ist wie folgt aufgebaut
(<tab> entspricht einem Tabulator, d.h. im Editor die Tabulator-Taste drücken)



Name: Abhängigkeiten
<tab>Kommando
.
.
.
<tab>Kommando


Der Name oder Target einer Regel ist im Makefile eindeutig zu wählen.
Eine Regel wird über ihren Namen aktiviert, sowohl innerhalb des Makefiles als auch von außerhalb, also wenn man make aufruft.



#> make Name


Wird beim Aufruf von make kein Name angegeben, wird die erste Regel im Makefile aktiviert.

Bei der Aktivierung werden zuerst die Abhängigkeiten geprüft.
Ist eine Abhängigkeit der Name einer weiteren Regel, wird diese Regel aktiviert.
Ist die Abhängigkeit eine Datei, wird geprüft, ob das Datum der letzten Änderung jünger ist, als da entsprechende Datum der Zieldatei, die von der Regel erzeugt wird.
Ist das der Fall, werden die Kommandos der Reihe nach ausgeführt.

Eine Regel kann beliebig viele Kommandos haben. Das Ende ist durch eine Leerzeile gekennzeichnet.

Beispiel: Makefile für Lektion 3

Datei Makefile:


lektion3: lektion3.o qttutwidget.o
<tab>g++ -o lektion3 lektion3.o qttutwidget.o -Wall -L/usr/lib/qt-3.0.3/lib -lqt-mt

lektion3.o: lektion3.cpp qttutwidget.h
<tab>g++ -c -o lektion3.o lektion3.cpp -Wall -I/usr/lib/qt-3.0.3/include

qttutwidget.o: qttutwidget.cpp qttutwidget.h
<tab>g++ -c -o qttutwidget.o qttutwidget.cpp -Wall -I/usr/lib/qt-3.0.3/include


Wir können nun mit


#> make lektion3

oder einfach nur


#> make


Das Programm aus Lektion 3 erstellen lassen und make kümmerst sich darum, welche Kommandos erforderlich sind.

Wie geht make dabei vor?

Zuerst wird die Regel lektion3 aktiviert.
Ihre Abhängigkeiten sind die beiden Targets lektion3.o und qttutwidget.o
Die Namen wurden so gewählt, dass sie wie die zuerzeugenden Dateien heißen, damit man leichter den Überblick behält.

Daher werden also zunächst die beiden abhängigen Regeln aktiviert.
Hat sich im Vergleich zu lektion3.o einer der beiden Dateien lektion3.cpp oder qttutwidget.h geändert, wird lektion3.o neu kompiliert.

Ähnliches gilt für qttutwidget.o

Hat eine der beiden Regeln ihr Kommando ausgeführt, führt nun auch die oberste Regel ihr Kommando aus und linkt das Programm neu.

Variablen
Zur leichteren Übertragbarkeit können in Makefiles auch Variablen verwendet werden.
Die Schreibweise ist dabei ähnlich wie in Shellskripten.

Datei Makefile, diesmal mit Variablen


QTDIR=/usr/lib/qt-3.0.3
INCLUDES=-I$(QTDIR)/include
LIBS=-L$(QTDIR)/lib -lqt-mt

lektion3: lektion3.o qttutwidget.o
<tab>g++ -o lektion3 lektion3.o qttutwidget.o -Wall $(LIBS)

lektion3.o: lektion3.cpp qttutwidget.h
<tab>g++ -o lektion3.o -c lektion3.cpp -Wall $(INCLUDES)

qttutwidget.o: qttutwidget.cpp qttutwidget.h
<tab>g++ -o qttutwidget.o -c qttutwidget.cpp -Wall $(INCLUDES)


Makefiles generieren mit qmake

Bei vielen Dateien und komplexeren Abhängigkeiten wird das händische Erstellen von Makefiles zu einer schwierigen und zeitaufwendigen Aufgabe.

Was liegt also näher, als nach einem Hilfmittel zu suchen, dass Makefiles erstellen leichter macht.
So ein Tool wird von Qt mitgebracht: qmake (http://doc.trolltech.com/3.0/qmake-manual.html)

Ganz ohne Steuerdatei geht es damit auch nicht, aber die qmake Projektdateien sind sehr simpel aufgebaut:

lektion3.pro


TEMPLATE = app

CONFIG += qt thread warn_on

SOURCES = lektion3.cpp qttutwidget.cpp
HEADERS = qttutwidget.h

TARGET = lektion3


Sehen wir uns die Datei genauer an:



TEMPLATE = app

Wir möchten ein Makefile für eine Applikation. qmake kennt verschiedene Vorlagen für unterschiedliche Projekttypen:
app - für eine Applikation. Das ist auch die Standardvorlage und wird benutzt, wenn nichts anderes angegeben wird.

lib - für eine Bibliothek

subdirs - erzeugt ein Makefile für eine Projekt, in dem die eigentlichen Projektteile in Unterverzeichnisse des Projektverzeichnisses liegen.



CONFIG += qt thread warn_on

Wir wollen mit Qt linken, mit der Version mit Threadunterstützung und wir hätten gerne Compilerwarnungen.
Das warn_on wird für den g++ zu einem -Wall.

Es gibt für die CONFIG noch eine Reihe andere Möglichkeiten, die alle in der Dokumentation zu qmake angeführt sind.

Das += wirkt dabei wie der C++ Operator +=, d.h. die neune Optione werden an eventuell zuvor gesetzte angefügt.
Dieser Operator kann auch bei SOURCES, HEADERS und INTERFACES benutzt werden.



SOURCES = lektion3.cpp qttutwidget.cpp

Diese Zeile gibt an, welche CPP Dateien zu unserem Projekt gehören.



HEADERS = qttutwidget.h

Diese Zeile gibt an, welche Header Dateien zu unserem Projekt gehören.
Das wird vorallem dann wichtig, wenn man eigene Signal/Slot Deklarationen hat.



TARGET = lektion3

Das gibt an, wie unser Endprodukt, in unserem Fall das Programm, heißen soll.

Um mit Hilfe dieser Datei und qmake ein Makefile erzeugen zu können, müssen wir in der Konsole einige Vorbereitungen treffen.



#> export QMAKESPEC=linux-g++

Das setzt die Umgebungsvariable QMAKESPEC, die qmake sagt, für welche Plattform und welchen Compiler das Makefile erzeugt werden soll.
Das ist bei Qt wichtig, da das selbe Programm auf verschiedenen Plattformen mit verschiedenen Compilern kompilierbar sein soll und das selbe auch für die Projektdatei gelten sollte.

Als nächstes überprüfen wir, ob die Umgebungsvariable QTDIR gesetzt ist:


#> echo $QTDIR
/usr/lib/qt-3.0.3

Ok, in unserem Fall ist alles in Ordnung.

Kommt kein Pfad, muss QTDIR erst gesetzt werden


#> export QTDIR=/usr/lib/qt-3.0.3


Bei Systemen wo die Headerdateien und Bibliotheken nicht unterhalb eines gemeinsamen Verzeichnis liegen,
wie das zum Beispiel bei Debian der Fall ist, muss man einen kleinen Trick anwenden :)

Dieser Trick besteht darin, ein Pseudo Qt Verzeichnis anzulegen, dass dann symbolische Links zu den einzelnen Verzeichnisen enthält.

Das macht man entweder als root in einem systemweiten Verzeichnis oder als User in einem Verzeichnis wo man Schreibrechte hat.

Hier als Beispiel mit root Rechten


#> mkdir /usr/qtdir
#> cd /usr/qtdir
#> ln -s /usr/lib/qt lib
#> ln -s /usr/bin bin
#> ln -s /usr/include/qt3 include

Damit enthält unser qtdir Verzeichnis die erwarteten Unterverzeichnisse bin, include und lib.
Unsere Variable QTDIR setzen wir dann auf /usr/qtdir

Nach diesen Vorbereitungen können wir nun qmake starten


#> qmake -o Makefile lektion3.pro

Das sieht wie ein Compileraufruf aus und hat praktisch eine sehr ähnliche Funktion.
-o gibt an wie das Produkt heißen soll, danach kommt die Datei, die als Grundlage dient, also unsere Projektdatei.

Jetzt können wir unser Projekt wie zuvor mit den manuell erzeugten Makefiles erstellen lassen


#> make


qmake hat noch ein paar weitere Targets erzeugt, zum Beispiel clean.
Ein


#> make clean

löscht alle Dateien, die durch einen normalen make Aufruf erzeugt werden, also das ausführbare Programm und die Objectfiles.

Wird die Projektdatei geändert, erkennt das das alte Makefile über eine Regel, die die Projektdatei als Abhängigkeit hat, und erzeugt ein neues Makefile.

anda_skoa
18-02-2003, 22:10
Ins Tutorial übernommen.

Ciao,
_