Introduction to Modular Programming
nástroj make
, konfiguračný súbor
Makefile
Video pre cvičenie
Motivácia
V prvom zadaní, do ktorého sa tento týždeň pustíte, budete mať okrem
iného za úlohu vytvoriť dva samostatné moduly. Aby ste ich zvládli
vytvoriť a správne manažovať, naučíte sa na tomto cvičení konfigurovať
nástroj make
pomocou jeho konfiguračného súboru
Makefile
. Táto znalosť vám v budúcnosti pomôže pri práci na
rozsiahlejších projektoch, ktoré sa budú skladať z viacerých
samostatných modulov.
Ciele
- Naučiť sa vytvoriť vlastný konfiguračný súbor
Makefile
. - Naučiť sa vytvárať vlastné ciele (targets) v súbore
Makefile
. - Porozumieť významu modulárneho programovania.
Postup
Project Creation
V prvom kroku si vytvoríte nový projekt na GitLab-e, do ktorého budú postupne pribúdať vaše zadania.
Úloha
Prihláste sa na GitLab a vytvorte si nový projekt.
Ak vytvárate projekt prvýkrát, urobte tak na tejto stránke. Návod na to, ako si inicializovať prostredie a ako pracovať s GitLab-om, nájdete na stránkach predmetu ZAP.
Názov projektu bude v tvare prog-X
, kde X
je poradové číslo vytváraného projektu. V materiáloch s cvičeniami
budeme používať názov projektu prog-2024
.
Súčasťou projektu je aj súbor README
, ktorý obsahuje len
jeden riadok s informáciou o skupine, ktorú navštevujete na cvičeniach v
tvare:
GROUP : A1
Upozornenie
Pokiaľ len skopírujete a vložíte postupnosť príkazov priamo z
GitLab-u, nezabudnite súbor README.md
premenovať na súbor
README
.
Upozornenie
Ak nebudete vedieť projekt u seba lokálne naklonovať alebo inak pracovať so vzdialeným repozitárom, skontrolujte svoje SSH kľúče! Návod ako na to nájdete napríklad v jednom z návodov predmetu Základy algoritmizácie a programovania.
Úloha
Zo stránky so zadaním projektu Top Secret si stiahnite jeho kostru.
Pokiaľ poznáte adresu súboru na stiahnutie, môžete si ho stiahnuť
pomocou príkazu wget
v tvare:
$ wget URL
kde URL
je adresa súboru na stiahnutie. Súbor sa
následne stiahne do priečinka, v ktorom ste tento príkaz zadali.
Pre rozbalenie zip
balíku môžete s výhodou použiť
konzolový nástroj unzip
v tvare:
$ unzip FILE
kde FILE
je názov, resp. cesta k zip
balíku
na rozbalenie.
Úloha
Vytvorte si vo svojom projekte priečinok ps1/
a
prekopírujte do neho všetky potrebné súbory z kostry zadania Top Secret.
Presná podoba štruktúry projektu tohto zadania sa nachádza na stránke požiadaviek. V
štruktúre projektu sa však nenachádzajú všetky potrebné súbory. Prázdne
súbory môžete vytvoriť buď príkazom touch
alebo uložením
prázdneho súboru vo vašom editore. Ak následne sputíte príkaz
tree
nad priečinkom projektu prog-2024
, jeho
výstup by mal vyzerať nasledovne:
$ tree prog-2024
prog-2024/
├── ps1/
│ ├── playfair.c
│ ├── playfair.h
│ ├── bmp.c
│ ├── bmp.h
│ ├── main.c
│ └── Makefile
└── README
Poznámka
Ak pridáte do svojho projektu nový súbor, je dobrým zvykom ho pridať rovno aj do git projektu pomocou príkazu
$ git add FILE
Function
reverse()
Aby sme nepracovali s prázdnym kódom, urobíme v tomto kroku
jednoduchú implementáciu funkcie reverse()
, ktorá patrí do
modulu bmp
. Jej implementáciu umiestnime do súboru
bmp.c
a odskúšame ju v súbore main.c
.
Úloha
V súbore bmp.c
vytvorte implementáciu funkcie
reverse()
.
Úloha
Otestujte vytvorenú funkciu reverse()
v hlavnej funkcii
main()
v súbore main.c
.
Pre otestovanie môžete použiť napr. fragment kódu, ktorý sa nachádza
v opise funkcie reverse()
v špecifikácii zadania:
int main(){
char* reversed = reverse("Hello world!");
("%s\n", reversed);
printf// "!DLROW OLLEH"
}
Úloha
Preložte a spustite projekt.
Program sa pokúste preložiť pomocou prekladača gcc
spolu
so zoznamom všetkých prepínačov, ktoré sú uvedené na stránke zadania. Výsledný
spustiteľný súbor nech sa volá jednoducho ps1
.
Úloha
Aktualizujte svoj projekt na git-e.
Okrem pridania súborov do lokálneho repozitára nezabudnite váš
aktualizovaný projekt odoslať aj do vzdialeného repozitára. Obsah
priečinka ps1/
vášho projektu by sa momentálne mal zhodovať
s požadovanou podobou projektu, ako je uvedená na stránke zadania.
Makefile Configuration
V tomto kroku si vytvoríte svoj vlastný súbor Makefile
,
ktorý povie nástroju make
čo má robiť. Súbor
Makefile
sa skladá zo zoznamu pravidiel, pomocou ktorých
nástroj make
preloží a zostaví výsledný program. Tento
súbor sa používa hlavne vtedy, ak pracujete na projekte, ktorého kód je
uložený vo viacerých súboroch, ako len v jednom.
Úloha
Vytvorte v súbore Makefile
pravidlo s názvom cieľa
all
, pomocou ktorého bude možné preložiť celý program.
Každé pravidlo v súbore Makefile
má nasledovnú
štruktúru:
target: dependencies
command
... command
Význam jednotlivých častí pravidla je nasledovný:
target
- Predstavuje názov súboru, ktorý bude vygenerovaný ako výstup (napr. výsledná spustiteľná binárka alebo objektový súbor). Cieľom však môže byť aj akcia, ktorá má byť vykonaná (napr.clean
).dependencies
- Zoznam súborov, na ktorých závisí zostavenie cieľa (a vykonanie pravidla). Tieto súbory musia existovať predtým, ako sa začnú vykonávať jednotlivé príkazy.commands
- Zoznam príkazov, ktoré sa vykonajú na zostavenie daného cieľa.
Upozornenie
Je veľmi dôležité, aby prvý znak pred každým príkazom (oddeľovač) bol
znak tabulátor. Ak sa tam bude nachádzať znak medzera, súbor
Makefile
nebude valídny a nástroj make
skončí
s chybou.
Ak pre programovanie používate editor vim
, v
konfigurácii je explicitne uvedená voľba set expandtab
,
ktorá nahradí znak tabulátor uvedeným počtom medzier. Pre
otvorený súbor môžete túto voľbu potlačiť napísaním príkazu v príkazovom
režime:
:set noexpandtab
Vašou úlohou je vytvoriť pravidlo s názvom cieľa all
. V
rámci príkazov tohto pravidla zabezpečte preloženie programu podobne,
ako ste to vykonali v predchádzajúcom kroku.
Poznámka
V tejto úlohe nemusíte použiť žiadnu závislosť a nechajte tak zoznam závislostí prázdny.
Úloha
Overte funkčnosť vytvoreného cieľa.
Funkčnosť overíte spustením príkazu make
nasledovne:
$ make TARGET
kde TARGET
je názov cieľa, ktorý chcete spustiť.
Ak ste postupovali správne, po spustení príkazu make
dôjde k preloženiu programu a vytvoreniu spustiteľného súboru
ps1
.
Poznámka
Ak pri spúšťaní príkazu make
neuvediete žiadny cieľ,
použije sa prvý cieľ v poradí, ktorý sa v súbore Makefile
nachádza.
Úloha
Vytvorte v súbore Makefile
pravidlá pre zostavenie
cieľov main.o
, playfair.o
a
bmp.o
, pomocou ktorých sa program čiastočne preloží, čím
vziknú tri rovnomenné objektové súbory. Upravte cieľ all
tak, aby vzniknuté objektové súbory spojil (zlinkoval) do spustiteľného
súboru ps1
.
Aktuálne nám cieľ all
umožňuje preložiť celý program
naraz. Na základe troch súborov .c
si však môžete všimnúť,
že program pozostáva z troch modulov: main
,
playfair
a bmp
.
Každý z týchto modulov je možné preložiť samostatne, čím sa vytvorí tzv. objektový súbor. Spojením objektových súborov je možné vytvoriť spustiteľný súbor.
Vytvorte tri nové ciele main.o
, playfair.o
a bmp.o
, pomocou ktorých bude možné preložiť daný modul
programu. Výsledkom bude objektový súbor .o
. K prekladu
použite prepínač -c
.
Poznámka
Prepínač -c
umožňuje preložiť program bez spojenia
(prelinkovania) zdrojových súborov. Výstupom takého prekladu je
objektový súbor.
Upravte cieľ all
tak, aby k prekladu použil objektové
súbory. Spojením týchto súborov vznikne spustiteľný súbor
ps1
.
Overte funkčnosť všetkých cieľov.
Adding Dependencies
Aktuálne dôjde k zostaveniu výsledného programu zakaždým po spustení
príkazu make
. To, ako zabezpečiť, aby sa tak dialo len pri
zmene niektorej zo závislostí, si ukážeme v tomto kroku.
Úloha
Aktualizujte cieľ all
tak, aby k prekladu došlo len
vtedy, ak sa niektorý zo súborov obsahujúcich zdrojové kódy
aktualizuje.
V predchádzajúcej úlohe pri zostavovaní cieľa neboli použité žiadne
závislosti. Závislosti je vo všeobecnosti možné charakterizovať ako
súbory, ktoré sú potrebné pre zostavenie daného cieľa. V prípade, ak sa
niektorá zo závislostí aktualizuje, nástroj make
daný cieľ
zostaví (preloží program). Ak pri jeho použití nedošlo k aktualizácii
žiadnej závislosti (všetky súbory sú aktuálne), výsledný cieľ nebude
zostavený.
Poznámka
Závislosti sa s výhodou používajú v prípadoch, ak zostavujete viacero cieľov. Ak následne urobíte zmenu v jednom súbore, nepotrebujete znovu prekladať všetky súbory, ale preložené budú len tie ciele, ktorých sa táto zmena dotkne.
Ako závislosti v tomto prípade použite všetky súbory, ktoré sa podieľajú na zostavení výsledného spustiteľného programu.
Úloha
Overte funkčnosť aktualizovaných cieľov a pridajte cieľ
ps1
.
Ak spustíte príkaz make
s cieľom bmp.o
alebo playfair.o
tentokrát, k prekladu dôjde len vtedy, keď
sa zmení niektorý zo súborov uvedených ako závislosti.
Avšak ak spustíte príkaz make
s cieľom all
,
stále dôjde k prekladu, aj keď sa súbory v závislostiach nezmenili.
Dôvodom je to, že nástroj make
sa snaží nájsť súbor s
rovnakým názvom, aký je názov cieľa, t.j. all
.
Vytvorte preto nový cieľ s rovnakým názvom, aký má výstup celého
prekladu: ps1
. Tento cieľ bude mať rovnaké závislosti ako
cieľ all
a po jeho zavolaní sa vykoná rovnaká operácia.
Pôvodný cieľ all
potom zmeňte tak, aby sa priamo odvolával
na nový cieľ ps1
.
Overte funkčnosť aktualizovaného cieľa all
. K prekladu
by malo dôjsť iba ak došlo k zmene v niektorom zo súborov.
Adding Variables
Pri bližšom pohľade na aktuálnu verziu súboru Makefile
sa dá všimnúť, že sa niektoré jeho časti opakujú. Napríklad sa neustále
opakuje zoznam volieb prekladača gcc
alebo aj názov
prekladača samotného. V súbore Makefile
môžeme tieto zápisy
zjednodušiť použitím vlastných premenných.
Úloha
Nahraďte v príkaze na preklad meno prekladača, zoznam jeho prepínačov a zoznam potrebných knižníc pomocou premenných.
Použitie premenných v súbore Makefile
je výhodné v
situáciách, keď definujete viacero cieľov, poprípade máte zadefinovaných
viacero príkazov pri jednom cieli. Jednoducho tak môžete zmeniť alebo
aktualizovať napr. prekladač, zoznam jeho parametrov alebo knižníc
požadovaných pri preklade.
Vytvorte preto v súbore Makefile
tieto premenné:
CC
- názov, resp. absolútna cesta k prekladačuCFLAGS
- zoznam prepínačov pre prekladačLDLIBS
- zoznam knižníc potrebných pre prekladOUTPUT
- názov výsledného spustiteľného súboru
Inicializácia premennej vyzerá podobne ako v jazyku C, akurát nie je potrebné uvádzať jej typ:
VARIABLE=VALUE
Na mieste, kde chcete použiť hodnotu premennej, zapíšete túto premennú v tvare:
$(VARIABLE)
Poznámka
V súbore Makefile
je možné rovnako používať aj
komentáre. Makefile
podporuje len jednoriadkové komentáre a
riadok, ktorý začína znakom '#'
bude ignorovaný. Poprípade
ignorovaná bude aj časť riadku za týmto znakom.
Úloha
V súbore Makefile
použite automatické premenné, ktoré
nástroj make
pozná.
Okrem vlastných definovaných premenných je možné v súbore
Makefile
použiť aj tzv. automatické premenné. Ich
kompletný zoznam nájdete v dokumentácii
k nástroju make
.
Pre potreby tejto úlohy si vystačíme s týmito automatickými premennými:
$@
- názov vytváraného cieľa daného pravidla$^
- zoznam všetkých závislostí
Použitím uvedených premenných je možné upraviť napr. pravidlo pre
zostavenie objektového súboru playfair.o
takto:
playfair.o: playfair.c
$(CC) $(CFLAGS) -c $^ $(LDLIBS) -o $@
Úloha
Overte funkčnosť aktualizovaných cieľov.
Pokiaľ ste postupovali správne, funkcionalita zostane nezmenená.
Extending Tasks with Additional Commands
Vytvorené pravidlá v súbore Makefile
aktuálne obsahujú
len jeden príkaz na preklad a vytvorenie objektového súboru. V tomto
kroku do procesu zostavovania výsledného projektu pridáme nástroj
cppcheck
, ktorý zabezpečí statickú analýzu kódu. Výhodou
bude, že ak niektorý zo zdrojových súborov neprejde statickou analýzou
kódu, preklad a zostavovanie programu sa zastaví.
Úloha
Pridajte pred príkaz s prekladom statickú kontrolu kódu pomocou
nástroja cppcheck
a overte funkčnosť vašej úpravy.
Ako bolo uvedené vyššie, v jednom pravidle sa môže nachádzať aj viac príkazov ako len jeden. Ak sa tak stane, tak v prípade, že dôjde pri vykonávaní niektorého príkazu ku chybe (návratový kód príkazu bude iný ako 0), žiadne ďalšie príkazy sa nebudú vykonávať. Túto vlastnosť je teda možné úspešne zahrnúť aj do procesu prekladu, kedy pred samotným prekladom použijeme nástroje napr. na statickú analýzu kódu alebo na jeho formátovanie a až potom sa vykoná samotný preklad.
Nástroj cppcheck
slúži
na statickú analýzu kódu. Pri kontrole zadaní sa cppcheck
bude používať nasledovne:
$ cppcheck --enable=performance,unusedFunction --error-exitcode=1 *.c
Cleanup and Git Push
Na záver vytvoríte dve pravidlá. Tým prvým vyčistíte projekt od nepotrebných súborov, ktoré vznikajú ako medzivýsledky prekladu. A tým druhým odošlete vytvorené zmeny do vášho Git repozitára.
Úloha
Vytvorte pravidlo s názvom cieľa clean
, pomocou ktorého
zmažete z disku spustiteľný súbor ps1
a všetky ostatné
objektové súbory .o
.
Poznámka
Pri zostavovaní pravidla nezabudnite s výhodou použiť premennú
OUTPUT
.
Upozornenie
Dajte si pozor, aby ste nesprávnym zápisom tohto cieľa nezmazali všetky súbory nachádzajúce sa vo vašom projekte!
Úloha
Vytvorte pravidlo s názvom cieľa push
, pomocou ktorého
odošlete vykonané vzmeny v projekte do vášho Git repozitára.
Do zoznamu príkazov tohto pravidla zahrňte ako príkaz
git commit
tak aj príkaz git push
.
Poznámka
Pred odoslaním zmien do Git-u je občas užitočné vykonať aj ďalšie
operácie. Napríklad automaticky preformátovať kód projektu podľa
špecifikácie. Nástroj, ktorý vieme na tento účel použiť, je napríklad Artistic Style, ktorý je
dostupný v príkazovom riadku ako astyle
. S vhodnými
prepínačmi je možné unifikovať výstupný kód ešte pred odoslaním projektu
do Git-u tak, ako to robia aj niektoré veľké vývojové prostredia.
Úloha
Overte funkčnosť vytvorených pravidiel.
Ak ste postupovali správne, po spustení príkazu make
s
cieľom clean
dôjde k odstráneniu spustiteľného súboru
ps1
z disku vrátane všetkých objektových súborov.
Ďalšie zdroje
Znenie zadania č. 1 - Top Secret
GNU Make - dokumentácia k nástroju
make
(a súboruMakefile
)Prednáška č. 5/2014 - časť prednášky je venovaná problematike modulárneho programovania