FreshSourcing. Čerstvé nápady. Kreatívne riešenia.

» Domov » Auto Tools » 4. čast

4. čast: Funkcia wildcard a patsubst, phony a force ciele, viacadresárové projekty

V štrvtej časti tohto seriálu si doplníme výklad o funkcii wildcard z minulej časti, pridáme novú funkciu patsubst a povieme si niečo o falošných (angl. phony) a force cieľoch.

Funkcia wildcard

Funkciu wildcard môžete použiť všade tam, kde sa nevykoná substitúcia zástupných znakov automaticky. Používa sa takto:

$(wildcard vzor)

Takýto text sa môže nachádzať na ľubovoľnom mieste súboru Makefile. Nahradí sa menami súborov, ktoré vyhovujú danému vzoru. Ak vyhovuje viacero mien, budú oddelené medzerami. Príklad:

all:
echo $(wildcard *.c)

Tento Makefile vypíše názvy všetkých súborov, ktoré sú ukončené príponou .c. Teraz si ukážeme spojenie tejto funkcie s funkciou patsubst (o nej si povieme o chvíľku). Predstavme si ideálny prípad, keď z každého zdrojového súboru vytvoríme práve jeden objektový súbor a všetky objektové súbory nakoniec zlinkujeme dokopy. Aby sme nemuseli vymenovať všetky zdrojové súbory, použijeme funkciu wildcard. Jej výstup použijeme ako vstup našej novej funkcie patsubst, ktorá nám zmení prípony zdrojových súborov .c za prípony objektov .o. Premennú OBJECTS teda môžeme zadefinovať napríklad takto:

OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c))

Ak máme v adresári napríklad súbory 1.c, 2.c, 3.c a main.c, premenná OBJECTS bude obsahovať text

1.o 2.o 3.o main.o

Ak by sme chceli mať náš Makefile kompletný, mohli by sme doplniť ešte tieto tri riadky:

all: program

program: $(OBJECTS)
cc -o program $(OBJECTS)

V našom ukážkovom Makefile využívame implicitné pravidlá používané na vygenerovanie objektového súboru zo zdrojového súboru s príponou .c; ak by sme chceli použiť naše vlastné pravidlá, museli by sme si ich, samozrejme, dopísať. Súbor Makefile napísaný podobne ako náš príklad má celkom milú výhodu - ak chceme pridať nejaké zdrojové súbory, nemusíme ho vôbec editovať.

Funkcia patsubst

Túto funkciu sme už použili. Jej syntax je takáto:

$(patsubst vzor, náhrada, text)

Funkcia vyhľadá v texte vzory, ktorým text vyhovuje (o vzoroch sme si rozprávali v minulej časti) a zamení ich náhradami. V podstate to funguje podobne, ako pri cieľoch a zdrojoch v pravidlách. Namiesto mena súboru (čo je prípad pravidiel) sa však použije text, ktorý definujeme pri volaní funkcie. Vo vzoroch a náhradách samozrejme môžeme použiť zástupné znaky (ako ich poznáme z minulej časti), napríklad znak % zastupuje skupinu ľubovoľných znakov. Ak by sme chceli použiť samotný znak % (percento), museli by sme použiť spätnú lomku (\ angl. backslash) a za ňou percento (teda \%); samotnú spätnú lomku napíšeme dvoma spätnými lomkami \\.

Ak v jednom názve súboru použijete viac rovnakých prípon, ktoré vyhovujú vzoru, nahradí sa iba posledná, teda premenná

OBJECTS=$(patsubst %.c, %.o, 1.c 2.c.c)

bude obsahovať text: 1.o 2.c.o a nie 1.o 2.o.o.

Ďalší spôsob, ako použiť funkciu patsubst je trošku jednoduchší (v skutočnosti ide o akúsi skratku pre funkciu patsubst). Syntax:

$(premenná:vzor=náhrada)

V premennej sa vyhľadá znak/vzor (na konci názvu súboru, teda za názvom musí nasledovať medzera alebo koniec premennej), ktorý sa jednoducho nahradí.

OBJECTS=1.c 2.c 3.c
$(OBJECTS:c=o)

Premenná OBJECTS bude obsahovať názvy "1.o 2.o 3.o". Samozrejme vzor môže obsahovať aj zástupné znaky.

