Recherche de site Web

Un guide rapide des Makefiles et de GNU Make pour les débutants


GNU Make est un utilitaire de développement qui détermine quelles parties d'une base de code particulière doivent être recompilées et peut émettre des commandes Linux pour effectuer ces opérations.

Cet outil d'automatisation de la compilation peut être utilisé avec n'importe quel langage de programmation dont la compilation peut être effectuée depuis le terminal en lançant des commandes, ce qui le rend inestimable pour C, C++ et de nombreux autres langages compilés.

Makefiles sous Linux

Pour utiliser GNU Make, nous avons besoin d'un ensemble de règles définissant la relation entre les différents fichiers de notre programme et des commandes pour mettre à jour chaque fichier. Celles-ci sont écrites dans un fichier spécial appelé ‘Makefile‘ ou ‘makefile‘.

La commande ‘make‘ utilise la base de données du makefile et les dernières dates de modification des fichiers pour décider quels fichiers doivent être recompilés.

Contenu d'un Makefile

En général, les makefiles contiennent cinq types d'éléments : règles explicites, règles implicites, définitions de variables, directives et commentaires.

  • Règles explicites spécifient comment créer ou recréer un ou plusieurs fichiers (appelés cibles) et quand le faire.
  • Règles implicites spécifient comment créer ou recréer des fichiers en fonction de leurs noms, décrivant comment un fichier cible est lié à un autre fichier portant un nom similaire.
  • Définitions de variables sont des lignes qui spécifient des valeurs de chaîne pour des variables à substituer plus tard dans le makefile.
  • Directives sont des instructions pour make afin de faire quelque chose de spécial lors de la lecture du makefile.
  • Les commentaires commencent par un symbole '#'. Toute ligne commençant par '#' est ignorée par make.

Structure des Makefiles

L'information qui indique à make comment recompiler un système provient de la lecture du makefile.

Un makefile simple consiste en des règles avec la syntaxe suivante :

target ... : prerequisites ...
	recipe
...
...
  • Une cible est le fichier de sortie généré par le programme, qui peut également être une cible factice (expliquée ci-dessous). Les exemples incluent les exécutables, les fichiers objets, ou les cibles factices comme clean, install, test ou all.
  • Un prérequis (également appelé dépendance) est un fichier utilisé comme entrée pour créer les fichiers cibles.
  • Une recette est l'action qu'exécute make pour créer le fichier cible à partir des prérequis. Il est nécessaire de mettre un caractère de tabulation avant chaque ligne de recette, sauf si vous spécifiez la variable .RECIPEPREFIX pour définir un autre caractère comme préfixe.

Un exemple de Makefile:

final: main.o end.o inter.o start.o
	gcc -o final main.o end.o inter.o start.o

main.o: main.c global.h
	gcc -c main.c

end.o: end.c local.h global.h
	gcc -c end.c

inter.o: inter.c global.h
	gcc -c inter.c

start.o: start.c global.h
	gcc -c start.c

clean:
	rm -f main.o end.o inter.o start.o final

Dans cet exemple, nous utilisons quatre fichiers sources C et deux fichiers d'en-tête pour créer l'exécutable final. Chaque fichier ‘.o’ est à la fois une cible et une prérequis dans le makefile. Remarquez la dernière cible nommée clean – c'est une action plutôt qu'un fichier réel.

Puisque nous n'avons normalement pas besoin de nettoyer pendant la compilation, ce n'est écrit comme une condition préalable dans aucune autre règle. Les cibles qui ne se réfèrent pas à des fichiers mais qui sont simplement des actions sont appelées cibles fictives. Elles n'ont généralement pas de conditions préalables comme les fichiers cibles ordinaires.

Comment GNU Make traite un Makefile

Par défaut, make commence avec le premier objectif dans le makefile, appelé le objectif par défaut. Dans notre exemple, final est le premier objectif. Comme ses prérequis incluent des fichiers objets, ceux-ci doivent être mis à jour avant de créer final. Chaque prérequis est traité selon sa propre règle.

