Olivier Poncet
Directeur Technique · CTO

Les autotools - automake

cover🔗 publié par Olivier Poncet le 27/01/2017 à 12:00

Nous avons vu précédemment que autotools est un terme générique utilisé pour désigner l’ensemble des outils de build du projet GNU, le GNU build system.

Dans ce billet, nous allons faire un focus sur l’outil automake, qui est un outil permettant de produire des fichiers Makefiles portables et normalement totalement compatibles POSIX ayant pour objectif de construire une solution logicielle complète, quelque-soit la plateforme, en ne spécifiant que le strict nécessaire. Du point de vue du programmeur, l’écriture d’un Makefile se résumera donc à décrire ce qu’il souhaite réaliser dans un langage de plus haut niveau que le Makefile standard.

Description d’un fichier Makefile.am

En général, il suffit de spécifier :

  • Une ligne qui déclare le nom du programme ou de la bibliothèque à construire.
  • Une liste de fichiers sources et d’entêtes.
  • Eventuellement une liste d’options à passer au compilateur (notamment, dans quels répertoires certaines entêtes sont disponibles).
  • Eventuellement une liste d’options à passer à l’éditeur de liens (quelles sont les bibliothèques dont le programme a besoin et dans quels répertoires elles se trouvent).

Cette description simple permet à automake de produire un Makefile standard proposant un certain nombre de cibles permettant de :

  • Construire le projet.
  • Construire et exécuter les tests unitaires.
  • Nettoyer les fichiers produits lors de la construction du projet.
  • Installer le(s) programme(s) et bibliothèque(s) dans les répertoires prévus à cet effet.
  • Désinstaller le(s) programme(s) et bibliothèque(s) des répertoires dans lesquels ils ont été installés.
  • Créer une archive de distribution des sources (tarball nommé paquet-version.tar.gz).
  • Vérifier que cette archive est auto-suffisante, et en particulier qu’il est possible de construire le projet dans un répertoire autre que celui où les sources sont déployées.

Automake prend aussi en charge le génération et la gestion des dépendances entre les sources, les entêtes, et les objets produits, afin que lorsqu’un fichier est modifié, la prochaine invocation de make sache ce qui doit être reconstruit.

Description d’un fichier Makefile.am

En général, il suffit de spécifier :

  • Une ligne qui déclare le nom du programme ou de la bibliothèque à construire.
  • Une liste de fichiers sources et d’entêtes.
  • Eventuellement une liste d’options à passer au compilateur (notamment, dans quels répertoires certaines entêtes sont disponibles).
  • Eventuellement une liste d’options à passer à l’éditeur de liens (quelles sont les bibliothèques dont le programme a besoin et dans quels répertoires elles se trouvent).

Cette description simple permet à automake de produire un Makefile standard proposant un certain nombre de cibles permettant de :

  • Construire le projet.
  • Construire et exécuter les tests unitaires.
  • Nettoyer les fichiers produits lors de la construction du projet.
  • Installer le(s) programme(s) et bibliothèque(s) dans les répertoires prévus à cet effet.
  • Désinstaller le(s) programme(s) et bibliothèque(s) des répertoires dans lesquels ils ont été installés.
  • Créer une archive de distribution des sources (tarball nommé paquet-version.tar.gz).
  • Vérifier que cette archive est auto-suffisante, et en particulier qu’il est possible de construire le projet dans un répertoire autre que celui où les sources sont déployées.

Automake prend aussi en charge le génération et la gestion des dépendances entre les sources, les entêtes, et les objets produits, afin que lorsqu’un fichier est modifié, la prochaine invocation de make sache ce qui doit être reconstruit.

Cibles standards

Un Makefile produit par automake supporte nativement un certain nombre de cibles standards dont la liste est décrite ci-après :

Cible Description
make all construit tout, les programmes, les bibliothèques, la documentation, etc. (équilavent à make)
make check construit et exécute les tests unitaires (s’il en existe)
make install installe tout ce qui a besoin d’être installé sur le système en utilisant le préfixe défini à la configuration
make install-strip identique à make install, mais retire en plus les informations et symboles de débuggage (strip)
make installcheck vérifie l’installation des programmes et bibliothèques précédemment installés, si spécifié et supporté
make uninstall désinstalle tout ce qui a précédemment été installé par make install
make mostlyclean nettoie ce qui doit être reconstruit
make clean nettoie les fichiers produits par make all, les objets, les programmes, les bibliothèques, etc.
make distclean identique à make clean, mais retire en plus les fichiers produits par le script configure
make maintainer-clean identique à make distclean, mais retire en plus les fichiers auto-générés s’il en existe (par lex, yacc, etc.)
make dist créé un tarball nommé paquet-version.tar.gz d’après les sources définis dans les fichiers Makefile.am
make distcheck identique à make dist, mais vérifie en plus que le logiciel compile, que les tests unitaires passent, etc.

