Embedded systems programming

Aktuality

2006/01/07 Přidány materiály k sedmé až desáté přednášce.

2005/11/17 Přidány materiály k šesté přednášce a zadání semestrální práce.

2005/11/08 Přidány materiály k páté přednášce.

GNU Make

Úvod

Tento článek popisuje filozofii a základní způsob použití programu make.

Článek je možné použít při tvorbě pravidel pro kompilaci z jazyků C a C++ a s trocho fantazie i k mnoha jiným věcem :o)

Upozorňuji na to, že uvedené příklady jsou zaměřeny na použití programu make. Další záležitosti související s dobrými programátorskými mravy, jako například komentáře a úprava zdrojových kódů nebo rozložení souborů do adresářů, zde nejsou dodřženy.

Odkazy

Řízení překladu

Při práci na složitých projektech, které obsahují více hlavičkových souborů začleněných do různých zdrojových souborů je velmi výhodné používat sofistikované řízení překladu. Pro velké projekty může být velmi časově náročné rekompilovat celý projekt při změně jednoho parametru, který ovlivňuje pouze minoritní část projektu a současně sledovat kde se změny projeví.

Výše uvedené problémy dokáže řešit program make.

Program make zajistí kompilaci projektu a při změně některého zdrojového souboru zajistí rekompilaci příslušných částí projektu.

Soubor Makefile

Program make se při práci s projektem řídí pravidly, která jsou definována v řídícím souboru.

Programátoři UNIXových systémů tento soubor označují názvem Makefile. Pokud chcete být poněkud nekonvenční můžete použít i jakýkoliv jiný název souboru a program make poté spustit s patřičnou volbou (make -f nekonvencniMakefile).

Pro jeden projekt může existovat více řídících souborů.

Soubor Makefile obsahuje sadu závislostí a pravidel.

  • Závislost - má cíl, tj. soubor, který má být vytvořen, a skupinu zdrojových souborů, na kterých cíl závisí.
  • Pravidlo - popisuje jak vytvořit cíl ze závislých souborů.

Program make akceptuje jako parametr cíl definovaný v souboru Makefile. Soubor Makefile může obsahovat více cílů. Je tak možné volit jak různé způsoby kompilace, tak i kompilaci částí projektu.

Bývá dobrým zvykem jako první uvádět cíl all, který vede k požadovanému výsledku. Důvodem je to, že pokud je program make spuštěn bez specifikace cíle začne zpracovávat cíl, který je uveden jako první v souboru Makefile.

Volby a parametry programu make

Program make má několik voleb. Kompletní výpis voleb lze nastudovat v manuálových stránkách (man make). Nejčastěji používané volby jsou:

  • -k, program make bude ignorovat chyby tak dlouho, jak jen to bude možné.
  • -n, program make vypíše příkazy, které by provedl, ale neprovede je.
  • -f, definuje jaký konfigurační soubor se má použít (tj. specifikuje Makefile).

Definování závislostí

Závislosti specifikují, jakým způsobem každý soubor výsledné aplikace souvisí se zdrojovými soubory.

Mějme modelový příklad, kdy výsledná aplikace vyžaduje (vznikla ze) soubory main.o, lib1.o a com.o. Tyto soubory závisí na dalších souborech. Celá závislost je uvedena níže.

  • aplikace
    • main.o
      • main.c
      • head1.h
    • lib1.o
      • lib1.c
      • head1.h
      • head2.h
    • com.o
      • com.c
      • head2.h
      • head3.h

Uvedené schéma lze převést do seznamu závislostí souboru Makefile následujícím způsobem.

aplikace: main.o lib1.o com.o

main.o: main.c head1.h

lib1.o: lib1.c head1.h head2.h

com.o: com.c head2.h head3.h

Při zápisu pravidel je nejprve uveden název cíle, dvojtečka, mezera nebo tabulátor a potom seznam souborů, které jsou potřeba k vytvoření cíle.

Pokud budeme chtít vytvořit více souborů, můžeme použít falešný cíl. Např. pokud budeme chtít kompilovat projekt a vytvořit dokumentaci, můžeme použít následující konstrukci.

all: aplikace dokumentace

aplikace: main.o lib1.o com.o

