Closed ckafrouni closed 4 years ago
Pour donner un peut plus de context, voilà comment j'effectue les tests CUnit
# TESTS `make test`
test: LinkedList.o factorisation.o CUnit_test.o
$(CC) -o $@ $^ $(GMP) $(CUnit)
./test
make clean
CUnit_test.o: CUnit_test.c $(OBJ_PATH)/*
$(CC) $(CFLAGS) -c $<
# $< est uniquement le CUnit_test.c
Cela me parait un bon point de départ. Une bonne façon est probablement de commencer avec un exemple super simple, style minmax dasn un autre blog post et de complexifier petit à petit le code en ajoutant Cunit, -g, gmp etc. Ce serait bien de mettre aussi cppcheck dans votre Makefile. valgrind et hellgrind pourraient aussi être intégrés après Cunit. gprof pourrait aussi avoir sa place, mais cela mériterait un autre article.
Il y a quelques fautes d'orthographe dans votre texte, on aura l'occasion de corriger ensemble celles de l'article final.
Au cas où vous n'avez toujours pas eu l'occasion de faire vos premiers pas avec cet outil très puissant, laissez-moi vous introduire au Makefile.
Un Makefile est un fichier contenant toutes les commandes que l'on aurait typiquement tapé dans le terminal pour compiler et lancer certaines parties de notre code. Les stocker dans ce fichier nous permet d'accéder à ces commandes en tapant simplement la commande make
suivie de potentiels methodes.
Ce sera plus simple à comprendre une fois que l'on se lance dans les examples..
Imaginons un repertoire très basique :
Tuto-Makefile
main.c addition.c multiplication.c Makefile
Où main.c est le fichier :
#include "addition.h"
#include "multiplication.h"
int main() {
int x = add(5, 10); // provient de "addition.h"
int y = mult(5, 10); // provient de "multiplication.h"
return 0;
}
Où addition.c et multiplication.c :
#include "addition.h" | #include "multiplication.h"
|
int add(int a, int b) { | int mult(int a, int b){
return a+b; | return a * b;
} | }
Pour un exemple pareil on pourrait bien taper chaque commande à chaque fois que l'on veut compiler notre programme main.
Mais on peut y arriver en mettant les commandes dans le Makefile de cette manière :
main: addition.o multiplication.o
gcc -o main addition.o multiplication.o
addition.o: addition.c addition.h
gcc -c addition.c
multiplication.o: multiplication.c multiplication.h
gcc -c multiplication.c
Le fait d'écrire les règles de compilation de cette manière nous permet d'uniquement passer la commande make
qui effectuera les trois opérations.
À savoir, la syntaxe est très importante, en effect le première élément avant " : " est le target et ceux après sont les dependances. Cela servira donc à l'outils make pour comprendre de quel manière il doit procéder aux compilations. Main ne sera donc compilé qu'une fois les fichiers objects *.o générés.
Pour l'instant, nous avons abordé la manière la plus basique d'écrire un makefile. pour chaque fichier on a du écrire deux lignes.. Pas très pratique tout ça. De plus que lorsque l'on compare les différentes lignes, on remarque qu'il y a beaucoup de répétions. Il suffit de vouloir changer de compilateur ( par exemple "clang" ) pour voir que l'on devra changer toutes les lignes où l'on trouve "gcc".
On peut y remédier avec l'utilisation de variables comme dans nos programmes. On rajoute en haut de notre fichier toutes nos variables, et on peut les accéder comme dans le terminal avec $( variable ) :
CC = gcc
CFlags = -Wall -Werror
main: addition.o multiplication.o
$(CC) $(CFlags) -o main addition.o multiplication.o
addition.o: addition.c addition.h
$(CC) $(CFlags) -c addition.c
multiplication.o: multiplication.c multiplication.h
$(CC) $(CFlags) -c multiplication.c
Le Makefile vient encore à notre secours car on a bien optimisé notre fichier, mais il y a encore beaucoup trop de lignes à mon goût, étant donné toutes les répétions.
J'introduis donc les variables magiques :
Un example s'impose:
# ...
main: addition.o multiplication.o
$(CC) $(CFlags) -o $@ $^
addition.o: addition.c addition.h
$(CC) $(CFlags) -c $<
multiplication.o: multiplication.c multiplication.h
$(CC) $(CFlags) -c $<
On peut d'autant plus voir les similitudes entres les lignes. Il y a sûrement une meilleure méthode pour écrire tout ça?
Oui !!
Avec le signe % . On ne doit plus réécrire les noms, et en une ligne on peut réécrire toutes les commandes servant à générer des fichier objects __.o.
# ...
main: addition.o multiplication.o
$(CC) $(CFlags) -o $@ $^
%.o: %.c %.h
$(CC) $(CFlags) -c $<
On va commencer par ma commande préférée.
make clean
. Pour nettoyer ! ( pour effacer en une commande tous les fichier générés, le fichier main et les fichiers objects __.o et à peut prés tout ce que l'on veut.
On peut donc rajouter à la fin du fichier :
clean:
rm main *.o
rm -f *.xml # Utile pour les fichier générer par valgrind et cppcheck
# rm ...
Vous l'avez donc deviné, on peut donc créer toutes sortes de commandes executables en tapant make <tartget-name>
En suivant ce que l'on a appris au point suivant, on peut créer une commande chargée d'effectuer tous nos tests avec par example make test
On a donc besoin d'un fichier pour nos "units tests".
Notre répertoire ressemble maintenant à :
Tuto-Makefile
main.c addition.c multiplication.c Makefile UnitTest.c
On peut donc rajouter
CUnit = -lcunit
# ...
test: UnitTest.o addition.o multiplication.o
$(CC) -o test $^ $(CUnit)
./test
make clean
On se rappelle que tous les fichiers __.o sont pris en charge par la commande vu plus haut. Donc pas besoins de rajouter une règle pour générer le fichier UnitTest.o supplémentaire.
On n'oublis pas de rajouter une variable pour les flags requis par la librairie "CUnit".
Pour récapituler, la commande make test
compile le fichier test, puis l'exécute, et finis par nettoyer le répertoire de tous les fichiers générés.
Comme pour make clean
et make test
, on peut créer une commande make allChecks
qui se chargera d'effectuer tous les checks nécessaires, Valgrind et CppCheck:
allChecks:
make main
make CppCheckMake
make ValgrindMake
Vous l'avez deviné, on a aussi besoin de commandes pour effectuer CppCheck et Valgrind.
CppCheckMake: *.c *.h
cppcheck --enable=all --inconclusive $^ 2> cppcheck.json
ValgrindMake: main.c
valgrind --xml=yes --xml-file="valgrind.xml" --leak-check=yes --track-origins=yes ./main
@obonaventure Voilà tout ce que j'ai fais pour l'instant. J'ai suivit votre conseil en commençant avec un exemple simple. Et en améliorant et introduisant de nouvelles méthodes pour améliorer le Makefile.
J'ai aussi fais très attentions aux fautes d'orthographes. :smile:
Bonjour, Cela me parait très bien. Il me reste de petites suggestions:
Vous pouvez faire une pull request
Merci
OB
Bonjour monsieur,
J'ai rajouté tous les liens nécessaires ainsi que des précisions supplémentaires à quelques endroits.
Par contre je ne vois pas comment ajouter un liens vers le projet complet (tous les fichiers abordé dans l'article). Est-ce que je peux rajouter un lien vers une page GitHub avec les fichiers?
Comme ça par example: Vous pouvez télécharger tous les fichiers ici.
Merci pour ce post qui a été intégré sur le blog
Malgré qu'un bon Makefile n'ai pas de rapport direct avec la réussite d'un projet, il peut être très utile pour optimiser l'expension d'un project.
On ne voit pas nécessairement leur utilité dans un project ne contenant qu'un fichier, mais petit à petit, plus le repertoire prends de l'ampleur plus le makefile sera complexe. Dès lors, il pourrait à son tours causé des bug, alors qu'il ne devait être qu'un outils. ( Je parle par expérience .. :sleepy: )
En faisant des recherches sur comments optimiser mon makefile, je suis tombé sur des articles contenants des pages et des pages sur le thème.
Je souhaiterais donc écrire un post contenant les méthodes principales qui peuvent être utiliser pour écrire un makefile digne de servir de fondations pour tout vos projets !!
Comme teaser, voici quelques examples venant de mon makefile:
Ou encore :
Voilà une partie de mon makefile qui peut encore être refactoriser pour le rendre plus fiable à long terme. Après tout, un bon développeur est un développeur paresseux, moins on répète de code, moins il y a de chances de faire d'erreur de copiage :innocent:.