Précisions concernant les différentes cibles de nettoyage :

  • Si make l’a construit, et que c’est un objet que l’on veut normalement reconstruire (par exemple un .o), alors make mostlyclean doit le supprimer.
  • Si make l’a construit, alors make clean doit le supprimer.
  • Si configure l’a construit, alors make distclean soit le supprimer.
  • Si le mainteneur du logiciel l’a construit à l’aide d’un outil spécial, alors make maintainer-clean doit le supprimer.

Certaines cibles peuvent potentiellement contenir des surcharges en ajoutant le suffixe -local afin de procéder à des actions non prévues en standard par automake.

L’exemple suivant permet d’afficher un message lorsque certaines cibles standards sont terminées :

# Makefile.am overriden targets
all-local:
	@echo "=== all done ==="

mostlyclean-local:
	@echo "=== mostlyclean done ==="

clean-local:
	@echo "=== clean done ==="

distclean-local:
	@echo "=== distclean done ==="

maintainer-clean-local:
	@echo "=== maintainer-clean done ==="

install-exec-local:
	@echo "=== install-exec done ==="

install-data-local:
	@echo "=== install-data done ==="

uninstall-local:
	@echo "=== uninstall done ==="

Nettoyer des fichiers

Si des fichiers doivent être explicitement supprimés alors qu’ils ne sont pas réellement produits lors de la construction du logiciel (par exemple après le passage des tests unitaires, …), des variables permettent de les référencer :

Variable Description
MOSTLYCLEANFILES liste des fichiers supplémentaires à nettoyer lors de l’exécution de make mostlyclean
CLEANFILES liste des fichiers supplémentaires à nettoyer lors de l’exécution de make clean
DISTCLEANFILES liste des fichiers supplémentaires à nettoyer lors de l’exécution de make distclean
MAINTAINERCLEANFILES liste des fichiers gérés par le mainteneur à nettoyer lors de l’exécution de make maintainer-clean
# Makefile.am cleanup
MOSTLYCLEANFILES = \
	*.out \
	$(NULL)

DISTCLEANFILES = \
	*.log \
	$(NULL)

Déclarer des sous-répertoires

Dans le langage automake, la variable SUBDIRS permet de spécifier les sous-répertoires à parcourir avant le traitement du répertoire courant.

# Makefile.am subdirectories
SUBDIRS = \
	subdir1 \
	subdir2 \
	subdir3 \
	$(NULL)

Déclarer une bibliothèque

Dans un fichier Makefile.am, le suffixe _LTLIBRARIES, signifiant « libtool libraries », est utilisé pour décrire la liste des bibliothèques à construire :

  • La variable lib_LTLIBRARIES permet de spécifier la liste des bibliothèques produites par libtool installables sur le système cible.
  • La variable noinst_LTLIBRARIES permet de spécifier la liste des bibliothèques produites par libtool non installables sur le système cible.

Le nom d’une bibliothèque est conventionnellement lib{nom-de-bibliotheque}.la, l’extension .la signifiant libtool archive. Ce nom sera utilisé pour décrire tous les objets la composant en utilisant systématiquement le préfixe lib{nom_de_bibliotheque}_la_ (tous les caractères non alphanumériques du nom de la bibliothèque sont remplacés par des caractères « underscore »).

La liste des objets composant une bibliothèque libtool est :

Variable Description
lib{nom_de_bibliotheque}_la_SOURCES contient la liste des sources et des entêtes non distribuables
lib{nom_de_bibliotheque}_la_include_HEADERS contient la liste des entêtes distribuables sur le système cible
lib{nom_de_bibliotheque}_la_CFLAGS contient éventuellement des options particulières de compilation
lib{nom_de_bibliotheque}_la_CPPFLAGS contient éventuellement des options particulières de préprocesseur
lib{nom_de_bibliotheque}_la_LDFLAGS contient éventuellement des options particulières d’édition de liens
lib{nom_de_bibliotheque}_la_LIBADD contient éventuellement la liste des bibliothèques dont l’objet dépend
lib{nom_de_bibliotheque}_la_includedir permet de spécifier le répertoire de destination des entêtes distribuables

Il est devenu courant de fournir un fichier de package compatible pkg-config. Pour cela, il suffit de déclarer ce fichier dans une variable suffixée par _DATA, de déclarer le répertoire de destination de ce fichier, généralement $(libdir)/pkgconfig, et si ce fichier est généré par autoconf, déclarer le nom de son template dans la variable EXTRA_DIST.

# Makefile.am library
lib_LTLIBRARIES = \
	libmylibrary.la \
	$(NULL)

libmylibrary_la_includedir = $(includedir)/mylibrary

libmylibrary_la_SOURCES = \
	mylibrary-priv.h \
	mylibrary.c \
	$(NULL)

