====== Makefile - Řízení překladu a sestavení programu ====== Příma kompilace programu prostřednictvím kompilátoru a příkazové je vhodnou pro prvotní seznámení se základními přepínači kompilátoru a linkeru. Pro komplexníší programy, ale také pro často opakované akce je mnohem výhodnější použít vhodný nástroj pro řízení překladu. Mezi ty základní patří zcela jistě make nebo případě Linuxu tzv. [[https://www.gnu.org/software/make|GNU Make]]. Není to jediný způsobem mezi další používáné nástroje patří např. cmake[[https://cmake.org/|cmake]] nebo třeba [[https://ninja-build.org/|Ninja]]. Pro celou řadu případů a řešení úloh v PRP nám plně postačí ''make'', který lze nalézt snad na všech distribucích Linuxu jako příkaz ''make'', případně jako ''gmake'' v BSD* systémech. Předpis jak program sestavit se zapisuje do tzv. ''Makefile'', a při volání ''make'' program hledá v aktuálním pracovním adresáři právě soubor s názvem ''Makefile''. Základní syntax není složitá, ale pokročilé Makefile, které zjednodušují zápis mohou být na první pohled relativně komplexní, neboť se používají zavedená jména proměnných. Nicméně vždy se jedná o deklarativní zápis pravidel jak vytvořit soubor (cíl) z jiných souborů (závislostí). Cílem tohoto textu není nahradit manuál ani existující návody, jak používat make, ale poskytnou minimální prakticky orientované příklady pro zjednodušení práce na domácích úlohách a jejich odevzdávání. Podrobnější návody lze nalézt napříklady vyhledáváním ''"gnu make tutorial"'' nebo ''"gnu make manual"''. Základní zápis pravidla má tvar cíl: závislosti akce kde //cíl// je jméno souboru nebo symbolické jméno cíle, //závislosti// je jeden nebo více souborů (proměnných nebo symbolických cílů) a //akce// příkaz, který se má vykonat, aby se splnil (vytvořil) //cíl//. Zároveň je detekováno, zdali existují a jsou splněny //závislosti// a pokud ne, hledá se a použije se nejdříve pravidlo pro vytvoření //závislostí// a teprve poté se zavolá příslušná akce. Řádek s akcí je odsazen jedním znakem tabulátor. Make obsahuje jak impliciní pravidla, tak umožňuje zápis explicitních pravidla, na kterých si nejdříve ukážeme základní použití. Následně si zápis Makefile souborů zjednodušíme využitím tzv. "''pattern rules''" nebo implicitních pravidel. ===== První makefile s explicitním zápisem ===== Mějme program se zdrojovým souborem ''program.c'', který můžeme zkompilovat do binárního spustitelného soubor ''a.out'' prostým voláním. ''clang program.c''. Raději však explicitně určíme výstupní soubor ''program'' voláním: % clang program.c -o program případně ještě oddělíme překlad souboru a linkování do spustitelné podoby: % clang -c program.c -o program.o % clang program.o -o program To je poměrné pracné a pokud je náš program složitější, budeme překládat a spouštět opakovaně. Napíšeme si proto jednodudchý předpis jak vytvořit ''program'' ze souboru ''program.c'', který uložímě do souboru ''Makefile'' program: program.c clang program.c -o program Nyní stačí zavolat ''make'' (případně ''gmake''), který načte ''Makefile'' v aktuálním pracovním adresáři a pokusí se sestavit první cíl, kterém je náš ''program'' závislý na ''program.c''. Make si u závislostí (na souborech) všímá tzv. //modification time// a pokud nedošlo ke změně, akce se neprovede, neboť výsledný soubor by byl stejný. % make make: 'program' is up to date To se hodí zejména v projektech s mnoha soubory, kde je pak výhodné kompilovat jednotlivé soubory zvláště do příslušných .o souborů a následně samostatně linkovat. Mimo jiné pak lze využít i paraleního překladu na strojích s více výpočetními jádry nebo procesory. Předpis pro překlad souboru a následné linkování může například vypadat následovně program.o: program.c clang -c program.c -o program.o program: program.o clang program.o -o program Tento předpis má jako první cíl ''program.o'', proto po volání ''make'' dojde pouze k vytvoření soubor ''program.o''. Uvedeme-li však cíl ''program'' jako argument programu ''make'' dojde k sestavení cíle ''program'', který je závislý na ''program.o'' a výstup může vypadat například % make program clang -c program.c -o program.o clang program.o -o program Abychom nemuseli explicitně uvádět cíl při každém volání ''make'' můžeme jako první dát implicitní cíl. V našem případě to uděláme vytvořením cíle ''bin'', který má jako závislost 'program'. bin: program Dále vytvoříme cíl ''clean'', ve kterém smažeme všechny soubory, vytvořené při překladu. Celý ''Makefile'' pak může vypadat následovně bin: program program.o: program.c clang -c program.c -o program.o program: program.o clang program.o -o program clean: rm -rf program program.o ===== Použití standardních proměnných ===== Výše uvedené příklady ''Makefile'' souborů lze jistě použít, ale vyžadují poměrné rozsáhlou editaci a uvedení jmen souborů na vícero místech. Mnohem praktičtější je využití vzorů pro překlad soubor, např. jak vytvořit soubory .o ze souborů .c %.o: %.c clang -c $< -o $@ kde je použita proměnná astupující soubor vyhovující pravé straně pravidla, tj. náš ''program.c'', a proměnná definující výstup (levou část pravidla), tj. ''program.o''. Dále není nutné uvádět explicitně kompilátor, ale je možné využít základních proměnných ''CC'' a také parametrů pro preprocessor (''CPPFLAGS'') a kompilátor ''CFLAGS''. %.o: %.c $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ Což v podstatě odpovídá výchozímu implicitnímu pravidlu pro kompilací Cčkových souborů. Výhoda použití zavedených proměnných je, že můžeme ovlinit překlad, aniž bychom muse-li měnit samotný ''Makefile''. Například komplilaci s optimizací ''-O2'' můžeme realizovat $ CFLAGS=-O2 gmake clang -O2 -c program.c -o program.o clang program.o -o program Nebo použití kompilátoru ''gcc'' % CC=gcc make gcc -O2 -pipe -c program.c -o program.o clang program.o -o program Komplexnější ''Makefile'', ve kterém je nutné pouze specifikovat jméno programu, který se má sestavit ze zdrojových souborů .c (resp. .o) pak může vypadat například TARGET = program OBJS = $(patsubst %.c,%.o,$(wildcard *.c)) CFLAGS +=-std=c99 -pedantic -Wall CFLAGS += -O2 bin: $(TARGET) $(OBJS): %.o: %.c $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ $(TARGET): $(OBJS) $(CC) $(OBJS) $(LDFLAGS) -o $@ clean: rm -rf $(OBJS) $(TARGET) Jméno programu nastavuje na první řádku hodnotou proměnné ''TARGET''. Na druhém řádku jsou jména všech souborů v pracovním adresáři končící na ''.c'' uložena do proměnné ''OBJS'', ale nejdříve je nahrazen výskyt ''.c'' za ''.o'', čímž získáme seznam všech //object// souborů, ze kterých sestavíme program. Následně specifikujeme nastavení kompilátoru a jeho verzi a to tak, aniž bychom přepsali nějaký původní obsah proměnné ''CFLAGS'', například specifikovaný proměnnou prostředí. Podobně přidáme mezi parametry kompilátoru optimizalizaci ''-O2''. Proměnnou ''TARGET'' použijeme jako závislost prvního cíle ''bin'', ale také jako cíl závislý na přeložených souborech $(OBJS), které jsou přeloženy specifikovaných pravidlem se dvěma '':'', které specifikuje jak individuálně vytvořit soubory uvedené v ''OBJS'' ze souborů ''.c''. Využití hvězdičkové konvence ''*.c'' skrývý úskalí při linkování programu, neboť při něm záleží na pořadí uvedených souborů. Proto můžeme být vhodnější explicitně uvést seznam souborů. Při sestavování programů s více souborů, je vhodné pamatovat jakým způsobem funguje make a jak detekuje změnu v souborech. Pokud nejsou v seznamu závislostí uvedený hlavičkové soubory (což se např. nedělá z důvodu snížení počtu přístup na souborový systém), tak při změně hlavičky funkce není automaticky přelože příslušný .o soubor a výsledný program může vykazovat nepředvídatelné chování. V takovém případě je řešením volat ''make clean'' a případně využít [[https://ccache.samba.org/| ccache]] nebo změnit ''Makefile'' či nástroj pro sestavení programu.