$(OBJECTS:%.c=%.o)

Funkcií na spracovanie obsahu premenných je viac, na ďalšie sa pozrieme v niektorom z pokračovaní.

Falošné ciele

Phony (falošné) ciele sú všetky ciele, ktorých názvy nie sú skutočné súbory. Minule sme si ukázali pravidlo, ktoré vyčistí projekt od všetkých vygenerovaných súborov (objekové súbory, core súbory a podobne). Také pravidlo môže vyzerať napríklad takto:

clean:
rm -f *.o core

Zavolať ho môžeme spustením príkazu:

make clean

Keďže príkaz rm nevytvorí súbor clean (cieľ pravidla), je pravdepodobné, že takýto súbor nebude existovať. Preto sa toto pravidlo vykoná vždy pri zavolaní príkazu make clean. Predstavte si, že by predsa len náhodou niekto súbor "clean" vytvoril - keďže pravidlo nemá uvedené "zdroje", budú sa automaticky považovať za "up to date" a príkazy, ktoré sú uvedené ďalej, sa nikdy nevykonajú (čo asi nechceme). Aby sme sa tomu vyhli, použijeme falošné cieľe. Ako? Do súboru Makefile pridáme tento riadok:

.PHONY: clean

Týmto povieme programu make, že cieľ clean je falošný a príkazy uvedené za ním sa majú vykonať vždy.

Podadresáre

Ak sa rozhodneme, že v našom projekte budeme mať viacero podadresárov (každý projekt - samozrejme nie typu "Hello, world" - by mal mať zdrojové súbory v samostatnom adresári; ak je v jednom projekte viacero programov, zdrojáky by mali byť pekne rozdelené), máme viacero možností, ako použiť program make. Najjednoduchšie by sa to dalo spraviť tak, že do súboru Makefile v hlavnom adresári by sme namiesto názvov súborov (napr. main.c) uviedli názov vrátane adresára (dir1/file1.c). Samozrejme, má to mnoho nevýhod (napr. zbytočne veľa písania).

Druhá cesta je schodnejšia - do každého adresára uložíme samostatný súbor Makefile. Ako však dáme viedieť hlavnému Makefile súboru, kde má hľadať ostatné? Napríklad takto:

SUBDIRS=dir1 dir2 dir3

subdirs:
for dir in $(SUBDIRS); do \
$(MAKE) -C $$dir; \
done
Najprv krátke vysvetlenie. Do premennej SUBDIRS uložíme názvy podadresárov. V pravidle subdirs sa nachádza krátky shell skript, ktorý pre každú hodnotu premennej SUBDIRS vykoná nasledujúci riadok. Premenná MAKE je nastavená automaticky, nemusíme ju definovať ručne. Parameter -C určí, v ktorom adresári bude make pracovať. Chcel by som upozorniť na použitý zápis premennej dir v predposlednom riadku - ak chceme použiť shell premennú, musíme ju zapísať takto; ak by sme použili iba jeden "dolár", nastala by substitúcia. Keďže premennú dir sme nikde predtým nedefinovali, nahradila by sa prázdnou hodnotou; v konečnom dôsledku by make nevedel, kde má pracovať, pretože prepínač -C musí mať jeden parameter - program sa skončí a vráti chybové hlásenie.

Takéto riešenie však má tiež mnoho nedostatkov. Napríklad: ak sa v podadresári vyskytne chyba (nepodarí sa preložiť nejaký súbor, atď), nikto nebude chybové hlásenie brať na vedomie. Jednoducho sa bude pokračovať ďalším podadresárom. Samozrejme, mohli by sme to obísť pridaním vhodných shell príkazov, ktoré by zabezpečili ukončenie v prípade chyby pri spracúvaní vnorených súborov. Program by sa však ukončil aj v prípade, že spustíme make s prepínačom -k (pokračuje v behu, aj keď nie je možné vytvoriť niektorý cieľ). Druhý problém je ten, že nemôžeme použiť paralelné spracovanie (prepínač -j), pretože v súbore Makefile je iba jedno pravidlo. Celý problém môžeme riešiť celkom elegantne pomocou falošných cieľov:

SUBDIRS=dir1 dir2 dir3
.PHONY: subdirs $(SUBDIRS)

