original in nl Wilbert Berendsen
nl to en Philip de Groot
en to de Guido Socher
Wilbert Berendsen ist professioneller Musiker und enthusiastischer Linuxbenutzer. Früher hat er mal Assemblercode für den Z80 geschrieben. Jetzt benutzt er Linux für all seine Arbeit. Aus Spaß schreibt er Einführungsartikel und unterhält eine kleine Webseite unter http://www.xs4all.nl/~wbsoft/. Viva open source!
Fast jeder, der Linux nutzt, hat das Programm make schon
einmal benutzt. make kommt beim Kompilieren des Kernels, bei
der Installation eines Softwarepaketes und an vielen anderen Stellen
zum Einsatz. 'Make' ist ein sehr wichtiges Werkzeug für die
Softwareentwicklung.
Make hat jedoch noch viel mehr Möglichkeiten!
In diesem Artikel werden wir sehen, daß make ein leistungsfähiges Werkzeug für tägliche Aufgaben wie das Schreiben von Artikeln, Büchern oder Webseiten sein kann. Im Laufe dieser Einführung werden wir auch viele andere 'Unix Tricks' kennenlernen. Am Ende dieses Artikels werde ich dann noch ein paar Tips zu Make geben. Bitte beachte: Wir reden über Linux, aber im Prinzip kann man make auf jedem Betriebssystem einsetzen.
Wir brauchen ein einfaches System, um das Layout vom Inhalt zu trennen. Eine sehr leistungsfähige Lösung wäre: Den Inhalt jedes Mal, wenn eine Seite angefragt wird, aus einer Datenbank zu lesen. PHP oder Microsoft ASP funktionieren so. Wir wollen jedoch einfach ganz normale HTML Seiten speichern. Desweiteren ändert sich der Inhalt nicht so oft, damit eine Datenbank wirklich Sinn macht.
Wir werden einige einfache Befehle benutzen, um die Webseite zu bauen.
Piet hat z.B den Kopfteil einer Seite in header.html und das Seitenende in der Datei footer.html. Diese könnten etwa so aussehen:
<html><!-- the header --> <head> <title>Piet and Jan productions</title> </head> <body bgcolor="white"> <table border="0" width="100%"><tr> <td bgcolor="#c040ff" valign="top"> This is our website<br> Some rubbish is written down here.<br> We are very interactive<br> so this is our telephone number:<br> <b>0123-456789</b> </td><td valign="top"> <!-- Put the contents here -->und hier footer.html:
<!-- the footer --> </td></tr></table> </body></html>Damit sind z.B die Unix Kommandos um aus Jans Seiten die endgültigen Seiten zu machen:
cat header.html /home/jan/Docs/website/index.html echo -n '<hr>Last modification: ' date '+%A %e %B' cat footer.htmlWie diese Kommandos funktionieren, kannst du in den man-Seiten nachlesen. Die Ausgabe dieser Kommandos wird dann in die Datei /home/piet/public_html/index.html umgeleitet:
{ cat header.html /home/jan/Docs/website/index.html echo -n '<hr>Last modification: ' date '+%A %e %B' cat footer.html } > /home/piet/public_html/index.htmlDiese Prozedur kann auch auf die andere Datei (offer.html) angewendet werden.
Jedoch macht es keinen Sinn, immer wieder diese Kommandos von Hand einzugeben. Wir schreiben uns ein kleines Skript. Das Skript sollte sowohl ausgeführt werden, wenn Jan eine Änderung macht als auch nach einer Änderung von Piet an header/footer. Nichts sollte jedoch gemacht werden, wenn keiner von beiden etwas geändert hat. Wir werden Linux für eine intelligente Lösung (lies: automatisch) benutzen!
Jetzt kommt make ins Spiel.
Make entscheidet, ob eine Anzahl von Kommandos ausgeführt werden müssen, basierend auf den Modifizierungszeiten der erzeugten Dateien und der Quelldateien.In anderen Worten: Falls eines der Quelldateien, die benötigt werden, um eine erzeugte Datei zu erstellen, neuer ist als die erzeugte Datei, dann müssen die Kommandos ausgeführt werden, und das Ziel dieser Kommandos ist es, eine neue Datei zu erzeugen.
Die erzeugte Datei nenne wir von nun an 'target' und die Quelldateien sind die `prerequisites'. Falls eines der `prerequisites' neuer als ein 'target' ist, dann , und nur dann, wird ein Kommando ausgeführt, um aus dem `prerequisites' ein neues 'target' zu machen.
Im augenblicklichen Arbeitsverzeichnis erzeugt man eine Datei mit dem Namen Makefile. Diese Datei enthält die Information, die von make benötigt wird.
make wird durch folgendes Kommando aufgerufen:
make target1 target2 ....
target ist optional. Falls das target fehlt, wird das erste im Makefile definierte target benutzt. Make sucht immer im augenblicklichen Arbeitsverzeichnis nach der Datei Makefile. Man kann mehr als nur ein target angeben.
# This is an example of a Makefile. # Comments can be put after a hash (#). target: prerequisites command target: prerequisites commando # and so on and so on.Wir fangen mit einem target an, gefolgt von einem Doppelpunkt und dann den benötigten prerequisites. Man kann eine Zeile verlängern, indem man sie mit einem backslash (\) beendet und dann in der nächsten Zeile fortsetzt.
Die folgenden Zeilen enthalten dann ein oder mehrere Kommandos. Jede Zeile ist ein einzelnes Kommando. Falls ein Kommando zu lang ist und über mehrere Zeilen gehen soll, benutzt man zwei backslashes (\\) am Ende der Zeile.
Wichtige Anmerkung: Die Kommandozeilen werden durch einen TAB eingerückt und nicht durch Leerzeichen! |
Make liest das Makefile und bestimmt für jedes target (angefangen mit dem ersten), ob das Kommando ausgeführt werden soll. Jedes target zusammen mit den prerequisites nennt man Regel.
# This Makefile builds Piets' and Jans' website, the potato-eaters. all: /home/piet/public_html/index.html /home/piet/public_html/offer.html /home/piet/public_html/index.html: header.html footer.html \ /home/jan/Docs/website/index.html { \ cat header.html /home/jan/Docs/website/index.html ;\ echo -n '<hr>Last modification: ' ;\ date '+%A %e %B' ;\ cat footer.html ;\ } > /home/piet/public_html/index.html /home/piet/public_html/offer.html: header.html footer.html \ /home/jan/Docs/website/offer.html { \ cat header.html /home/jan/Docs/website/index.html ;\ echo -n '<hr>Last modification: ' ;\ date '+%A %e %B' ;\ cat footer.html ;\ } > /home/piet/public_html/offer.html # the end
Hier haben wir drei targets, 'all', index.html und offer.html. Die einzige Funktion des targets 'all' ist die anderen beiden targets als prerequisites zu haben. Diese werden beide getestet, weil 'all' selbst kein Dateiname ist und 'all' damit immer ausgeführt wird. Später werden wir einen eleganteren Weg kennenlernen, um targets, die keine Dateinamen sind zu definieren).
Falls Kopf- und Fuß-Dateien geändert würden, würden beide Seiten neu generiert. Falls Jan nur eine seiner Seiten modifiziert, wird auch nur eine neue Seite generiert. 'make' macht's.
Das Makefile hat ein Problem: es ist nicht so übersichtlich. Zum Glück gibt es verschiedene Möglichkeiten, die Sache einfacher zu machen.
variable = valueWir greifen auf den Wert einer Variablen mit dem Ausdruck $(variabele) zu. Im folgenden haben wir das in unser Makefile eingebaut:
# This Makefile builds Piets' and Jans' website, the potato-eaters. # Directory where the website is stored: TARGETDIR = /home/piet/public_html # Jans' directory: JANSDIR = /home/jan/Docs/website # Files needed for the layout: LAYOUT = header.html footer.html all: $(TARGETDIR)/index.html $(TARGETDIR)/offer.html $(TARGETDIR)/index.html: $(LAYOUT) $(JANSDIR)/index.html { \ cat header.html $(JANSDIR)/index.html ;\ echo -n '<hr>Last modification: ' ;\ date '+%A %e %B' ;\ cat footer.html ;\ } > $(TARGETDIR)/index.html $(TARGETDIR)/offer.html: $(LAYOUT) $(JANSDIR)/offer.html { \ cat header.html $(JANSDIR)/index.html ;\ echo -n '<hr>Last modification: ' ;\ date '+%A %e %B' ;\ cat footer.html ;\ } > $(TARGETDIR)/offer.html # the endEs ist eine Konvention, große Buchstaben für Variablen zu benutzen. Jetzt ist es viel einfacher z.B Verzeichnise zu verschieben.
Was sollen wir machem, wenn wir noch mehr Dokumente haben. Das Makefile würde sehr lang. Für jedes Dokument eine Regel wird auch wieder unübersichtilch. Hier helfen Wildcards.
Falls `Pattern Rules' benutzt werden, kommt in der Regelzeile noch ein Feld hinzu.
Multiple targets: pattern : prerequisite prerequisite ... commandDas pattern ist ein Ausdruck der gültig für alle Targets ist. Ein Prozentzeichen dient dabei als Wildcard:
Ein Beispiel:
/home/bla/target1.html /home/bla/target2.html: /home/bla/% : % commandsMake vergleicht die zwei Targets mit dem pattern und macht daraus dann folgende Regeln:
/home/bla/target1.html: target1.html commands /home/bla/target2.html: target2.html commandsDas Prozentzeichen in `/home/bla/%' wird mit dem target `/home/bla/target1.html' zu `target1.html' und damit wird das prerequisite `%' zu `target1.html':
Für unsere Webseite benutzen wir nun folgende Regel:
$(TARGETDIR)/index.html $(TARGETDIR)/offer.html: \ $(TARGETDIR)/% : $(JANSDIR)/% \ $(LAYOUT)Bleibt noch ein Problem: Wie benutzt man diese Variablen in Kommandos?
Die spezielle Variable $< enthält das erste prerequisite und die Variable $@ expandiert immer zum augenblicklichen target.
Mit diesen Variablen ist es möglich, eine Regel mit Kommando zu verallgemeinern:
$(TARGETDIR)/index.html $(TARGETDIR)/offer.html: $(TARGETDIR)/% : \ $(JANSDIR)/% \ $(LAYOUT) { \ cat header.html $< ;\ echo -n '<hr>Last modification: ' ;\ date '+%A %e %B' ;\ cat footer.html ;\ } > $@Voilà! Das funktioniert nun für beide Dateien.
Hier ist nun unser komplettes Makefile:
# This Makefile builds Piets' and Jans' website, the potato-eaters. # Directory where the website is published: TARGETDIR = /home/piet/public_html # Jans' directory: JANSDIR = /home/jan/Docs/website # Files needed for the layout: LAYOUT = header.html footer.html # These are the webpages: DOCS = $(TARGETDIR)/index.html $(TARGETDIR)/offer.html # Please change nothing below this line;-) # ------------------------------------------------------------- all: $(DOCS) $(DOCS): $(TARGETDIR)/% : $(JANSDIR)/% $(LAYOUT) { \ cat header.html $< ;\ echo -n '<hr>Last modification: ' ;\ date '+%A %e %B' ;\ cat footer.html ;\ } > $@ # the endSo sollte es aussehen. Falls mehr Dokumente hinzukommen, ist das sehr leicht zu machen. Wir müssen nur die Variable DOCS ändern.
Die Person, die mit dem Makefile arbeitet, sollte einfach sehen können, wie es arbeitet, ohne sich zu wundern, wie es wohl funktioniert.
TEXTS = index.html offer.html yetanotherfile.html # Please change nothing below this line;-) # ------------------------------------------------------------- DOCS = $(addprefix $(TARGETDIR)/,$(TEXTS)) all: $(DOCS) # and so onWas wir hier sehen, ist eine spezielle make Funktion. Anstelle einer Variablen kann man einen kompletten Ausdruck zwischen den Klammern benutzen. Mit solchen Ausdrücken (vordefinierten Funktionen von make) kann man Text auf verschiedene Art und Weise modifizieren.
Die Funktion $(addprefix prefix,list) fügt zu jedem Element in der Liste einen Prefix hinzu.
Die Elemente in der Liste müssen durch Leerzeichen getrennt sein.
Am Anfang haben wir schon erwähnt, daß das target 'all' keine Datei namens 'all' erzeugt (die Regel enthält keine Kommandos). Was passiert jedoch, wenn diese Datei 'all' zufällig existiert und auch noch neuer als alle anderen Dateien ist...?
Es gibt eine einfache Möglichkeit make zu sagen, daß ein bestimmtes target ausgeführt werden soll und keine Datei erzeugt. Solch ein target markiert man als 'phony' (nicht wirklich). Das funktioniert so:
.PHONY: allNun sieht das ganze Makefile so aus:
# This Makefile builds Piets' and Jans' website, the potato-eaters. # Directory where the website is published: TARGETDIR = /home/piet/public_html # Jans' directory: JANSDIR = /home/jan/Docs/website # Files needed for the layout: LAYOUT = header.html footer.html # These are the names of the webpages: TEXTS = index.html offer.html yetanotherfile.html # Please change nothing below this line;-) # ------------------------------------------------------ DOCS = $(addprefix $(TARGETDIR)/,$(TEXTS)) .PHONY: all all: $(DOCS) $(DOCS): $(TARGETDIR)/% : $(JANSDIR)/% $(LAYOUT) { \ cat header.html $< ;\ echo -n '<hr>Last modification: ' ;\ date '+%A %e %B' ;\ cat footer.html ;\ } > $@ # the endSpeichere die Datei und vergiß sie! Lediglich TEXTS muß vielleicht manchmal geändert werden. Jetzt ist das Unterhalten der Webseite sehr einfach. Wir können sogar crontab benutzen und das Makefile täglich ausführen.
Z.B ist die einfache Art und Weise, wie die Dokumente generiert werden, nicht fehlerfrei. Falls Jan versehentlich am Ende seiner Dateien </body></html> schreibt, werden einige Browser den Fußteil von Piet nicht anzeigen. Man kann jedoch grep, perl oder tcl einsetzen, um das zu beheben.
Natürlich kann Jan auch einfach normale Textdateien schreiben und wir benutzen folgendes sed Kommando, um Paragraphen einzuführen (Leerzeilen durch <P> ersetzen):
sed -e 's/^\s*$/<p>/g'Weiterhin kann Jan seinen Text mit LyX schreiben und lyx2html kann benutzt werden, um daraus HTML zu machen. Deiner Phantasie sind unter Linux keine Grenzen gesetzt.
Wir haben uns noch keine Gedanken gemacht, wie Bilder behandelt werden sollen. Auch hier gibt es viele Möglichkeiten, aber das hat eigentlich nichts mit make zu tun.
Ich hoffe, es wurde klar, wie Make im Prinzip funktioniert und wie man viele Sachen mit einem guten Makefile vereinfachen kann.
Mit 'phony' targets (.PHONY: target), verschiedene Funktionen realisieren.Ein Beispiel ist die Konfiguration des Linuxkernels:
Tippt man make menuconfig erfolgt die Konfiguration des Kernels interaktiv. Mit make xconfig wird ein graphisches Konfigurationstool gestartet.
Beide targets haben eigentlich nichts mit dem Bau des Kernels zu tun. Sie erzeugen nur ein Benutzerinteface zur Konfiguration.
Hierzu brauchen wir ein Makefile mit folgenden Phony targets:
Auf diese Art kann man HTML aus einer Textdatei erzeugen und in einem weiteren Schritt das Layout dieser erzeugten HTML Datei verbessern:
TEMPLATE = layout1/Template1.txt /home/httpd/sales/sales.html: sales.html $(TEMPLATE) perl Scripts/BuildPage.pl -template $(TEMPLATE) $< > $@-new mv -f $@-new $@ sales.html: sales.txt aptconvert -toc $@ $<Überlege, wie die Dateien erneuert würden, wenn sich Template1.txt ändert.
Ein Kommando, das als ersten Buchstaben ein '@' hat wird nicht auf dem Bildschrim ausgegeben.
target: prerequisite @cc -o target prerequisiteFalls ein Kommando mit '-' beginnt, wird make nicht abbrechen, wenn das Kommando einen Fehler erzeugt (z.B. Löschen eines nicht existenten Files):
.PHONY: clean clean: -rm -r $(tempdir)Um zu sehen, was bestimmte make Kommandos machen, z.B make install kann man make mit der Option -n laufen lassen. Damit wird nichts ausgeführt, sondern nur angezeigt:
wilbert@nutnix:~ > make -n install install -m 755 program /usr/local/bin install -m 644 program.1 /usr/local/man/man1 wilbert@nutnix:~ >
Falls man ein Dollarzeichen ($) nicht als Makevariable, sondern als Teil eines Dateinamens oder Shellkommandos braucht, muß man ein Doppel-Dollarzeichen ($$) benutzen:
# A Makefile # Don't try this at home! :-) source = menu.txt help.txt target: $(source) for i in $(source) ;\ do \ if [ "$$i" = "menu.txt" ] ;\ then \ doThis $$i ;\ else \ doThat $$i ;\ fi ;\ done > targetMake wird vor der Übergabe der Kommandos an die Shell seine eigenen Variablen ersetzen und alle $$ in $ ändern.
info makeAuch die GNOME und KDE Helpbrowser oder das nützliche tkinfo Programm können statt info benutzt werden.
Links:
Viel Spaß!