Compilation d'un programme à plusieurs fichiers avec make

  • Informations supplémentaires à fournir à make (par rapport au cas d'un seul fichier) : la liste des fichiers à compiler ; les contraintes d'ordre de compilation.
  • Ces informations sont codées sous forme de "dépendances".

Notion de dépendance dans make

Une dépendance dans make :

cible: prérequis1 prérequis2 ...

Ce qui signifie :

  • Les prérequis doivent exister pour pouvoir créer ou mettre à jour la cible.
  • La cible, si elle existe, n'est pas à jour si un prérequis a été modifié plus récemment que la cible.
  • Pour créer ou mettre la cible à jour, il faut d'abord créer ou mettre à jour les prérequis, et, récursivement, les prérequis des prérequis.

Dépendance pour l'édition de liens

Écrire que l'exécutable dépend de tous les objets. Exemple :

my_program: foo.o bar.o my_program.o
  • Y compris l'objet correspondant au programme principal.
  • Tous les objets, même si le programme principal n'utilise pas directement tous les modules.
  • Utiliser comme cible le nom du fichier contenant le programme principal, sans suffixe.

  • Comme l'un des prérequis est my_program.o, c'est-à-dire le nom de la cible avec un suffixe .o, make comprend qu'il doit faire une édition de liens pour créer la cible à partir de tous les prérequis.

  • Nota bene : nous avons ainsi implicitement donné la liste des fichiers à compiler, ce sont les fichiers obtenus en remplaçant .o par .f.

La commande d'édition de liens

make connaît la commande d'édition de liens, mais il utilise par défaut le compilateur C. Il faut lui dire d'utiliser le compilateur Fortran pour l'édition de liens :

LINK.o = $(FC) $(LDFLAGS) $(TARGET_ARCH)

Notion de règle dans make

Une règle est l'association d'une dépendance et d'une recette (une commande) :

cible : prérequis
   recette

La recette est censée permettre de créer (ou mettre à jour) la cible. Attention : la recette doit être précédée d'une tabulation et non d'espaces.

Règles implicites

make connaît déjà certaines règles utilisant les suffixes des fichiers : règles implicites, ou encore pattern rules.

Les régles implicites utilisent les variables automatiques :

  • $^ : tous les pré-requis
  • $< : premier pré-requis
  • $@ : cible

Manipulation 5 : la base de données de make

man make

Afficher la base de données de make :

make -p -f /dev/null >make_db.txt

Regarder en particulier :

  • la définition de LINK.o
  • la commande d'édition de liens, dans la règle %: %.o
  • la commande de compilation, dans la règle %.o: %.f

Dépendances entre les objets

Indiquer les dépendances induites par l'utilisation de modules Fortran en écrivant des dépendances entre les objets. Exemple : 3 files

bar.o: foo.o plouf.o

indique bien que bar.f doit être compilé (ou re-compilé) après foo.f et plouf.f.

Lignes include en Fortran

Si un source Fortran contient une ligne include alors il faut tenir compte de cette dépendance dans le makefile : on ajoute le fichier inclus dans les prérequis de l'objet. Exemple :

include

foo.o: bar.h

Forme compilée d'une bibliothèque statique

La bibliothèque "statique" compilée se présente sous la forme de :

  • Un fichier "archive", dont le nom commence par "lib" et dont le suffixe est .a. Exemple : libjumble.a. Plus pratique que l'ensemble de fichiers objets (.o), contient cet ensemble.
  • Un ou plusieurs fichiers .mod. Exemple : jumble.mod.

Compilation d'un programme utilisant une bibliothèque

Le compilateur a besoin des fichiers .mod de la bibliothèque au moment de la compilation des objets. Trouver le répertoire contenant les .mod de la bibliothèque. Puis, dans le GNUmakefile, compléter FFLAGS avec l'option supplémentaire -I suivie du répertoire trouvé. Par exemple, si les .mod de la bibliothèque sont dans le répertoire ~/.local/include, ajouter à FFLAGS l'option -I${HOME}/.local/include.

Le compilateur a besoin de l'archive lib.a au moment de l'édition de liens. Trouver le répertoire contenant cette archive. Puis définir la variable LDLIBS. make utilise la variable LDLIBS à l'édition de liens. Cf. la règle : %: %.o dans la base de données de make. Par exemple, si libjumble.a est dans le répertoire ~/.local/lib ajouter dans le GNUmakefile la ligne :

LDLIBS = -L${HOME}/.local/lib -ljumble

Utilisation de plusieurs bibliothèques

  • Les archives et les .mod des bibliothèques peuvent se trouver dans plusieurs répertoires. On ajoute autant d'options -L et -I que nécessaire.
  • L'ordre des options -L et -I n'a pas d'importance.
  • Une option -l pour chaque bibliothèque.

  • Si une bibliothèque fait appel à une autre bibliothèque alors l'ordre des options -l est important : les bibliothèques doivent apparaître dans l'ordre "appelant appelé". Exemple : si la bibliothèque plouf1 appelle plouf2 alors ‑lplouf1 doit être avant -lplouf2 dans LDLIBS.

Manipulation 6

Écrire un makefile pour le programme Coriolis sachant que :

  • le programme utilise les bibliothèques Numer_Rec_95 et Jumble ;
  • les bibliothèques (déjà compilées) sont dans ~/.local ;
  • Numer_Rec_95 utilise Jumble ;
  • L'unité de programme principale est dans le fichier coriolis.f.

Compiler. Puis, pour voir l'effet qu'aurait une modification du fichier coriolis.f, taper :

touch coriolis.f

et recompiler.

Nota bene :

  • Rappel : make traite par défaut la première cible rencontrée. Donc il est judicieux de mettre la dépendance de l'exécutable avant les dépendances entre les objets.
  • La simple commande make produit une série de commandes : compilations des objets, édition de liens.
  • make a déduit un ordre de compilation à partir des dépendances entre objets.
  • make ne recompile que le nécessaire.

Génération automatique des dépendances

make ne regarde pas l'intérieur des fichiers, ne connaît pas les langages de programmation, ne peut donc pas générer tout seul les dépendances entre objets induites par l'utilisation de modules Fortran et les dépendances induites par les include Fortran. Nécessité de lui adjoindre un autre outil.

Utiliser makedepf90. Dépôt des sources de makedepf90. Installer via le gestionnaire de paquet de votre distribution Linux. Sur Ubuntu :

apt install makedepf90

Manipulation 7

Dans un terminal, dans le dossier du programme Coriolis, sur la ligne de commande :

makedepf90 -free -Wmissing -Wconfused -nosrc -u numer_rec_95 -u jumble *.f

Nota bene : l'option -u indique qu'un module doit être ignoré. Elle est utilisée pour un module de bibliothèque, qui ne fait donc pas partie des sources.

Notion de cible fictive dans make

Une cible fictive (phony target) ne désigne pas un fichier. Si make essaie de fabriquer cette cible, il la considère donc toujours non à jour, et exécute la recette correspondante. make ne perd pas de temps à chercher une règle implicite qui permettrait de créer cette cible. Syntaxe :

.PHONY: nom de la cible

Nous utiliserons des cibles fictives pour la combinaison de make et makedepf90 et pour la règle clean.

Combinaison de make et makedepf90

Idée : nous redirigeons la sortie standard de makedepf90 vers un fichier, que nous appelons depend.mk, et nous faisons référence à ce fichier dans le makefile :

include depend.mk

Il est inutile de re-créer la liste des dépendances lorsqu'on modifie le moindre caractère dans un fichier source. Nous voulons donc que depend.mk ne soit créé que lorsqu'il n'existe pas ou sur demande explicite de l'utilisateur (dans le cas où une nouvelle instruction use ou une ligne include est insérée dans le programme).

Si nous mettons les fichiers sources en prérequis de la liste des dépendances :

sources = liste des fichiers .f
depend.mk: ${sources}
   makedepf90 ${sources} >depend.mk

alors le fichier depend.mk est recréé automatiquement à chaque modification d'un fichier source. Ce n'est pas ce que nous voulons. Il faut donc une règle sans prérequis :

depend.mk:
   makedepf90 ${sources} >depend.mk

Permet bien la création automatique de depend.mk s'il n'existe pas. Mais pas la mise à jour. Si depend.mk existe, la commande :

make depend.mk

donne :

make: « depend.mk » est à jour.

Avec seulement une cible fictive :

.PHONY: depend
depend:
   makedepf90 ${sources} >depend.mk

depend.mk n'est pas créé automatiquement lorsqu'il n'existe pas. Le makefile ne marche pas. Il affiche simplement le message d'erreur :

GNUmakefile:ligne: depend.mk: No such file or directory
make: *** Pas de règle pour fabriquer la cible « depend.mk ».
Arrêt.

Il faut donc les deux cibles, réelle et fictive :

.PHONY: depend
depend depend.mk:
makedepf90 [options] ${sources} >depend.mk

Rappel : s'assurer que l'exécutable reste la première cible du makefile.

Création de la liste des objets à partir de la liste des sources

Nous avons besoin de la liste des objets comme prérequis de l'exécutable et de la liste des sources comme prérequis de depend. Ce sont les mêmes listes au suffixe près. Ne pas les dupliquer. Utiliser une fonctionnalité de make qui modifie la valeur d'une variable :

sources = liste des fichiers .f
objects := $(sources:.f=.o)

Nota bene : même s'il existe des fichiers inclus par une ligne include Fortran, ils n'ont pas à apparaître dans la variable sources.

Manipulation 8

Générer automatiquement les dépendances dans le makefile du programme Coriolis. Tester la création automatique et la mise à jour.

Règle clean

Convention : la cible clean existe dans la plupart des makefiles et permet d'effacer les produits du makefile. clean ne devrait effacer que ce que le makefile est capable de regénérer. Il est donc conseillé de ne pas utiliser de wildcard dans la recette.

.PHONY: clean
    clean: rm -f my_executable ${objects}

Nota bene : make n'a pas la liste des .mod, il n'y a donc pas de moyen simple de les supprimer dans clean. Mais ce n'est pas grave : si on veut s'assurer de tout recompiler (si par exemple on a changé une option de compilation), la suppression des fichiers objets et de l'exécutable répond bien à ce besoin.

Rappel : s'assurer que l'exécutable (et non la règle clean) reste la première cible du makefile.

Manipulation 9

Ajouter une règle clean au makefile du programme Coriolis. Tester.