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_collector
et 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 hello a besoin d’un fichier message ;
  • 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 message ne change pas, lancer la recette hello ne fera rien
  • si le fichier message est plus récent que le fichier hello, lancer la recette hello aura pour effet de reconstruire hello.

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 :

  • CC pour changer le compilateur à appeler
  • CFLAGS pour 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 de libstu.a
  • clean : supprime les .o générés
  • fclean : dépend de clean et supprime l’archive libstu.a
  • re : dépend de fclean puis de all.

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 :

  • all qui dépend de c7 ;
  • c7 qui compile le projet d’exemple :
    • dépend des objets et de libstu.a ;
  • libstu.a :
    • va compiler la bibliothèque dans ../libstu,
    • copie la bibliothèque compilée dans le repertoire courrant ;
  • clean :
    • supprime les .o,
    • appelle clean sur la bibliothèque ;
  • fclean :
    • supprime le binaire c7 et libstu.a,
    • dépend de clean,
    • appel fclean sur la bibliothèque ;
  • re :
    • dépend de fclean et de all.