main.o: main.c head1.h

lib1.o: lib1.c head1.h head2.h

com.o: com.c head2.h head3.h

dokumentace: aplikace.tex

Definování pravidel

Pravidla poisují jak má program daný cíl vytvořit. Tj. pravidla se týkají konkrétních závislostí a stejně tak jsou v souboru Makefile zapisována.

Všechna pravidla musejí být uvedena na řádcích, které začínají tabulátorem! Je nutné správně nakonfigurovat textový editor! Jestliže není toto pravidlo dodrženo, nelze program make spustit nebo výsledek neodpovídá očekávání. Obdobné problémy může způsobit mezera na konci řádku.

Pro náš příklad bychom mohli soubor Makefile rozšířit o následující pravidla:

aplikace: main.o lib1.o com.o
<TAB>gcc -Wall -o aplikace main.o lib1.o com.o

main.o: main.c head1.h
<TAB>gcc -Wall -c main.c

lib1.o: lib1.c head1.h head2.h
<TAB>gcc -Wall -c lib1.c

com.o: com.c head2.h head3.h
<TAB>gcc -Wall -c com.c

Tím jsme definovali pravidla kompilace jednotlivých cílů a program make je připraven realizovat naše požadavky. Stáhněte si zdrojové soubory a připravený Makefile. Je vhodné, aby si uživatel zkusil napsat Makefile sám ve svém oblíbeném textovém editoru.

Pokud provedete jakoukoliv změnu ve zdrojových souborech, make překompiluje jen soubory, kterých se změna týká. Tj. nekompiluje celý projekt! Zkuste změnit řetězec libovolné funkce printf.

Pokud budeme chtít odstranit objektové soubory z adresáře projektu můžeme přidat cíl clean. Cíl clean nezávisí na ničem (řádek za clean: zůstává prázdný) a je tedy vždy považován za aktuální. Příkaz - říká programu make, aby výsledek operace ignoroval.

Obdobně můžeme vytvořit i cíl dokumentace. Ten zajistí generování dokumentace pomocí programu LaTeX. Cíl clean je rozšířen o vymazání řídících souborů programů LaTeX.

Obě uvedená zlepšení znázorňuje následující příklad.

all: aplikace dokumentace

aplikace: main.o lib1.o com.o
<TAB>gcc -Wall -o aplikace main.o lib1.o com.o

main.o: main.c head1.h
<TAB>gcc -Wall -c main.c

lib1.o: lib1.c head1.h head2.h
<TAB>gcc -Wall -c lib1.c

com.o: com.c head2.h head3.h
<TAB>gcc -Wall -c com.c

dokumentace: dokumentace.tex
<TAB>pdfcslatex dokumentace.tex

clean:
<TAB>-rm -rf *.o *.aux *.log

Stáhněte si zdrojové soubory a připravený Makefile a vyzkoušejte si je.

Vyzkoušejte předchozí příklad s příkazy make aplikace a make dokumentace. První z nich pouze zkompiluje projekt a druhý vytvoří dokumentaci. Nepřícházíme tedy o výhodu provést jen ty operace, které potřebujeme. Obdobně můžeme pracovat i s ostatními cíli.

Komentáře

Komentáře v souboru Makefile začínají znakem # a pokračují až do konce řádku.

Doporučuje se velmi pečlivě komentovat!

Makra

Makra se definují obdobně jako při programování v bashi. Makro se definuje konstrukcí NAZEV_MAKRA=hodnota a k makru se přistupuje zápisem $(NAZEV_MAKRA) nebo ${NAZEV_MAKRA}. Pro firemní potřeby se doporučuje používat první způsob zápisu.

Makro lze zrušit konstrukcí NAZEV_MAKRA= a zbytek řádku prázdný.

Příklad si můžeme ukázat na modifikaci předcházejícího souboru Makefile. Může být výhodné definovat volby kompilátoru pomocí makra.

CFLAGS = -g -Wall # Produce debugging information and turns on all optional warnings

all: aplikace dokumentace

aplikace: main.o lib1.o com.o
<TAB>gcc $(CFLAGS) -o aplikace main.o lib1.o com.o

main.o: main.c head1.h
<TAB>gcc $(CFLAGS) -c main.c

