2.13. Le "make"

Vous recevez les sources du logiciel "make". Vous copiez les fichiers dans un répertoire initialement vide, et vous imprimez le contenu de ce répertoire. On restera dans ce répertoire pendant tout l'exercice. Constatant qu'il comporte un fichier de nom Makefile, vous imprimez ce fichier. Ces impressions sont données ci-dessous.
A- Construire le graphe de dépendances entre les fichiers.
B- Vous ne disposez pas encore du programme "make". Que faut-il faire pour le créer?
C- A la suite de A, vous disposez maintenant de ce programme. Vous en demandez l'exécution. Expliquez ce qui se passe.
D- Vous modifiez le fichier "basepage.h", et lancez la commande "make install". Énoncez toutes les commandes qui sont exécutées.
E- Vous créez le répertoire "/usr/local/src/make", puis lancez la commande "make backup". Que se passe-t-il?
F- Vous lancez la commande "make erase". Que se passe-t-il? Disposez-vous encore du programme "make"?
Contenu du répertoire:
-rw------- 1 u 		 2071 Jun 15 10:01 basepage.h
-rw------- 1 u 		 6841 Jun 15 10:01 check.c
-rw------- 1 u 		 3448 Jun 15 10:01 getenv.c
-rw------- 1 u 		 5416 Jun 15 10:01 getopt.c
-rw------- 1 u 		 8068 Jun 15 10:01 h.h
-rw------- 1 u 		15843 Jun 15 10:01 input.c
-rw------- 1 u 		10084 Jun 15 10:01 macro.c
-rw------- 1 u 		25598 Jun 15 10:01 main.c
-rw------- 1 u 		14933 Jun 15 10:01 make.c
-rw------- 1 u 		 4238 Jun 15 10:01 make.man
-rw------- 1 u 		 1176 Jun 15 10:01 Makefile
-rw------- 1 u 		38345 Jun 15 10:01 makshell.c
-rw------- 1 u 		 5792 Jun 15 10:01 reader.c
-rw------- 1 u 		 3467 Jun 15 10:01 readme
-rw------- 1 u 		 8507 Jun 15 10:01 rules.c
-rw------- 1 u 		 1319 Jun 15 10:01 stat.h
-rw------- 1 u 		 6398 Jun 15 10:01 syserr.c
Contenu du fichier Makefile[2]:
# Makefile for make utility.
#
CFLAGS =
LDFLAGS =
DESTDIR = /usr/bin
WORKDIR = /usr/local/sys/travail
BACKDIR = /usr/local/src/make
PROG = make
OBJS = check.o input.o macro.o main.o make.o makshell.o\
		reader.o rules.o
LIBES = getenv.o getopt.o syserr.o
FILES = check.c input.c macro.c main.c make.c makshell.c\
		reader.c rules.c getenv.c getopt.c syserr.c\
		basepage.h h.h stat.h make.man makefile readme
$(PROG): $(OBJS) $(LIBES)
	ld $(LDFLAGS) -o $(PROG) $(OBJS) $(LIBES)

$(OBJS): h.h
getenv.o main.o: basepage.h
main.o makshell.o: stat.h
install: $(PROG)
	cp $(PROG) $(DESTDIR)/$(PROG)
	rm $(PROG)
clean:
	rm $(OBJS) $(LIBES) $(PROG)
erase: clean
	rm $(FILES)
backup:
	cp $(FILES) $(BACKDIR)
restore:
	cd $(BACKDIR)
	cp $(FILES) $(WORKDIR)
	cd $(WORKDIR)
help:
	@echo Makefile options:
	@echo help: Print this message
	@echo default: Create $(PROG)
	@echo install: Move $(PROG) to $(DESTDIR)
	@echo clean: Remove $(PROG) and all .o files
	@echo erase: Erase ALL files
	@echo backup: Copy source files to $(BACKDIR)
	@echo restore: Copy files to $(WORKDIR)
Solution de l'exercice 2.13.

2.13.1. Question A

Le graphe de dépendances se déduit du fichier Makefile de l'énoncé. Tous les fichiers potentiels doivent être mentionnés dans ce graphe, même s'ils ne sont jamais créés. Il en est ainsi, par exemple, du fichier install qui ne sera en fait jamais créé par le programme make. Le graphe est donc le suivant:

La règle de dépendance install: $(PROG) définit le premier arc du graphe. La règle $(PROG): $(OBJS) $(LIBES) définit la dépendance de make sur tous les fichiers “*.o”. La règle implicite sur les suffixes conjointement avec les fichiers du répertoire définit les arc entre les “*.o” sur les “*.c” correspondants. Enfin les arc vers les “*.h” se déduisent des règles:
$(OBJS): h.h
getenv.o main.o: basepage.h
main.o makshell.o: stat.h
La règle erase: clean définit le dernier arc.

