© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 04/08/2000 -
Dernière mise à jour : 08/02/2001
Fichiers standardExemples simples
Fichiers utilisateur
Commande impérative : exemple 1Commentaire
Commandes multiples : exemple 2
Commande conditionnelle : exemple 3
macro "utilisateur"Arguments de la commande make
macros standard
Accessoirement, la commande make peut être utilisée en lieu et place d'une commande complexe, comme un alias.
Le principe de la compilation conditionnelle est de ne déclencher une opération (compilation, édition de lien) que si elle est nécessaire, par exemple si une modification d'un fichier a rendu un fichier objet .o, une bibliothèque ou un exécutable obsolètes.
make [options] |
make -f MonMake [options] |
#
[1]
# Fichier Makefile # # Mise à blanc de l'écran # clear : [2] -->|clear [3] |
Les lignes précédées d'un caractère '#' sont des commentaires : ligne [1] par exemple.
La ligne [2] est appelée la cible : identificateur clear en colonne 1, suivie du caractère ':'.
La ligne [3] est la commande
associée à la cible clear. Attention : le
symbole -->| qui précède la commande indique
que la ligne doit impérativement commencer par une tabulation, mais
celle-ci se traduit à la visualisation par une indentation, par
exemple de 8 caractères, et le fichier apparaît ainsi :
#
[1]
# Fichier Makefile # # Mise à blanc de l'écran # clear : [2] clear [3] |
L'exécution de ce fichier par :
make clear |
provoque la mise à blanc de l'écran, comme le fait la
commande Unix clear, ou l'alias
classique cls :
alias cls "clear" |
#
# Fichier MakeClean # # Suppressions des fichiers objets d'attribut .o # clean : -->|clear [1] -->|echo "Nettoyage des fichiers .o :" [2] -->|rm -f *.o -v [3] |
Les lignes [1], [2] et [3] sont les commandes associées à la cible clean. Ce sont ces commandes que make fait exécuter en lançant successivement autant de shells qu'il y a de lignes (ici 3).
L'exécution de ce fichier par :
make -f MakeClean |
provoque par exemple l'affichage suivant :
echo "Nettoyage des fichiers .o :"
[1']
Nettoyage des fichiers .o : [2'] rm -f *.o -v destruction de `exo_01.o' destruction de `main.o' destruction de `nsSysteme.o' destruction de `nsUtil.o' duo> |
La commande clear (ligne [1]) provoque le nettoyage de l'écran. La ligne [1'] est l'affichage de la commande exécutée (ligne [2]) par le shell, alors que la ligne [2'] est le résultat de l'exécution de cette commande.
#
# Fichier Makefile # # Compilation et édition de lien du fichier # source exo_01.cxx en un exécutable exo_01 # exo_01 : exo_01.cxx [1] -->|g++ -o exo_01 exo_01.cxx [2] |
Ce fichier provoque la compilation du fichier exo_01.cxx
par la commande g++, puis l'édition de liens, le fichier
exécutable étant nommé exo_01. L'identificateur
exo_01.cxx
de la ligne [1] est appelé une dépendance.
En effet, la cible est l'exécutable exo_01, qui dépend du
fichier exo_01.cxx : si exo_01.cxx est plus récent
que exo_01, la compilation doit être effectuée. Dans
le cas contraire, on obtient l'affichage suivant :
make: `exo_01' is up to date.
duo> |
Ce fichier provoque la compilation du fichier exo_01.cxx par la commande g++, puis l'édition de liens, le fichier exécutable étant nommé exo_01.
En l'absence de dépendances, la cible est impérative et les commandes associées sont toujours effectuées. C'est le cas de l'exemple 1.
En présence de dépendances, le déclenchement des commandes associées dépend de plusieurs facteurs :
cible : exo.cxx
-->|echo "toujours effectuee" |
#
# fichier Makefile # exo.o : exo.cxx -->|g++ -c exo.cxx |
L'exemple suivant illustre les deux cas :
duo>ls -l
total 299 -rw-r----- 1 mathieu dmml 1226 aoû 7 12:07 Makefile ... -rwx------ 1 mathieu dmml 16 aoû 7 12:09 exo.cxx -rw-r----- 1 mathieu dmml 956 aoû 7 12:07 exo.o duo>make g++ -c exo.cxx duo>ls -l
|
exo.o : clear_screen exo.cxx
-->|g++ -c exo.cxx # clear_screen : -->|clear |
Quelle que soit la date de exo.cxx, la compilation est toujours déclenchée.
#/**
#* #* @File : Makefile #* #* @Authors : D. Mathieu #* M. Laporte #* #* @Date : 02/08/2000 #* #* @Version : V1.0 #* #* @Synopsis : Makefile de l'exo #* #**/ # # Edition de liens # exo : exo.o nsUtil.o -->|g++ -s -o exo exo.o nsUtil.o # exo.o : exo.cxx nsUtil.h -->|g++ -c exo.cxx # nsUtil.o : nsUtil.cxx nsUtil.h -->|g++ -c nsUtil.cxx |
#
# Makefile # exo.o : exo.cxx nsUtil.h -->|g++ -c exo.cxx # nsUtil.o : nsUtil.cxx nsUtil.h -->|g++ -c nsUtil.cxx # exo : exo.o nsUtil.o -->|g++ -s -o exo exo.o nsUtil.o |
la ligne de commande :
duo>make -f Makefile |
ne provoquera que la compilation de exo.cxx en exo.o (première cible).
Il est possible de forcer la commande make
à atteindre une cible particulière en l'indiquant dans la
ligne de commande :
duo>make -f Makefile exo |
Cette fois, c'est la recompilation complète et l'édition de liens qui seront effectuées si nécessaire. Il est donc important que la cible principale soit toujours indiquée avant les cibles intermédiaires. Noter de plus que, même dans ce cas, le fichier Makefile pourra être utilisé aussi pour des recompilations partielles en indiquant à make la cible désirée.
#
# fichier Makefile # exo : exo.o -->|g++ -o exo exo.o # exo.o : exo.cxx -->|g++ -c exo.cxx # clear : -->|clear |
et les commandes :
duo>make
[1]
duo>make exo [2] duo>make exo.o [3] duo>make clear [4] duo>make exo clear [5] |
provoquent :
CEXCFCTSYST_HXX = CExcFctSyst.hxx $(CEXC_H)
CEXCFCTSYST_H = CExcFctSyst.h $(CEXC_H) $(CEXCFCTSYST_HXX) # NSSYSTEME_H = nsSysteme.h $(NSSYSTEME_HXX) \ $(CEXC_H) [1] \ $(CEXCFCTSYST_H) [2] # ... # $(nom) : $(nom).o nsUtil.o nsSysteme.o main.o g++ -s -o $(nom) main.o $(nom).o nsUtil.o [3] \ nsSysteme.o [4] |
La ligne [3] commence par une tabulation, les lignes [1], [2] et [4] ne contiennent pas de caractère de tabulation.
Pièges :
A chaque ligne de commande, make lance
un shell auquel il passe la ligne comme argument. Une ligne de commande
de make peut donc correspondre à plusieurs commmandes de
shell,
séparées par l'un des caractères ; | ou &.
Par exemple :
# fichier Makefile
# cible : clear; grep toto *.h | less [1] g++ exo.cxx [2] |
Un premier shell est lancé (ligne [1]), qui nettoie l'écran, recherche la chaîne toto dans tous les fichiers .h du répertoire courant et les redirige vers la commande less qui les affiche page par page. A la fin de ces opérations, la session de shell est terminée et make relance une nouvelle session shell qui effectue toutes les phases de compilation de exo.cxx (ligne [2]).
Il importe donc que toutes les commandes qui doivent être exécutées dans la même session shell soient sur la même ligne, sous peine de surprises, comme dans l'exemple ci-dessous dans lequel l'utilisateur voulait se placer sous le répertoire essai pour effectuer la compilation du fichier exo.cxx :
# fichier Makefile
# cible : cd /users/etud2/taralf/essai g++ exo.cxx |
La première ligne de commande est exécutée par un shell qui prend comme répertoire de travail le répertoire courant, change bien de répertoire, mais se termine aussitôt.
La seconde ligne est exécutée par un second shell qui prend aussi comme répertoire de travail le répertoire courant, puis effectue la compilation.
Il aurait fallu écrire :
# fichier Makefile
# cible : cd /users/etud2/taralf/essai; g++ exo.cxx |
ou
# fichier Makefile
# cible : cd /users/etud2/taralf/essai; \ g++ exo.cxx |
mais sans tabulation sur la dernière ligne !!!
Considérons l'exemple ci-dessous :
# fichier MakeNom
# nom = exo_01 [1] # $(nom) : $(nom).o g++ -o $(nom) $(nom).o # $(nom).o : $(nom).cxx g++ -c $(nom).cxx |
La ligne [1] est définit la macro d'identificateur nom par la suite de caractères exo_01. Le nombre d'espaces de part et d'autre du signe = est quelconque.
Le contenu de la macro nom est noté $(nom). Le fichier est donc interprété par make rigoureusement comme ci-dessus.
Une autre utilisation permet de prendre en compte
les dépendances induites par les fichiers inclus indirectement.
Considérons par exemple les cinq fichiers suivants :
// fichier exo.cxx
// #include "A.h" #include "D.h" ... |
// fichier A.h
// #include "B.h" ... |
// fichier B.h
// #include "C.h" ... |
// fichier C.h
// #include "D.h" ... |
// fichier A.h
// ... |
La compilation de exo.cxx doit être
effectuée non seulement chaque fois que le fichier source est lui-même
modifié, ce que détecte make, mais aussi lorsque
A.h
ou D.h sont eux-même modifiés, dépendances
que make ne peut déterminer seul. Il faut donc les indiquer
explicitement dans le fichier Makefile. Plus difficile, le fichier
A.h
peut lui-même être modifié indirectement par la modification
de B.h, de C.h ou de D.h. Il appartient donc
à l'utilisateur d'indiquer aussi ces dépendances indirectes
:
# fichier Makefile
# exo.o : exo.cxx A.h B.h C.h D.h g++ -c exo.cxx |
La maintenance d'un tel fichier est très rapidement impossible, et source d'erreurs, en particulier si plusieurs fichiers sources dépendent des mêmes fichiers inclus. Il faut donc dupliquer les dépendances, ce qui n'est jamais une bonne chose en informatique.
Il est beaucoup plus sain de construire une fois
pour toutes des macros de dépendances directes pour les fichiers
inclus, et de les utiliser ensuite, comme le montre l'exemple ci-dessous
:
# fichier Makefile
# D_H = D.h [1] C_H = C.h $(D_H) [2] B_H = B.h $(C_H) A_H = A.h $(B_H) # exo.o : exo.cxx $(A_H) $(D_H) [3] g++ -c exo.cxx |
L'utilisateur n'indique dans les dépendances d'une cible que les macros des fichiers inclus directement visibles (lignes [1], [2] ou [3] par exemple). Nous avons, par convention, donné à la macro le même identificateur que le fichier .h, en majuscule (conformément aux traditions du langage C et d'Unix), et remplacé le . (point) interdit par un _ (souligné). Cette méthode peut paraître lourde mais les macros doivent être construites au fur et à mesure que les fichiers inclus sont ajoutés.
# fichier Makefile
# nom = exo_01 # $(nom) : $*.o g++ -o $@ $*.o # $(nom).o : $*.cxx g++ -c $*.cxx |
duo>make -f MakeNom nom=exo_02 |
Cette possibilité est largement utilisée
pour positionner des macros lors de la compilation d'un programme. Considérons
par exemple le fichier essai.cxx suivant :
// fichier essai.cxx
#include "Config.h"
#ifdef __DEBUG__
// ... #ifdef VERSION_3
// ... #ifdef MSV5
class CPoint
}; // CPoint |
Le code compilé dépend de trois macros destinées au préprocesseur :
// fichier "Config.h"
#define __DEBUG__
|
Il est beaucoup plus intéressant de supprimer
ce fichier et de les positionner directement dans la ligne de commande
de compilation (voir Commande Unix g++ )
au moyen de l'option -D :
g++ essai.cxx -DMSV5 -D__DEBUG__ -DVERSION_4 -c |
ou, en passant par le fichier Makefile :
# fichier Makefile
# nom = essai version = GCC option = __NODEBUG__ compilo = VERSION_3 # ... $(nom).o : $(nom).cxx g++ $(nom).cxx -D$(version) -D$(compilo) -D -$(option) -c |
au moyen de la commande :
duo>make nom=tp_08 |
en utilisant les options par défaut, ou
duo>make version=MSV5 option=__DEBUG__ compilo=VERSION_4 |
# fichier Makefile
# include Depend.h # exo.o : exo.cxx $(A_H) $(D_H) g++ -c exo.cxx |
# fichier Depend.h
# D_H = D.h C_H = C.h $(D_H) B_H = B.h $(C_H) A_H = A.h $(B_H) |
Managing Projects with make, Ed. O'Reilly & Associates
© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique