Introduction to Modular Programming

nástroj make, konfiguračný súbor Makefile

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ť modulárnemu programovaniu v jazyku C.

Postup

Krok #1: Project Creation

V prvom kroku si vytvoríte nový projekt na git-e, do ktorého budú postupne pribúdať vaše zadania.

Úloha 1.1

Prihláste sa na git a vytvorte si nový projekt s názvom prog-2017.
Poznámka:  Nezabúdajte, že záleží na veľkosti písmen! Ak sa názov vášho projektu nebude volať presne prog-2017, Aréna ho nebude vedieť identifikovať a tým pádom ani vyhodnotiť.

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
Pokiaľ len skopírujete a vložíte postupnosť príkazov priamo z GitLab-u, nezabudnite súbor README.md premenovať na súbor README.

Úloha 1.2

Zo stránky so zadaním projektu K 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 1.3

Vytvorte si vo svojom projekte priečinok ps1/ a prekopírujte do neho všetky potrebné súbory z kostry zadania K.

Presná podoba štruktúry projektu tohto zadania sa nachádza na stránke požiadaviek. Ak následne sputíte príkaz tree nad priečinkom projektu prog-2017, jeho výstup by mal vyzerať nasledovne:

$ tree prog-2017
prog-2017/
├── ps1
│   ├── hof.h
│   ├── k.c
│   ├── k.h
│   ├── main.c
│   ├── Makefile
│   └── score
└── README

1 directory, 8 files

Úloha 1.4

V priečinku projektu ps1/ vytvorte kostru súboru main.c.

Súbor main.c bude obsahovať hlavnú funkciu programu main(). V tejto funkcii si vytvorte premennú hracieho poľa typu struct game a priraďte úvodné hodnoty jednotlivým prvkom záznamu. Tento typ je zadefinovaný v súbore k.h.

V hlavnej funkcii programu zavolajte funkciu add_random_tile(), ktorej deklarácia sa nachádza v súbore k.h a definícia sa nachádza v súbore k.c. Funkcia pridá do hracieho poľa písmeno na náhodnom mieste.

Výsledná podoba súboru main.c môže teda vyzerať nasledovne:

#include "k.h"
#include "hof.h"

int main(){
    struct game game = {
        .board = {
            {'A', ' ', 'A', ' '},
            {'A', ' ', 'A', ' '},
            {'A', ' ', 'A', ' '},
            {'A', ' ', 'A', ' '}
        },
        .score = 0
    };
    add_random_tile(&game);
}
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

Úloha 1.5

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

Úloha 1.6

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.

Krok #2: Makefile Configuration

V tomto kroku si vytvoríte svoj vlastný súbor Makefile, ktorý bude predstavovať konfiguráciu pre nástroj make. Skladá sa zo zoznamu cieľov (z angl. target) a premenných. Tento súbor sa používa hlavne vtedy, ak pracujete na programe, ktorého kód je uložený vo viacerých súboroch, ako len v jednom.

Úloha 2.1

Vytvorte v súbore Makefile cieľ all, pomocou ktorého bude možné preložiť celý program.

Súbor Makefile sa skladá z niekoľkých cieľov, pomocou ktorých je možné zadefinovať príslušné správanie.

Všeobecná štruktúra cieľa vyzerá nasledovne:

TARGET: DEPENDENCIES
[TAB] SYSTEM_COMMAND1
    ...
[TAB] SYSTEM_COMMANDn
pričom:
  • TARGET je názov cieľa,
  • DEPENDENCIES je zoznam závislostí pre zostavenie daného cieľa, a
  • SYSTEM_COMMAND1SYSTEM_COMMANDn je zoznam príkazov, ktoré sa majú v rámci daného cieľa vykonať.

Upozornenie:  Je veľmi dôležité, aby prvý znak pred každým príkazom 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.

Vašou úlohou je vytvoriť cieľ s názvom all. V rámci príkazov, ktoré tvoria telo tohto cieľa 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 2.2

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

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 2.3

Vytvorte v súbore Makefile tri ďalšie ciele main.o, k.o a hof.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 game.

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, k a hof.

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, k.o a hof.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 game.

Overte funkčnosť všetkých cieľov.

Poznámka:  Všimnite si, že k prekladu dôjde zakaždým, keď príkaz make spustíte.

Úloha 2.4

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 2.5

Overte funkčnosť aktualizovaných cieľov a pridajte cieľ game.

Ak spustíte príkaz make s cieľom hof.o alebo k.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: game. 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ľ game.

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.

Úloha 2.6

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)

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 2.7

Overte funkčnosť aktualizovaných cieľov.
Pokiaľ ste postupovali správne, funkcionalita zostane nezmenená.

Úloha 2.8

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

Krok #3: Cleanup

V tomto poslednom kroku vytvoríte cieľ, pomocou ktorého vyčistíte projekt od nepotrebných súborov. Pri preklade totiž budú vznikať rozličné súbory ako medzivýsledky prekladu. Vytvoríte preto cieľ clean, pomocou ktorého tieto súbory zmažete.

Úloha 3.1

Vytvorte cieľ clean, pomocou ktorého zmažete z disku spustiteľný súbor game 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 3.2

Overte funkčnosť vytvoreného cieľa.
Ak ste postupovali správne, po spustení príkazu make s cieľom clean dôjde k odstráneniu spustiteľného súboru game z disku vrátane všetkých objektových súborov.

Diskusia

Upozornenie: Do svojich príspevkov nevkladajte správne riešenia úloh a ani ich od ostatných nežiadajte! Nepoužívajte sprosté slová! Takéto príspevky budú zmazané! Riaďte sa podľa pravidiel etického kódexu.