Als Ausgangspunkt der folgenden Gedanken dient das kleine makefile aus dem letzten Abschnitt:
CXX = g++ # der zu verwendende C++-Compiler CXXFLAGS = -c # Optionen zum Kompilieren von C++ CC = gcc # Compiler für C und gleichzeitig Linker haupt: haupt.o up.o db.o $(CXX) $^ -o $@ haupt.o: haupt.cpp up.h db.h up.o: up.cpp up.h db.h db.o: db.cpp db.h .c.o: $(CXX) $(CFLAGS) $<
Das Linkkommando ist wirklich projektspezifisch, und kann beim Erstellen des makefile leicht festgelegt werden; es wird sich selten ändern, und eine solche Änderung ist auch nicht fehlerkritisch.
Alle anderen projektspezifischen Angelegenheiten (außer den Abhängigkeiten der Quelltexte) können mit Makrodefinitionen so angelegt werden, daß sie leicht wartbar sind.
Der einzige fehlerkritische Punkt besteht in den Abhängigkeiten der
Quelltexte untereinander.
Diese zu finden ist intellektuell nicht sehr anspruchsvoll:
eine irgendwas.o hängt von irgendwas.cpp
ab; weiterhin muß man
den Quelltext nach #include "
..."
-Anweisungen
durchsuchen, alle so gefundenen Dateinamen als weitere prerequisites
vermerken, sowie die gefundenen Includedateien rekursiv wiederum nach
#include "
..."
-Anweisungen durchsuchen.
Auch wenn diese Arbeit prinzipiell nicht schwierig ist, so ist sie
doch ab einer gewissen Projektgröße mühsam und fehlerträchtig, zumal
man bei jeder Änderung an #include "
..."
-Anweisungen
das makefile sorgfältig korrigieren muß.
Da man eine solche mühsame, aber eigentlich nicht zu schwierige Aufgabe am besten delegiert, stellt sich die Frage, wem man das Herausfinden der Abhängigkeiten überlassen könnte. Es drängt sich geradezu auf, ein Programm zu schreiben (unter vernünftigen Systemen vielleicht als Shellskript?), welches die Regeln kennen muß, nach denen der Compiler Includedateien sucht, und dementsprechend die Abhängigkeiten liefert.
Wer mit einem GNU-System arbeiten darf, hat auch hier Glück: Der gcc
beziehungsweise g++ kennen die Optionen -M
und
-MM
.
Damit aufgerufen kompilieren sie die angegebenen Quelltexte nicht,
sondern liefern nur zur Standardausgabe die Abhängigkeiten,
praktischerweise gleich im passenden Format für das makefile.
Die beiden Varianten unterscheiden sich darin, daß -M
alle
Includedateien auswertet, während -MM
nur die mit
"
..."
eingebundenen Dateien durchsucht (nicht die mit
<
...>
eingebundenen).
Beispielaufruf:
klaus@aw38:~/skript_cpp_20011124/module > gcc -MM *.cpp db.o: db.cpp db.h haupt.o: haupt.cpp up.h db.h up.o: up.cpp up.h db.h
Diese Ausgabe könnte man jetzt manuell in das makefile
übernehmen; eleganter ist es aber, die Abhängigkeiten in einer
eigenen Datei stehen zu lassen (beispielsweise dep.make) und
stattdessen in das makefile eine include
-Anweisung zu
schreiben. Dadurch wird der Inhalt der angegebenen Datei beim Lesen
des makefile mit ausgewertet. Der Vorteil dieser Vorgehensweise
liegt darin, daß man jetzt eine eigene Datei für die Abhängigkeiten hat, die man als Target im
Makefile angeben kann:
CXX = g++ # der zu verwendende C++-Compiler CXXFLAGS = -c # Optionen zum Kompilieren von C++ CC = gcc # Compiler für C und gleichzeitig Linker haupt: haupt.o up.o db.o $(CXX) $^ -o $@ dep.make: $(CXX) $(CFLAGS) -MM haupt.cpp up.cpp db.cpp > $@ include dep.make
Beim ersten Aufruf existiert die Includedatei noch nicht; weil aber eine Regel für das Target dep.make vorhanden ist, wird die Datei von make automatisch erzeugt.
Als Lohn der Mühe muß man sich nicht mehr im Detail um die
Abhängigkeiten der Quelltexte kümmern, sondern braucht nur nach
Änderungen an Includeanweisungen einmalig:
make dep.make
aufrufen, um das makefile zu aktualisieren.
AnyWare@Wachtler.de