Lekce 2 - Céčko a Linux – Makefile
V minulé lekci, Céčko a Linux - Úvod, jsme se podívali na to, jak používat překladač, jaké jsou jeho možnosti a jak je můžeme prakticky využít.
Co ale, když budeme mít více souborů? Museli bychom každý přeložit
zvlášť a nakonec je slinkovat dohromady. A když vyvíjíme, často
překládáme i několikrát během deseti minut... Určitě budete souhlasit,
že psát pokaždé příkazy znovu by bylo minimálně trošku nepraktické.
Naštěstí tu máme program make, který toto vše udělá za nás. Jeho
použití je velmi jednoduché – ve složce s naším projektem si vytvoříme
soubor Makefile a napíšeme, co chceme, aby dělal. Jak, to si dnes ukážeme.
Jednoduchý příklad
Vytvoříme následující program – hello.c
#include <stdio.h> #include <stdlib.h> int main(void) { printf("Hello world!\n"); return 0; }
A do stejné složky soubor se jménem Makefile a tímto obsahem.
# Projekt: Hello world! # Autor: David Novák # Datum: 16.2.2015 CC=gcc CFLAGS= -std=c99 -pedantic -Wall -Wextra -g hello: hello.c $(CC) $(CFLAGS) hello.c -o hello
Náš jednoduchý program všichni důvěrně znáte, takže o něm mluvit
nebudu. Když se podíváme na Makefile, vidíme jako první věc hlavičku –
jméno projektu, autor, datum, případně poznámky nebo cokoliv uznáme za
vhodné. Je to dobrý zvyk (kdekoliv nejen v Makefile) a jeho dodržováním
usnadníte ostatním lidem práci s vaším kódem.
Jak psát Makefile
Komentáře začínají znakem #, jak jste si jistě všimli a mohou být kdekoliv (například za příkazem). Cokoliv za znakem # až do konce řádku je bráno jako komentář. Hned po hlavičce si můžeme všimnout deklarace proměnných CC (zkratka pro C Compiler) a CFLAGS (názvy si samozřejmě můžete zvolit libovolné). Tyto příkazy samozřejmě můžeme napsat pokaždé znovu (případně pro každý soubor mohou být jiné), ale většinou je budeme chtít pro snadnou změnu deklarovat na začátku souboru.
Poslední částí je definice pravidla hello. „hello“ je název pravidla
(cíle) – většinou se jedná o název generovaného souboru nebo akce,
kterou chceme vykonat (například clean). Následuje dvojtečka a seznam
závislostí. Závislosti jsou soubory nebo jiná pravidla, které pravidlo
vyžaduje. Na dalším řádku (MUSÍ začínat tabulátorem!)
uvedeme příkaz, který bude vykonán. Příkaz končí koncem řádku a
pravidlo je ukončeno řádkem, který nezačíná tabulátorem. Příkazů
můžeme napsat kolik chceme (každý začíná tabulátorem). Pravidlo se
vykoná jen pokud jsou k dispozici všechny soubory vypsané v závislostech.
Pokud máme v závislostech další pravidla, prvně se všechny vykonají a až
potom se provede naše pravidlo. Tak... To byla nudná teorie a teď se
podíváme, jak to prakticky použít.
Otevřeme si terminál ve složce s naším projektem a napíšeme příkaz make. Pokud jste Makefile opsali správně, zobrazí se vám něco takového.

Vidíme, že tímto krátkým, jednoduchým příkazem jsme vykonali totéž, jako kdybychom napsali „gcc -std=c99 -pedantic -Wall -Wextra -g hello.c -o hello“. Pěkné, že? Ale to zdaleka není vše, co make umí! Další příjemná vlastnost je, že pravidlo se vykoná pouze tehdy, je-li čas vytvoření cílového souboru starší než čas některého souboru v seznamu závislostí. V praxi to znamená, že se přeloží pouze ty soubory, u kterých jsme provedli změnu. To je u větších projektů značná úspora času.
Další možnosti
Náš Makefile si trochu rozšíříme. Hlavičku pro zkrácení nebudu uvádět.
CC=gcc CFLAGS= -std=c99 -pedantic -Wall -Wextra -g NAME=hello $(NAME): hello.c $(CC) $(CFLAGS) hello.c -o $(NAME) clean-bin: rm -f $(NAME) # smaže binární soubor clean-bck: rm -f *~ *.bak # smaže všechny záložní soubory clean: clean-bin clean-bck # smaže binární soubor i zálohy
Jak vidíte, do pravidel v Makefile můžeme napsat i klasické příkazy bashe. Dále si můžete všimnout, že jsem vytvořil proměnnou NAME s názvem generovaného souboru. Používáme ji již na třech místech a kdybychom se rozhodli ji změnit, bylo by to otravné přepisovat. Nakonec jsem vytvořil tři další pravidla, přičemž nejzajímavější je poslední pravidlo clean, které vykoná clean-exe a clean-bck.
Pravidla máme. Jak je ale použít? Make funguje tak, že když jej zavoláme bez parametrů, vykoná první pravidlo a pak skončí. V našem případě to znamená, že příkazem make spustíme kompilaci souboru hello.c. Pokud chceme zavolat jiné pravidlo, je to velmi jednoduché – stačí jej napsat jako parametr (argument).

