Jak na pro­jek­ty v ja­zy­ce C

© Damig, 2004 – 2023
Koncept

Ať maká make – správa projektů pomocí programu make

V předchozí kapitole jsme viděli, že programy jde překládat ručně z příkazového řádku. Pamatovat si ovšem všechny přepínače a opisovat je znovu při dalším překladu není moc pohodlné. Chtělo by to nějaký konfigurační soubor, kam by se potřebné parametry jednou zadaly a pak už na ně nebylo potřeba myslet.

U některých projektů je potřeba překládat různé části různým způsobem a často i tytéž části různým způsobem pro různé účely, například pro ladění, testování, profilovací analýzu a podobně. Naštěstí tu je make (čti mejk), program, který tohle všechno odmaká za nás. Umí toho ještě více, je totiž programovatelný.

Provádět tyto změny v stále dokola v dialogových oknech není ani rychlé ani pohodlné. Dialogová okna pro toto nejsou dost flexibilní. Autoři rozumně navržených IDE to vědí, a obvykle umožňují řídit překlad nejen pomocí vlastních dialogů, ale i pomocí programu make, čímž dávají programátorovi maximální možnou volnost.

Informace z této kapitoly tedy pro vás budou užitečné jak v případě, že vám k práci stačí příkazový řádek a textový editor, tak i v případě, že používáte vývojové prostředí.

Program make

Make je program pro automatické kompilování programů, které jsou složeny z více modulů. Využijeme jej však i u jednoduchých projektů, které jsou tvořeny jediným zdrojovým souborem. Make totiž umí daleko více než jen překládat. Můžeme pomocí něj spravovat celý projekt. Ukážeme si, že s jeho pomocí hravě vyčistíme celý projekt od pomocných souborů vytvořených překladačem nebo záložních souborů vytvořených editorem. Také si ukážeme, jak všechny důležité soubory projektu zabalit do archívu a další užitečné věci.

Podrobnější informace o programu make najdete na jeho manuálové stránce (man make), na info stránce (info make) nebo na webu. V tomto dokumentu jsou popsány pouze některé vybrané vlastnosti, které ale využijete jak u menších, tak i rozsáhlých projektů.

Existuje více verzí programu make, zejména na různých platformách. Základní principy tohoto programu ale fungují ve všech verzích stejně. Program make je součástí standardu POSIX, na jiných systémech je distribuován společně s překladačem a ostatními nástroji (ve Windows například s GCC v rámci MinGW a Cygwin), ale je i součástí komerčních překladačů (např Borland). Používá jej i většina vývojářských GUI prostředí.

Jak se používá make

Jak se tedy make používá? Předpokládejme, že máte zdrojové soubory svého projektu v jedné složce. V předchozí kapitole jsme si vyrobili soubor helloworld.c, tak si pro něj vytvořte složku a zkopírujte ho tam. Nyní musíme v této složce vytvořit soubor Makefile. V něm vzápětí popíšeme, jak má probíhat překlad.

Až budeme mít soubor Makefile hotový (viz archiv s ukázkovým příkladem), otevřete příkazový řádek v naší nově vyrobené složce a z příkazového řádku spustíte příkaz make. Program make ve složce najde soubor Makefile a podle tohoto předpisu začne spouštět jednotlivé nástroje, v našem případě překladač. Později si ukážeme, že může spouštět prakticky cokoliv.

Podle dosavadního popisu by se mohlo zdát, že místo programu make bychom mohli používat shellové skripty (resp. ve Windows dávkové soubory). Program make toho ale umí více. Především u projektů složených z více modulů umí rozhodnout, zda a na které části projektu je opravdu nutné volat překladač (viz dále). To činí překlad mnohem efektivnějším.

Jednoduchý Makefile

Aby program make věděl, co má s projektem dělat, potřebuje k tomu soubor se jménem Makefile (bez koncovky). Je to obyčejný textový soubor, který je nejlépe umístit v hlavní složce projektu. Skládá ze specifikace pravidel a proměnných. V ukázce 1 máme Makefile, pro překlad programu tvořeného jediným zdrojovým souborem helloworld.c. Překlad pomocí tohoto skriptu bude přesně odpovídat tomu, co jsme viděli v ukázce 3 v kapitole Překládáme.

Komentáře

Komentáře se uvozují znakem # (mřížka) a končí na konci řádku. Je dobré na začátek souboru vložit hlavičku s podpisem, datem vytvoření a pokud je to složitější Makefile, i popis nejdůležitějších cílů.

Proměnné

ukázce 1 je na řádcích 5 a 6 specifikace proměnných. V tomto případě jsem si takto definoval překladač a parametry překladu. Proměnné je možné definovat kdekoli v souboru. Hodnotu proměnné získáme tak, že napíšeme referenci na proměnnou pomocí $(JMENOPROMENNE) nebo ${JMENOPROMENNE}, jak je vidět na řádku 9.

