Chapitre 7 • Makefiles
Utilisation d’un framework de tests unitaire et création de votre bibliothèque de code.
Cartouche
| Champ | Valeur |
|---|---|
| Auteur·e | Élise |
| Édition | 2024-02-02 |
| Taille des équipes | 1 personne |
| Rendu | via git, dépôt $YEAR_pgc_c7, droits en lecture à delivery_collectoret dépôt libstu, droits en lecture à delivery_collector |
Barème
| Critère | Points |
|---|---|
| Makefile lib | 5 |
| Makefile lib test | 5 |
| Makefile du projet qui compile la bibliothèque | 5 |
| Makefile du projet qui compile avec la bibliothèque | 5 |
| Total des critères | 20 |
| Note maximale | 20 |
Règlement
La réalisation de cet exercice est assujettie aux règles en vigueur dans l’école en matière de triche et de normalisation. Tricher vous expose à de graves sanctions.
Vous devez respecter les règles de normalisation suivantes : https://git.ecole-89.com/eriizu/coding_style/src/branch/main/norm.md
Le non-respect de la norme vous fera perdre une partie ou la totalité des points qui auraient pu être acquis, sur l’ensemble du rendu.
Instructions de rendu
Respectez à la lettre les noms de fichiers et leur emplacement dans le dépôt de rendu. S’il vous est demandé de rendre un fichier nommé hello.c sans qu’un nom de dossier soit précisé : rendez un fichier hello.c à la racine de votre dépôt.
La présence d’une étoile * signifie que le nom du fichier peut être n’importe lequel dès lors qu’il porte l’extension ou l’affixe (préfixe/suffixe) demandée. Elle signifie aussi qu’il est possible de rendre plusieurs fichiers.
La présence d’une double étoile ** signifie que les fichiers peuvent être placés n’importe où dans un dossier, y compris dans des sous-dossiers.
Pensez à faire des commits et des push fréquemment. Autrement nous ne pouvons pas vous aider à retrouver vos fichiers perdus.
Synopsis
Lorsqu’on travaille avec des projets en C et en C++, une méthode commune de gérer les sources du projet c’est les Makefiles. On a utilisé les Justfiles juste avant qui sont un format de fichier qui s’inspire des Makefiles.
L’objectif du jour c’est d’apprendre à se servir des Makefiles et de voir comment ils peuvent nous aider.
On va voir notamment comment :
- écrire des recettes ;
- écrire des recettes avec des dépendances ;
- déclarer des variables ;
- transformer des variables ;
- recompiler que la partie d’un projet qui a changé ;
- compiler un projet qui est dans un autre dossier.
Théorie
Fichier et commande
Le nom du fichier qu’il vous faut à la racine de vos projets c’est simplement Makefile. Avec une majuscule.
Les commandes qui se servent d’un Makefile utilisent le binaire make.
Lorsqu’on fera des commandes sur un projet qui est dans un autre dossier, on utilisera make -C chemin_du_dossier
Recettes basiques
La syntaxe de base d’une recette c’est :
nom_de_la_recette:
commandes de la recette
Exemple
hello:
echo hello
On peut lancer la recette hello avec make hello.
Le but d’une recette dans un Makefile, c’est de construire un fichier qui porte le même nom. Ce qui fait que le Makefile ici pense que l’on construit un fichier qui s’appelle hello.
Pour vous le prouver créez un fichier hello et essayez la recette à nouveau : affiche-t’elle toujours hello ?
Supprimez le fichier hello et lancez la recette à nouveau : est-ce que ça fonctionne à nouveau ?
Pour respecter un peu plus l’utilité d’un Makefile, changeons notre recette hello et faisons lui créer un fichier du même nom :
hello:
echo "hey there!" > hello
Enfin, pour nettoyer les produits sur un projet, par convention, on fait une recette fclean :
fclean:
@rm -vf hello
Le symbole @ devant une ligne permet de ne pas afficher la commande au moment de l’exécution.
Si vous vous demandez pourquoi on appelle pas la recette clean, c’est simplement parce que celle-ci a un autre usage : on s’en servira pour supprimer les fichiers de compilation intermédiaire (comme les .o).
Recettes avec des dépendances
Avant d’exécuter une recette, on peut avoir besoin d’autres fichiers que le Makefile est en mesure de fabriquer.
Mettons :
- notre recette
helloa besoin d’un fichiermessage; - nous avons une recette
message:
message:
echo "this is the makefile speaking!" > message
hello:
echo -n "hey there, " | cat - message > hello
Si on fait la commande make hello, on va avoir une erreur. On pourrait faire la commande make message avant de lancer make hello pour ne pas avoir d’erreur, mais il existe une façon automatique de le faire : écrire le nom de la recette dont l’actuelle dépend juste après les deux points.
hello: message
echo -n "hey there, " | cat - message > hello
Cette syntaxe nous permet en plus de bénéficier d’une fonctionnalité supplémentaire des Makefile :
- si le fichier
messagene change pas, lancer la recettehellone fera rien - si le fichier
messageest plus récent que le fichierhello, lancer la recettehelloaura pour effet de reconstruirehello.
Dépendances automatiques
Si vous mettez un fichier dont l’extension est .o en dependence d’une recette, make va essayer de compiler le .c du même nom.
Si vous avez une recette comme :
patate: main.o
gcc main.o -o patate
Sans écrire de recette main.o make va partir du principe que vous voulez compiler main.c et en faire un .o et le compiler avant d’exécuter votre recette.
$ make
cc -c -o main.o main.c
gcc main.o -o patate
On remarque la commande avec cc qui a été générée par make.
On peut ajouter des options à cette commande générée en utilisant deux variables :
CCpour changer le compilateur à appelerCFLAGSpour changer les options de compilation
Exemple :
CFLAGS = -Wall -Wextra -Werror -Iinclude
CC = clang
patate: main.o
$(CC) main.o -o patate
$ make patate
clang -Wall -Wextra -Werror -Iinclude -c -o main.o main.c
clang main.o -o patate
Variables
Vos Makefiles peuvent avoir des variables.
Les déclarer est facile il suffit d’écrire clé=valeur
SRCS = src/main.c \
src/riri.c \
src/fifi.c \
src/loulou.c
CFLAGS = -Wall -Wextra -Werror -Iinclude
On peut se servir des variables comme ceci :
example:
echo $(SRCS)
Changer les extensions de tous les fichiers d’une variable
On peut facilement produire une nouvelle liste de fichiers en changeant les extensions de la liste originale avec la syntaxe suivante.
example:
echo $(SRCS:.c=.o)
En général on se sert de cette fonctionnalité pour créer une liste d’objets :
OBJS = $(SRCS:.c=.o)
Exercices
Compilation de la bibliothèque
Dépôt : libstu
Fichier à rendre : Makefile
Écrivez une recette libstu.a qui compile toutes les sources de votre bibliothèque et qui les regroupe dans une archive (avec ar rc.) Si un des fichiers sources change, seul celui-ci doit être recompilé.
Vous devez aussi avoir les recettes :
all: dépend delibstu.aclean: supprime les.ogénérésfclean: dépend decleanet supprime l’archivelibstu.are: dépend defcleanpuis deall.
Compilation de vos tests
Dépôt : libstu
Fichier à rendre : Makefile
Écrivez une recette ut.out (unit tests dot out). Elle doit transformer tous vos fichiers sources et fichiers de tests en objets et les compiler ensemble. Elle doit faire la compilation finale avec -lcriterion.
La recette clean doit aussi supprimer les objets de tests.
La recette fclean doit aussi supprimer le binaire ut.out.
Compilation depuis un autre projet
Dépôt : $YEAR_pgc_c7
Fichier à rendre : Makefile
Créez un Makefile dans ce dépôt.
Il vous faut une recette libstu.a, elle doit :
- lancer la compilation dans
../libstu; - copier le binaire résultant.
fclean doit supprimer le binaire copié et appeler fclean sur la bibliothèque.
clean doit appeler clean sur la bibliothèque.
Dans une recette, on peut exécuter une recette d’un autre Makefile en précisant le dossier dans lequel il est :
example:
$(MAKE) -C ../libstu [recette que l'on veut appeller]
fclean:
$(MAKE) -C ../libstu fclean
Compilation du projet
Dépôt : $YEAR_pgc_c7
Fichiers à rendre : Makefile, src/main.c
Créez un fichier src/main.c :
#include <stdlib.h>
#include "stu.h"
int main(void)
{
const char *str0 = "hello world";
char *str1;
str1 = stu_strdup("hallo world");
str1[1] = 'e';
if (stu_strcmp(str0, str1) == 0) {
stu_puts("everything is fine");
} else {
stu_puts("that doesn't look right");
}
free(str1);
}
Mettez le dans la variable SRCS du Makefile.
Créez une variable OBJS dans le Makefile qui transforme chaque fichier de SRCS en .o.
Dans une variable CFLAGS ajoutez le drapeau -I../libstu/include pour pouvoir utiliser stu.h dans main.c.
Créez une règle c7 qui dépend de OBJS et de libstu.a et qui fait la compilation finale avec les objets et libstu.a en un fichier exécutable qui s’appelle c7.
À la fin vous devez avoir les recettes :
allqui dépend dec7;c7qui compile le projet d’exemple :- dépend des objets et de
libstu.a;
- dépend des objets et de
libstu.a:- va compiler la bibliothèque dans
../libstu, - copie la bibliothèque compilée dans le repertoire courrant ;
- va compiler la bibliothèque dans
clean:- supprime les
.o, - appelle
cleansur la bibliothèque ;
- supprime les
fclean:- supprime le binaire
c7etlibstu.a, - dépend de
clean, - appel
fcleansur la bibliothèque ;
- supprime le binaire
re:- dépend de
fcleanet deall.
- dépend de