TP n°5 : Outils de développement

Programmes modulaires : make

Introduction

Un gros programme est typiquement découpé en modules, chacun ayant un fichier d'en-têtes .h et un fichier de code .c. Si un fichier de code est modifié, il faut le recompiler lorsqu'on recompile le programme. Si c'est un fichier d'en-têtes qui est modifié, il faut recompiler tous les fichiers qui y font appel. Pour faire automatiquement la gestion des dépendances, on utilise make, qui lit dans le fichier appelé Makefile les dépendances et les instructions de compilation.

Il y a plusieurs variantes du programme make, ayant des syntaxes légèrement différentes. Il y a une norme POSIX, mais la syntaxe de GNU make devient de plus en plus dominante, car il est possible d'installer cette version sur n'importe quel Unix, et qu'elle apporte des fonctionnalités supplémentaires. Néanmoins, pour écrire un Makefile portable, il convient de faire attention à ne pas utiliser de GNU-ismes.

make fonctionne à l'aide de règles, qui lui indiquent quels ordres effectuer. Les règles sont décrites dans le Makefile. Il y a aussi des règles par défaut, qui sont typiquement visibles dans /usr/share/mk/*.mk sous MacOS X, etc.

Les règles peuvent être explicites, comme

toto: toto.c toto.h
	gcc -O -g -W -Wall -o toto toto.c
qui indique d'exécuter la commande gcc ... si l'un des fichiers toto.c ou toto.h est plus récent que toto. Les règles peuvent aussi être implicites, et en particulier se baser sur les suffixes des noms de fichiers, comme par exemple la règle par défaut ci-dessous
.c:
        ${CC} ${CFLAGS} ${LDFLAGS} -o $@ $<
qui signifie que si un fichier xxx.c est plus récent que le fichier xxx, alors il faut exécuter la commande.

Dans une commande, la syntaxe ${VAR} signifie qu'on utilise le contenu de la variable VAR, tandis que la syntaxe $@ représente le nom de la cible (le vieux fichier), $< le nom des sources (la liste des fichiers dont l'un est plus récent) et $* le préfixe de $@ (ce qui précède le dernier point).

make appelé sans arguments exécute la première règle du fichier Makefile. Si des dépendances de cette règle doivent être exécutées, elle le sont auparavant. Si make est appelé avec des arguments, ce sont les règles correspondantes qui sont exécutées, et les règles dont elles dépendent.

Exercices

Que fait make s'il n'y a pas de Makefile ? Et make toto ? Et make toto CC=lcc ?

Comprendre que la résolution des dépendances par make est un tri topologique. Fabriquer un Makefile qui fait échouer ceci.

Décrire un générateur de Makefile qui regarde les fichiers .c pour détecter les .h dont ils dépendent. Regarder ce que font cpp -M, mkdep, makedepend et ccmakedep.

Portabilité : configure

Introduction

Le projet GNU, dont l'un des premiers travaux a été de réécrire les commandes Unix, a vite eu besoin d'un outil permettant de détecter automatiquement la configuration du système afin de pouvoir automatiquement compiler ses programmes sur n'importe quelle machine.

L'utilitaire autoconf est utilisé par le développeur du logiciel pour, à partir d'un fichier configure.in qu'il a écrit manuellement, fabriquer un programme appelé configure. L'administrateur système voulant installer le logiciel de chez GNU doit alors simplement taper les instructions suivantes :

./configure
make
make install
L'utilisateur voulant installer le logiciel dans son homedir doit taper les instructions suivantes :
./configure --prefix=$HOME
make
make install

Exercices

Installer le programme XXX en regardant ce qui se passe.

Gestion personnelle des versions : RCS

Introduction

Lors du développement d'un logiciel, certaines modifications sont mauvaises, et il n'est pas rare de vouloir revenir à une version précédente. Une solution est de créer un nouveau répertoire à chaque modification et d'y recopier les fichiers source. Pour économiser de la place sur le disque, on préfère utiliser un système qui ne garde en mémoire que les vérifications.

Historiquement, c'est SCCS qui est d'abord apparu, stockant dans un fichier d'archive la version initiale et toutes les modifications qui y ont été apportées. RCS quant à lui stocke la version finale et les modifications en ordre chronologique inverse, ce qui est mieux.

RCS permet aussi de verrouiller l'accès à un fichier pour que plusieurs développeurs ne le modifient pas en même temps. Sa gestion des versions permet un développement sous forme d'arbre, où plusieurs développeurs testent en parallèle des versions distinctes.

Exercices

Utiliser ci et co pour garder trace des modifications. Regarder le contenu du fichier ,v.

Développement en groupe à distance : CVS

Pour de gros projets où les développeurs n'ont pas de compte sur une machine commune, une architecture de type client/serveur est nécessaire. CVS permet de se connecter à un serveur qui centralise les différentes versions des fichiers. En interne, CVS utilise RCS.

Un autre avantage de CVS est que seul l'administrateur du serveur a le pouvoir de détruire de l'information, car les autres utilisateurs sont obligés de passer par les commandes CVS.

Comparaison et modification de fichiers : diff et patch

Introduction

Si on n'utilise ni CVS ni RCS, et qu'un fichier a été modifié, une façon de savoir quelles sont les modifications est de comparer les deux versions avec diff -u.

La commande patch permet de transformer le fichier d'origine en sa nouvelle version à l'aide du résultat de diff. Cela a deux applications :

Exercices

Écrire un programme dans un fichier X. Modifier ce programme, ce qui donne le fichier X1. Modifier X à un autre endroit, ce qui donne le fichier X2. Utiliser diff et patch pour faire un fichier Y qui inclut toutes ces modifications.

Que se passe-t-il si X1 et X2 ont modifié X au même endroit ?


NB: les derniers éléments, concernant la gestion des versions et l'écriture à plusieurs d'un programme s'appliquent aussi à d'autres contextes, comme l'écriture d'un livre avec LaTeX.