Řešením samozřejmě je vždy vše přeložit a sestavit, např. pomocí nějakého scriptu. Script je v UNIXu něco jako dávkový soubor v DOSu. Takovéto řešení není ovšem vůbec elegantní a překládat či sestavovat nezměněné soubory je zcela zbytečné.
Pro tyto případy poskytuje UNIX utilitu make, která dokáže řídit posloupnost akcí v závislosti na mnoha okolnostech. Utilita make se nevyužívá jen pro řízení překladu a sestavení programů v C, ale má mnohem širší použití. Například když potřebujeme udržovat nové verze souborů, závisejících na jiných souborech. Typicky se používá pro vytváření a údržbu knihoven nebo archivů. A pokud získáváte programy z Internetu, nepochybně jste se s ní setkali, protože jejich instalace většinou probíhá právě pomocí make.
Myslím, že nebude na škodu, když ještě jednou shrnu použití utility make. A to velice stručně tak, jak byste ho mohli nalézt v různých příručkách a manuálech. Tedy:
make je nástroj pro správu projektů, vhodný zejména pro programování rozsáhlejších úloh. Udržuje, aktualizuje a obnovuje skupinu programů.
make [ -f makefile ] [ příznaky ] jména ]
Parametry příkazu jsou nepovinné, což označují hranaté závorky. Parametr s názvem makefile
označuje soubor, který obsahuje závislosti souborů a popisy cílů. Parametr jména
nařizuje programu make
provést pouze vyjmenované cíle v souboru uvedeného jako makefile
.
Poznámka:
V případě, že použijeme více přepínačů
(příznaků), není nutné je jednotlivě uvozovat
pomlčkou (znakem -). Je možné je zřetězit
například tak, jako v následujícím příkladě:
Program make provádí příkazy v makefile, aby došlo ke změně jednoho nebo více cílů uvedených v parametru jméno. Argument (parametr) je typicky program. Jestliže není uveden příznak -f, hledají se postupně následující soubory:
Tyto soubory obsahují popisy závislostí a jejich hledání se provádí ve výše uvedeném pořadí.
Jestliže makefile je zastoupen znakem -, popisy závislostí jsou očekávány na standardním vstupu. Je také možné specifikovat více než jen jeden argument -f makefile.
Program make aktualizuje jediný cíl, jestliže je na cílu závislý. Všechny předpokládané soubory z cíle jsou rekurzivně přidávány do seznamu cílů. Chybějícím souborem míníme změněný (neaktuální) soubor.
Argument makefile obsahuje sekvence specifických závislostí vstupů. První řádka vstupu je prázdná, samostatná. Dále je uveden nenulový (neprázdný) seznam cílů následovaný dvojtečkou a pak seznam předem daných souborů nebo závislostí, který již může být prázdný. Za textem následuje středník a všechny další řádky začínající tabulátorem <TAB> jsou příkazy shellu, jež jsou vykonány pro aktualizaci cílu!
Pozor:
První řádka nezačíná ani tabulátorem (<TAB>),
ani znakem čísla (#). Začíná tak nová
závislost či definice makra. Znak čísla uvozuje
komentář. Nikdy nenahrazujte tabulátor
mezerami!
Struktura argumentu makefile je následující:
cíl: závislost1 závislost2 ...;
příkaz shellu 1;
.
.
příkaz shellu n;
Poznámka:
V případě že za textem uvádíte středníky, může
vám make během své činnosti vypsat hlášku (null
command). Pokud středníky neuvádíte, make
proběhne naprosto bezproblémově.
Za zpětným lomítkem(\ ) a sekvencí nové řádky (RET) mohou pokračovat příkazy shellu. Shell je příkazový interpret v systému Unix. Všechno tisknutelné v make (vyloučeny pouze počáteční tabulátory) shellu přímo vyhovuje. Jednoduchý příklad ilustruje použití zpětného lomítka:
echo a\
b
Tyto vstupy produkují za sebou jdoucí ab. A tento výstup je přesně to samé, jako by to bylo produkováno shellem. Znak čísla (#) a nová řádka oblopuje komentáře. Tedy jednoduše. Vše co následuje za # je komentář. Následující makefile "říká", že pgm závisí na dvou souborech a.o, b.o, a že tyto jednotlivě závisejí na jejich příslušných (korespondujících) zdrojových souborech a příkazovém (hlavičkovém) souboru incl.h:
pgm: a.o b.o
cc a.o b.o -o pgm
a.o: incl.h a.c
cc -c a.c
b.o: incl.h b.c
cc -c b.c
Příkazové řádky jsou vykonány najednou, každá s vlastním shellem. První jeden, nebo dva znaky v příkazu mohou být následující:
Jestliže je prezentováno @, je potlačen tisk příkazu. Je-li uvedeno -, make ignoruje chyby. Řádka je po vykonání vytištěna. V případě, že je prezentován příznak -s nebo vstup .SILENT: anebo počáteční sekvence obsahuje @, řádka vytištěna není. Příznak -n specifikuje tisk bez spuštění (vykonání) příkazů. Pokud ale příkazová řádka obsahuje řetězec $(MAKE), je vždy vykonána. Příznak -t aktualizuje soubor s pozměněnými daty bez vykonání příkazů. To může být vhodné třeba v případě, že ve zdrojovém souboru jste pozměnili nebo přidali nějaký komentář a tudíž je zcela zbytečné provádět celý překlad a aktualizovat na něm závislé soubory.
Při normálním ukončení make je návratová (vracená) hodnota příkazů nenulová. Je-li na příkazové řádce uveden příznak -i nebo v makefile cíl .IGNORE, či počáteční znaková sekvence obsahuje -, pak jsou případné chyby ignorovány. Pokud je uveden příznak -k, v případě chyby zastaví práci na právě prováděném vstupu (větvi). Pokračuje ale v provádění dalších vstupů (závislostí), u kterých nedošlo k chybě a jsou nezávislé na neúspěšném vstupu. Příznak -b umožňuje spustit staré "make soubory" (psané pro starší verze make) bez chyb. Rozdíl mezi starou a současnou verzí je ten, že současná verze vyžaduje, aby všechny závislé řádky měly (možná nulový nebo implicitní) příkaz jím asociovaný. Předchozí verze make udávala, že jestliže příkaz nebyl specifikován explicitně, pak byl příkaz nulový. Přerušení nebo ukončení způsobí vymazání cíle, jestliže cíl není závislý na speciálním jméně .PRECIOUS.
Prostředí je vždy čteno při spuštění make. Všechny proměnné jsou přijímány do definic makra a jako takové zpracovány. Příznak -e způsobí přehlédnutí prostředí ukládané do makra v makefile.
Příkaz make pracuje ve třech kompatibilních módech. O typu módu je rozhodnuto z hodnot z prostředí proměnných PROG_ENV a z cesty, kterou je make vykonán. Proměnná PROG_ENV má dvě platné hodnoty:
V BSD módu je vyvolán make kompatibilní Berkeley. Tento prostředek /bin/sh je vždy použit jako příkazový interpret, který nebere ohled na hodnoty shellu, a příkazy jsou vypisovány (echovány) na standardní výstup s prefixem <tab>.
V módu POSIX je vyvolán make kompatibilní POSIX takový, že proměnná prostředí SHELL je vždy ignorována. SHELL je vždy nahrazen (přepsán) proměnnou MAKESHELL, kde shell je pokaždé použit k vykonání příkazů a příkazy jsou vypisovány na standardní výstup s prefixem <tab>.
Proměnná prostředí MAKEFLAGS je vždy zavedena při spuštění make pro všechny legální vstupní příznaky (kromě -p, -f a -d) definované pro příkazový řádek. Dále, při vyvolání procedury, make vytvoří proměnnou jestliže není v prostředí, vloží do ní aktuální příznak a rozšíří ji do vyvolaných příkazů. MAKEFLAGS tedy vždy obsahuje aktuální vstupní příznak. To se jeví jako velmi výhodné pro super-makes. Totiž, tak jako výše uvedeno, když je uveden příznak -n, příkaz $(MAKE) je přesto spuštěn. Proto se jednou může provést make -n rekurzivně v celém softwarovém systému, abychom viděli, jak má být spuštěn. To protože -n je dán v MAKEFLAGS a zabraňuje další vyvolání z $(MAKE). Toto je jedna cesta překládání všeho z makefile souborů pro softwarový projekt bez veškeré aktualizace.
Makra mohou být definována čtyřmi rozdílnými způsoby. Samotná makra mohou být definována implicitně uvnitř make. Do definic makra jsou přijímány všechny proměnné prostředí. Makra mohou být definována také v makefile, právě tak jako při make na příkazové řádce. Implicitně jsou vnitřní makra přepisována proměnnými prostředí, makra definovaná v makefile nedbají na proměnné prostředí (přehlíží je) a makra definovaná na příkazové řádce nedbají na makra definované v makefile. Implicitně je tedy dána priorita definic maker. Následující výčet těchto definic je uveden pro přehlednost a uspořádán od nejnižší priority:
Příznak -e změní implicitní priority tak, aby proměnné prostředí nedbaly maker definovaných v makefile. Vstupy formy string1=string2 jsou definicí makra. String2 je definován jako komentář se všemi velkými znaky, nebo nevyvázaná nová řádka.
Další výskyty $(string1[:subst1=[subst2]]) jsou nahrazeny obsahem string2. Závorky (kulaté) jsou nepovinné, jestliže je užit samotný znak jména makra a který není substituční sekvencí. Substituční sekvence subst1=subst2 je nepovinná. Jestliže je ale specifikována, všechny nepřekrývající se výskyty subst1 v uvedeném makru jsou nahrazeny proměnnou subst2. Výskyt subst1 musí být uveden před koncem slova string1. Řetězce, nyní míněné substitučního typu, jsou uvedeny znaky prázdnými, tabulátory, či znaky nové řádky, a začínajícími řádky. Příklad užití substituční sekvence je patrný v sekci knihovny - odstavec.
Poznámka:
Maker se hojně využívá. Makro je možné v příkazu
vyvolat jako ${jméno_makra} , např.:
Makro MACHINE je definováno při make, abychom mohli vzít v úvahu stroj nezávislých souborů makefile. Implicitní řetězec pro MACHINE je "vax", "mips" nebo "alpha". To podle toho, z jakého stroje je aplikace spuštěna. Makro MACHINE je možné předefinovat jedním z daných řetězců pro vývojové práce pro jiné platformy.
Vnitřně udržovaných maker (vnitřních maker) je celkem pět. Jsou vhodná k zapisování pravidel pro sestavování cílů.
Čtyři z pěti maker mohou mít alternativní formy. Když je doplněna volba velkého D nebo F do každého ze čtyř maker, znamená to změnu části adresáře pro D, nebo části souboru pro F. Tedy makro $(@D) se vztahuje k části adresáře z řetězce $@. Jestliže zde není adresářová část, je generována sekvence znaků ./. Pouze makro $? je vyloučené z těchto alternativních forem. Důvody pro to jsou diskutabilní.
Poznámka:
Pokud chceme v makefile použít určitá
speciální jména, je třeba je uvádět jako cíl -
tedy s dvojtečkou na konci a pokud
možno samostatně na novém řádku.
Určitá jména (pro instanci, která končí příponou .o) mají předběžné požadavky na soubory .c, .s, které mohou být odvozeny. Jestliže se nezměněné příkazy pro takový soubor objeví v makefile a jestliže odvozené předpoklady existují, pak předpoklad je přeložen (kompilován) do make cíle. V takovém případě má make odvozená pravidla, která dovolí (umožní) sestavovat soubory z jiných souborů pomocí vyšetřování přípon a rozhodování o použití nějakého vhodného odvozovacího pravidla. Běžná implicitní odvozovací pravidla jsou následující:
.c.c .sh.sh .c.o .c .o .c .c .s.o
.s .o .y.o .y .o .l.o .l .o .y.c
.y .c .l.c .c.a .c .a .s .a .h .h
Vnitřní pravidla pro program make jsou obsažena ve zdrojovém souboru rules.c. Tato pravidla mohou být lokálně modifikována. K vytištění (vypsání) výstupních pravidel přeložených uvnitř make ve formě vhodné k rekompilaci (přestavení) je použit následující příkaz z /bin/sh:
make -fp - 2>/dev/null </dev/null
Pouhá zvláštnost v tomto výstupu je (nulový) řetězec, který tiskne printf(3), když je předán nulový řetězec. Tilda (~) ve výše uvedených pravidlech odkazuje na SCCS soubor. Tedy pravidlo .c .o bude transformovat SCCS C zdrojový soubor na objektový soubor (.o). Protože s. z SCCS souborů je předpona (prefix), je to neslučitelné s make příponou 'point-to-view'. Proto tilda je cesta změn všech odkazů souboru na odkazy SCCS soubory.
Pravidlo pouze s jednou příponou (to je .c:) je definice, která říká, jak sestavit x z x.c. Druhá přípona je opravdu nulová. To je užitečné při sestavování cílů pouze z jednoho zdrojového souboru (např. shell procedury, samotné C programy).
Dodatečné přípony jsou dány jako seznam závislostí pro cíl jménem .SUFFIXES. Řazení je významné. První možné jméno, pro které jak soubor tak i závislost existuje, je odvozeno jako předpoklad. Implicitně je dán seznam následovně:
.SUFFIXES: .o .c .y .l .s
Zde znovu výše uvedený příkaz pro tisk vnitřních pravidel zobrazí seznam přípon implementovaných na běžném stroji. Akumuluje mnohonásobné seznamy přípon; .SUFFIXES: bez závislostí čistí seznam přípon.
Stručný a jednoduchý příklad:
pgm: a.o b.o
cc a.o b.o -o pgm
a.o b.o: incl.h
Výše uvedený příklad může být takto jednoduše zapsán, protože make má množinu vnitřních pravidel pro sestavení souborů. Uživatel smí přidat pravidla do tohoto seznamu, samotným vložením do souboru makefile.
Určitá makra jsou použita implicitními pravidly, aby umožnila začlenění volitelných přepínačů ve výsledných příkazech. Například CFLAGS, LFLAGS a YFLAGS jsou užity jako příznaky překladače k cc, lex a yacc samostatně. A znovu doporučena předchozí metoda pro prohlížení běžných pravidel.
Odvozování z předpokladů může být kontrolováno. Například pravidlo k vytvoření souboru s příponou .o ze souboru .c je specifikováno jako vstup s .c.o: jako cíl a ne závislosti. Příkazy shellu asociované cílem definujícím pravidlo pro vytvoření .o souboru z .c souboru. Každý cíl, který v něm nemá přepínače a začíná tečkou, je identifikován jako pravidlo a není pravým cílem.
Jestliže cíl nebo jméno závislosti obsahuje závorky, je přejímán do archivu knihovny. Řetězec v závorkách odkazuje na položku v knihovně. Tedy lib(file.o) i $(LIB)(file.o) ukazují do archivu, který obsahuje file.o. (Toto přijímá LIB makro, které musí být nejdříve definováno.) Výraz typu $(LIB)(file1.o file2.o) je nepřípustný.
Pravidla náleží do archivu knihoven a mají formu .XX .a, kde .XX je přípona, ze které je vyrobena položka archivu. Bohužel vedlejší produkt z běžné implementace žádá rozdílný XX z přípon archivu položek. Tedy lib(file.o) nesmí být explicitně závislý na file.o. V příkladě je to přijímáno, když zdrojové soubory jsou všechny zdroje typu C:
lib: lib(file1.o) lib(file2.o) lib(file3.o)
@echo lib is now up-to-date
.c.a:
$(CC) -c $(CFLAGS1) $<
ar rv $@ $*.o
rm -f $*.o
Ve skutečnosti je pravidlo .c.a - zapsané výše - sestaveno v make a není tedy v tomto příkladě nutné. Více zajímavý, ale více omezený příklad z udržování archivu knihovny má následující konstrukci:
lib: lib(file1.o) lib(file2.o) lib(file3.o)
$(CC) -c $(CFLAGS) $(?:.o=.c)
ar rv lib $?
rm $?
@echo lib is now up-to-date
.c.a:;
V tomto příkladě je užit substituční mód z expanze makra. $? seznam je definován do množiny jmen objektového souboru (uvnitř lib), jehož C zdrojové soubory jsou zastaralé. Substituční mód převádí .o do .c. (Bohužel, jeden nesmí transformovat na .c .) Deaktivuje (znepřístupní) tedy .c.a: pravidlo, které by mělo vytvořit každý objektový soubor, jeden po druhém. Uvedený typ konstrukce začíná velmi nešikovně, pokud archiv knihovny obsahuje směs programů v jazyce C a programů assembleru.
-i
.
Jména souboru se znaky =, : nebo @ se nevykonávají.
Příkazy, které jsou přímo spustitelné v shellu, jmenovitě např. cd, jsou účinné na nové řádce v make.
Syntaxe (lib(file1.o file2.o file3.o))
je neplatná - nepřípustná.
Není možné sestavit lib(file.o) z file.o. Makro $(a:.o=.c )
neprovádí žádné příkazy.
Také již víme, že jednotlivá pravidla v makefile říkají, kdy a jak znovu vytvořit soubor, který je cílem daného pravidla. Uveďme jednoduchý příklad, kde cílem je soubor prog, který je závislý na souborech, ze kterých vzniká překladem.
# Makefile pro vytvoření prog z a.c a b.c
prog: a.c b.c defs.h
cc -o prog a.c b.c
Spuštěním programu make bez parametrů lze provést zpracování souboru Makefile:
$ make
cc -o prog a.c b.c
Další spuštění programu make provede překlad pouze tehdy, byl-li některý ze zdrojových textů modifikován.
$ make
make: 'prog' is up to date.
Překlad rozsáhlejších programů je vhodné rozdělit - nejdříve překladem zdrojových textů vytvoříme objektové soubory, ze kterých pak v samostatném kroku sestavíme spustitelný program. Výše uvedený příklad bude tedy vypadat následovně:
prog: a.o b.o
cc -o prog a.o b.o
a.o: a.c defs.h
cc -c a.c
b.o: b.c defs.h
cc -c b.c
Po spuštění make nalezne první pravidlo, před jeho zpracováním však musí nalézt a zpracovat všechna pravidla na kterých závisí. V tomto případě bude nejprve znovu přeložen změněný zdrojový text. Výsledný objektový soubor pak bude sestaven spolu s objektovým souborem uchovaným z předchozího překladu ve spustitelný program.
Po změně souboru a.c program make spustí pouze dva příkazy:
$ make
cc -c a.c
cc -o prog a.o b.o
Pokud požadujeme vytvoření pouze některého cíle, můžeme tento cíl zadat jako argument příkazu make:
$ make b.o
cc -c b.c
Seznam závislostí v pravidle může být i prázdný. Toho se někdy využívá pro definici cílů, které nejsou soubory. Následující cíl definuje vymazání všech objektových souborů v aktuálním adresáři.
clean:
rm *.o
Toto pravidlo můžeme vložit do předchozího Makefile
a spustit jako: $ make clean
rm *.o
A jelikož program make obsahuje množinu interních pravidel pro překlad nejčastějších druhů souborů, může být předchozí příklad za pomoci těchto pravidel zapsán stručněji:
prog: a.o b.o
cc -o prog a.o b.o
a.o: a.c defs.h
b.o: b.c defs.h
Poznámka:
Pokud cíl uvedeme na příkazovém řádku a je možné
ho vytvořit podle některého předdefinovaného
pravidla, nemusí být v Makefile tento cíl
uveden.
Setkáte-li se se soubory makefile při instalaci programů získaných po síti, stačí si většinou pozorně prohlédnout definice maker na jeho začátku. Zde je možné předefinovat např. adresáře, typ překladače, atd. Do dalšího obsahu souboru již není nutné dále zasahovat.
progr: prvni.o druhy.o
cc -o progr prvni.o druhy.o
prvni.o: prvni.c defin.h
cc -c prvni.c
druhy.o: druhy.c defin.h
cc -c druhy.c