Mach deinen Job mit make!

ArticleCategory: []

Software Development

AuthorImage:[Here comes a small picture of you]

[Wilbert Berendsen]

TranslationInfo:[Info concerning writer(s) and translator(s)]

original in nl Wilbert Berendsen

nl to en Philip de Groot

en to de Guido Socher

AboutTheAuthor:[a short biography about the author]

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!

Abstract:[a small summary/description of this article]

Dieser Artikel über make zeigt, wie make funktioniert und wie es für andere Dinge als nur Softwareentwicklung benutzt werden kann.

ArticleIllustration:[illustration]

[Illustration]

ArticleBody:[The real article: put the text and html-codes here]

Einführung

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.

Beispiel: Eine Webseite bauen

Wir wollen eine Webseite bauen, die von unterschiedlichen Leuten unterhalten wird. Jan kümmert sich um die Seiten. Piet um das Layout.

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.html
Wie 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.html
Diese 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.

Das erst Zusammentreffen mit make

Das info-Manual des GNU make ist ein fantastisches Dokument. Es fokussiert jedoch von Anfang an auf eine Programmierumgebung. Aus diesem Grund möchte ich die Funktionen von make in einem breiteren Zusammenhang darstellen:
    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.

Makefile Syntax

# 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.

Ein Makefile für unser Beispiel

In unserem Beispiel sieht das Makefile so aus:
# 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.

Vereinfachung des Makefiles

Variablen

Mit Variablen kann man ein Makefile stark vereinfachen. Variablen definiert man wie folgt:
variable = value
Wir 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 end
Es 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.

Regeln mit Wildcards

Regeln mit Wildcards oder `Pattern Rules' ermöglichen es, dieselben Kommandos für alle möglichen targets zu benutzen.

Falls `Pattern Rules' benutzt werden, kommt in der Regelzeile noch ein Feld hinzu.

Multiple targets: pattern : prerequisite prerequisite ...
    command
Das 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/% : %
    commands
Make 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
    commands
Das 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?

Automatische Variablen

Zum Glück definiert make selbst einige Variablen. Einige dieser Variablen bezeichnet man als automatische Variablen. Diese Variablen erhalten vor der Ausführung eines Kommandos den Wert von target und prerequisite.

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 end
So 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.

Letzte Optimierungen

Wir würden gerne die Dokumente in der Variable DOCS ausführen, jedoch ohne bei einer Änderung immer den vollen Pfad angeben zu müssen. Dazu führen wir eine neue Variable TEXTS ein:
TEXTS = index.html  offer.html  yetanotherfile.html

# Please change nothing below this line;-)
# -------------------------------------------------------------
DOCS =  $(addprefix $(TARGETDIR)/,$(TEXTS))

all: $(DOCS)

# and so on
Was 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: all
Nun 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 end
Speichere 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.

Letzte Anmerkungen

Natürlich kann man das Beispiel modifizieren und auf andere Situationen anwenden.

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.

Tips

Weitere Informationen

Wietere Infromationen, wie make funktioniert, kann man in dem `GNU Make Handbuch' finden. Man kann das Handbuch mit folgenden Kommandos unter Linux lesen:
info make
Auch die GNOME und KDE Helpbrowser oder das nützliche tkinfo Programm können statt info benutzt werden.

Links:

Viel Spaß!