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.cqui 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.
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
.
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 installL'utilisateur voulant installer le logiciel dans son homedir doit taper les instructions suivantes :
./configure --prefix=$HOME make make install
Installer le programme XXX en regardant ce qui se passe.
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.
Utiliser ci
et co
pour garder
trace des modifications. Regarder le contenu du fichier ,v
.
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.
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 :
patch
réunifier les modifications
(dans la plupart des cas).
É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.