Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 2 - Céčko a Linux – Makefile

V minulé lekci, Céčko a Linux - Úvod, jsme se podívali na to, jak používat překladač, jaké jsou jeho možnosti a jak je můžeme prakticky využít.

Co ale, když budeme mít více souborů? Museli bychom každý přeložit zvlášť a nakonec je slinkovat dohromady. A když vyvíjíme, často překládáme i několikrát během deseti minut... Určitě budete souhlasit, že psát pokaždé příkazy znovu by bylo minimálně trošku nepraktické. Naštěstí tu máme program make, který toto vše udělá za nás. Jeho použití je velmi jednoduché – ve složce s naším projektem si vytvoříme soubor Makefile a napíšeme, co chceme, aby dělal. Jak, to si dnes ukážeme. :)

Jednoduchý příklad

Vytvoříme následující program – hello.c

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
  printf("Hello world!\n");
  return 0;
}

A do stejné složky soubor se jménem Makefile a tímto obsahem.

# Projekt: Hello world!
# Autor:   David Novák
# Datum:   16.2.2015

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

hello: hello.c
    $(CC) $(CFLAGS) hello.c -o hello

Náš jednoduchý program všichni důvěrně znáte, takže o něm mluvit nebudu. Když se podíváme na Makefile, vidíme jako první věc hlavičku – jméno projektu, autor, datum, případně poznámky nebo cokoliv uznáme za vhodné. Je to dobrý zvyk (kdekoliv nejen v Makefile) a jeho dodržováním usnadníte ostatním lidem práci s vaším kódem. :)

Jak psát Makefile

Komentáře začínají znakem #, jak jste si jistě všimli a mohou být kdekoliv (například za příkazem). Cokoliv za znakem # až do konce řádku je bráno jako komentář. Hned po hlavičce si můžeme všimnout deklarace proměnných CC (zkratka pro C Compiler) a CFLAGS (názvy si samozřejmě můžete zvolit libovolné). Tyto příkazy samozřejmě můžeme napsat pokaždé znovu (případně pro každý soubor mohou být jiné), ale většinou je budeme chtít pro snadnou změnu deklarovat na začátku souboru.

Poslední částí je definice pravidla hello. „hello“ je název pravidla (cíle) – většinou se jedná o název generovaného souboru nebo akce, kterou chceme vykonat (například clean). Následuje dvojtečka a seznam závislostí. Závislosti jsou soubory nebo jiná pravidla, které pravidlo vyžaduje. Na dalším řádku (MUSÍ začínat tabulátorem!) uvedeme příkaz, který bude vykonán. Příkaz končí koncem řádku a pravidlo je ukončeno řádkem, který nezačíná tabulátorem. Příkazů můžeme napsat kolik chceme (každý začíná tabulátorem). Pravidlo se vykoná jen pokud jsou k dispozici všechny soubory vypsané v závislostech. Pokud máme v závislostech další pravidla, prvně se všechny vykonají a až potom se provede naše pravidlo. Tak... To byla nudná teorie a teď se podíváme, jak to prakticky použít. :)

Otevřeme si terminál ve složce s naším projektem a napíšeme příkaz make. Pokud jste Makefile opsali správně, zobrazí se vám něco takového.

Makefile v Linuxu - Linux a programování v jazyce C

Vidíme, že tímto krátkým, jednoduchým příkazem jsme vykonali totéž, jako kdybychom napsali „gcc -std=c99 -pedantic -Wall -Wextra -g hello.c -o hello“. Pěkné, že? Ale to zdaleka není vše, co make umí! Další příjemná vlastnost je, že 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í. V praxi to znamená, že se přeloží pouze ty soubory, u kterých jsme provedli změnu. To je u větších projektů značná úspora času.

Další možnosti

Náš Makefile si trochu rozšíříme. Hlavičku pro zkrácení nebudu uvádět.

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

$(NAME): hello.c
    $(CC) $(CFLAGS) hello.c -o $(NAME)

clean-bin:
    rm -f $(NAME)   # smaže binární soubor

clean-bck:
    rm -f *~ *.bak  # smaže všechny záložní soubory

clean: clean-bin clean-bck  # smaže binární soubor i zálohy

Jak vidíte, do pravidel v Makefile můžeme napsat i klasické příkazy bashe. Dále si můžete všimnout, že jsem vytvořil proměnnou NAME s názvem generovaného souboru. Používáme ji již na třech místech a kdybychom se rozhodli ji změnit, bylo by to otravné přepisovat. Nakonec jsem vytvořil tři další pravidla, přičemž nejzajímavější je poslední pravidlo clean, které vykoná clean-exe a clean-bck.

Pravidla máme. Jak je ale použít? Make funguje tak, že když jej zavoláme bez parametrů, vykoná první pravidlo a pak skončí. V našem případě to znamená, že příkazem make spustíme kompilaci souboru hello.c. Pokud chceme zavolat jiné pravidlo, je to velmi jednoduché – stačí jej napsat jako parametr (argument).

Příkaz clean v Linuxu - Linux a programování v jazyce C

Co udělat, když chceme například použít vlastní hlavičkový soubor? Stačí jej pouze připsat do závislostí.. Ukážeme si jak. :)

Pro účely tohoto tutoriálu si vytvoříme jednoduchý hlavičkový soubor my_config.h

#ifndef __MY_CONFIG__

#define __MY_CONFIG__

    #define MOJE_CISLO 42

#endif

Program upravíme následovně:

#include <stdio.h>
#include <stdlib.h>

#include "my_config.h"

