Warning
This page is located in archive.

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

courses:b0b36prp:tutorials:make [2018/09/28 19:25] (current)
Line 1: Line 1:
 +====== 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í).
 +
 +<note important>​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"''​.</​note>​
 +
 +Základní zápis pravidla má tvar
 +
 +<​code>​
 +cíl: závislosti
 + akce
 +</​code>​
 +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.
 +<note important>​Řádek s akcí je odsazen jedním znakem tabulátor.</​note>​
 +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:
 +<code shell>
 +% clang program.c -o program
 +</​code>​
 +případně ještě oddělíme překlad souboru a linkování do spustitelné podoby:
 +<code shell>
 +% clang -c program.c -o program.o
 +% clang program.o -o program
 +</​code>​
 +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''​
 +<code make>
 +program: program.c
 + clang program.c -o program
 +</​code>​
 +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ý.
 +
 +<code shell>
 +% make
 +make: '​program'​ is up to date
 +</​code>​
 +
 +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ě
 +<code make>
 +program.o: program.c
 + clang -c program.c -o program.o
 +
 +program: program.o
 + clang program.o -o program
 +</​code>​
 +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
 +<code make>
 +% make program
 +clang -c program.c -o program.o
 +clang program.o -o program
 +</​code>​
 +
 +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'​.
 +<​code>​
 +bin: program
 +</​code>​
 +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ě
 +<​code>​
 +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
 +</​code>​
 +===== 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
 +<code make>
 +%.o: %.c
 + clang -c $< -o $@
 +</​code>​
 +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''​. ​
 +<​code>​
 +%.o: %.c
 + $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
 +</​code>​
 +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
 +<​code>​
 +$ CFLAGS=-O2 gmake
 +clang  -O2 -c program.c -o program.o
 +clang program.o -o program
 +</​code>​
 +Nebo použití kompilátoru ''​gcc''​
 +<​code>​
 +% CC=gcc make
 +gcc -O2 -pipe -c program.c -o program.o
 +clang program.o -o program
 +</​code>​
 +
 +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
 +<​code>​
 +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)
 +</​code>​
 +
 +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''​.
 +
 +<note important>​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ů. </​note>​
 +
 +<note tip>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.</​note>​
courses/b0b36prp/tutorials/make.txt · Last modified: 2018/09/28 19:25 by faiglj