2.13.2. Question B

Comme on ne dispose pas encore du programme make, il faut en simuler le comportement à la main. Les fichiers “*.o” n'existant pas, ils doivent être créés par une commande identique à la commande associée à la règle implicite des suffixes, et appelant le compilateur. Au lieu de lancer ces commandes une à une, on peut utiliser les structures de contrôle du langage de commandes, et donc exécuter:
for i in *.c; do cc -c $i; done
Lorsque ceci est terminé, on dispose de tous les fichiers “*.o” nécessaires, et il reste à exécuter la commande pour créer le programme make. Ici encore, on peut utiliser le mécanisme de substitution du langage de commandes, puisque les fichiers “*.o” du répertoire sont précisément ceux correspondant aux macros $(OBJS) et $(LIBES):
ld -o make *.o

2.13.3. Question C

Lorsqu'on demande l'exécution du programme make sans paramètre, il construit le graphe de dépendances ci-dessus, et fait les vérifications de date en partant du nœud du graphe défini par la première règle du fichier Makefile, donc à partir de make. Comme ce fichier vient d'être construit, il est plus récent que tous ceux dont il dépend, qui sont eux-mêmes plus récents que ceux dont ils dépendent. Les dates des fichiers étant compatibles avec le graphe de dépendances, il n'y a rien à faire.

2.13.4. Question D

Lorsqu'on lance la commande make install, le programme make va de nouveau construire le graphe ci-dessus, mais en faisant le contrôle à partir de install. Il constatera d'abord que le fichier install n'existe pas, et doit donc être construit. Par ailleurs, il constatera également que les fichiers main.o et getenv.o sont antérieurs au fichier basepage.h, puisque ce dernier vient d'être modifié. Il lancera donc les deux commandes:
cc -c main.c et cc -c getenv.c
Les deux fichiers main.o et getenv.o étant maintenant postérieurs au fichier make, puisqu'ils viennent d'être recréés, le programme lancera la commande de construction du fichier make, c'est-à-dire, après remplacement des macros:
ld -o make check.o input.o macro.o main.o make.o makshell.o\
reader.o rules.o getenv.o getopt.o syserr.o
Enfin, il lancera les commandes de création du fichier install, ainsi qu'elles sont définies dans le fichier Makefile, après remplacement des macros:
cp make /usr/bin/make
rm make
Constatons que la première recopie le fichier make dans le répertoire /usr/bin, et le supprime du répertoire courant. Notons que le fichier install n'est toujours pas créé, mais que le programme considère néanmoins, pour cette exécution, qu'il existe maintenant et qu'il est à jour.

2.13.5. Question E

Lors de la commande make backup, le programme make sera cette fois trouvé par les règles de recherche dans le répertoire /usr/bin, pourvu que ces règles de recherche précisent bien ce répertoire. Du fait de la question précédente, il s'agit bien de celui que l'on vient de construire. Il constatera que le fichier backup n'existe pas et doit donc être créé par la commande associée définie dans le fichier Makefile, et qui est, après remplacement des macros:
cp check.c input.c macro.c main.c make.c makshell.c\
	reader.c rules.c getenv.c getopt.c syserr.c basepage.h\
	h.h stat.h make.man makefile readme /usr/local/src/make
Cette commande recopie la totalité des fichiers fournis initialement, éventuellement modifiés (pour basepage.h) dans le répertoire /usr/local/src/make qui vient d'être créé. Notons que les fichiers “*.o” ne sont pas recopiés dans ce répertoire.

2.13.6. Question F

Lors de l'exécution de la commande make erase, le programme constate qu'il doit créer d'abord le programme clean, puisqu'il n'existe pas. Il exécute donc la commande:
rm check.o input.o macro.o main.o make.o makshell.o\
	reader.o rules.o getenv.o getopt.o syserr.o make
Le fichier clean n'est toujours pas créé, mais il considère qu'il existe et est à jour. Il passe donc à la construction du fichier erase, en lancant la commande:
rm check.c input.c macro.c main.c make.c makshell.c\
	reader.c rules.c getenv.c getopt.c syserr.c basepage.h\
	h.h stat.h make.man makefile readme
Le fichier erase n'existe toujours pas, mais il considère le travail terminé.
Constatons que la première commande supprime tous les fichiers que l'on a créé au cours de l'exercice. Le fichier make n'existait déjà plus dans le répertoire courant depuis la question D. La deuxième commande supprime tous les fichiers qui étaient initialement dans le répertoire, qui est donc vide maintenant. Nous disposons toujours du programme make, puisqu'il est dans /usr/bin depuis la question D!

[2] Le caractère @ évite l'impression de la commande lors de son exécution.