int main(void)
{
  printf("Hello world!\n"
    "Moje číslo je: %d\n", MOJE_CISLO);
  return 0;
}

A Makefile bude vypadat takto:

# Projekt: Hello world!
# Autor:   David Novák
# Verze:   1.1
# Datum:   16.2.2015

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

$(NAME): $(NAME).c my_config.h  # zde jsme připsali „my_config.h“
    $(CC) $(CFLAGS) $(NAME).c -o $(NAME)

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

clean-bck:
    rm -f *~ *.bak  # smaže všechny záložní soubory

clean: clean-exe clean-bck  # smaže binární soubor i zálohy

Když teď spustíme make a následně program, výstup bude něco takového:

Příkaz make v Linuxu - Linux a programování v jazyce C

Pro zajímavost si ještě ukážeme, co by se stalo, kdyby my_config.h chyběl.

Chybějící config - Linux a programování v jazyce C

Makefile pro větší projekt

Jednoduché, že? Co ale, když má náš projekt více modulů? Obecně se dá říct, že modul je samostatná jednotka, která je do jisté míry nezávislá na hlavním programu (a například mu poskytuje nějaké služby). Možná by se dal trochu přirovnat k objektu (ale tohle berte prosím volně... :) ). Každopádně se skládá z .c a .h souboru. Pokud tedy máme hlavní soubor a k tomu ještě další modul, máme dva soubory, které musíme přeložit a slinkovat. Minule jsme si řekli o přepínači -c a tohle je přesně případ, kdy se bez něj neobejdeme.

Takhle bude vypadat náš jednoduchý modul.c

#include "modul.h"

#include <stdio.h>
#include <stdlib.h>

int vypocet(int a, int b)
{
    return (a + b)*a;
}

Takhle jeho hlavičkový soubor modul.h

#ifndef __MY_MODUL__

#define __MY_MODUL__

extern int vypocet(int a, int b);

#endif

Jen bych podotkl, že pro vytváření modulů a hlavičkových souborů platí určité konvence a vyplatí se je dodržovat. Tyto příklady jsou pouze pro účely tutoriálu a pro reálný vývoj jsou minimálně nekompletní. O modulárním programování si možná v budoucnosti povíme něco víc. Každopádně, pokud vás to zajímá, na internetu se dá najít množství informací na toto téma.

A takhle náš upravený hlavní soubor.

#include <stdio.h>
#include <stdlib.h>

#include "my_config.h"

int main(void)
{
  printf("Hello world!\n"
    "Moje číslo je: %d\n", MOJE_CISLO);
  printf("Výpočet: %d\n", vypocet(2,5));
  return 0;
}

Nakonec musíme upravit náš Makefile.

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

$(NAME): modul.o $(NAME).o       # slinkování přeložených souborů
    $(CC) $(CFLAGS) $(NAME).o modul.o -o $(NAME)

$(NAME).o: $(NAME).c my_config.h  # překlad hlavního souboru
    $(CC) $(CFLAGS) -c $(NAME).c -o $(NAME).o

modul.o: modul.c modul.h   # překlad modulu
    $(CC) $(CFLAGS) -c modul.c -o modul.o

Není to moc hezké, že? A když budeme mít projekt o desítkách modulů, nebude to ani příliš praktické. Když bychom si vytvořili nový hlavičkový soubor, museli bychom otevřít Makefile a provést změny.. Jde to ale lépe ;)

NAME=hello
OBJFILES=$(NAME).o modul.o  # zde napíšeme všechny moduly

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

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

# Startovací pravidlo - pro přehlednost
all: $(NAME)

# Generování závislostí
# při změně souborů spustíme 'make dep'
dep:
    $(CC) -MM *.c >dep.list

-include dep.list

# závěrečné slinkování
$(NAME): $(OBJFILES)
    $(CC) $(CFLAGS) $(OBJFILES) -o $@

Vypadá to jinak, že? Generování závislostí přenecháme překladači – takhle stačí při každé změně (např. přidáme hlavičkový soubor) jen zavolat make dep. Přepínač -MM je specialita k tomuto určená – překladač jen vygeneruje závislosti (include). $< a $@ jsou tzv. automatické proměnné. $< obsahuje název zdrojového souboru a $@ název cílového souboru. Získají je z hlavičky pravidla (pro nás je důležité, že to funguje... ;) ). Nakonec bych měl asi ještě vysvětlit vzorové pravidlo.. Znak % je tzv. žolík (angl. wildcard) a zastupuje libovolný text. Vzorové pravidlo funguje tak, že v seznamu skutečných pravidel (to nám vygeneroval překladač) najde ty, které mu odpovídají – cíl je soubor s koncovkou .o, který má v závislostech alespoň jeden soubor s koncovkou .c.

Tento Makefile můžete použít na většinu projektů a pokud byste chtěli, můžete se podívat do dokumentace nebo na internet a určitě ho i rozšířit o další možnosti jako třeba generování dokumentace pomocí doxygen (trochu složitější) nebo zabalení celého projektu do archivu. To je pro dnešek vše. :)

Příště, v lekci Céčko a Linux - Debugging, se podíváme na debugging a ladící nástroje pod Linuxem.


 

Předchozí článek
Céčko a Linux - Úvod
Všechny články v sekci
Linux a programování v jazyce C
Přeskočit článek
(nedoporučujeme)
Céčko a Linux - Debugging
Článek pro vás napsal David Novák
Avatar
Uživatelské hodnocení:
21 hlasů
Autor se zajímá především o nízkoúrovňové programování (C/C++, ASM) a návrh hardwaru (VHDL).
Aktivity