2.5. Make

2.5.1. Was ist make?

Wenn Sie an einem einfachen Programm mit nur einer oder zwei Quelltextdateien arbeiten, ist die Eingabe von

% cc file1.c file2.c

zwar nicht aufwendig, wird aber mit zunehmender Anzahl der Quelltextdateien sehr lästig—und auch das Kompilieren kann eine Weile dauern.

Eine Möglichkeit dies zu umgehen besteht in der Verwendung von Objektdateien, wobei man nur die Quelltextdateien neu kompiliert, die verändert wurden. So könnten wir etwa folgendes erhalten:

% cc file1.o file2.ofile37.c

falls wir seit dem letzten Kompiliervorgang nur die Datei file37.c verändert haben. Dadurch könnte der Kompiliervorgang um einiges beschleunigt werden, es muß jedoch immer noch alles von Hand eingegeben werden.

Oder wir könnten uns ein Shell Skript schreiben. Dieses würde jedoch alles immer wieder neu kompilieren, was bei einem großen Projekt sehr ineffizient wäre.

Was ist, wenn wir hunderte von Quelltextdateien hätten? Was ist, wenn wir in einem Team mit anderen Leuten arbeiten würden, die vergessen uns Bescheid zu sagen, falls sie eine der Quelltextdateien verändert haben, die wir ebenfalls benutzen?

Vielleicht könnten wir beide Lösungen kombinieren und etwas wie ein Shell Skript schreiben, welches eine Art magische Regel enthalten würde, die feststellt, welche Quelltextdateien neu kompiliert werden müssten. Alles was wir bräuchten wäre ein Programm, das diese Regeln verstehen könnte, da diese Aufgabe etwas zu kompliziert für eine Shell ist.

Dieses Programm heißt make. Es liest eine Datei namens makefile, welche ihm sagt, wie unterschiedliche Dateien voneinander abhängen, und berechnet, welche Dateien neu kompiliert werden müssen und welche nicht. Zum Beispiel könnte eine Regel etwas sagen wie “wenn fromboz.o älter als fromboz.c ist, bedeutet dies, daß jemand die Datei fromboz.c verändert haben muß, und diese daher neu kompiliert werden muß.” Das makefile enthält außerdem Regeln die make sagen, wie die Quelltextdatei neu kompiliert werden muß, was dieses Tool noch sehr viel mächtiger macht.

Makefiles werden normalerweise im selben Verzeichnis wie die Quelltextdateien abgelegt, zu denen sie gehören, und kann makefile, Makefile oder MAKEFILE heißen. Die meisten Programmierer verwenden den Namen Makefile, da diese Schreibweise dafür sorgt, daß die Datei gut lesbar ganz oben in der Verzeichnisliste aufgeführt wird. [1]

2.5.2. Beispielhafte Verwendung von make

Hier ist eine sehr einfache make Datei:

foo: foo.c
	cc -o foo foo.c

Sie besteht aus zwei Zeilen, einer Abhängigkeitszeile und einer Erzeugungszeile.

Die Abhängigkeitszeile hier besteht aus dem Namen des Programms (auch Ziel genannt), gefolgt von einem Doppelpunkt und einem Leerzeichen, und anschließend dem Namen der Quelltextdatei. Wenn make diese Zeile liest überprüft es die Existenz von foo; falls diese Datei existiert vergleicht es das Datum der letzten Änderung von foo mit der von foo.c. Falls foo nicht existiert, oder älter als foo.c ist, liest es die Erzeugungszeile um herauszufinden, was zu tun ist. Mit anderen Worten, dies ist die Regel die festlegt, wann foo.c neu kompiliert werden muß.

Die Erzeugungszeile beginnt mit einem tab (drücken Sie dazu die tab-Taste) gefolgt von dem Befehl, mit dem Sie foo manuell erzeugen würden. Wenn foo veraltet ist, oder nicht existiert, führt make diesen Befehl aus, um die Datei zu erzeugen. Mit anderen Worten, dies ist die Regel die make sagt, wie foo.c kompiliert werden muß.

Wenn Sie also make eingeben wird dieses sicherstellen, daß foo bzgl. Ihrer letzten Änderungen an foo.c auf dem neuesten Stand ist. Dieses Prinzip kann auf Makefiles mit hunderten von Zielen—es ist bei FreeBSD praktisch möglich, das gesamte Betriebssystem zu kompilieren, indem man nur make world im richtigen Verzeichnis eingibt!

Eine weitere nützliche Eigenschaft der makefiles ist, daß die Ziele keine Programme sein müssen. Wir könnten zum Beispiel eine make Datei haben, die wie folgt aussieht:

foo: foo.c
	cc -o foo foo.c

install:
	cp foo /home/me