Proměnné obsahují textové hodnoty. Na řádku 6 je vidět, že proměnná může obsahovat i mezery. Potřebujeme-li uložit do proměnné text obsahující znak dolaru, je potřeba jej napsat dvakrát $$.

Texty v proměnných lze jednoduše spojovat tak, že se kdekoli kde to má význam uvedou dvě reference na proměnné vedle sebe. Máme-li například proměnnou koncovka=.c, tak lze vytvořit jinou proměnnou soubor=helloworld$(koncovka), jejímž obsahem je nyní text "helloworld.c".

# Projekt: Hello world!
# Autor:   David Martinek
# Datum:   16.8.2014

CC=gcc                              # překladač jazyka C
CFLAGS=-std=c99 -pedantic -Wall -g  # parametry překladače

hello: helloworld.c
	$(CC) $(CFLAGS) helloworld.c -o hello
Makefile popisující překlad projektu, který je tvořený jediným zdrojovým souborem, helloworld.c. Ve skutečnosti by šlo tento Makefile napsat jednodušeji pomocí tzv. implicitních pravidel a proměnných, ale to by nám v našem ukázkovém příkladu skoro nic nezbylo. (archiv s příkladem)

Pravidla

Nejdůležitější částí souboru Makefile je specifikace pravidel. Každé pravidlo se skládá z cíle, závislostíkódu. Význam je jednoduchý. Úkolem pravidla je dosáhnout cíle pomocí kódu, pokud předtím byly splněny závislosti. Cílem se zpravidla myslí soubor, který je třeba vyrobit. Závislosti jsou obvykle soubory nebo cíle jiných pravidel, které jsou nutné k vytvoření cílového souboru. Kód je pak seznam příkazů, které slouží k výrobě cílového souboru, obvykle ze souborů uvedených v závislostech.

V našem souboru je jednoduché pravidlo na řádcích 8 a 9. V našem případě se cíl jmenuje hello (řádek 8) a je to název souboru, který má toto pravidlo za úkol vyrobit. Závislost je v tomto případě pouze jediná – soubor helloworld.c. Kód našeho pravidla (řádek 9) volá překladač gcc, který vezme soubor helloworld.c a vyrobí z něj soubor hello.

Schéma obecného pravidla vidíme v ukázce 2. Název cíle musí být ukončen dvojtečkou. Závislosti jsou uvedeny na stejném řádku jako cíl a za dvojtečkou jich může být libovolný počet (tedy i nulový). Závislosti jsou vzájemně odděleny mezerami. Kód musí následovat bezprostředně za specifikací cíle. Není možné vkládat prázdné řádky. Řádky s kódem musí začínat tabulátorem! Blok kódu končí řádkem, který nezačíná tabulátorem.

Pokud je cíl jméno souboru, znamená to, že pravidlo bude generovat soubor s tímto jménem. Potom se v závislostech musí uvést jména všech souborů, které jsou potřeba ke zkonstruování cíle. V případě projektů v jazyce C, je potřeba zde uvést jméno zdrojového souboru (.c) a jména všech lokálních hlavičkových souborů (.h), které tento zdrojový soubor používá.

cíl: [závislosti]
TABULÁTOR[příkaz]
TABULÁTOR[příkaz]
Schéma obecného pravidla pro make. Části v hranatých závorkách jsou nepovinné. Každý řádek v bloku kódu musí začínat znakem tabulátoru – ověřte si, že váš editor skutečně zapisuje do souboru znaky tabulátoru a nenahrazuje je posloupností mezer.

Spouštění a vyhodnocení pravidel

Program make jde spouštět dvěma způsoby. V obou případech je nutné spouštět program make ve složce, kde se nachází soubor Makefile. První způsob je spouštění make bez parametru a vidíme ho v ukázce 3. V tomto případě se spouští v pořadí první pravidlo v souboru Makefile.

Máme-li Makefile s více pravidly, můžeme název libovolného pravidla zadat jako parametr (viz ukázka 4). My zatím máme v našem jednoduchém Makefile pouze jediné pravidlo, takže v tomto případě spuštění make hello bude mít stejný efekt, jako prosté spuštění make bez parametru.