La recompilation se produit si des modifications ont été apportées aux fichiers source ou en-tête, ou si le fichier objet n'existe pas du tout. Après avoir recompilé les fichiers objets nécessaires, make décide s'il faut relier à nouveau final. Cela se produit si final n'existe pas ou si l'un des fichiers objets est plus récent.

Par exemple, si nous modifions inter.c et exécutons make, cela recompilerait ce fichier source pour mettre à jour inter.o puis lier final.

Utilisation des variables dans les Makefiles

Dans notre exemple, nous avons dû lister tous les fichiers objets deux fois dans la règle pour final:

final: main.o end.o inter.o start.o
	gcc -o final main.o end.o inter.o start.o

Pour éviter une telle duplication, nous pouvons introduire des variables pour stocker des listes de fichiers. L'utilisation de variables facilite également la maintenance du makefile.

Voici une version améliorée :

CC = gcc
CFLAGS = -Wall -Wextra -O2
OBJ = main.o end.o inter.o start.o
TARGET = final

$(TARGET): $(OBJ)
	$(CC) -o $(TARGET) $(OBJ)

main.o: main.c global.h
	$(CC) $(CFLAGS) -c main.c

end.o: end.c local.h global.h
	$(CC) $(CFLAGS) -c end.c

inter.o: inter.c global.h
	$(CC) $(CFLAGS) -c inter.c

start.o: start.c global.h
	$(CC) $(CFLAGS) -c start.c

clean:
	rm -f $(OBJ) $(TARGET)

Remarquez également que nous avons défini CC pour le compilateur et CFLAGS pour les options de compilation, ce qui facilite le changement des options du compilateur ou même le changement de compilateur pour l'ensemble du projet.

Règles pour nettoyer le répertoire source

Comme on le voit dans l'exemple, nous pouvons définir des règles pour nettoyer le répertoire source en supprimant les fichiers indésirables après la compilation. Mais supposons que nous ayons un fichier réel appelé clean – comment pouvons-nous faire la distinction entre le fichier et la cible ? C'est là qu'interviennent les cibles phony.

Une cible fictive n'est en réalité pas le nom d'un fichier ; c'est juste un nom pour une recette à exécuter lorsqu'elle est demandée explicitement. Les principales raisons d'utiliser des cibles fictives sont d'éviter les conflits avec des fichiers portant le même nom et d'améliorer les performances.

Voici un détail important : la recette pour clean ne sera pas exécutée par défaut lors de l'exécution de make. À la place, vous devez l'invoquer explicitement avec make clean.

Pour déclarer correctement une cible factice, utilisez la directive .PHONY :

.PHONY: clean all install

clean:
	rm -f $(OBJ) $(TARGET)

all: $(TARGET)

Meilleures pratiques modernes pour Makefile

Voici quelques pratiques contemporaines à considérer :

Utilisez les règles de motif pour réduire la répétition :

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

Ajouter une cible par défaut : 'tous'

.PHONY: all
all: $(TARGET)

Inclure la génération de dépendances pour le suivi automatique des en-têtes :

DEPS = $(OBJ:.o=.d)
-include $(DEPS)

Ajoutez plus de cibles factices pour les opérations courantes :

.PHONY: install test run

Utilisez des variables automatiques comme $@ (cible), $> (première condition préalable), et $^ (toutes les conditions préalables) pour rendre les règles plus génériques.

Conclusion

Essayez maintenant de créer des makefiles pour votre propre base de code. GNU Make reste un outil de construction puissant et largement utilisé, en particulier pour les projets C et C++.

Comprendre les makefiles vous aidera à travailler avec d'innombrables projets open-source et vous donnera un contrôle précis sur votre processus de compilation. N'hésitez pas à commenter avec vos questions ou vos expériences !