Co udělat, když chceme například použít vlastní hlavičkový soubor?
Stačí jej pouze připsat do závislostí.. Ukážeme si jak.
Pro účely tohoto tutoriálu si vytvoříme jednoduchý hlavičkový soubor my_config.h
#ifndef __MY_CONFIG__ #define __MY_CONFIG__ #define MOJE_CISLO 42 #endif
Program upravíme následovně:
#include <stdio.h> #include <stdlib.h> #include "my_config.h" int main(void) { printf("Hello world!\n" "Moje číslo je: %d\n", MOJE_CISLO); return 0; }
A Makefile bude vypadat takto:
# Projekt: Hello world! # Autor: David Novák # Verze: 1.1 # Datum: 16.2.2015 CC=gcc CFLAGS= -std=c99 -pedantic -Wall -Wextra -g NAME=hello $(NAME): $(NAME).c my_config.h # zde jsme připsali „my_config.h“ $(CC) $(CFLAGS) $(NAME).c -o $(NAME) clean-exe: rm -f $(NAME) # smaže binární soubor clean-bck: rm -f *~ *.bak # smaže všechny záložní soubory clean: clean-exe clean-bck # smaže binární soubor i zálohy
Když teď spustíme make a následně program, výstup bude něco takového:

Pro zajímavost si ještě ukážeme, co by se stalo, kdyby my_config.h chyběl.

Makefile pro větší projekt
Jednoduché, že? Co ale, když má náš projekt více modulů? Obecně se
dá říct, že modul je samostatná jednotka, která je do jisté míry
nezávislá na hlavním programu (a například mu poskytuje nějaké služby).
Možná by se dal trochu přirovnat k objektu (ale tohle berte prosím volně...
). Každopádně se skládá z
.c a .h souboru. Pokud tedy máme hlavní soubor a k tomu ještě další modul,
máme dva soubory, které musíme přeložit a slinkovat. Minule jsme si řekli
o přepínači -c a tohle je přesně případ, kdy se bez něj neobejdeme.
Takhle bude vypadat náš jednoduchý modul.c
#include "modul.h" #include <stdio.h> #include <stdlib.h> int vypocet(int a, int b) { return (a + b)*a; }
Takhle jeho hlavičkový soubor modul.h
#ifndef __MY_MODUL__ #define __MY_MODUL__ extern int vypocet(int a, int b); #endif
Jen bych podotkl, že pro vytváření modulů a hlavičkových souborů platí určité konvence a vyplatí se je dodržovat. Tyto příklady jsou pouze pro účely tutoriálu a pro reálný vývoj jsou minimálně nekompletní. O modulárním programování si možná v budoucnosti povíme něco víc. Každopádně, pokud vás to zajímá, na internetu se dá najít množství informací na toto téma.
A takhle náš upravený hlavní soubor.
#include <stdio.h> #include <stdlib.h> #include "my_config.h" int main(void) { printf("Hello world!\n" "Moje číslo je: %d\n", MOJE_CISLO); printf("Výpočet: %d\n", vypocet(2,5)); return 0; }
Nakonec musíme upravit náš Makefile.
CC=gcc CFLAGS= -std=c99 -pedantic -Wall -Wextra -g NAME=hello $(NAME): modul.o $(NAME).o # slinkování přeložených souborů $(CC) $(CFLAGS) $(NAME).o modul.o -o $(NAME) $(NAME).o: $(NAME).c my_config.h # překlad hlavního souboru $(CC) $(CFLAGS) -c $(NAME).c -o $(NAME).o modul.o: modul.c modul.h # překlad modulu $(CC) $(CFLAGS) -c modul.c -o modul.o
Není to moc hezké, že? A když budeme mít projekt o desítkách modulů,
nebude to ani příliš praktické. Když bychom si vytvořili nový
hlavičkový soubor, museli bychom otevřít Makefile a provést změny.. Jde to
ale lépe
NAME=hello OBJFILES=$(NAME).o modul.o # zde napíšeme všechny moduly CC=gcc CFLAGS= -std=c99 -pedantic -Wall -Wextra -g # vzorové pravidlo pro generování všech objektových souborů %.o : %.c $(CC) $(CFLAGS) -c $< # Startovací pravidlo - pro přehlednost all: $(NAME) # Generování závislostí # při změně souborů spustíme 'make dep' dep: $(CC) -MM *.c >dep.list -include dep.list # závěrečné slinkování $(NAME): $(OBJFILES) $(CC) $(CFLAGS) $(OBJFILES) -o $@
Vypadá to jinak, že? Generování závislostí přenecháme překladači
– takhle stačí při každé změně (např. přidáme hlavičkový soubor)
jen zavolat make dep. Přepínač -MM je specialita k tomuto určená –
překladač jen vygeneruje závislosti (include). $< a $@ jsou tzv.
automatické proměnné. $< obsahuje název zdrojového souboru a $@ název
cílového souboru. Získají je z hlavičky pravidla (pro nás je důležité,
že to funguje... ). Nakonec
bych měl asi ještě vysvětlit vzorové pravidlo.. Znak % je tzv. žolík
(angl. wildcard) a zastupuje libovolný text. Vzorové pravidlo funguje tak, že
v seznamu skutečných pravidel (to nám vygeneroval překladač) najde ty,
které mu odpovídají – cíl je soubor s koncovkou .o, který má v
závislostech alespoň jeden soubor s koncovkou .c.
Tento Makefile můžete použít na většinu projektů a pokud byste
chtěli, můžete se podívat do dokumentace nebo na internet a určitě ho i
rozšířit o další možnosti jako třeba generování dokumentace pomocí
doxygen (trochu složitější) nebo zabalení celého projektu do archivu. To
je pro dnešek vše.
Příště, v lekci Céčko a Linux - Debugging, se podíváme na debugging a ladící nástroje pod Linuxem.