make.
Mais dans un projet un peu plus important, le fichier Makefile
contient des paramètres à
modifier "avec votre éditeur de texte favori", ainsi qu'on
le lit couramment dans les fichiers
README.
./configure;
make; make install.
autoconf, automake
et les
outils associés.
gcc helloworld.c
-o helloworld. Pour compiler ce programme simple, on n'a
pas besoin de se compliquer la
vie. Exemple suivant: helloworld, mais sur deux fichiers source.
main.c
#include <stdio.h>
#include "helloworld.h"
int main(int argc, char *argv[]) {
hello();
exit(0);
}
helloworld.h
void hello();
helloworld.c
#include <stdio.h>
void hello() {
printf("Bonjour le monde\n");
}
Pour le compiler, voici trois solutions:
gcc helloworld.c main.c -o helloworld
gcc -c helloworld.c
gcc -c main.c
gcc main.o helloworld.o -o helloworld
make
Eh bien, nous n'avons plus qu'à écrire
ce fichier Makefile. Pour ceux qui m'auraient pris au
sérieux, un bon bouquin sur Makefile fera l'affaire. Mais
j'ai mieux à vous proposer. Faisons
générer notre Makefile à un programme.
Ce programme, il s'appelle configure.
Ça y est, on s'y
retrouve ?
./configure, cela crée un fichier Makefile.
make, cela compile le
programme en utilisant le Makefile
généré ainsi. Et make
install,
c'est
comme d'habitude. Et ce configure, d'où vient-il ? Il est
généré grâce à
autoconf. Et automake
génère d'autres fichiers nécessaires
au bon
fonctionnement de configure.
Avant de commencer, il semble que la plupart des projets contiennent
leurs fichiers sources dans
un sous-répertoire src. Nous allons
faire de même en créant ce répertoire
(mkdir src), puis en y
déplaçant nos trois fichiers sources.
J'appellerai racine du
projet le répertoire qui contient src.
Maintenant, il faut un fichier de configuration qui s'appelle configure.in.
La
méthode la plus simple est d'utiliser l'outil autoscan,
puis au fur et à mesure que le projet
s'améliorera, on éditera le fichier configure.in
à la main pour y ajouter les lignes
supplémentaires.
Le programme autoscan
génère un seul fichier configure.scan.
On le lance
depuis la racine du projet. Puis il faut le renommer en configure.in,
et l'éditer. Voici mon
configure.in après édition:
configure.in
dnl Process this file with autoconf to produce a configure script.
AC_INIT(src/helloworld.c)
AM_CONFIG_HEADER(config.h)
AM_INIT_AUTOMAKE(helloworld, 0.0.1)
dnl Checks for programs.
AC_PROG_CC
AC_PROG_INSTALL
dnl Checks for libraries.
dnl Checks for header files.
AC_HEADER_STDC
dnl Checks for typedefs, structures, and compiler characteristics.
dnl Checks for library functions.
AC_OUTPUT([Makefile src/Makefile])
dnl, cela signifie commentaire. On est habitués à lire #, // ou /* */. Ici, c'est dnl. C'est comme ça et on ne discute pas.
Que manque-t-il maintenant ? On n'a pas dit quels fichiers compiler.
Cela se fait dans le fichier
src/Makefile.am. Sa structure dans notre cas simple
est la suivante:
src/Makefile.am
bin_PROGRAMS = helloworld
helloworld_SOURCES = main.c helloworld.c helloworld.h
La première ligne indique dans bin_PROGRAMS
le nom des binaires à générer. Dans
notre
cas, on ne générera que le binaire helloworld.
La seconde ligne indique les fichiers sources qu'on compilera pour
générer le binaire helloworld.
Notons au passage que le nom helloworld_SOURCES
n'est pas choisi au hasard. C'est
bien [nom du binaire]_SOURCES.
Si l'on voulait créer deux binaires, on aurait le
src/Makefile.am suivant:
bin_PROGRAMS = bin1 bin2
bin1_SOURCES = source1.c
bin2_SOURCES = source2.c source2.h
Mais revenons à nos moutons, il ne reste plus qu'un fichier
à créer. C'est le fichier
Makefile.am à la racine du projet. Lui
contiendra la liste des sous-répertoires dans
lesquels on trouve les autres Makefile.am. On y
trouve simplement:
Makefile.am
SUBDIRS=src
Notons aussi que la syntaxe des Makefile.am est la même pour tous les répertoires. Seulement, à la racine du projet, il n'y a pas de fichiers sources. Et dans le répertoire src, il n'y a pas de sous-répertoires. Sinon, on aurait bien sur mélangé les deux types de contenus de Makefile.am de ci-dessus.
Ça y est, on peut utiliser autoconf
et automake. Commençons par
exécuter
aclocal, sans arguments. On
génère ainsi un fichier aclocal.m4
qui contient
des macros nécessaires pour la suite. Puis autoconf,
sans arguments non plus génère
le tant attendu script configure. Ensuite, on peut
utiliser autoheader pour
créer le fichier de configuration config.h.in
nécessaire pour config.h. Et pour
terminer, automake -a -c.
Et là, ça se complique car il y a des messages d'erreurs et des warnings. Pour les messages d'erreur, sur ma configuration, ce n'est pas grave. En effet, on m'indique que certains fichiers n'ont pas pu être copiés, mais ils l'ont tout de même été. Pour les warnings, ils indiquent que des fichiers manquent. Chez moi, ce sont NEWS, README, AUTHORS et ChangeLog.
Il faut les créer, et un simple touch NEWS
suffit. Il en est de même pour les trois
autres fichiers. Cependant, il est plus sympathique de mettre un nom et
un e-mail dans
AUTHORS, et de mettre une première ligne
dans ChangeLog afin de signaler quand le
projet est parti. En ce qui concerne NEWS, j'ai
l'habitude d'y mettre un résumé du
ChangeLog. Ainsi, les développeurs iront
voir ChangeLog, et les utilisateurs se
contenteront de NEWS qui leur est plus parlant.
Quant au README, chacun y
met ce qu'il veut. J'utilise aussi un fichier TODO
qui contient une sorte de mémento de ce
qu'il me reste à faire, qui indique aux autres
développeurs là où ils peuvent
participer; et aux
utilisateurs, il signale que la fonctionnalité qui leur
manque tellement, elle est prévue.
A ce stade, tous les fichiers sont créés.
Relançons automake -a pour tenir
compte des
fichiers qu'on a ajoutés. Et là, il n'y a plus de
messages d'erreur ou de warning. Et c'est fini!
Pour la peine, exécutons ./configure.
Miracle, ça fait comme les autres programmes.
Puis make. Et là, on voit les
lignes de compilation, qui sont un peu plus complexes
que celles qu'on aurait écrites dans un Makefile
fait "à la main". Pas de surprise si l'on
fait make install: on n'a pas les droits
d'écriture dans /usr/local. Il faut être
root pour cela. Le résultat serait d'installer le binaire helloworld
dans
/usr/local/bin.
autoconf, automake
et ./configure ne se limitent pas
à
générer des fichiers au début puis
basta. A chaque modification du fichier configure.in,
il
faut exécuter autoconf. Et
à chaque modification d'un fichier Makefile.am,
il
faut exécuter automake -a pour
prendre en compte les changements.
Les changements sont de plusieurs natures. La plus évidente
est le changement de version. Il
suffit d'éditer le début de configure.in,
puis de relancer autoconf.
Parallèlement, il devient indésirable de coder le
numéro de version du programme dans les sources.
Il vaut mieux utiliser la constante VERSION
qui contient toujours le numéro de
version indiqué dans configure.in. De
même pour la constante PACKAGE qui
contient le nom du projet.
Un autre changement pourrait être un ajout d'une librairie. Dans ce cas, il faut ajouter un test dans configure.in, et signaler la librairie dans les Makefile.am concernés. Nous allons traiter ce cas dans la partie III.
Qu'apportent autoconf/automake
par rapport à un simple Makefile ?
Ces outils apportent bien sur les tests de l'environnement de
compilation. Mais on peut aussi
utiliser make avec des cibles autres que la
cible par défaut (make), la
cible install (make install) ou même
la cible clean (make clean).
Pour nettoyer, il y a donc make clean qui se
contente de supprimer les objets et
binaires créés lors de la
précédente compilation. Mais il y a aussi make
distclean qui
supprime tous les fichiers ne faisant pas partie du package. Ainsi, les
Makefile seront
supprimés puisque c'est le rôle de configure
de créer ces fichiers. Les fichiers de
cache seront aussi supprimés. C'est avec make
distclean que l'on supprime tous les
fichiers non essentiels au projet. Attention, avant de lancer le
premier make
distclean, faites une copie de sauvegarde de votre projet.
En effet, si un fichier
essentiel au projet n'est pas listé dans configure.in
ou dans un Makefile.am,
ou si
une erreur s'est glissée quelque part dans ces fichiers, le
fichier qui était essentiel risque
d'être supprimé. Je le sais, ça m'est
déjà arrivé. Il est toujours bon
d'avoir une
sauvegarde...
Pour faire le tarball contenant le projet, l'habitude était
de faire un tar cvzf
projet-version.tar.gz,
précédé d'un make clean
ou maintenant make
distclean. Il y a plus simple: make dist
créera ce tarball
projet-version.tar.gz, dans notre cas helloworld-0.0.1.tar.gz.
Et ce tarball
contiendra tout ce qu'il faut et rien de plus.
Pour ne pas réinventer le monde, on utilise toujours le travail des autres, en l'occurrence via les librairies que les autres laissent à notre disposition. Nous allons en utiliser une pour voir comment adapter le projet à l'utilisation de cette librairie. J'ai choisi d'utiliser zlib car j'ai déjà écrit un article à ce sujet (LMAG 22), mais je n'avais pas mis d'exemple car cela aurais été peu illustratif et éventuellement rébarbatif. Alors maintenant, c'est l'occasion: notre programme helloworld va lire ce qu'il doit afficher, dans un fichier compressé. Un clone simple de zcat.
main.c
#include <stdio.h>
#include "helloworld.h"
int main(int argc, char *argv[]) {
hello(argc, argv);
exit(0);
}
helloworld.h
void hello(int argc, char *argv[]);
helloworld.c
#include <stdio.h>
#include <zlib.h>
void hello(int argc, char *argv[]) {
char buffer[1024];
gzFile *FH;
FH = gzopen(argv[1], "rb");
while(gzgets(FH, buffer, 1024) != Z_NULL) {
printf("%s",buffer);
}
gzclose(FH);
}
Remarque importante: n'utilisez cet exemple qu'à titre d'illustration de l'article. Il est truffé de bugs. Ainsi, on ne vérifie pas la présence du fichier fourni en argument. L'argument lui-même n'est pas testé....
Le changement majeur dans le projet est l'utilisation de la zlib, ce qui se voit dans helloworld.c
Répercutons ce changement avec automake
et autoconf. Commençons par
éditer configure.in.
La première chose que l'on peut faire est de changer le
numéro de version, et mettre 0.0.2 à la
place de 0.0.1.
Puis il faut ajouter une ligne pour tester la présence de la
librairie zlib:
dnl Checks for libraries.
AC_CHECK_LIB(z, gzopen)
Explication de texte: la macro AC_CHECK_LIB
vérifie la présence de la librairie
donnée en premier argument. z
signifie libz. Si on avait voulu tester libtruc, on
aurait mis truc. Et pour tester la présence de cette
librairie, AC_CHECK_LIB a besoin
d'un nom d'une fonction contenue dans cette librairie. J'ai choisi gzopen.
J'airai pu
prendre gzclose ou même uncompress.
Et c'est tout! C'est tout car on utilise une librairie simple à utiliser. Cela n'est pas le cas pour gnome ou glib qui sont plus complexes à mettre en oeuvre. Mais on y viendra plus tard.
Voyons si vous avez suivi: que dois-je faire maintenant ? Je dois
lancer autoconf
pour que la modification de configure.in soit prise
en compte. Puis ./configure;
make comme d'habitude.
Ajoutons maintenant la librairie pthread. Aucune difficulté supplémentaire: je vous le laisse en exercice. Pierre Ficheux a d'ailleurs écrit un article sur cette librairie dans LinuxMag il y a déjà un certain temps. A vos archives!
Autoconf et automake
avec gnome, ce sujet a déjà
été abordé dans le
numéro 10 de votre revue favorite. Je vais reprendre le
sujet et le traiter plus en détail ici.
D'autre part, je ne mettrai pas de listing d'exemple ici car un
programme gnome est bien plus long
qu'un helloworld, et cet article ne traite pas de la programmation
gnome. Limitons-nous au cas
général (et gardons gimp-1.4.0 pour quand
gimp-1.2.0 sera sorti :-).
Les fichiers et répertoires à créer sont les suivants:
Un programme gnome parle plusieurs langues. Il faut donc
créer un répertoire po/ et un
répertoire
intl/. C'est le programme gettextize -c
qui va s'en occuper. Puis il faut mettre dans le fichier
po/POTFILES.in tous les fichiers susceptibles de
contenir des chaînes de caractères à
traduire. Si on reprend
l'exemple helloworld, POTFILES.in contiendrait:
POTFILES.in
src/main.c
src/helloworld.c
Dans configure.in, il faut ajouter les lignes
suivantes, avant la dernière ligne:
ALL_LINGUAS=""
AM_GNU_GETTEXT
Dans la dernière ligne, on ajoute les fichiers suivants aux
fichiers existants du
AC_OUTPUT: intl/Makefile
po/Makefile.in.
Voici ce que cela peut donner. J'inclus aussi les modifications
liées à GNOME et j'ai laissé la ligne
concernant
zlib:
configure.in
dnl Process this file with autoconf to produce a configure script.
AC_INIT(src/main.c)
AM_INIT_AUTOMAKE(gnomeprog, 0.1.0)
AM_ACLOCAL_INCLUDE(macros)
GNOME_INIT
dnl Checks for programs.
AC_PROG_CC
AC_PROG_INSTALL
AC_PROG_MAKE_SET
AM_PROG_LIBTOOL
AC_ISC_POSIX
dnl Checks for libraries.
AC_CHECK_LIB(z, gzopen)
dnl Checks for header files.
AC_HEADER_STDC
dnl Checks for typedefs, structures, and compiler characteristics.
AC_C_CONST
dnl Checks for library functions.
GNOME_COMPILE_WARNINGS
GNOME_X_CHECKS
CFLAGS="$CFLAGS -Wall"
ALL_LINGUAS=""
AM_GNU_GETTEXT
localedir=${datadir}/locale
AC_SUBST(localedir)
AC_SUBST(CFLAGS)
AC_SUBST(CPPFLAGS)
AC_SUBST(LDFLAGS)
AC_OUTPUT([intl/Makefile macros/Makefile po/Makefile.in src/Makefile Makefile])
J'ai aussi ajouté l'option -Wall
au compilateur, afin qu'il signale tout ce qui n'est
pas propre. C'est aussi pour montrer comment ajouter des options au
compilateur.
Le Makefile.am de la racine ne bouge pas. Par
contre, le Makefile.am du répertoire
src doit être largement
modifié. En voici un modèle:
src/Makefile.am
DEFS=@DEFS@ -DLOCALEDIR=\"${localedir}\"
INCLUDES = -I$(top_srcdir) -I$(includedir) \
-DG_LOG_DOMAIN=\"gnomeprog\" \
-DGNOMELOCALEDIR=\""$(datadir)/locale"\" \
`gnome-config --cflags gnome` \
-I../intl -I$(top_srcdir)/intl \
-DPREFIX=\"${prefix}\" -DDATA_DIR=\"${datadir}\"
bin_PROGRAMS = gnomeprog
gnomeprog_SOURCES = main.c interface.c interface.h
gnomeprog_LDADD = @INTLLIBS@ @GTK_LIBS@ @GNOMEUI_LIBS@
gnomeprog_LDFLAGS = $(LDFLAGS) @GNOME_LIBDIR@ @GNOME_INCLUDEDIR@
Notez la ligne INCLUDES qui contient
plusieurs variables intéressantes comme
PREFIX.
Un autre fichier à créer est acconfig.h.
D'après le manuel de gettext
(info gettext), on peut le
récupérer dans les fichiers du package gettext.
Mais en
voici un exemplaire:
acconfig.h
#undef ENABLE_NLS
#undef HAVE_CATGETS
#undef HAVE_GETTEXT
#undef HAVE_LC_MESSAGES
#undef HAVE_STPCPY
#undef HAVE_LIBSM
#undef HAVE_POPT
#undef TARGET
Ce fichier contient des initialisations de variables qui iront dans le fichier config.h.
Et pour terminer, il manque le répertoire macros/.
Ceci est l'aspect le moins documenté de
la programmation extra gnome. Il semble qu'il faille créer
ce répertoire, puis y copier les macros
venant soit d'un répertoire contenant de la documentation
sur gnome, soit d'un programme existant.
Le premier cas n'étant pas toujours valable, rabattons-nous
sur gnome-hello que l'on peut trouver
ici:
ftp://ftp.gnome.org/pub/GNOME/stable/sources/GnomeHello/GnomeHello-0.1.tar.gz
Cette solution a aussi l'avantage de montrer un exemple simple de programme qui marche aux débutants.
autoconf
automake
./configure
make
Et cela passe comme une lettre à la poste. Que reste-t-il à faire ? Il reste à traduire la version française et à savoir que faire pour ajouter une nouvelle langue, ce qui ne manquera pas d'arriver au fur et à mesure que votre programme acquérira ses lettres de noblesses.
Au stade ou nous en sommes, le programme est facilement
internationalisable. Je supposerai que
vous savez comment faire un programme qui supporte
l'internationalisation avec gnome. J'entends
par cela les initialisations et le fait de mettre _("string")
plutôt que
"string". Sinon, retour à LinuxMag
10. Je n'aborderai que l'aspect qui concerne
autoconf/automake
ici.
Pour ajouter une nouvelle langue, il faut d'abord un fichier xx.po. Vous allez créer le fr.po, et d'autres gens sur internet vous enverrons leurs fichiers dans leur langue. Prenons le cas fr.po.
Première étape: copier le
fichier po/monprog.pot en fr.po.
Puis éditer de
fichier. Le début est simple à comprendre: il
faut compléter les trous. La suite est toujours
faite ainsi:
msgid "a string"
msgstr ""
I faut mettre la traduction du texte msgid
dans msgstr: cela devient:
msgid "a string"
msgstr "une chaîne de caractères"
Et ainsi de suite.
Seconde étape: le fichier fr.po,
ou n'importe quelle autre traduction, il faut le
placer dans le répertoire po/. Puis il
faut éditer le fichier configure.in et
ajouter la bonne langue dans la variable ALL_LINGUAS.
Nous avions
ALL_LINGUAS="". Cela devient ALL_LINGUAS="fr".
Puis, si un Allemand nous envoie son de.po, on
mettra ce de.po dans le répertoire
po/, puis on éditera à nouveau
configure.in pour y mettre ALL_LINGUAS="fr
de".
Maintenant bien sur, la séquence que vous devez
connaître par coeur maintenant:
autoconf
automake
./configure
make
Pour éditer les fichiers *.po, n'importe quel éditeur de texte convient. Mais il faut tout de même indiquer que Emacs est bien adapté à cette tâche (ce n'est pas de la pub, je n'utilise pas Emacs!).
J'ai essayé d'aborder quelques points qui sont
habituellement survolés car n'étant pas de la
programmation pure. J'espère que cet article vous permettra
à partir d'un simple programme de
pouvoir utiliser ./configure; make; make install,
et si c'est un programme gnome, de
pouvoir faire parler le programme en plusieurs langues. Je n'ai pas
cherché à m'étendre sur
certains points comme la zlib ou la programmation GNOME. D'autres
articles sont là pour cela. Je
n'ai pas abordé non plus d'autres outils
nécessaires à la bonne gestion d'un projet, comme
utiliser CVS, répondre rapidement aux mails ou encore
l'intérêt de diffuser le projet le plus
souvent possible. Cet article avait pour but d'aborder un maximum de ce
qui interfère avec
automake et autoconf. J'espère que j'y suis parvenu, et que
ceux qui n'ont pas craqué avant la fin
sauront aller aussi loin que leurs projets le nécessitent.
Yves Mettier
Ingénieur Systèmes et réseaux chez
Admiral
Coauteur de gtktalog
http://gtktalog.sourceforge.net