GNU Autotools - Aperçu
Ce texte présente...
- La famille des outils GNU Autotools
- Quelques avantages d'utilisation de ces outils
- Quelques inconvénients...
- Un exemple concret d'utilisation des Autotools
Ce texte ne présente pas...
- Toutes les possibilités offertes par les Autotools
- L'intégration de gettext aux Autotools
- La construction de librairies partagées avec les Autotools
Avertissement
Je tiens à mentionner au préalable que je ne suis pas du tout un expert des autotools et que je n'ai pas non plus du tout une grande expérience de ces outils. J'ai écrit ce document dans le but de donner une idée des nombreuses possibilités qu'ils offrent, et de donner envie aux personnes qui les découvrent de les utiliser. Il s'agit donc plus de faire partager l'expérience des autotools que j'ai eu dans le cadre de la diffusion de mon projet open-source, et de la compilation de références utiles à ceux qui voudront approfondir le sujet.Table des matières
1. Introduction
Les Autotools, aussi appelés 'build system', sont des outils fournis par le projet GNU pour faciliter la fabrication de paquets à partir du code source d'un programme. Ils visent également à assurer la portabilité du paquet ainsi construit, permettant de l'installer sur une grande variété de plate-formes UNIX différentes. Lorsqu'on télécharge un paquet contenant les sources d'un logiciel depuis internet, il est très souvent indiqué de taper la succession suivante d'instructions pour construire le binaire du logiciel:
./configure make make install
Cette facilité d'installation est rendue possible grâce à la chaîne d'outils Autotools, dont je présente ici quelques-uns des maillons, ainsi qu'un exemple d'utilisation de ceux-ci. À la fin de ce texte pourront également être trouvées quelques références permettant d'approfondir les points non abordés ici.
2. La famille des Autotools
Les Autotools se composent principalement des outils suivants:
autoconf
, qui simplifie la configurationautomake
, qui simplifie l'écriture des Makefilelibtool
, qui simplifie la création des bibliothèques dynamiques (non abordé ici)
Remarque : D'autres logiciels GNU sont fréquemment utilisés conjointement aux Autotools, comme make, gettext, pkg-config, ou gcc, mais ils ne seront pas abordés ici.
2.1 Autoconf
Autoconf va permettre de construire le script configure
. Pour ce
faire, il se base sur le fichier configure.in
ou
configure.ac
que l'éditeur du logiciel écrit lui-même, pour
produire le script configure
. Nous verrons comment construire un
exemple simple de fichier configure.ac
dans la partie "les fichiers indispensables".
Le script configure
est utile pour obtenir de
nombreuses informations sur le système cible, et va permettre de répondre à des
questions comme :
- Quel est le compilateur, et où est-il ?
- Quels sont les fonctions disponibles sur la machine ?
- Quel sont les caractéristiques du CPU ?
Pour résumer, il s'agit donc de déterminer avec précision l'environnement sur
lequel on veut installer notre logiciel, afin d'adapter la compilation à ce
nouveau système. De nombreuses variables d'environnement seront d'ailleurs fixées
suite au lancement du script configure. Toutes les caractéristiques du système que
le script aura pu détecter se trouvent dans le fichier config.log
,
dont un exemple se trouve en annexe 1 (les quelques premières lignes uniquement,
le fichier complet faisant au minimum un millier de lignes...).
2.2 Automake
Automake est utilisé pour créer des Makefile
aussi portables que
possible. De la même manière que pour autoconf, automake a besoin d'un fichier
Makefile.am
dans lequel on doit spécifier les fichiers sources du
projet, le binaire que l'on veut générer... Ce fichier Makefile.am
sera ensuite transformé par automake en Makefile.in
, qui servira
enfin à générer le Makefile
final. Les problématiques abordées par
automake sont les suivantes :
- Où sont les bibliothèques ?
- Où installer le binaire généré, les manpages,... ?
- Comment connaître les dépendances ?
2.3 Libtool
Libtool est quand à lui l'outil permettant de construire une bibliothèque statique ou dynamique. Mon expérience de cet outil n'est pas assez grande pour détailler son utilisation ici, et je renvoie aux liens indiqués en fin de ce document pour plus de détails sur libtool.
3. Le pour et le contre
3.1 Les inconvénients des Autotools
J'ai rencontré peu d'inconvénients dans l'utilisation des autotools, mais on peut tout de même citer:
- Ce n'est pas la meilleure façon de distribuer du code source pour Windows (utilisation de scripts compatibles Bourne Shell lors du processus d'installation, utilisation de make,...). Ceci peut être résolu en installant l'environnement Cygwin avant d'installer le logiciel.
- J'ai galéré pour incorporer
gettext
(internationalisation) à l'environnement des autotools. Maintenant ça marche bien, mais la doc est un peu trapue (je suis pas très doué non plus peut-être ;) - Lourdeur de l'ensemble, comparé à un Makefile unique. Mais on peut éviter de
stocker tous les scripts dans le dépôt
CVS
(voir utilisation avec cvs).
3.2 Les avantages des Autotools
Les autotools offrent des avantages non négligeables :
- Importante portabilité (j'ai pu le vérifier lors de la diffusion de
calcurse
, qui fonctionne sans modification sur de nombreuses plate-formes différentes comme: [Open, Free, Net]BSD, Linux depuis la Slackware jusqu'à la Ubuntu, Darwin, Solaris. - le Makefile généré contient de nombreuses cibles intéressantes, qui permettent de simplifier la distribution, l'installation et la maintenance : all, dist, check, distcheck, doc, install, uninstall, clean, distclean, maintainer-clean, ...
- Les GNU Autotools ne sont nécessaires que sur la machine du développeur du logiciel. Une fois que l'on diffuse celui-ci, il est inutile d'avoir ces autotools installés sur la machine cible pour la compilation du programme.
4. Un exemple concret
Je présente dans cette partie l'utilisation que j'ai faite des autotools dans
le cadre de mon projet open-source calcurse
.
4.1 L'arborescence du projet
Lorsque l'on fait un checkout du projet depuis le dépôt cvs, on obtient les fichiers suivants à la racine du répertoire:
AUTHORS NEWS README TODO ChangeLog Makefile.am configure.ac autogen.sh
ainsi que les trois répertoires suivants:
src/ po/ doc/
Les fichiers NEWS, README, AUTHORS et ChangeLog sont obligatoires dans les paquets se conformant aux normes GNU. Les fichiers configure.ac et Makefile.am sont ceux relatifs aux autotools, et je les décris plus en détails dans la partie suivante. Pour autogen.sh, c'est un script que j'ai écrit et qui lance automatiquement le mécanisme des autotools pour construire le script configure. Cela évite que ce gros script soit stocké inutilement dans le dépôt cvs. Je présente plus en détails autogen.sh dans la partie utilisation avec cvs.
Pour ce qui est des répertoires, src/ contient les .c et .h, mais aussi la
manpage calcurse.1
et le script Makefile.am, que nous verrons
ci-dessous. Le répertoire po/ contient les fichiers .po, utilisés pour la
traduction du logiciel, par l'intermédiaire de l'API gettext. Le répertoire doc/
contient la documentation au format html.
4.2 Les fichiers indispensables
Pour faire marcher les autotools, nous n'avons besoin que des trois fichiers suivants:
- À la racine:
Makefile.am
,configure.ac
- Dans le répertoire src/ :
Makefile.am
Voici le code du fichier configure.ac qui se trouve à la racine:
# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. # # $calcurse: configure.ac,v 1.8 2007/01/16 10:55:43 culot Exp $ AC_PREREQ(2.59) AC_INIT(calcurse, 1.7, frederic@monsite.org) AM_INIT_AUTOMAKE AM_GNU_GETTEXT([external]) AC_CONFIG_SRCDIR([src/calcurse.c]) AC_CONFIG_HEADER([config.h]) # Checks for programs. AC_PROG_CC # Checks for ncurses. AC_CHECK_HEADERS([ncurses.h], [ AC_CHECK_LIB(ncurses, initscr, [ LIBS="$LIBS -lncurses" AC_DEFINE(HAVE_LIBNCURSES, 1, [Define to 1 if you have the 'ncurses' library (-lncurses).]) ], AC_MSG_ERROR(The ncurses library is required in order to build the program!)) ], AC_MSG_ERROR(The ncurses header is required in order to build the program!)) # Checks for pthread. AC_CHECK_HEADERS([pthread.h], [ AC_CHECK_LIB(pthread, pthread_create, [ LIBS="$LIBS -lpthread" AC_DEFINE(HAVE_LIBNCURSES, 1, [Define to 1 if you have the 'pthread' library (-lpthread).]) ], AC_MSG_ERROR(The pthread library is required in order to build the program!)) ], AC_MSG_ERROR(The pthread header is required in order to build the program!)) # Checks for header files. AC_HEADER_STDC AC_CHECK_HEADERS([stdlib.h string.h sys/time.h time.h stdio.h unistd.h getopt.h \ sys/types.h]) # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_STRUCT_TM AC_HEADER_STDBOOL # Checks for library functions. AC_FUNC_MALLOC AC_FUNC_MKTIME AC_FUNC_STRFTIME AC_CHECK_FUNCS([floor mkdir strchr]) AC_OUTPUT(Makefile src/Makefile po/Makefile.in po/Makefile)
Le configure.ac contient les différents tests qu'il faudra effectuer pour
vérifier la présence des librairies nécessaires à la compilation.
Ce script assez long peut être généré facilement en utilisant la commande
autoscan
. Autoscan va examiner les sources du programme à la
recherche des bibliothèques qui sont utilisées, et créer un fichier
configure.scan. Il faut ensuite vérifier ce configure.scan pour être sûr que
toutes les bibliothèques présentes sont nécessaires, puis renommer simplement le
fichier en configure.ac.
Le deuxième fichier obligatoire qui se trouve à la racine est Makefile.am. En voici le source:
# $calcurse: Makefile.am,v 1.2 2006/09/09 20:21:16 culot Exp $ AUTOMAKE_OPTIONS= gnu SUBDIRS = po src EXTRA_DIST = ABOUT-NLS \ doc/manual_en.html \ doc/manual_fr.html \ doc/manual_de.html \ doc/manual_es.html ACLOCAL_AMFLAGS = -I m4
Ce fichier est plus simple que le précédent, et contient des informations sur les répertoires importants du projet dans lequel on voudra mettre un Makefile. Il contient également les fichiers annexes que l'on veut rajouter dans le paquet final (ici la documentation html), et le style du paquet que l'on veut créer. Ici il s'agit d'un paquet type gnu, mais il existe d'autres types comme gnits ou foreign. Cela permet de gérer la conformité par rapport aux standards préconisés par GNU.
Le dernier fichier important est le Makefile.am contenu dans le répertoire src/, qui se présente comme suit:
# $calcurse: Makefile.am,v 1.3 2006/09/22 13:12:06 culot Exp $ AUTOMAKE_OPTIONS= gnu bin_PROGRAMS= calcurse calcurse_SOURCES= calcurse.c apoint.c event.c todo.c utils.c\ calendar.c vars.c io.c help.c custom.c args.c\ day.c recur.c notify.c\ apoint.h event.h todo.h utils.h calendar.h\ vars.h io.h help.h custom.h args.h i18n.h\ day.h recur.h notify.h LIBS= -lncurses -lpthread -lm LDADD= @LTLIBINTL@ datadir= @datadir@ localedir= $(datadir)/locale DEFS= -DLOCALEDIR=\"$(localedir)\" @DEFS@ man_MANS= calcurse.1 EXTRA_DIST= calcurse.1
Dans ce fichier on retrouve le nom de l'exécutable à produire, la liste des fichiers sources, les librairies nécessaires, et la liste des pages de manuel à inclure dans le paquet.
Une fois que l'on a ces trois fichiers indispensables, il ne reste plus qu'à lancer la succession des scripts des autotools pour construire un bôôô paquet pour son projet ;)
4.3 L'enchainement des commandes
Pour construire notre script configure, voici l'enchainement des commandes à effectuer:
aclocal -I m4 autoheader automake --gnu --copy --add-missing autoconf
aclocal permet de créer le fichier aclocal.m4, qui contient des macros m4 (langage de macros utilisé par les autotools) pour effectuer les tests de présence des librairies et autres. aclocal se base sur ce que l'on a écrit dans configure.ac pour générer ses macros de test.
autoheader génère un fichier
config.h.in contenant des symboles pour le pré-processeur. Ce sont les
#define présentant toutes les options possibles pour le logiciel (par exemple si
on peut avoir l'internationalisation, on aura un #undef
ENABLE_NLS
). Les options sont inactives par défaut, et le script
configure va générer un fichier config.h contenant la même liste de symboles,
activés ou non suivant le choix de l'installateur du logiciel. Pour reprendre
l'exemple précédent, si configure est lancé avec l'option
--enable-nls
, on retrouvera dans le fichier config.h généré le
symbole: #define ENABLE_NLS 1
. J'ai reporté en annexe un bout de ce
fichier config.h, pour avoir une idée des différentes variables possibles (c'est
dedans qu'est défini par exemple le numéro de version, que l'on peut réutiliser
ensuite dans tout le programme, et qui avait été fixé dans configure.ac par
l'instruction: AC_INIT(calcurse, 1.7, frederic@monsite.org)
).
automake permet de générer le fichier Makefile.in à partir de
Makefile.am. Ce fichier Makefile.in sera transformé en Makefile par la suite,
lors de l'exécution de configure. Les options passées en argument permettent de
se conformer aux normes GNU (--gnu
), c'est-à-dire qu'il vérifie la
présence des fichiers README, AUTHORS, NEWS
et ChangeLog. Cela permet aussi
de rajouter d'autres fichiers annexes qui pourraient manquer
(--add-missing
), et de les copier en dur (--copy
) au
lieu de simplement créer des liens symboliques.
Remarque : Cette étape nécessite l'utilisation de perl.
On termine enfin avec autoconf, qui va nous permettre d'obtenir notre script configure. C'est ce script que lancera l'utilisateur, et qui produira le Makefile sur base du Makefile.in.
Un petit dessin issu de Wikipedia (crédits: Dominik Menke) pour résumer le processus, à l'attention de ceux près du radiateur qui ont roupillé pendant l'explication:
4.4 Utilisation avec cvs
Le script configure généré grâce aux autotools est énorme (300k, plus gros que le binaire du projet lui-même...). Surtout que l'exécution des scripts autotools génère également d'autres fichiers annexes (Makefile.in, config.*,...). Il est donc dommage de stocker tout ça avec le reste dans le dépôt cvs, surtout que l'on peut regénérer l'ensemble simplement en utilisant la succession de commandes présentées au paragraphe précédent. Par contre, je me suis rendu compte à l'usage que cela devenait vite fastidieux, et je n'ai toujours pas réussi à me souvenir de l'ordre dans lequel on doit lancer les différents scripts. Je me suis donc fait un petit code pour regénérer automatiquement configure. C'est ce code que je stocke dans le dépôt cvs, et il prend beaucoup moins de place. J'ai reporté en annexe ce script de regénération automatique, que j'ai appelé autogen.sh (on retrouve cette démarche dans pas mal d'autres projets, où ce fichier peut être également appelé bootsrap.sh ou build.sh suivant l'inspiration de son auteur).
5. Ressources
Enorme manuel d'Autoconf, dans plusieurs format différents.
[2] http://www.gnu.org/software/automake/manual/
Manuel d'Automake
[3] http://www.gnu.org/software/libtool/
Page du projet Libtool
[4] Linux Magazine - Hors série Les Dossiers numéro 1
Janvier/Février/Mars 2004 - pages 50 à 55
Article expliquant la création d'une bibliothèque avec Libtool
6. Annexes
Extrait du fichier config.log
This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by calcurse configure 1.7, which was generated by GNU Autoconf 2.59. Invocation command line was $ ./configure CFLAGS=-Wall -g --prefix=/home/user --with-libiconv-prefix=/usr/local --with-libintl-prefix=/usr/local --no-create --no-recursion ## --------- ## ## Platform. ## ## --------- ## hostname = puffy.my.domain uname -m = i386 uname -r = 4.0 uname -s = OpenBSD uname -v = GENERIC#1107 /usr/bin/uname -p = Intel(R) Celeron(R) M processor 1.30GHz ("GenuineIntel" 686-class) /bin/uname -X = unknown /bin/arch = unknown /usr/bin/arch -k = OpenBSD.i386 /usr/convex/getsysinfo = unknown hostinfo = unknown /bin/machine = unknown /usr/bin/oslevel = unknown /bin/universe = unknown PATH: /sbin PATH: /usr/sbin PATH: /usr/local/sbin PATH: /bin PATH: /usr/bin PATH: /usr/local/bin PATH: /usr/X11R6/bin PATH: /usr/games PATH: /home/user/bin PATH: . ## ----------- ## ## Core tests. ## ## ----------- ## configure:1367: checking for a BSD-compatible install configure:1422: result: /usr/bin/install -c configure:1433: checking whether build environment is sane configure:1476: result: yes configure:1541: checking for gawk configure:1570: result: no configure:1541: checking for mawk configure:1570: result: no configure:1541: checking for nawk configure:1557: found /usr/bin/nawk configure:1567: result: nawk configure:1577: checking whether make sets $(MAKE) configure:1597: result: yes ../..
Extrait du fichier config.h
/* config.h. Generated by configure. */ /* config.h.in. Generated from configure.ac by autoheader. */ /* Define to 1 if translation of program messages to the user's native language is requested. */ #define ENABLE_NLS 1 /* Define to 1 if you have the MacOS X function CFLocaleCopyCurrent in the CoreFoundation framework. */ /* #undef HAVE_CFLOCALECOPYCURRENT */ /* Define to 1 if you have the `floor' function. */ /* #undef HAVE_FLOOR */ /* Define to 1 if you have the <getopt.h> header file. */ #define HAVE_GETOPT_H 1 /* Define if the GNU gettext() function is already present or preinstalled. */ #define HAVE_GETTEXT 1 /* Define to 1 if your system has a GNU libc compatible `malloc' function, and to 0 otherwise. */ #define HAVE_MALLOC 1 /* Define to 1 if you have the <ncurses.h> header file. */ #define HAVE_NCURSES_H 1 /* Define to 1 if you have the <pthread.h> header file. */ #define HAVE_PTHREAD_H 1 /* Define to 1 if stdbool.h conforms to C99. */ #define HAVE_STDBOOL_H 1 /* Define to 1 if you have the <stdint.h> header file. */ #define HAVE_STDINT_H 1 /* Define to 1 if you have the <stdio.h> header file. */ #define HAVE_STDIO_H 1 /* Name of package */ #define PACKAGE "calcurse" /* Define to the address where bug reports for this package should be sent. */ #define PACKAGE_BUGREPORT "frederic@monsite.org" /* Define to the full name of this package. */ #define PACKAGE_NAME "calcurse" /* Define to the full name and version of this package. */ #define PACKAGE_STRING "calcurse 1.7" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "calcurse" /* Define to the version of this package. */ #define PACKAGE_VERSION "1.7" /* Version number of package */ #define VERSION "1.7"
Script autogen.sh
#!/bin/sh # # Copyright (c) 2004-2006 Frederic Culot # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # # Send your feedback or comments to : calcurse@culot.org # Calcurse home page : http://culot.org/calcurse # # $calcurse: autogen.sh,v 1.1.1.1 2006/07/31 21:00:02 culot Exp $ # # # autogen.sh - Generates all the necessary files to build calcurse from # cvs tree. # PKG_NAME=calcurse AC_VERSION="2 59" AUTOCONF_VERSION=2.59 AC_FLAGS= AM_VERSION="1 9" AUTOMAKE_VERSION=1.9 AM_FLAGS="--gnu --copy --add-missing" GETTEXT_VERSION="0 14" GETTEXT_FLAGS="--copy --no-changelog" ACLOCAL_FLAGS="-I m4" SRCDIR=`dirname $0` test -z "$SRCDIR" && SRCDIR=. export AUTOMAKE_VERSION AUTOCONF_VERSION # Function to check if we are at the top level of calcurse package. check_directory_level() { (test -f $SRCDIR/configure.ac) || { printf "\n\n**Error**: Directory "\`$SRCDIR\'" does not appear to" printf "\nbe the top-level $PKG_NAME directory.\n" exit 1 } } # Clean previous files before running scripts clean_old_files() { printf "Cleaning old files ... " rm -rf configure config.log aclocal.m4 \ config.status config autom4te.cache \ po/Makefile.in.in printf "done\n" } # Clean useless backup files clean_backup_files() { printf "Cleaning backup files ... " rm -rf configure.ac\~ Makefile.am\~ printf "done\n" } # Function to check for a program availability check_program() { PROGRAM=$1 printf "Checking for $PROGRAM ... " ($PROGRAM --version) < /dev/null > /dev/null 2>&1 || { printf "\n\n**Error**: You must have $PROGRAM installed." printf "\nDownload the appropriate package for your distribution," printf "\nor get the source tarball at ftp://ftp.gnu.org/pub/gnu/" printf "\n" exit 1 } FOUND=`which $PROGRAM` printf "$FOUND\n" } # Function to check a program's version # (there must be a better way, but I am not good at sed...) check_program_version() { PROGRAM=$1; MAJOR=$2; MINOR=$3 printf "Checking that $PROGRAM version is at least $MAJOR.$MINOR ... " VERSION=`$PROGRAM --version |head -n 1|sed 's/([^)]*)//g;s/^[a-zA-Z\.\ \ \-]*//;s/ .*$//'` MAJOR_FOUND=`echo $VERSION | cut -d. -f1` MINOR_FOUND=`echo $VERSION | sed s/[-,a-z,A-Z].*// | cut -d. -f2` [ -z "$MINOR_FOUND" ] && MINOR_FOUND=0 WRONG= if [ -z "$MAJOR_FOUND" -lt "$MAJOR" ]; then WRONG=1 elif [ "$MAJOR_FOUND" -eq "$MAJOR" ]; then if [ "$MINOR_FOUND" -lt "$MINOR" ]; then WRONG=1 fi fi if [ ! -z "$WRONG" ]; then printf "\n\n**Error**: found version $MAJOR_FOUND.$MINOR_FOUND," printf "\nwhich is too old. You should upgrade $PROGRAM." printf "\nDownload the appropriate package for your distribution," printf "\nor get the source tarball at ftp://ftp.gnu.org/pub/gnu/" printf "\n" exit 1 else printf "OK, found $MAJOR_FOUND.$MINOR_FOUND\n" fi } # Dirty hack to run gettextize: problem is that it demands to # press Return no matter what... This gets rid of that demand. run_gettext() { PROGRAM=gettextize printf "Running $PROGRAM $GETTEXT_FLAGS ... " sed 's:read .*< /dev/tty::' `which $PROGRAM` > my-gettextize chmod +x my-gettextize (printf "\n" | ./my-gettextize $GETTEXT_FLAGS > /dev/null 2>&1) || { printf "\n\n**Error**: $PROGRAM failed.\n" exit 1 } # now restore the files modified by gettextize (test -f configure.ac~) && mv -f configure.ac~ configure.ac (test -f Makefile.am~) && mv -f Makefile.am~ Makefile.am mv -f po/Makevars.template po/Makevars rm my-gettextize printf "OK\n" } # Function to run a program run_program() { PROGRAM=$1 shift PROGRAM_FLAGS=$@ printf "Running $PROGRAM $PROGRAM_FLAGS ... " $PROGRAM $PROGRAM_FLAGS > /dev/null 2>&1 || { printf "\n\n**Error**: $PROGRAM failed.\n" exit 1 } printf "OK\n" } # Main echo " --- $PKG_NAME autogen script ---\n" check_directory_level clean_old_files check_program gettext check_program_version gettext $GETTEXT_VERSION check_program aclocal check_program autoheader check_program automake check_program_version automake $AM_VERSION check_program autoconf check_program_version autoconf $AC_VERSION run_gettext run_program aclocal $ACLOCAL_FLAGS run_program autoheader run_program automake $AM_FLAGS run_program autoconf $AC_FLAGS clean_backup_files printf "\nYou can now run the configure script to obtain $PKG_NAME Makefile.\n"