Makefile

De IP7
Aller à : navigation, rechercher

Ce wiki permet de faire et de comprendre les makefiles en une dizaine de minutes.

Un makefile est un fichier qui va décrire un ensemble de règles et de commandes que va exécuter votre shell. Un exemple simple :

$> cat Makefile

regle1:
  echo "Je suis une commande de la règle 1"

regle2:
  echo "Je suis une commande de la règle 2"

$> make regle1
echo "Je suis une commande de la règle 1"
Je suis une commande de la règle 1
$> make regle2
echo "Je suis une commande de la règle 2"
Je suis une commande de la règle 2
$>

Comme on peut le remarquer, la commande effectuée est affichée avant d'être exécutée. On peut faire en sorte que la commande ne soit pas affichée en précédant la commande du caractère '@'.

$> cat Makefile

regle:
  @echo "Hello World!"

$> make regle
Hello World!
$>

Vocabulaire

Dans les makefiles, il y a des règles, des commandes, des dépendances et des variables.

Règles

Les règles désignent le nom de ce qui se trouve à gauche du ':' dans un Makefile.

$> cat Makefile

regle:
$>

Lorsqu'on veut exécuter une règle, on utilise l'utilitaire en ligne de commande make. Ainsi make all appelle la règle all. De plus par défaut, make sans argument exécute la première règle déclarée dans le Makefile.

$> cat Makefile

all:
  echo "Hello World!"

$> make all
echo "Hello World!"
Hello World!
$>

Commandes

Les commandes désignent le contenu d'une règle. Les commandes sont situées à la suite d'une règle. Lors d'une règle, si une commande échoue (elle renvoie un entier différent de 0), l'exécution des commandes s'arrête.

$> cat Makefile

regle:
  cmd1
  cmd2
  cmd3
$>

Par défaut, une commande est affichée avant d'être exécutée. Pour ne pas l'afficher, il suffit de mettre le caractère '@'.

$> cat Makefile

regle:
  @echo "Hello World!"

$> make regle
Hello World!
$>

Dépendances

Les dépendances sont des règles qui sont appelées par d'autre règles. Elles sont situées à droite du ':' lors de la déclaration d'une règle.

$> cat Makefile

regle1: regle2 regle3
  cmd1

regle2:
  cmd2

regle3:
  cmd3
$>

Variables

Les variables sont des constantes lors de l'exécution d'un Makefile. Elles sont généralement déclarées avant toute règle. On les nomme en majuscule.

$> cat Makefile

MSG = "This is a message."

regle:
  echo $(MSG)
$>

Exemple pour le langage C

Makefile a tout d'abord été inventé pour la compilation dans le langage C. Un petit rappel : le code source en C sont des fichiers à l'extension '.c', les fichiers objects ont l'extension '.o'. En C, il faut d'abord compiler chaque fichier source '.c' en '.o', puis réassembler tous les '.o' dans un même programme. Voici un Makefile qui permet de compiler un programme écrit en langage C.

$> cat Makefile

NAME = prog
FILES = code1.c \
        code2.c \
        code3.c

OBJ = $(FILES:%.c=%.o)

CC = gcc
FLAGS = -Wall -Wextra -Werror

all: $(NAME)

$(NAME): $(OBJ)
  $(CC) $(FLAGS) $(OBJ) -o $(NAME)

%.o: %.c
  $(CC) $(FLAGS) -c $? -o $@

clean:
  rm -rf $(OBJ)

fclean: clean
  rm -rf $(NAME)

re: fclean all

$> make
gcc -Wall -Wextra -Werror -c code1.c -o code1.o
gcc -Wall -Wextra -Werror -c code2.c -o code2.o
gcc -Wall -Wextra -Werror -c code3.c -o code3.o
gcc -Wall -Wextra -Werror code1.o code2.o code3.o
$>

Ce Makefile fait beaucoup de choses : plusieurs notions ont été utilisées pour ce Makefile:

  • Les règles peuvent aussi être des noms de règles.
  • Une règle peut avoir à la fois des dépendances et des commandes, uniquement des dépendances, uniquement des commandes ou aucune dépendance et aucune commande.
  • Il existe un moyen de traiter des variables, ici dans OBJ, on remplace tous les ".c" par des ".o".
  • NAME dépend de OBJ, mais OBJ est une liste de fichiers. La règle "%.o" est alors appelée. Le '%' désigne ici le caractère '*' en bash par exemple.