Při spouštění příkazu make bez parametrů (viz ukázka 3) probíhá vyhodnocení pravidel takto:

  1. Program make spuštěný bez parametrů vyhledá pravidlo, které se v souboru Makefile vyskytuje na prvním místě. Na pořadí ostatních pravidel potom nijak nezáleží.
  2. Pokud se v závislostech tohoto pravidla (část za dvojtečkou v hlavičce pravidla) vyskytují cíle jiných pravidel, nejprve se vyhodnotí všechna tato pravidla v pořadí v jakém jsou vyjmenována za dvojtečkou. Vyhodnocení ostatních pravidel nezáleží na pořadí jejich uvedení v souboru, ale na pořadí, v jakém jsou uvedeny v závislostech právě zpracovávaných pravidel.
  3. Pokud je cíl jméno souboru a v závislostech se také vyskytují jména souborů, 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í. Jinými slovy, pravidlo se vykoná jen když je to potřeba, tj. když se zdrojový kód změnil od posledního překladu.

V našem případě se spustí překlad souboru helloworld.c a výsledkem bude spustitelný soubor hello. V prostředí Windows bychom museli Makefile upravit tak, aby se generoval soubor hello.exe.

$ make
gcc -std=c99 -Wall -pedantic -g helloworld.c -o hello
Program make po spuštění vypíše, co dělá. V našem případě dělá totéž, co jsme v minulé kapitole dělali ručně.

Správa projektu pomocí make

Překlad není to jediné, co jde s programem make dělat. Makefileukázce 4 umí nejenom překládat, ale také balit do archívu, odstraňovat zbytečné soubory a spouštět debugger. Jistě si dokážete sami představit (a doplnit) pravidla pro další činnosti, které patří ke správě projektu. Můžete například vyrábět programátorskou dokumentaci pomocí programu Doxygen, spouštět různé konfigurace svého programu pro testovací účely, spouštět profilovací analýzu a podobně.

Projekt spravovaný ukázkovým Makefile si můžete stáhnout a rozbalit ve své složce. Pokud nyní ve složce projektu spustíme program make bez parametru, bude se chovat způsobem popsaným výše. Pokud jako parametr použijeme cíl některého z pravidel, vykoná se toto zadané pravidlo. Například když na příkazovém řádku napíšete make debug, zkontroluje se nejdříve, zda je už sestaven program hello, pokud ne, tak se nejdříve zavolá překladač, a potom se spustí debugger ddd nakonfigurovaný tak, aby byl schopen volat editor gvim. Máte-li jiný oblíbený debugger, můžete samozřejmě Makefile příslušným způsobem upravit a volat jej místo ddd.

Zvláštní pozornost zaslouží cíl .PHONY. Toto pravidlo slouží pro označení tzv. falešných cílů – tedy cílů, které nemají význam "cílový soubor", ale spíše "návěští". Tyto falešné cíle neprodukují soubory shodné se svým jménem. Tyto cíle se používají právě k tomu, aby se spouštěly přes parametr programu make. Pokud by cíl .PHONY v souboru Makefile nebyl, ve většině případů by to nemělo vliv na jeho funkčnost. Problémy by mohly nastat, kdyby se v naší složce objevil soubor, jehož jméno se shoduje s některým falešným cílem. Program make by se potom pokoušel porovnávat čas jeho vytvoření a pravidlo by přestalo fungovat. Proto je dobré cílem .PHONY označit všechny falešné cíle, abychom se vyhnuli případným problémům.

# Projekt: Hello world!
# Autor:   David Martinek
# Datum:   17.8.2014
#
# Použití:
#   - překlad:      make
#   - ladit:        make debug
#   - zabalit:      make pack
#   - vyčistit:     make clean
#   - vyčistit vše: make clean-all

# jméno projektu
NAME=hello

# překladač a parametry překladu
CC=gcc
CFLAGS=-std=c99 -Wall -pedantic -g

# obsah projektu
ALLFILES=helloworld.c Makefile

# překlad
$(NAME): helloworld.c
	$(CC) $(CFLAGS) helloworld.c -o $(NAME)

# falešné cíle
.PHONY: debug pack clean clean-exe clean-all

# spouští ladění
debug: $(NAME)
	export XEDITOR=gvim;ddd $(NAME)

# archivuje projekt
pack:
	tar cvzf $(NAME).tar.gz $(ALLFILES)
	zip $(NAME).zip $(ALLFILES)

# maže záložní soubory
clean:
	rm -f *~ *.bak

# maže binární soubory
clean-exe:
	rm -f $(NAME)

# maže vše
clean-all: clean-exe clean
S tímto souborem Makefile lze provádět správu jednoduchého projektu. Kromě překladu lze spouštět debugger, archivovat projekt nebo mazat smetí. (archiv s příkladem)

Překlad projektu s moduly

Všechny předchozí příklady sloužily k překladu programu, který je tvořen jediným zdrojovým souborem. Program make ovšem především slouží k překladu projektů, které jsou tvořeny více moduly (viz kapitolu Moduly a knihovny). U těchto projektů je potřeba vytvořit v Makefile pravidlo pro generování objektového souboru každého modulu a také pravidlo pro jejich slinkování.