subdirs: $(SUBDIRS)

$(SUBDIRS):
make -C $@

dir2: dir3

Posledný riadok zabezpečí, aby nebol adresár dir2 spracovaný skôr ako adresár dir3 - toto je dôležité, ak používame paralelné spracovanie a program v adresári dir2 potrebuje program z adresára dir3.

Falošné ciele by nikdy nemali byť uvedené medzi zdrojmi skutočných pravidiel (resp. pravidiel so skutočnými cieľmi), inak sa príkazy v takomto pravidle vykonajú vždy.

Ak sú zdrojové súbory viacerých programov v jednom adresári, pravidlá pre tvorbu jednotlivých programov sú uložené v jednom súbore Makefile. Pretože štandardný cieľ je prvý cieľ v súbore, kontroloval by sa vždy iba prvý program. Môžeme si však pomôcť: vytvoríme falošný cieľ all a názvy programov uvedieme ako jeho zdroje:

.PHONY: all
all: program1 program2 program3

program1: file1.o file2.o
cc -o $@ $<

program2: file3.o file4.o
cc -o $@ $<

program3: file5.o file6.o
cc -o $@ $<

%.o: %.c
cc -o $@ -c $<

Ak chcete skompilovať všetky programy, spustite make, ak by ste chceli vytvoriť povedzme programy program1 a program2, napíšete make program1 program2.

Ak jedno falošné pravidlo má ako zdroj uvedené iné falošné pravidlo, funguje to ako podprogram (procedúra, funkcia bez parametrov a návratovej hodnoty - nazvite si to, ako chcete...). Napríklad:

.PHONY: distclean clean
TARGET=program

distclean: clean
rm -f $(TARGET)

clean:
rm -f *.o core

Ak spustíme make clean, zmažú sa všetky objekty a prípadný súbor core, ak zadáme make distclean, okrem objektov sa zmaže aj zlinkovaný program.

Možno vás napadlo spraviť takýto súbor:

.PHONY a b

all: a
a: b
b: a

Vznikne teda nekonečný cyklus. Ak si myslíte, že takto zahltíte program make, alebo dokonca zmrazíte svoj počítač, ste na omyle - program make je dostatočne inteligentný a cyklickú závislosť hravo odhalí. Tu je jeho odpoveď:

make: Circular b <- a dependency dropped.

Force ciele

Predstavme si pravidlo, ktoré nemá zdroje a jeho cieľ je neexistujúci súbor - toto pravidlo by sa malo vykonať vždy, keď sa kontroluje jeho cieľ (teda spustíme make s parametrom - cieľom tohto pravidla, alebo tento cieľ vyžaduje iné pravidlo - ako svoj zdroj). Ak chceme dosiahnuť, aby sa príkazy takého pravidla vykonali vždy, môžeme na to ísť aj touto cestou:

clean: FORCE
rm -f *.o core $(TARGET)

FORCE:

"Prázdne" pravidlo FORCE prinúti pravidlo clean, aby vždy vykonalo svoje príkazy. Na názve FORCE nie je nič magické, jednoducho je to zaužívaný názov. Ako ste si mohli všimnúť, výsledok má rovnaký efekt, ako:

.PHONY: clean

Možno vás zaujíma, prečo sme použili FORCE namiesto PHONY. Dôvod je prostý - niektoré klony programu make slovo .PHONY jednoducho nepoznajú, takže sa kľudne môžete stretnúť aj s FORCE pravidlami.

Záver

V tejto časti sme prebrali ďalšie užitočné veci, bez ktorých to s programom make ďaleko nedotiahnete. V nasledujúcej časti si preberieme ďalšie zaujímavosti. Keď skončíme s programom make (o niekoľko častí), pomaličky sa pustíme do programov autoconf, automake, libtool a ďalších, ktoré vám tvorbu Makefile súborov výrazne uľahčia, navyše budú vaše programy prenositeľné medzi mnohými operačnými systémami, To je v dnešnej dobe, keď sa systémy dajú počítať na desiatky (ak nie na stovky), viac ako potrebné . Samozrejme, prenositeľnosť závisí aj od vášho kódu, ale aj pre to existuje pár pravidiel.


Oto Komiňák


Článok bol uverejnený v magazíne PC Revue 04/2002.