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. GNU Make. Není to jediný způsobem mezi další používáné nástroje patří např. cmakecmake nebo třeba 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í).
“gnu make tutorial”
nebo “gnu make manual”
.
Základní zápis pravidla má tvar
cíl: závislosti akcekde 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.
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.
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 programpří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 programTo 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 programNyní 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 programTento 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: programDá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
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 programNebo 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
.
*.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ů.
make clean
a případně využít ccache nebo změnit Makefile
či nástroj pro sestavení programu.