Jak vypadá část souboru Makefile pro projekt tvořený čtyřmi moduly si můžete prohlédnout na ukázce 5. Tip – pokud přidáte na začátek seznamu pravidel pomocné startovací pravidlo, nezáleží na pořadí jednotlivých pravidel pro překlad modulů a časem jej můžete přesměrovat jinam.

Protože tyto činnosti jsou velmi časté, lze si ušetřit práci a využitím tzv. vzorových pravidel a proměnných. Tuto situaci demonstruje ukázka 6.

Univerzální pravidlo najdeme na řádcích 8 a 9. Znaky procent (%) ve specifikaci cíle znamenají žolíky – tedy libovolný text, který se bude vyhledávat. Funguje to tak, že se v seznamu skutečných pravidel hledají taková, která odpovídají vzoru popsaném ve vzorovém pravidle. V našem případě se hledají pravidla, jejichž jméno cíle má koncovku .o a v závislostech se objevují soubory s koncovkou .c. Sekce závislostí může obsahovat i soubory, které této specifikaci nevyhovují, ale stačí jediný, který vyhovuje, aby pak vyhovovalo celé pravidlo. Když se tedy najde pravidlo, které odpovídá vzorovému pravidlu, naroubuje se ke skutečnému pravidlu kód vzorového pravidla.

Na řádcích 9 a 24 ukázky 6 si pak všimněte použití speciálních proměnných $<$@. Tyto takzvané automatické proměnné obsahují skutečný název zdrojového souboru ($<) a cílového souboru ($@), které se získají z hlavičky pravidla.

# pomocné startovací pravidlo, nemá žádné tělo, slouží jenom jako odkaz
all: program

modul1.o: modul1.c modul1.h
	$(CC) $(CFLAGS) -c modul1.c -o modul1.o
modul3.o: modul3.c modul3.h
	$(CC) $(CFLAGS) -c modul2.c -o modul2.o
modul2.o: modul2.c modul1.h modul2.h
	$(CC) $(CFLAGS) -c modul2.c -o modul2.o
program.o: program.c modul1.h modul2.h modul3.h
	$(CC) $(CFLAGS) -c program.c -o program.o
program: modul1.o modul2.o modul3.o program.o
	$(CC) $(CFLAGS) modul1.o modul2.o modul3.o program.o -o program
Část souboru Makefile pro překlad projektu tvořeného ze čtyř modulů.
NAME=program
OBJFILES=$(NAME).o modul1.o modul2.o modul3.o

CC=gcc
CFLAGS=-std=c99 -Wall -pedantic -Wextra -g

# vzorové pravidlo pro generování všech objektových souborů
%.o : %.c
	$(CC) $(CFLAGS) -c $<

# Startovací pravidlo
all: $(NAME)

## ## ## 
# pravidla bez těla - to se sem doplní z univerzálního pravidla
modul1.o: modul1.c modul1.h
modul2.o: modul2.c modul1.h modul2.h
modul3.o: modul3.c modul3.h
program.o: program.c modul1.h modul2.h modul3.h
## ## ## 

# Slinkování všech objektových souborů do jednoho spustitelného programu.
$(NAME): $(OBJFILES)
	$(CC) $(CFLAGS) $(OBJFILES) -o $@
Makefile pro překlad projektu tvořeného ze čtyř modulů. V tomto případě se ale využívá vzorové pravidlo pro překlad. Pravidla pro jednotlivé moduly tak nyní mohou obsahovat pouze závislosti.

Automatické generování závislostí

Část předchozího souboru Makefile, která je označena řádky s textem ## ## ##, lze generovat automaticky. Je to mnohem spolehlivější, než vytvářet závislosti ručně. Jak se to dělá si můžete prohlédnout v ukázce 7. Seznam závislostí generuje překladač s přepínačem -MM.

Vždy, když se závislosti změní (tj. vytvoříte nový modul nebo hlavičkový soubor), je potřeba ručně zavolat pravidlo make dep a vytvořit tak soubor dep.list, který bude obsahovat všechna potřebná pravidla pro generování modulů i s jejich skutečnými závislostmi. Příkaz -include dep.list (v ukázce 7 na řádku 7) vloží na toto místo obsah tohoto textového souboru. Poté, co je tento soubor vytvořen, se překlad spouští obvyklým způsobem pomocí volání programu make.

## ## ##
# Generování závislostí
dep:
	$(CC) -MM *.c >dep.list

## vloží vygenerované závislosti
-include dep.list
## ## ##
Část souboru Makefile pro generování závislostí překladačem. Touto částí lze nahradit kód v předchozí ukázce vyznačený stejnými komentáři.