original in nl Wilbert Berendsen
Wilbert Berendsen is professioneel musicus en Linux enthousiast. Hij heeft ooit eens intensief Z80 assembly gehackt en gebruikt nu Linux voor al zijn productiewerk. Voor de lol schrijft hij inleidende artikelen en hij onderhoudt een kleine website op http://www.xs4all.nl/~wbsoft/. Viva open source!
Bijna iedereen die Linux gebruikt, is wel eens met het programma make in aanraking gekomen. Het doet zijn werk wanneer we een programma of de kernel willen bouwen vanaf de broncode, wanneer we pakketten willen installeren enz. 'Make' is dus een belangrijke tool voor het ontwikkelen van software. Maar make kan nog veel meer!
In dit document zullen we zien dat make een krachtige hulp kan zijn bij dagelijks werk zoals het schrijven van artikelen, boeken of het samenstellen van een leuke website. Terloops zullen veel andere 'unix handigheidjes' de revue passeren. Aan het slot van dit verhaal nog enkele tips over hoe je make verder nog kan gebruiken. Let wel: we hebben het af en toe over Linux, maar in feite kun je make op ieder besturingssysteem gebruiken.
We hebben een simpel systeem nodig om de layout en de inhoud van elkaar te scheiden. Een krachtige manier is natuurlijk: de inhoud uit een database lezen, telkens als de pagina wordt opgevraagd. Dit is hoe bijvoorbeeld PHP en Microsoft Active Server Pages werken. Maar, we hebben slechts de mogelijkheid om simpele HTML (HyperText Markup Language) op te slaan. Bovendien verandert de inhoud in ons simpele geval niet zo vaak dat een database efficiënt is.
Wij gaan dus met een paar simpele commando's een site in elkaar smeden.
Piet stopt bijvoorbeeld de kop van de site in header.html en de voet in footer.html. header.html kan er zo uitzien:
<html><!-- de header --> <head> <title>Piet en Jan producties</title> </head> <body bgcolor="white"> <table border="0" width="100%"><tr> <td bgcolor="#c040ff" valign="top"> Dit is onze website<br> Hier staat nog wat onzin.<br> We zijn heel interactief<br> dus dit is ons telefoonnummer:<br> <b>0123-456789</b> </td><td valign="top"> <!-- hier de inhoud -->En dit is footer.html:
<!-- de footer --> </td></tr></table> </body></html>De unix commando's om dan van Jan's index.html de uiteindelijke pagina te bouwen zijn bijvoorbeeld:
cat header.html /home/jan/Docs/website/index.html echo -n '<hr>Laatst gewijzigd: ' date '+%A %e %B' cat footer.htmlZie de manual pages van deze commando's. De file die deze commando's tot resultaat hebben wordt dus naar de standaard uitvoer geleid, en die kunnen we simpel vangen in een file:
{ cat header.html /home/jan/Docs/website/index.html echo -n '<hr>Laatst gewijzigd: ' date '+%A %e %B' cat footer.html } > /home/piet/public_html/index.htmlHetzelfde kan ook met de andere file, aanbod.html. Hiermee hebben we in feite een klein script gebouwd, dat in staat is de website voor ons te maken.
Echter, telkens dit commando invoeren is natuurlijk ondoenlijk. We kunnen er natuurlijk een uitvoerbaar shellscript van maken en dit telkens runnen als Jan zijn index heeft bijgewerkt. Maar ook als Piet besluit de header of footer aan te passen moet dit script gerund worden! En als Jan op de een of andere dag niets heeft veranderd hoeft het script ook niet te draaien. En we draaien niet voor niets Linux, dus we willen het slimmer kunnen doen (lees: vanzelf)!
Welnu, hier is het, dat make om de hoek komt kijken.
[ ] make bepaalt op grond van de <time-stamp> (de datum dus) van een doel-bestand, en de <time-stamps> van bron-bestanden of een reeks commando's uitgevoerd moet worden ja of nee.Met andere woorden: Als één van de bron-bestanden, nodig voor het maken van het doel-bestand, <<nieuwer>> is dan het doel-bestand, dan wordt een bepaalde reeks commando's uitgevoerd. Die commando's zullen dan het doel-bestand vernieuwen.
De doel-file is een `target', de bron-files zijn `prerequisites' (eerste vereisten). De commando's worden dus uitgevoerd als een van de `prerequisites' nieuwer is dan de `target' (of als de target niet bestaat). Zijn alle prerequisites ouder dan of even oud als de target, dan worden de commando's niet uitgevoerd, en beschouwt make de target als up-to-date.
In de directory waarin we werken moet een bestand met de naam Makefile worden aangemaakt, dat deze informatie voor make bevat. Als dat eenmaal gebeurd is hoeven we nog slechts `make' in te typen en de commando's nodig voor het bijwerken van de targets worden automatisch uitgevoerd.
Make wordt aangeroepen met
make? ? ....
waarbij de target optioneel is (bij weglating wordt de eerste target in de Makefile gebruikt). Make kijkt altijd in de huidige directory voor de Makefile. Meer dan één target kan tegelijk worden opgegeven.
# Dit is een voorbeeld van een Makefile. # Commentaar mag achter hekjes (#) staan. target: prerequisites commando target: prerequisites commando # enz enz.De target staat dus steeds vooraan, gevolgd door een dubbele punt (:) en de prerequisites die nodig zijn. Als er veel prerequisites zijn, kun je de regel eindigen met een backslash (\) en op de volgende regel verder gaan.
Op de volgende regel(s) staan één of meer commando's. Elke regel wordt door make gezien als een afzonderlijk commando. Wil je meerdere regels in één commando hebben, dan moet je de regels weer met een backslash (\) eindigen. Make zal de commando's dan verbinden door ze als het ware op 1 regel te zetten. Daarom moeten we in dat geval de commando's ook scheiden met een puntkomma (;), voor de shell die ze uitvoert.
Let op: de commando's moeten worden ingesprongen met een TAB, dus niet 8 spaties!
Make leest de Makefile, en bepaalt voor elke gevraagde target (beginnend bij de eerste) of de commando's moeten worden uitgevoerd. Elke target, samen met zijn prerequisites en commando's wordt een `rule' (regel) genoemd.
Wanneer make zonder argumenten wordt aangeroepen, zal hij enkel de eerst gevonden target uitvoeren.
# Deze Makefile bouwt de website van Piet en Jan, de aardappeleters. all: /home/piet/public_html/index.html /home/piet/public_html/aanbod.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>Laatst gewijzigd: ' ;\ date '+%A %e %B' ;\ cat footer.html ;\ } > /home/piet/public_html/index.html /home/piet/public_html/aanbod.html: header.html footer.html \ /home/jan/Docs/website/aanbod.html { \ cat header.html /home/jan/Docs/website/index.html ;\ echo -n '<hr>Laatst gewijzigd: ' ;\ date '+%A %e %B' ;\ cat footer.html ;\ } > /home/piet/public_html/aanbod.html # the end
We hebben nu drie targets, `all' en de bestanden index.html en aanbod.html van de website. De target `all' hebben we enkel gemaakt zodat hij de beide andere als prerequisite kan hebben. Die worden dus beide getest. Omdat `all' zelf niet de naam van een bestaande file is, wordt de target `all' dus altijd uitgevoerd. (Later zullen we een elegantere manier zien waarmee targets die geen file zijn kunnen worden gedefiniëerd.)
Als de header of footer werden gewijzigd, worden beide pagina's opnieuw gemaakt. Wijzigt Jan een van de pagina's, dan wordt alleen die pagina opnieuw gemaakt. Simpelweg `make' intypen is nu voldoende!
Het nadeel is natuurlijk, dat de Makefile nu niet bepaald overzichtelijk is. Gelukkig zijn er veel manieren om alles simpeler te maken!
variabele = waardeWe refereren naar een variabele met de uitdrukking $(variabele). Als we hiermee de Makefile bewerken, ziet het er al vriendelijker uit:
# Deze Makefile bouwt de website van Piet en Jan, de aardappeleters. # Directory waar de website staat: TARGETDIR = /home/piet/public_html # Jan's directory: JANSDIR = /home/jan/Docs/website # Bestanden nodig voor de layout: LAYOUT = header.html footer.html all: $(TARGETDIR)/index.html $(TARGETDIR)/aanbod.html $(TARGETDIR)/index.html: $(LAYOUT) $(JANSDIR)/index.html { \ cat header.html $(JANSDIR)/index.html ;\ echo -n '<hr>Laatst gewijzigd: ' ;\ date '+%A %e %B' ;\ cat footer.html ;\ } > $(TARGETDIR)/index.html $(TARGETDIR)/aanbod.html: $(LAYOUT) $(JANSDIR)/aanbod.html { \ cat header.html $(JANSDIR)/index.html ;\ echo -n '<hr>Laatst gewijzigd: ' ;\ date '+%A %e %B' ;\ cat footer.html ;\ } > $(TARGETDIR)/aanbod.html # the endEen goede gewoonte is het om hoofdletters te gebruiken voor variabelen. Het is nu ook veel eenvoudiger om bijvoorbeeld de doel-directory snel te wijzigen.
Je kunt als je dat wilt voor elk document een andere manier definiëren waarmee het in de goede layout wordt gezet. Maar wat als we nu vele documenten hebben die allemaal in dezelfde layout moeten? De Makefile zou wel erg lang worden, terwijl er veel herhalingen in zitten. Ook dit kunnen we weer vereenvoudigen!
Bij het gebruik van pattern rules verandert de syntax van een regel; er wordt een extra patroon veld ingevoegd:
meerdere targets: patroon : prerequisite prerequisite ... commandoHet patroon is een uitdrukking die op alle targets zou moeten kunnen passen, waarbij een procentteken wordt gebruikt om variabele gedeelten van een target-naam te vatten.
Een voorbeeld:
/home/bla/target1.html /home/bla/target2.html: /home/bla/% : % commando'sAls make dit leest, wordt het geëxpandeerd tot 2 regels, waarbij het patroon bepaalt welk gedeelte van de target-naam in het procentteken wordt gevat.
In het prerequisites-veld staat het procentteken voor het deel dat in het patroon door datzelfde procentteken gevat werd.
Make expandeert het bovenstaande dus aldus:
/home/bla/target1.html: target1.html commando's /home/bla/target2.html: target2.html commando'sHet procentteken in het patroon `/home/bla/%' krijgt bij target `/home/bla/target1.html' dus de waarde `target1.html', waardoor de prerequisite `%' expandeert tot `target1.html'.
Voor onze website komt een rule er nu alsvolgt uit te zien:
$(TARGETDIR)/index.html $(TARGETDIR)/aanbod.html: $(TARGETDIR)/% : $(JANSDIR)/% \ $(LAYOUT)Nu zitten we nog met 1 probleem: Hoe kunnen we deze variabelen in de commando's gebruiken? De commando's waren immers ook voor de beide targets enigszins verschillend?
De speciale variabele $ wordt gebruikt om de eerste prerequisite aan te geven, en de variabele $@ expandeert altijd tot de huidige target.
Met behulp van deze variabelen kunnen we de complete rule dus als volgt `generaliseren':
$(TARGETDIR)/index.html $(TARGETDIR)/aanbod.html: $(TARGETDIR)/% : $(JANSDIR)/% \ $(LAYOUT) { \ cat header.html $< ;\ echo -n '<hr>Laatst gewijzigd: ' ;\ date '+%A %e %B' ;\ cat footer.html ;\ } > $@Voilà! Deze ene regel werkt nu dus voor beide files!
Voor de volledigheid nu weer eens de volledige Makefile, met nog wat optimalisaties:
# Deze Makefile bouwt de website van Piet en Jan, de aardappeleters. # Directory waar de website staat: TARGETDIR = /home/piet/public_html # Jan's directory: JANSDIR = /home/jan/Docs/website # Bestanden nodig voor de layout: LAYOUT = header.html footer.html # Dit zijn de web pagina's: DOCS = $(TARGETDIR)/index.html $(TARGETDIR)/aanbod.html # beneden deze lijn niets veranderen;-) # ------------------------------------------------------------- all: $(DOCS) $(DOCS): $(TARGETDIR)/% : $(JANSDIR)/% $(LAYOUT) { \ cat header.html $< ;\ echo -n '<hr>Laatst gewijzigd: ' ;\ date '+%A %e %B' ;\ cat footer.html ;\ } > $@ # the endDit begint erop te lijken. Als er meerdere documenten bijkomen, kunnen die vrij eenvoudig in de Makefile (aan de DOCS variabele) worden toegevoegd, zonder al te veel typwerk.
Uiteindelijk moet zelfs degene die de Makefile onderhoudt gemakkelijk kunnen zien hoe het werkt, zonder telkens weer de werking ervan te moeten ontcijferen!
TEKSTEN = index.html aanbod.html nogeenbestand.html # beneden deze lijn niets veranderen;-) # ------------------------------------------------------------- DOCS = $(addprefix $(TARGETDIR)/,$(TEKSTEN)) all: $(DOCS) # enzWat we hier zien, is een specialistische functie van make: In plaats van een variabele-naam kan tussen de haakjes ook een complete uitdrukking staan, waarmee tekst op allerlei manieren kan worden bewerkt.
Het speciale commando $(addprefix prefix,lijst) voegt aan elk element van de lijst een prefix toe, in het voorbeeld de inhoud van de TARGETDIR variabele plus een slash (/).
De items in de lijst worden door spaties gescheiden, vandaar dat het niet echt een goed idee is om bestandsnamen waar spaties in voorkomen met make te bewerken.
Tot slot: in het begin zeiden we al dat de target `all' geen bestand creëert
met de naam `all' (er zijn immers geen commando's in die regel) en daarom
telkens wordt uitgevoerd. Maar wat als er Er is een simpele manier om make te vertellen dat een bepaalde target
altijd moet worden uitgevoerd en niet verwijst naar een file op de harde
schijf. Dit doen we door die target als `phony' (onecht) te markeren. Dat gaat
alsvolgt:
Bijvoorbeeld de simpele manier waarop het document in elkaar wordt gezet is
niet waterdicht: Als Jan zijn artikelen per ongeluk met
</body></html> eindigt, wordt (door de meeste browsers) de door Piet
gemaakte footer.html niet afgebeeld. Met grep, perl of tcl kunnen we nog wat
handiger titels uit Jans documenten overbrengen in de header van de site.
Jan kan natuurlijk ook gewoon platte tekst schrijven, en met een sed commando
worden dan alle wit-regels in <P> veranderd:
Een andere template-constructie is natuurlijk ook mogelijk.
We hebben ook niet stil gestaan bij hoe eventuele plaatjes worden overgebracht
(geschaald, geconverteerd of gecomprimeerd) naar de web directory. Dat kan
natuurlijk ook automatisch!
In dit voorbeeld moet Piet leestoegang hebben in Jans website directory. Het
interessante van deze scheiding van taken is, dat zij vertaald kan worden
naar zeer grote organisaties! Piet kan zelfs aan de andere kant van de
wereld inloggen, of zijn homedir over NFS mounten. De voorbeelden kunnen
echter ook gebruikt worden voor werk door één gebruiker.
Het basisidee over hoe een Makefile werkt, en hoe simpel je dagelijks
werk ineens wordt als je een goede Makefile hebt, is hopelijk een beetje
duidelijk geworden!
Een verzameling bestanden kan ook functioneren als bron voor het op
verschillende manieren samenstellen van een eindproduct of actie.
Door middel van `phony' targets (.PHONY: target) kunnen eenvoudige
functies makkelijk worden gebundeld. Een voorbeeld hiervan is de configuratie
van de Linux kernel.
Met make menuconfig wordt de configuratie gestart via een interactief
menu. Met make xconfig wordt de configuratie gestart via een Tcl/Tk
interface onder X.
Deze beide targets hebben dus niets met de eigenlijke bouw van de kernel te
maken, zij vormen gewoon een makkelijke interface naar de noodzakelijke
functies (zoals configuratie van de kernel).
Je zou dan een Makefile moeten maken met bijvoorbeeld de volgende PHONY
targets:
Bestanden die voor een bepaalde target als prerequisite genoemd worden, kunnen
zelf ook weer target zijn voor een volgende bewerking.
Zo kun je bijvoorbeeld uit een tekstbestand HTML genereren, en die HTML weer
in een completere layout zetten. Een voorbeeld:
Als je een commando met een `@' laat beginnen, wordt het niet afgebeeld door
Make:
Als je het dollarteken ($) nodig hebt als onderdeel van bijvoorbeeld een
filenaam of een shell commando, gebruik het dan dubbel ($$):
Links naar meer informatie over make:
Veel plezier!
.PHONY: all
De hele Makefile ziet er nu alsvolgt uit:
# Deze Makefile bouwt de website van Piet en Jan, de aardappeleters.
# Directory waar de website staat:
TARGETDIR = /home/piet/public_html
# Jan's directory:
JANSDIR = /home/jan/Docs/website
# Bestanden nodig voor de layout:
LAYOUT = header.html footer.html
# Dit zijn de namen van de web pagina's:
TEKSTEN = index.html aanbod.html nogeenbestand.html
# beneden deze lijn niets veranderen;-)
# ------------------------------------------------------
DOCS = $(addprefix $(TARGETDIR)/,$(TEKSTEN))
.PHONY: all
all: $(DOCS)
$(DOCS): $(TARGETDIR)/% : $(JANSDIR)/% $(LAYOUT)
{ \
cat header.html $< ;\
echo -n '<hr>Laatst gewijzigd: ' ;\
date '+%A %e %B' ;\
cat footer.html ;\
} > $@
# the end
Die sla je op en vergeet je! Nu kun je met een simpele make, desnoods in je
crontab, altijd je webpagina up-to-date houden, en de layout netjes van de
inhoud scheiden!
Slotopmerkingen
Dit voorbeeld kan natuurlijk worden aangepast voor andere situaties.
sed -e 's/^\s*$/<p>/g'
Daarnaast kan Jan natuurlijk ook zijn teksten in LyX
schrijven, en gebruike
men een programma als lyx2html om er html van te maken. Mogelijkheden te
over!
Tips
Een voorbeeld
Stel je voor: Je hebt een grote verzameling documenten, die je als één geheel
bewaart, onderhoudt en distribueert. Je wil samenvattingen kunnen maken, in
draft en verkleind kunnen printen, in topkwaliteit printen, publiceren naar
het web etc. alles vanuit dezelfde bronbestanden (bijvoorbeeld
LaTeX!).
enz. enz. Zo kunnen complexe handelingen worden gedefiniëerd in een Makefile,
en vervolgens aangeroepen via een simpel te onthouden interface.
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 $@ $<
Zie hoe de file ook wordt bijgewerkt als de Template1.txt was veranderd.
target: prerequisite
@cc -o target prerequisite
Als je een commando met een `-' laat beginnen, wordt het Make proces niet
afgebroken als dat commando een fout veroorzaakt (bijvoorbeeld probeert een
niet bestaande file te wissen):
.PHONY: clean
clean:
-rm -r $(tempdir)
Als je wilt zien wat een bepaalde `make' aanroep doet, (bijvoorbeeld
make install) maar je wilt per se niet dat het echt gedaan wordt,
gebruik dan de -n optie op de prompt:
wilbert@nutnix:~ > make -n install
install -m 755 program /usr/local/bin
install -m 644 program.1 /usr/local/man/man1
wilbert@nutnix:~ >
# 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 > target
Make zal, voordat het commando naar de shell wordt gestuurd om te worden
uitgevoerd, netjes zijn eigen variabelen substitueren, en alle dubbele
dollartekens door enkele vervangen.
Meer informatie
Meer informatie over de werking van make en alle mogelijkheden is te vinden in
de `GNU Make Manual'. Op je Linux systeem kan die worden bekeken met:
info make
Natuurlijk kan je de GNU Make Manual ook bestuderen met de GNOME en KDE
helpbrowsers of het handige programma tkinfo.