Wir können make sagen welches Ziel wir erzeugt haben wollen, indem wir etwas wie folgt eingeben:

% make target

make wird dann nur dieses Ziel beachten und alle anderen ignorieren. Wenn wir zum Beispiel make foo mit dem obigen makefile eingeben, dann wird make das Ziel install ignorieren.

Wenn wir nur make eingeben wird make immer nur nach dem ersten Ziel suchen und danach mit dem Suchen aufhören. Wenn wir hier also nur make eingegeben hätten, würde es nur zu dem Ziel foo gehen, gegebenenfalls foo neu kompilieren, und danach einfach aufhören, ohne das Ziel install zu beachten.

Beachten Sie, daß das install-Ziel von nichts anderem abhängt! Dies bedeutet, daß der Befehl in der nachfolgenden Zeile immer ausgeführt wird, wenn wir dieses Ziel mittels make install aufrufen. In diesem Fall wird die Datei foo in das Heimatverzeichnis des Benutzers kopiert. Diese Vorgehensweise wird häufig bei makefiles von Anwendungen benutzt, damit die Anwendung nach erfolgreicher Kompilierung in das richtige Verzeichnis installiert werden kann.

Dieser Teil ist etwas schwierig zu erklären. Wenn Sie immer noch nicht so richtig verstanden haben, wie make funktioniert, wäre es das Beste, sie erstellen sich selber ein einfaches Programm wie “hello world” und eine make Datei wie die weiter oben angegebene, und experimentieren damit selber ein bißchen herum. Als nächstes könnten Sie mehrere Quelltextdateien verwenden, oder in Ihrer Quelltextdatei eine Header-Datei includen. Der Befehl touch ist an dieser Stelle ganz hilfreich—er verändert das Datum einer Datei, ohne das Sie diese extra editieren müssen.

2.5.3. Make und include-Dateien

C-Code beginnt häufig mit einer Liste von Dateien, die included werden sollen, zum Beispiel stdio.h. Manche dieser Dateien sind include-Dateien des Systems, andere gehören zum aktuellen Projekt, an dem Sie gerade arbeiten:

#include <stdio.h>
#include "foo.h"

