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

  1. Naučiť sa vytvoriť vlastný konfiguračný súbor Makefile.
  2. Naučiť sa vytvárať vlastné ciele (targets) v súbore Makefile.
  3. 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

Ú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

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!");
    printf("%s\n", reversed);
    // "!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.

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.

Ú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.

Ú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.

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ý.

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ču
  • CFLAGS - zoznam prepínačov pre prekladač
  • LDLIBS - zoznam knižníc potrebných pre preklad
  • OUTPUT - 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)

Ú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.

Ú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.

Ú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