Ich bin alles andere als ein Experte in Sachen Makefiles und beschreibe
hier lediglich, was ich verwendet habe bzw. glaube verstanden zu
haben. Das folgende kann voellig falsch sein. Ich uebernehme
keine Garantie irgendwelcher Art und hafte nicht fuer Schaeden, die
durch Verwendung der folgenden Informationen entstehen koennen.
Wer sich mal den Quelltext der automatisch erzeugten *.bpg-Dateien
(Borland Project Group, erzeugt ab mindestens Delphi 5) angesehen hat,
der wird sich gefragt haben, wozu die eigentlich gut sind. Eine solche
Datei sieht typischerweise so aus:
#---------------------------------------------------------
VERSION = BWS.01
#---------------------------------------------------------
!ifndef ROOT
ROOT = $(MAKEDIR)\..
!endif
#---------------------------------------------------------
MAKE = $(ROOT)\bin\make.exe -$(MAKEFLAGS) -f$**
DCC = $(ROOT)\bin\dcc32.exe $**
BRCC = $(ROOT)\bin\brcc32.exe $**
#---------------------------------------------------------
PROJECTS = Project1.exe Project2.exe
#---------------------------------------------------------
default: $(PROJECTS)
#---------------------------------------------------------
Project1.exe: Project1.dpr
$(DCC)
Project2.exe: Project2.dpr
$(DCC)
Leute mit C oder Unix / Linux Erfahrung werden dieses Format evtl.
wiedererkennen. So sehen typische Makefiles aus. Fuer alle anderen ist
wohl eine Erklaerung angebracht: Make ist ein Tool, mit dem haeufig bei
C Programmen die einzelnen Schritte der Programmerstellung
automatisiert werden, also in der Regel mindestens
-
Aufruf des Compilers fuer alle .c Dateien um Objekt-Dateien (.o) zu erzeugen.
-
Aufruf des Linkers fuer alle in Schritt 1 erzeugten .o Dateien, um ein Executable zu erzeugen.
Ein Makefile beinhaltet eine Beschreibung dessen, was zu tun ist. Make
kann wesentlich mehr als das oben aufgezaehlte, z.B. beliebige Programme
aufrufen, nicht nur einen C-Compiler und Linker. Auch Delphi bringt ein
Make-Programm mit, es befindet sich dort, wo alle Executables stehen, in
einer Standardinstallation unter 'c:\programme\borland\delphi5\bin'.
Warum sollte man ueberhaupt Make verwenden, wo doch Delphi so eine schoene
IDE hat?
Die IDE ist wunderbar zum Entwickeln und interaktiven Compilieren
von Programmen. Sie eignet sich weniger gut, um ein bestehendes
Projekt automatisch zu erzeugen. Dazu werden haeufig Batchdateien verwendet,
die jedoch um einiges weniger flexibel sind als ein Makefile.
Ich selbst habe fuer einige Zeit die erweiterte Batchsprache von 4NT zu
diesem Zweck eingesetzt, da mir die Moeglichkeiten des Standard Commandline
Interpreters unter Windows zu begrenzt waren. Make habe ich erst letzte
Woche 'entdeckt' und stellte fest, dass sich damit einiges wesentlich
einfacher automatisieren lies als mit 4NT.
Nochmal das Beispiel von oben:
#---------------------------------------------------------
VERSION = BWS.01
#---------------------------------------------------------
!ifndef ROOT
ROOT = $(MAKEDIR)\..
!endif
#---------------------------------------------------------
MAKE = $(ROOT)\bin\make.exe -$(MAKEFLAGS) -f$**
DCC = $(ROOT)\bin\dcc32.exe $**
BRCC = $(ROOT)\bin\brcc32.exe $**
#---------------------------------------------------------
PROJECTS = Project1.exe Project2.exe
#---------------------------------------------------------
default: $(PROJECTS)
#---------------------------------------------------------
Project1.exe: Project1.dpr
$(DCC)
Project2.exe: Project2.dpr
$(DCC)
Was bedeutet das nun alles?
Es gibt in diesem Beispiel folgende Hauptelemente:
-
Kommentare, dies sind die Zeilen, die mit einem # beginnen.
-
Makrozuweisungen, das sind alle Zeilen, die mit einem (meist gross geschriebenen) Makronamen, gefolgt von einem Gleichheitszeichen beginnen. Dem Makro wird als Wert das zugewiesen, was rechts vom Gleichheitszeichen steht.
-
Make-Anweisungen, das sind die Zeilen, die mit einem ! beginnen. Hier wird die Abarbeitung des Makefiles gesteuert. Im obigen Beispiel bedeutet '!ifndef ROOT', dass die Zeile bis zum folgenden '!endif' nur dann ausgefuehrt werden sollen, wenn das Makro ROOT keinen Wert hat.
-
Make Targets (Ziele), das sind die Zeilen, die mit einem Wort (meist einem Dateinamen) gefolgt von : beginnen. Rechts vom Doppelpunkt stehen alle Dateien oder andere Targets, die das jeweilige Target benoetigt, um erzeugt zu werden. Die Zeilen danach, die eingerueckt sind, sind Anweisungen an Make, was es zu tun hat. (Hinweis: Unix-Makefiles benutzen zur Einrueckung normalerweise ein TAB (Ascii 9) Zeichen, Borlands Make scheint jedoch auch Spaces zu akzeptieren.)
Zu Kommentaren muss ich wohl nicht viel sagen. ;-)
Auf der rechten Seite des Gleichheitszeichens kann auch ein Ausdruck der
Form $(MAKRONAME) vorkommen. Das bedeutet, dass der Inhalt des
Makros MAKRONAME an dieser Stelle eingesetzt wird. Die Zeile
bedeutet, dass dem Makro ROOT der Wert des Makros MAKEDIR gefolgt
von '\..' zugewiesen wird. MAKEDIR ist ein vordefiniertes Makro, welches
als Wert das Verzeichnis enthaelt, in dem sich das Make Executable befindet.
Hat also z.b. MAKEDIR den Wert
'c:\programme\borland\delphi5\bin', so erhaelt ROOT den Wert
'c:\programme\borland\delphi5\bin\..'. Die folgende Anweisung
DCC = $(ROOT)\bin\dcc32.exe $**
weist dann dem Makro DCC den Wert des Makros ROOT gefolgt von
'bin\dcc32.exe $**' zu. Damit ergibt sich fuer DCC der Wert
'c:\programme\borland\delphi5\bin\..\bin\dcc32.exe $**'.
Hinweis: Es ist nicht moeglich, ein Makro sich selbst zuzuweisen, d.h.
folgendes
fuehrt zu einem Fehler, weil Make diese Anweisung rekursiv unendlich
oft auszufuehren versucht.
Ausser dem im Beispiel angegebenen !ifndef/!endif gibt es noch weitere:
-
!elif ist eine Kombination aus !else und !if, welches ein zusaetzliches !endif einspart.
-
!else brauche ich wohl nicht erklaeren ;-)
-
!error, gefolgt von einer Fehlermeldung gibt diese Meldung aus und beendet Make
-
!if erlaubt zusaetzlich zu !ifdef und !ifndef andere Bedingungen
-
!include, gefolgt von einem Dateinamen, fuegt eine andere Datei an der Stelle ein, wo diese Anweisung steht.
-
!message, gefolgt von einer Meldung, gibt diese auf den Bildschirm aus.
-
!undef, gefolgt von einem Makronamen loescht dieses Makro, danach ergibt ein !ifdef auf dieses Makro false.
(Target = Ziel, deshalb 'das Target', oder?)
Obiges Beispiel enthaelt drei Make-Targets, naemlich
-
default
-
Project1.exe und
-
Project2.exe
Alle diese Targets sind explizite Targets, d.h. sie erzeugen einen
kompletten Dateinamen, nicht nur eine Extension.
Das Target 'default' enthaelt keine Anweisungen sondern lediglich ein
Liste von Vorbedingungen:
Dies bedeutet, dass alle in dem Makro PROJECTS angegebenen Dateien
(bzw. Targets) erzeugt werden muessen, bevor das Target default erzeugt
werden kann.
Das Target 'Project1.exe' hat die Vorbedingung 'Project1.dpr', d.h.
um es zu erzeugen, muss die Datei 'Project1.dpr' existieren und neuer
sein als 'Project1.exe':
Project1.exe: Project1.dpr
$(DCC)
Wenn diese Bedingung erfuellt ist, werden die danach augefuehrten
Befehle ausgefuehrt. In diesem Fall wird der Wert des Makros DCC
als Befehl betrachtet. Wie schon weiter oben festgestellt, hat es
den Wert 'c:\programme\borland\delphi5\bin\..\bin\dcc32.exe $**'.
Der erste Teil davon duerfte klar sein, es handelt sich um den
Aufruf des Delphi Commandline Compilers, der sich im Unterverzeichnis
'bin' der Delphi 5 Installation befindet. Als Parameter wird $**
uebergeben, ein spezielles vordefiniertes Makro, welches Make durch
die Liste der Dateien ersetzt, die das Target als Voraussetzungen
hatte, in diesem Fall also 'Project1.dpr'.
Der komplette Befehl sieht also wie folgt aus:
c:\programme\borland\delphi5\bin\..\bin\dcc32.exe Project1.dpr
Dies ruft den Compiler auf, der das Projekt neu compiliert.
Make versucht immer das erste explizite Target automatisch zu erzeugen,
wenn man ihm kein Target vorgibt. In obigem Beispiel ist das das Target
'default', aber der Name ist beliebig.
Ruft Make in einem Verzeichnis auf, in dem sich eine Delphi Projekt Group
befindet, so wird man enttaeuscht, es passiert nichts:
Fatal: Unable to open makefile
Ein etwas erfahrenerer Windows Anwender (oder vielleicht eher DOS Anwender)
wird vielleicht versuchen, die .bpg Datei als Parameter zu uebergeben, aber
auch das fuehrt nur zu der gleichen Fehlermeldung.
Ruft man Make mit der Option -h oder -? auf, so erhaelt man eine kurze
Zusammenfassung der Optionen. Daraus geht hervor, dass man die
.bpg-Datei mit der Option -f angeben muss:
fuehrt endlich zum Erfolg. Der Compiler wird fuer beide Projekte
aufgerufen und diese erzeugt.
Wer es einfacher mag, der kann eine Datei 'Makefile' mit folgendem
Inhalt im Projektverzichnis erstellen:
!include ProjectGroup.bpg
Make sucht naemlich, wenn es keine -f Option uebergeben bekommt nach
einer Datei diesen Namens. Die !include Anweisung bewirkt dann, dass
die eigentliche .bpg Datei ausgewertet wird.
Der Delphi Commandline Compiler (dcc32) hat ein Feature, welches evtl.
aergerliche Auswirkungen haben kann. Er liest seine Konfiguration aus
der Datei '<projektname>.cfg'. Diese Datei wird von der IDE automatisch
erzeugt und enthaelt die Einstellungen des Projekts, wie sie auch in
der .dof-Datei stehen, nur eben in dem Format, wie es dcc32 versteht.
Das hoert sich jetzt an, als sei es eine gute Sache und meist ist es
das auch, aber in der Regel benutzt man Makefiles um Executables mit
definierten Einstellungen zu erzeugen. Wenn nun aber die .cfg-Datei
jedesmal geaendert wird, wenn man eine Einstellung in der IDE aendert,
so kann man sich leicht auch wichtige Einstellungen, z.B. die fuer
Debug-Informationen ueberschreiben, welche man in der Regel in
automatisch erzeugten Executables nicht haben will.
Abhilfe schafft hier, die benoetigten Einstellungen im Makefile
zu definieren und als Parameter and dcc32 zu uebergeben und die
.cfg-Datei zu loeschen:
EXEOUT = q:\MyProject
UNITOUT = $(EXEOUT)\dcu
DCCOPT = -D- -I-
DCC = $(ROOT)\bin\dcc32.exe -E"$(OUTPUT)" -N"$(UNITOUTPUT)" $DCCOPT $**
Project1.exe: Project1.dpr
del Project1.cfg
$(DCC)
Wie man sieht, kann man in einem Makefile auch Shell-Befehle ausfuehren
lassen, in diesem Fall 'del'.
Etwas komplizierter wird es, wenn man den 'del' Befehl nicht fuer jedes
einzelne Projekt angben will. In diesem Fall kann man ein sogenanntes
implizites Target verwenden, welches Make angibt, wie man aus einer
.dpr Datei eine .exe Datei erzeugt:
Project1.exe: Project1.dpr
.dpr.exe:
del $(<:.dpr=.cfg)
$(DCC)
Die erste Zeile ist wieder die Angabe, dass 'Project1.exe' aus 'Project1.dpr'
erzeugt wird (eigentlich ist sie in diesem Fall nicht notwendig, aber die
Delphi IDE weigert sich, eine .bpg-Datei zu laden, wenn nicht fuer alle
Projekte explizite Targets angegeben sind). Es gibt keine Regeln, wie
dieses Target erzeugt wird. Deshalb sucht Make nach einem impliziten Target,
welches beschreibt, wie man aus einer .dpr Datei eine .exe Datei erzeugt.
Dieses zweite Target sieht ziemlich kompliziert aus, weil es ein weiteres
Feature von Make verwendet: String-Ersetzung in Makros.
Zunaechst ruft es den 'del' Befehl auf und uebergibt ihm das Makro $< als
Parameter. Da wir aber nicht die .dpr Datei, sondern die .cfg Datei loeschen
wollen, muss die Extension geaendert werden. Dies geschiet durch das Anhaengen
von ':.dpr=.cfg' an das Makro. Zusaetzlich muss das Makro in Klammern gesetzt
werden, damit Make erkennt, dass der gesamte Ausdruck und nicht nur das
erste Zeichen zum Makro gehoert.
Die Zeile bedeutet also: Nimm eine .dpr Datei, ersetze alle Vorkommen von
'.dpr' durch '.cfg' und uebergib das als Parameter fuer den 'del' Befehl.
Die zweite Anweisung ist dann wieder einfach der Aufruf des
Commandline-Compilers.
Achtung: Wenn man in der IDE ein neues Projekt zu einer Project Group
hinzufuegt, so erzeugt sie es nicht nur einen weiteren Eintrag fuer das
Makro 'PROJECTS' sondern fuegt ein neues Target fuer dieses Projekt ein, in
dem ein Aufruf fuer den Compiler steht. Will man obiges implizites Target
verwenden, so muss man diesen Aufruf manuell aus der .dpg Datei loeschen.
Die IDE erzeugt zu jedem Projekt automatisch eine .res-Datei, die das
Icon des Programms und die Versionsinformationen enthaelt. Wer wie ich
ein Sourcecode-Verwaltungsprogramm verwendet, der mag es wahrscheinlich
ebenfalls nicht, dass sich dort drin Binaerdateien ansammeln. Ich fuer
mein Teil checke .res-Dateien nicht ein sondern lasse sie jedesmal von
der IDE neu erzeugen. Die Versionsinformationen bleiben dabei erhalten,
denn diese nimmt die IDE aus der .dof-Datei. Das Programmicon wird
jedoch nur in der .res-Datei gespeichert und geht damit verloren.
Das ist ziemlich aergerlich und deshalb habe ich mir folgende Loesung
fuer das Problem ausgedacht:
Ich speichere das Programmicon als <projectname>.ico im gleichen Verzeichnis
wie die .dpr Datei.
Nun rufe ich bei jedem Compile-Vorgang ein selbstgeschriebenes
(Delphi-)Programm, welches eine .dof-Datei ausliest (das ist eine INI-Datei,
man kann also TIniFile benutzen) und mir daraus eine .rc-Datei mit den
Versionsinformationen generiert und auch gleich die .ico-Datei, falls sie
existiert, an diese anhaengt:
// Versionsinformationen hier...
MAINICON ICON LOADONCALL MOVEABLE DISCARDABLE IMPURE <projectname>.ico
Aus der so erzeugten .rc-Datei kann man dann mit dem Borland Resource
Compiler wieder eine .res-Datei erzeugen. Und damit man das
nicht manuell machen muss, trägt man die Aufrufe einfach in das
Makefile ein:
#---------------------------------------------------------
VERSION = BWS.01
#---------------------------------------------------------
!ifndef ROOT
ROOT = $(MAKEDIR)\..
!endif
#---------------------------------------------------------
MAKE = $(ROOT)\bin\make.exe -$(MAKEFLAGS) -f$**
DCC = $(ROOT)\bin\dcc32.exe $**
BRCC = $(ROOT)\bin\brcc32.exe $**
#---------------------------------------------------------
# Mein Utility:
DOF2RC = Dof2Rc.exe $**
#---------------------------------------------------------
PROJECTS = Project1.exe Project2.exe
# die Resource-Dateien, wieder mit String-Ersetzung
RESOURCES = $(PROJECTS:.exe=.res)
#---------------------------------------------------------
default: $(RESOURCES) $(PROJECTS)
#---------------------------------------------------------
# diese beiden Eintraege, damit die IDE nicht motzt
Project1.exe: Project1.dpr
Project2.exe: Project2.dpr
.dof.rc:
&$(DOF2RC)
.rc.res:
&$(BRCC)
Auf diese Weise erhaelt man mit einem Aufruf von Make
fertige Executables inclusive Icons und Versionsinformationen.
Natuerlich kann es sein, dass das alles noch viel einfacher geht. Ich
bin, wie schon oben gesagt, alles andere als ein Makefile-Experte.
Leider scheint Borland vergessen zu haben, die Dokumentation zu Make
in die Delphi-Installation aufzunehmen. Unter folgender Adresse kann man
sich jedoch die Hilfe zu den Commandline-Tools des Borland C++ Builders
herunterladen, in der unter anderem auch Make beschrieben wird:
ftp://ftp.borland.com/pub/bcppbuilder/techpubs/bcb5/b5tool.zip
(c) Copyright 2002 by Thomas Mueller, alle Rechte vorbehalten
This document was generated using AFT v5.096
|