int main(....

Um sicherzustellen, daß diese Datei neu kompiliert wird, wenn foo.h verändert wurde, müssen Sie diese Datei Ihrem Makefile hinzufügen:

foo: foo.c foo.h

Sobald Ihr Projekt größer wird und Sie mehr und mehr eigene include-Dateien verwalten müssen wird es nur noch sehr schwer möglich sein, die Übersicht über alle include-Dateien und Dateien, die von diesen abhängen, beizubehalten. Falls Sie eine include-Datei verändern, jedoch das erneute Kompilieren aller Dateien, die von dieser Datei abhängen, vergessen, werden die Folgen verheerend sein. Der gcc besitzt eine Option, bei der er Ihre Dateien analysiert und eine Liste aller include-Dateien und deren Abhängigkeiten erstellt: -MM.

Wenn Sie das Folgende zu Ihrem Makefile hinzufügen:

depend:
	gcc -E -MM *.c > .depend

und make depend ausführen, wird die Datei .depend mit einer Liste von Objekt-Dateien, C-Dateien und den include-Dateien auftauchen:

foo.o: foo.c foo.h

Falls Sie foo.h verändern werden beim nächsten Aufruf von make alle Dateien, die von foo.h abhängen, neu kompiliert.

Vergessen Sie nicht jedes mal make depend aufzurufen, wenn Sie eine include-Datei zu einer Ihrer Dateien hinzugefügt haben.

2.5.4. FreeBSD Makefiles

Makefiles können eher schwierig zu schreiben sein. Glücklicherweise kommen BSD-basierende Systeme wie FreeBSD mit einigen sehr mächtigen solcher Dateien als Teil des Systems daher. Ein sehr gutes Beispiel dafür ist das FreeBSD Portssystem. Hier ist der grundlegende Teil eines typischen Makefiles des Portssystems:

MASTER_SITES=   ftp://freefall.cdrom.com/pub/FreeBSD/LOCAL_PORTS/
DISTFILES=      scheme-microcode+dist-7.3-freebsd.tgz

.include <bsd.port.mk>

Wenn wir jetzt in das Verzeichnis dieses Ports wechseln und make aufrufen, passiert das Folgende:

  1. Es wird überprüft, ob sich der Quelltext für diesen Port bereits auf Ihrem System befindet.

  2. Falls dies nicht der Fall ist wird eine FTP-Verbindung zu der URL in MASTER_SITES aufgebaut und der Quelltext heruntergeladen.

  3. Die Checksumme für den Quelltext wird berechnet und mit der schon bekannten und für sicher und gut empfundenen verglichen. Damit wird sichergestellt, daß der Quelltext bei der Übertragung nicht beschädigt wurde.

  4. Sämtliche Anpassungen, die nötig sind, damit der Quelltext unter FreeBSD funktioniert, werden vorgenommen—dieser Vorgang wird auch patchen genannt.

  5. Alle speziellen Konfigurationen, die am Quelltext nötig sind, werden vorgenommen. (Viele UNIX® Programmdistributionen versuchen herauszufinden, auf welcher UNIX-Version sie kompiliert werden sollen und welche optionalen UNIX-Features vorhanden sind—an dieser Stelle erhalten sie die Informationen im FreeBSD Ports Szenario).

  6. Der Quelltext für das Programm wird kompiliert. Im Endeffekt wechseln wir in das Verzeichnis, in das der Quelltext entpackt wurde, und rufen make auf—die eigene make-Datei des Programms besitzt die nötigen Informationen um dieses zu bauen.

  7. Wir haben jetzt eine kompilierte Version des Programmes. Wenn wir wollen können wir dieses jetzt testen; wenn wir überzeugt vom Programm sind, können wir make install eingeben. Dadurch werden das Programm sowie alle zugehörigen Dateien an die richtige Stelle kopiert; es wird auch ein Eintrag in der Paketdatenbank erzeugt, sodaß der Port sehr einfach wieder deinstalliert werden kann, falls wir unsere Meinung über dieses geändert haben.

Ich glaube jetzt werden Sie mit mir übereinstimmen, daß dies ziemlich eindrucksvoll für ein Skript mit vier Zeilen ist!

Das Geheimnis liegt in der letzten Zeile, die make anweist, in das makefile des Systems mit dem Namen bsd.port.mk zu sehen. Man kann diese Zeile zwar leicht übersehen, aber hierher kommt all das klevere Zeugs—jemand hat ein makefile geschrieben, welches make anweist, alle weiter oben beschriebenen Schritte durchzuführen (neben vielen weiteren Dingen, die ich nicht angesprochen habe, einschließlich der Behandlung sämtlicher Fehler, die auftreten könnten) und jeder kann darauf zurückgreifen, indem er eine einzige Zeile in seine eigene make-Datei einfügt!

Falls Sie einen Blick in die makefiles des Systems werfen möchten, finden Sie diese in /usr/share/mk. Es ist aber wahrscheinlich besser, wenn Sie damit noch warten, bis Sie ein bißchen mehr Praxiserfahrung mit makefiles gesammelt haben, da die dortigen makefiles sehr kompliziert sind (und wenn Sie sich diese ansehen sollten Sie besser eine Kanne starken Kaffee griffbereit haben!)

2.5.5. Fortgeschrittene Verwendung von make

Make ist ein sehr mächtiges Werkzeug und kann noch sehr viel mehr als die gezeigten einfachen Beispiele weiter oben. Bedauerlicherweise gibt es mehrere verschiedene Versionen von make, und sie alle unterscheiden sich beträchtlich voneinander. Der beste Weg herauszufinden was sie können ist wahrscheinlich deren Dokumentation zu lesen—hoffentlich hat diese Einführung Ihnen genügend Grundkenntnisse vermitteln können, damit Sie dies tun können.

Die Version von make, die in FreeBSD enthalten ist, ist Berkeley make; es gibt eine Anleitung dazu in /usr/share/doc/psd/12.make. Um sich diese anzusehen, müssen Sie

% zmore paper.ascii.gz

in diesem Verzeichnis ausführen.

Viele Anwendungen in den Ports verwenden GNU make, welches einen sehr guten Satz an “info”-Seiten mitbringt. Falls Sie irgendeinen dieser Ports installiert haben wurde GNU make automatisch als gmake mit installiert. Es ist auch als eigenständiger Port und Paket verfügbar.

Um sich die Info-Seiten für GNU make anzusehen müssen Sie die Datei dir in /usr/local/info um einen entsprechenden Eintrag erweitern. Dies beinhaltet das Einfügen einer Zeile wie

 * Make: (make).                 The GNU Make utility.

in die Datei. Nachdem Sie dies getan haben können Sie info eingeben und dann den Menüeintrag make auswählen (oder Sie können in Emacs die Tastenkombination C-h i verwenden).

Fußnoten

[1]

Verwenden Sie nicht MAKEFILE mit lauter Großbuchstaben, da diese Schreibweise häufig für Dokumentationsdateien wie README benutzt wird.

Wenn Sie Fragen zu FreeBSD haben, schicken Sie eine E-Mail an <[email protected]>.
Wenn Sie Fragen zu dieser Dokumentation haben, schicken Sie eine E-Mail an <[email protected]>.