lib1.o: lib1.c head1.h head2.h
<TAB>gcc $(CFLAGS) -c lib1.c

com.o: com.c head2.h head3.h
<TAB>gcc $(CFLAGS) -c com.c

dokumentace: dokumentace.tex
<TAB>pdfcslatex dokumentace.tex

clean:
<TAB>-rm -rf *.o *.aux *.log

Stáhněte si zdrojové soubory a připravený Makefile a vyzkoušejte si je.

Není na škodu vyzkoušet si redefinování maker. Stáhněte si připravený Makefile a postupně odkomentujte jednotlivé řádky.

CFLAGS = -g -Wall

all:
<TAB>echo $(CFLAGS)

#CFLAGS =
#CFLAGS = -ansi -Wall

clean:
<TAB>-rm -rf *.o *.aux *.log

Pokud chceme modifikovat někté makro přímo z příkazové řádky můžeme jeho hodnotu zadat jako parametr příkazu make. Definice uvedené v příkazovém řádku potlačí definice uvedené v souboru Makefile. Vyzkoušejte např.:

make CFLAGS="-O -Wall -ansi"

Interní makra

Program make má vestavěno několik interních maker. Tato makra se liší od uživatelsky definovaných maker tím, že jsou expandována až těsně před použitím, takže se význam jednotlivých maker může s postupným zpracováním souboru Makefile měnit. Je velmi důležité uvědomit si tuto skutečnost vždy, když používáme interní makra programu make!

Níže je uveden seznam a funkce nejdůležitějších interních maker programu make.

  • $? - Seznam předpokladů změněný později než aktuální cíl.
  • $@ - Název aktuálního cíle.
  • $< - Název aktuálního předpokladu.
  • $* - Název aktuálního předpokladu bez přípony.

Práci s interními makry dokumentuje následující příklad.

CC = gcc CFLAGS = -g -Wall # Produce debugging information and turns on all optional warnings

all: aplikace dokumentace

aplikace: main.o lib1.o com.o
<TAB>$(CC) $(CFLAGS) -o aplikace main.o lib1.o com.o
<TAB>@echo "Seznam predpokladu: " $?
<TAB>@echo "Aktuální cil: " $@

main.o: main.c head1.h
<TAB>$(CC) $(CFLAGS) -c main.c
<TAB>@echo "Aktuální cil: " $@
<TAB>@echo "Seznam predpokladu: " $?
<TAB>@echo "Nazev aktualního predpokladu: " $<
<TAB>@echo "Nazev aktualního predpokladu bez pripony: " $*

lib1.o: lib1.c head1.h head2.h
<TAB>$(CC) $(CFLAGS) -c lib1.c
<TAB>@echo "Aktuální cil: " $@
<TAB>@echo "Seznam predpokladu: " $?
<TAB>@echo "Nazev aktualního predpokladu: " $<
<TAB>@echo "Nazev aktualního predpokladu bez pripony: " $*

com.o: com.c head2.h head3.h
<TAB>$(CC) $(CFLAGS) -c com.c
<TAB>@echo "Aktuální cil: " $@
<TAB>@echo "Seznam predpokladu: " $?
<TAB>@echo "Nazev aktualního predpokladu: " $<
<TAB>@echo "Nazev aktualního predpokladu bez pripony: " $*

dokumentace: dokumentace.tex
<TAB>pdfcslatex dokumentace.tex

clean:
<TAB>-rm -rf *.o *.aux *.log

Znakem @ (např. @echo "Nazev aktualního predpokladu bez pripony: " $*) před pravidlem říká programu make, aby pravidlo před provedením nevypisoval na standardní výstup. Vypsání výsledku pochopitelně neovlivňuje.

Stáhněte si zdrojové soubory a připravený Makefile a vyzkoušejte si je.

Zabudovaná pravidla

Program make má velké množství zabudovaných pravidel. Tato pravidla obsahují makra, takže je můžeme využít při psaní vlastních configuračních souborů. Je důležité pečlivě zvážit, zda použít zabudované pravidlo nebo definovat svoje vlastní. Tato rozhodnutí mohou mít velký vliv na přenositelnost software!

Zabudovaná pravidla lze získat spuštěním programu make s volbou -p.