libmylibrary_la_include_HEADERS = \
	mylibrary.h \
	$(NULL)

libmylibrary_la_CPPFLAGS = \
	-I$(top_srcdir) \
	$(NULL)

libmylibrary_la_LDFLAGS = \
	-L$(top_builddir) \
	-version-info 0:0:0 \
	$(NULL)

libmylibrary_la_LIBADD = \
	-lX11 -lXext \
	$(NULL)

pkgconfigdir = $(libdir)/pkgconfig

pkgconfig_DATA = \
	mylibrary.pc \
	$(NULL)

EXTRA_DIST = \
	mylibrary.pc.in \
	$(NULL)
# mylibrary.pc.in
prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@

Name: mylibrary
Description: my super useful library
Version: @PACKAGE_VERSION@
Requires: 
Cflags: -I${includedir}
Libs: -L${libdir} -lmylibrary

Déclarer un programme

Dans un fichier Makefile.am, le suffixe _PROGRAMS est utilisé pour décrire la liste des programmes à construire :

  • la variable bin_PROGRAMS permet de spécifier la liste des programmes installables sur le système cible.
  • la variable noinst_PROGRAMS permet de spécifier la liste des programmes non installables sur le système cible.

Le nom d’un programme est relativement libre. Ce nom sera utilisé pour décrire tous les objets le composant en utilisant systématiquement le préfixe {nom_de_programme}_ (tous les caractères non alphanumériques du nom du programme sont remplacés par des caractères « underscore »).

La liste des objets composant un programme est :

Variable Description
{nom_de_programme}_SOURCES contient la liste des sources et des entêtes
{nom_de_programme}_CFLAGS contient éventuellement des options particulières de compilation
{nom_de_programme}_CPPFLAGS contient éventuellement des options particulières de préprocesseur
{nom_de_programme}_LDFLAGS contient éventuellement des options particulières d’édition de liens
{nom_de_programme}_LDADD contient éventuellement les bibliothèques avec lesquelles lier le programme
# Makefile.am program
bin_PROGRAMS = \
	myprogram \
	$(NULL)

myprogram_SOURCES = \
	myprogram.c \
	myprogram.h \
	$(NULL)

myprogram_CPPFLAGS = \
	-I$(top_srcdir) \
	$(NULL)

myprogram_LDFLAGS = \
	-L$(top_builddir) \
	$(NULL)

myprogram_LDADD = \
	$(top_builddir)/mylibrary/libmylibrary.la \
	-lcrypto -lm \
	$(NULL)

Déclarer un test unitaire

Dans un fichier Makefile.am, le préfixe check_ est utilisé pour décrire tout ce qui est lié aux tests unitaires :

  • la variable check_SCRIPTS permet de spécifier la liste des scripts permettant de lancer les tests unitaires.
  • la variable check_PROGRAMS permet de spécifier la liste des programmes spécifiques contenant les tests unitaires.
  • la variable TESTS permet de spécifier la liste des tests unitaires à exécuter.

Le nom d’un programme de tests unitaires est relativement libre. Ce nom sera utilisé pour décrire tous les objets le composant en utilisant systématiquement le préfixe {nom_de_programme}_ (tous les caractères non alphanumériques du nom du programme sont remplacés par des caractères « underscore »).

La liste des objets composant un programme de tests unitaires est :

Variable Description
{nom_de_programme}_SOURCES contient la liste des sources et des entêtes
{nom_de_programme}_CFLAGS contient éventuellement des options particulières de compilation
{nom_de_programme}_CPPFLAGS contient éventuellement des options particulières de préprocesseur
{nom_de_programme}_LDFLAGS contient éventuellement des options particulières d’édition de liens
{nom_de_programme}_LDADD contient éventuellement les bibliothèques avec lesquelles lier le programme
# Makefile.am unit-test
check_SCRIPTS = \
	testsuite.sh \
	$(NULL)

check_PROGRAMS = \
	testsuite \
	$(NULL)

testsuite_SOURCES = \
	testsuite.c \
	testsuite.h \
	$(NULL)

testsuite_CPPFLAGS = \
	-I$(top_srcdir) \
	-I$(top_srcdir)/mylibrary \
	$(NULL)

testsuite_LDFLAGS = \
	-L$(top_builddir) \
	$(NULL)

testsuite_LDADD = \
	$(top_builddir)/mylibrary/libmylibrary.la \
	$(NULL)

TESTS = \
	testsuite.sh \
	$(NULL)

EXTRA_DIST = \
	testsuite.sh \
	$(NULL)

MOSTLYCLEANFILES = \
	testsuite.log \
	$(NULL)

What else ?

Vous avez pu constater au travers ces quelques lignes que cet outil est doté de très nombreuses possibilités de construction de projets. Dans le prochain billet, nous parlerons de son allié, libtool.