UCL-INGI / LEPL1503-Blog

Blog du projet P3 à l'UCLouvain
MIT License
0 stars 10 forks source link

Utilisation de variables "magiques" - Makefile #12

Closed ckafrouni closed 4 years ago

ckafrouni commented 4 years ago

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:

# Initialisé des variables :
CC=gcc
CFLAGS=-g -Wall -Werror
CUnit=-lcunit
GMP=-lgmp

OBJ_PATH=UTILS
INPUT_FILE_EXAMPLE=Input-Output/example_input.txt

Ou encore :

all: main

# make
main: main.c factorisation.o LinkedList.o
    $(CC) $(CFLAGS) -o $@ $^ $(GMP) $(CUnit)

factorisation.o: $(OBJ_PATH)/initial/factorisation.*
    $(CC) $(CFLAGS) -c $(OBJ_PATH)/withLinkedL/$(*F).c

LinkedList.o: $(OBJ_PATH)/withLinkedL/LinkedList.*
    $(CC) $(CFLAGS) -c $(OBJ_PATH)/withLinkedL/$(*F).c

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:.

ckafrouni commented 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
obonaventure commented 4 years ago

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.

ckafrouni commented 4 years ago

Makefiles! Variables Magiques?

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..

Les Basics

Imaginons un repertoire très basique :

Tuto-Makefile

main.c addition.c multiplication.c Makefile

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;
}

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.

Variables dans les Makefiles?

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

Et les Variables Magiques ??

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 $<

Des commandes supplémentaire?

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>

UnitTests

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.

CppCheck & Valgrind

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.

ckafrouni commented 4 years ago

@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:

obonaventure commented 4 years ago

Bonjour, Cela me parait très bien. Il me reste de petites suggestions:

Vous pouvez faire une pull request

Merci

OB

ckafrouni commented 4 years ago

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?

ckafrouni commented 4 years ago

Comme ça par example: Vous pouvez télécharger tous les fichiers ici.

obonaventure commented 4 years ago

Merci pour ce post qui a été intégré sur le blog