====== make ====== GNU make (https://www.gnu.org/software/make/) je nástroj pro automatizované vytváření cílových souborů ze zdrojových souborů. Často se používá pro překlad zdrojových souborů (např. C/C++) na spustitelné binární soubory, lze jej však s výhodou použít pro všechny typy úloh kde se z nějakých souborů vytvářejí jiné soubory. Make zpracuje předpis, kterému říkáme "makefile" a který je zpravidla uložen v souboru s názvem "Makefile" nebo "makefile". Makefile je souborem pravidel v následujícím tvaru: target: dependencies commands **target** je název pravidla, který se zpravidla shoduje s názvem cílového souboru který chceme vytvořit **dependencies** (závislosti) je nepovinný seznam souborů nebo jiných pravidel, která jsou potřeba pro aplikaci tohoto pravidla. Pokud tyto soubory neexistují, make se pokusí najít pravidla pro jejich vytvoření a rekurzivně je vykoná. Pokud některý soubor nelze ani vytvořit, celé pravidlo selže. **commands** je sekvence příkazů (mohou být na více řádek), jejichž vykonáním dojde k provedení pravidla. Příkazy v Makefile musí být odsazeny tabulátorem. Pozor na editory které při stisku tabulátoru vloží sekvenci mezer! Make se při vykonávání pravidel chová velmi jednoduše: pokud jsou splněny (existují) požadované //dependencies//, vykoná se sekvence příkazů. Přitom se však nijak nekontroluje, že spuštěním uvedených příkazů se opravdu vytvoří soubor //target// -- mezi hlavičkou pravidla a příkazy není žádná implicitní vazba (ta je na autoru Makefile podle napsaných příkazů). Hlavní výhodou make je, že provádí pouze ta pravidla, která jsou nutná a přeskakuje ta, která by se prováděla zbytečně. Typickým případem je situace, kdy máme projekt sestávající z více zdrojových souborů, všechny soubory již máme přeložené (předchozím spuštěním make), provedeme změnu v jednom ze souborů a chceme projekt znovu přeložit. V takovém případě make spustí pouze překlad změněných souborů a ostatní ponechá přeložené od minule. Aby však tato funkcionalita pracovala správně, je nutné aby byl Makefile napsán korektně, což zahrnuje následující pravidla: * jedno pravidlo provádí pouze jednu elementární operaci (typicky převádí právě jeden soubor na jiný) * jméno pravidla které vytváří soubor se shoduje se jménem souboru * pravidlo má korektně uvedené závislosti Rozhodování o vykonání pravidla se provádí podle následujícího postupu: * pokud cílový soubor neexistuje, pravidlo se vždy provede. * pokud cílový soubor existuje, pravidlo se provede pokud je čas změny některého souboru v //dependencies// novější než čas poslední změny cílového souboru. Je zřejmé, že pokud dojde k nekonzistenci časových značek souborů, make nemusí fungovat správně. V takových případech může být nejbezpečnější všechny vygenerované soubory smazat a nechat make vytvořit celý projekt znovu. ===== Spouštění make ===== Make se spouští příkazem "make" v adresáři, ve kterém se nachází Makefile. Pokud jej spustíme bez parametrů, provádí se první pravidlo v souboru Makefile. Parametrem příkazu make je možné zadat název jiného pravidla, které se má vykonat. Pravidla v závislostech se vykonávají podle potřeby rekurzivně. Pokud kterékoliv pravidlo selže (nejsou k dispozici závislosti, nebo některý příkaz skončí chybou), je zastaven celý proces a make vrací chybu. Při běhu makefile vypisuje všechny prováděné příkazy, podle čehož lze snadno sledovat, která pravidla se provádějí. V případě, že vše proběhne v pořádku, vrací make návratovou hodnotu a nemusí vypisovat žádné hlášení na výstup. ===== Triviální příklad ===== Mějme aplikaci sestávající ze tří souborů //src1.c//, //src2.c// a //src3.c//, jejichž překladem má vzniknout binární spustitelný soubor. Funkce //main()// je v jednom z nich. Součástí projektu může být i několik hlavičkových souborů (//.h//), které však z hlediska volání kompilátoru nejsou důležité. Sestavení aplikace vyžaduje nejprve přeložení jednotlivých zdrojových souborů na binární .o soubory a jejich následné "slinkování" do spustitelného souboru "aplikace". Makefile pro sestavení této aplikace pak může vypadat následovně: aplikace: src1.o src2.o src3.o gcc -o aplikace src1.o src2.o src3.o src1.o: src1.c gcc -c src1.c src2.o: src2.c gcc -c src2.c src3.o: src3.c gcc -c src3.c Je zřejmé že pokud by se psal Makefile takto (samostatné pravidlo pro každý soubor), bylo by to poměrně pracné a náročné na údržbu v případě dalšího vývoje. Berte to proto pouze jako ukázku pro vysvětlení základní funkcionality. Průběh make: * Po spuštění příkazu "make" se provede první pravidlo "aplikace", neboť soubor s názvem "aplikace" zatím neexistuje. * Pravidlo "aplikace" je závislé na souborech src1.o -- src3.o, které také neexistují. Naleznou se tedy vhodná pravidla pro jejich vytvoření a ta se vykonají. * Pro každý neexistující .o soubor se spustí odpovídající pravidlo, které jej vytvoří z příslušného .c souboru. * Po překladu všech .c souborů na .o se může vykonat příkaz pravidla "aplikace", který provede slinkování. ===== Pokročilé možnosti make ===== ==== Generická pravidla ==== Pokud máme řadu souborů, které se zpracovávají stejným příkazem (např. kompilace .c souborů), není nutné psát pravidlo pro každý soubor zvlášť, ale je možné využít generické pravidlo ve tvaru %.o: %.c gcc -c -o $@ $< Toto pravidlo se aplikuje vždy, když potřebujeme vytvořit soubor .o a existuje soubor .c se stejným jménem. ''$<'' je proměnná make, za kterou se dosadí jméno vstupního .c souboru, proměnná ''$@'' má hodnotu cíle pravidla, tedy .o souboru. Toto pravidlo v sobě navíc implicitně zahrnuje závislost .o souboru na .c souboru. ==== Proměnné v Makefile ==== Proměnné (též makra) v Makefile nám umožňují jej parametrizovat a pomáhají jeho udržitelnosti. Zvláště užitečné jsou proměnné pro uložení řetězců nebo seznamů, které se opakují na mnoha místech. Proměnné se deklarují v úvodní části Makefile, ještě před prvním pravidlem, ve tvaru PROMENNA=hodnota Hodnotu proměnné pak mohu použít v příkazu, v závislostech, jménu pravidla, nebo při definici jiných proměnných zápisem $(PROMENNA) === Příklad === Při překladu projektu v jazyce C potřebujeme často pracovat se seznamem všech .o souborů. OBJ=main.o src1.o src2.o bin: $(OBJ) gcc -o bin $(OBJ) clean: rm $(OBJ) Následující Makefile slouží k překladu projektu v jazyce C. Pokud se rozhodneme použití syntaxe jazyka C++ ve zdrojových kódech, nebo budeme chtít změnit parametry kompilátoru, uděláme změnu pohodlně na jednom řádku, který najdeme na začátku souboru Makefile. OBJ=... CC=gcc CFLAGS=-g -Wall -Iinclude bin: $(OBJ) $(CC) -o $@ $(OBJ) %.o: %.c $(CC) -o $@ -c $(CFLAGS) $< specific.o: specific.c $(CC) -o $@ -c $(CFLAGS) -Dspecificka_definice $< Proměnná ''$@'', použitá v pravidlu bin se nahradí názvem pravidla (tj. v tomto případě řetězcem "bin"). Pro vytvoření každého souboru se hledá nejspecifičtější pravidlo - v tomto případě se tedy soubor specific.c překládá jiným příkazem než ostatní .c soubory) (TODO: zkontrolovat zda se nepoužije první vyhovující pravidlo) ===== Další možnosti make ===== Make nám nabízí řadu funkcí a maker pro ulehčení práce. SRC=$(wildcard *.c) Za makro //wildcard// make dosadí seznam všech souborů odpovídajících dané masce. OBJ=$(patsubst %.c,%.o,$(SRC)) Makro //patsubst// umožňuje provést nahrazení části názvu v seznamu souborů. 3 parametry makra jsou vzor pro nalezení, vzor pro nahrazení a vstupní seznam souborů. Uvedený příklad nahradí všechna jména .c souborů v seznamu SRC odpovídajícími jmény .o souborů a uloží nový seznam do proměnné OBJ. ===== Pokročilé řešení závislostí při překladu kódu v C/C++ ===== Nikde ve výše uvedených příkladech překladu C/C++ kódu se nijak neřešila závislost na header souborech vložených ve zdrojových souborech direktivou #include. Protože make nijak neanalyzuje prováděné příkazy, nemůže si být této závislosti vědom a při změně některého .h souboru automaticky nevyvolá překlad .c souborů které jej vkládají, jak bychom požadovali. Toto chování je možné vynutit přidáním header souborů do závislostí pravidel pro jednotlivé soubory: soubor1.o: soubor1.c header1.h header2.h soubor2.o: soubor2.c header1.h header3.h header4.h Tato pravidla mohou existovat i vedle stávajícího pravidla pro překlad (např. .c.o: ...), není tedy nutné příkaz pro překlad u každého z nich opakovat. Uvedený způsob je však velmi náročný na údržbu v rámci vývoje, neboť při přidání header souboru do .c souboru je nutné upravit i Makefile. Tento problém je možné s využitím překladače gcc vyřešit pomocí přepínače -MM, který umožňuje vygenerovat seznam závislostí na header soubory ve tvaru kompatibilním s formátem Makefile. Tento výstup můžete uložit do samostatného souboru, který pak vložíte do vašeho Makefile direktivou include. Funkční a efektivní řešení na tomto principu však není triviální a před jeho použitím doporučuji se inspirovat vhodnými příklady. Jinou možností jak překládat komplikované projekty s korektním uvažováním závislostí je použít sofistikovanější nástroj, určený specificky pro danou činnost (překlad C/C++ projektů), který tyto závislosti hlídá automaticky. Takovým nástrojem je např. Meson Build system: http://mesonbuild.com/