De plus, si je modifie code3.c que j'ai déjà compilé, seule le code3.c va être recompilé après l'exécution de make. Exemple:

$> cat Makefile

NAME = prog
FILES = code1.c \
        code2.c \
        code3.c

OBJ = $(FILES:%.c=%.o)

CC = gcc
FLAGS = -Wall -Wextra -Werror

all: $(NAME)

$(NAME): $(OBJ)
  $(CC) $(FLAGS) $(OBJ) -o $(NAME)

%.o: %.c
  $(CC) $(FLAGS) -c $? -o $@

clean:
  rm -rf $(OBJ)

fclean: clean
  rm -rf $(NAME)

re: fclean all

$> make
gcc -Wall -Wextra -Werror -c code1.c -o code1.o
gcc -Wall -Wextra -Werror -c code2.c -o code2.o
gcc -Wall -Wextra -Werror -c code3.c -o code3.o
gcc -Wall -Wextra -Werror code1.o code2.o code3.o
$> echo "" >> code3.c
$> make
gcc -Wall -Wextra -Werror -c code3.c -o code3.o
gcc -Wall -Wextra -Werror code1.o code2.o code3.o
$>

Alors comment ça marche ?

  • Si la règle ne désigne pas de fichier, les dépendances et les commandes sont tout le temps exécutées.
  • Si la règle désigne un fichier et que les dépendances ne sont pas des fichiers, ni les dépendances ni les commandes ne sont exécutées.
  • Si la règle et les dépendances désignent des fichiers, alors les commandes sont exécutées si et seulement si la date de dernière modification des dépendances est plus récente que la date de modification de la règle.

La dernière remarque montre bien que code1.c et code2.c ne sont pas recompilés dans l'exemple précédent.

Phony

Comment fait-on si, par erreur, on souhaite appeler une règle du nom d'un fichier qui existe déjà ?

Les phony (ou phony targets) sont des informations que l'on donne au Makefile. Elles listent l'ensemble des règles qui, même si le fichier associé à la règle existe, exécute les dépendances et les commandes.

$> cat Makefile

all:
  @echo "Hello"
$> make
Hello
$> touch all
$> make
make: 'all' is up to date.
$> vim Makefile // Ici on rajoute le .PHONY
$> cat Makefile

.PHONY: all
all:
  @echo "Hello"
$> make
oui
$>

Makefile générique pour le C

$> cat Makefile

NAME = [Ici on met le nom du programme]
FILES = [Ici on liste tous les fichiers .c]

OBJ = $(FILES:%.c=%.o)

CC = gcc
FLAGS = -Wall -Wextra -Werror

.PHONY: all
all: $(NAME)

$(NAME): $(OBJ)
  $(CC) $(FLAGS) $(OBJ) -o $(NAME)

%.o: %.c
  $(CC) $(FLAGS) -c $? -o $@

.PHONY: clean
clean:
  rm -rf $(OBJ)

.PHONY: fclean
fclean: clean
  rm -rf $(NAME)

.PHONY: re
re: fclean all
$>

Remarques utiles

Shell dans les variables

On peut exécuter des commandes shell dans les variables. Exemple:

$> cat Makefile

FILES = $(shell ls *.c)

list:
  @echo $(FILES)
$>

Ce Makefile va afficher tous les fichiers avec l'extension ".c" dans le dossier actuel.

Multiple makefiles

Si, dans votre dossier, il y a plusieurs makefiles, donc avec des noms différents, on peut spécifier le fichier Makefile utilisé par make en argument.

$> ls -1p
src/
Makefile.install
Makefile.setup
$> make -f Makefile.install
[Some stuff]

Makefile et les sous-dossiers

Si vous voulez utiliser un Makefile qui se trouve dans un sous-dossier ou même dans n'importe quel dossier qui ne soit pas le dossier courant, on peut le spécifier également en argument.

$> ls -1p
build/
Makefile
$> ls -1p build/
Makefile
$> cat Makefile
all:
  @echo "Main folder"
$> cat build/Makefile
all:
  @echo "build folder"
$> make -C build
build folder

Conclusion

Bravo, vous avez tout lu :). Ce wiki est une introduction sur les Makefiles. Certaines notions ont été omises volontairement. En effet, même si elles sont présentes dans les makefiles, elles sont peu utilisées. Pour plus d'informations, vous pouvez regarder la documentation officielle de Makefile.