make -p

Příponová pravidla

Pro některé projekty je třeba vytvořit nové pravidlo pro určitý typ souborů. Předpokládejme, že typ souboru je určen jeho příponou. Např. zdrojové soubory napsané v jazyce C++ mají v UNIXových systémech obvykle příponu cc, kdežto v systémech DOS příponu cpp. Zkusíme napsat pravidlo, které nám umožní kompilovat soubory s příponou cpp (může se hodit při portování software).

První možností jak naučit program make zpracovat neznámý soubor, je specifikovat pravidlo pro každý cíl zvlášť. To je sice pro několik souborů našeho prvního případu možné, ale již zde značně nepřehledné a při opravě pravidla může snadno dojít k chybám.

Druhou možností je naučit program make jak vytvořit objektový soubor ze zdrojového souboru s příponou cpp. Program make již zná způsob jak pospojovat objektové způsoby, takže budeme hotovi.

Příponová pravidla mají syntaxi .<stará přípona>.<nová přípona>

Dle předchozího je tedy možné zapsat nové pravidlo následovně:

.cpp.o
<TAB>$(CC) -x c++ $(CFLAGS) -I$(INCLUDE) -c $<

Uvedené pravidlo říká následující:

  • Převáděj soubory s příponou .cpp na soubory s příponou .o.
  • Použij kompilátor definovaný makrem $(CC).
  • Použij parametr kompilátoru -x c++ (pro gcc tento parametr specifikuje jazyk vstupního souboru, v našem případě tedy C++).
  • Použij volby kompilátoru definované makrem $(CFLAGS).
  • Hlavičkové soubory hledej kromě aktuálního adresáře také v adresářích definovaných makrem $(INCLUDE). Viz -I$(INCLUDE).
  • Pouze kompiluj nelinkuj. Viz -c.
  • Vestavěné makro $< expanduje aktuální předpoklad, tj.. soubor s původní příponou. Soubor musí být uveden obecně, protože jej ještě neznáme a jeho obecnou definici lze zajistit uvedeným způsobem.

Praktický příklad

Následně bude uveden a okomentován Makefile projektu využívající kompilátor sdcc (Small Device C Compiler), který lze použít pro procesoy řady x51.

CC = sdcc # compiler
MFLAGS = --model-small --stack-loc 0xbf
LFLAGS = --code-loc 0x00 --idata-loc 0x80

all: main.ihx

OBJECTS = main.rel netlayer.rel

main.ihx: $(OBJECTS)
<TAB>$(CC) $(MFLAGS) $(LFLAGS) $(OBJECTS) lib.io.v02.13.lib -L /home/phnizdil/cvsroot/project001/lib/
<TAB>./checksum.py -i main.ihx -o main.hex -startaddr 0x00 -stopaddr 0x1fff -word # add checksum to hexfile

clean:
<TAB>rm -f core *~ \#* *.asm *.cdb *.rel *.hex *.ihx *.lst *.map *.rst *.sym *.lnk *.lib *.bin *.mem

%.rel: %.c
<TAB>$(CC) -c $(CFLAGS) $(MFLAGS) $<

První blok definuje uživatelská makra, která obsahují definici kompilátoru a jeho parametrů.

Druhý blok obsahuje definici cíle all.

Třetí blok definuje makro, které obsahuje soubory na nichž je závislý cíl all.

Pátý blok obsahuje definici závislostí cíle main.ihx, na kterém je závislý cíl all a pravidla pro tento cíl. Povšimněte si, definování závislostí pomocí makra OBJECTS.

Šestý blok obsahuje závislost clean, která se neliší od dříve používaných.

Sedmý blok obsahuje definici cílů .rel a pravidlo pro kompilaci těchto cílů. Znak % zde působí jako zástupný symbol jména cíle. Pozor, na jiných místech souboru Makefile může mít znak % jiný význam!

Závěr

Program make má daleko více možností než je uvedeno v tomto článku. Nicméně informace zde obsažené umožňují plnohodnotnou práci na jednodušších projektech.

Zvídavý čtenář jistě rád navštíví oficiální manuál programu make.

 
Updated: 2005/11/02 Petr Hnízdil e-mail: petr_zavináč_hnizdil_tečka_com