Retrouvez cet article dans : Linux Magazine 108
Dans l’article précédent, nous avons mis en place quelques techniques courantes liées au développement de projets libres. Nous allons aborder ici des techniques supplémentaires en relation avec la distribution de bibliothèques.
|
1 |
Préambule |
Nous allons reprendre le projet du numéro 105 et le modifier pour en faire une bibliothèque. Si vous partez du contenu de l’archive Hello-0.0.0.tar.bz2 obtenue à la fin de l’article précédent, voici les modifications à effectuer :
|
Note |
|
J’utiliserai ici l’arborescence déjà présentée, c’est-à-dire : un répertoire source contenant les sous-répertoires et les sources tels qu’obtenus au désarchivage du fichier Hello-0.0.0.tar.bz2 (ce répertoire racine s’appelle alors Hello-0.0.0 au lieu de hello) ; un répertoire de construction hello_build ; un répertoire permettant de vérifier les fichiers installés hello_install. |
Créez un fichier d’en-tête src/hello.h :
|
01: // hello.h 02: 03: #ifndef _HELLO_H 04: #define _HELLO_H 05: 06: class Hello 07: { 08: public: 09: Hello(); 10: void say(); 11: private: 12: static const int hcount; 13: }; 14: 15: #endif /* _HELLO_H */ 16: |
Renommez le fichier src/main.cpp en src/hello.cpp et modifiez-le ainsi :
|
01: // hello.cpp 02: 03: #include <iostream> 04: 05: #include "config.h" 06: #include "hello.h" 07: 08: #ifdef HELLO_NLS_ENABLED 09: #include <libintl.h> 10: #define _(String) dgettext (HELLO_NLS_PACKAGE, String) 11: #else /* HELLO_NLS_ENABLED */ |
|
12: #define _(String) String 13: #endif /* HELLO_NLS_ENABLED */ 14: 15: const int Hello::hcount = HELLO_COUNT; 16: 17: Hello::Hello() 18: { 19: #ifdef HELLO_NLS_ENABLED 20: bindtextdomain (HELLO_NLS_PACKAGE, HELLO_NLS_LOCALEDIR); 21: #endif /* HELLO_NLS_ENABLED */ 22: } 23: 24: void Hello::say() 25: { 26: for (int i=hcount; i>0; i--) 27: std::cout << _("Hello World !") << std::endl; 28: 29: return; 30: } 31: |
|
Remarque |
|
À ceux qui ont trouvé l’usage de la fonction dgettext exagérée dans le projet Hello du mois dernier, j’avais déjà en tête la modification du code en bibliothèque et, dans ce cas, l’appel à dgettext est maintenant justifié. |
Modifiez les cibles de construction et d’installation du fichier src/CMakeLists.txt :
|
01: # src CmakeLists.txt 02: 03: include_directories (${Hello_SOURCE_DIR}/src ${Hello_BINARY_DIR}/src) 04: 05: add_library (hello SHARED hello.cpp) 06: 07: install (TARGETS hello DESTINATION lib) 08: install (FILES hello.h DESTINATION include) 09: |
Construire une bibliothèque avec CMake n’est pas plus compliqué que construire un programme. Il a suffit de remplacer la commande add_executable par add_library. L’option SHARED indique que nous allons obtenir une bibliothèque partagée libhello.so.
Voilà, en compilant et en installant le projet, nous obtenons le résultat suivant :
|
~/hello_build $ cmake -DCMAKE_INSTALL_PREFIX=../hello_install ../Hello-0.0.0 ... ~/hello_build $ make ... ~/hello_build $ make install ... ~/hello_build $ ls -R ../hello_install ../hello_install: include lib share |
|
../hello_install/include: hello.h ../hello_install/lib: libhello.so ../hello_install/share: ... |
Au cours de cet article, je vais garder le principe suivi dans le précédent, c’est-à-dire une modification incrémentale des fichiers et de l’arborescence du projet. Vous trouverez l’état final du projet dans le contenu de l’archive libHello-0.0.0.tar.bz2, librement téléchargeable sur le site http://www.arvernux.fr rubrique Téléchargements.
|
2 |
Fournir un module CMake |
D’origine, CMake fournit des modules permettant de rechercher les bibliothèques les plus communes sur le système et de définir des variables permettant de préciser au compilateur le chemin vers les headers et à l’éditeur de liens l’emplacement des bibliothèques nécessaires. Par exemple, si votre projet nécessite la bibliothèque bzip2, vous aurez besoin du module FindBZip2 :
|
# top-level CmakeLists.txt ... find_package (BZIP2 REQUIRED) ... |
Le module ainsi appelé va rechercher la bibliothèque et positionner une série de variables : BZIP2_FOUND, BZIP2_INCLUDE_DIR, BZIP2_LIBRARIES,... Si la bibliothèque n’est pas trouvée, CMake arrête le processus de configuration avec un message d’erreur. Pour compiler et lier votre projet, il suffira d’utiliser le contenu des variables :
|
include_directories (${BZIP2_INCLUDE_DIR}) target_link_libraries (exemple ${BZIP2_LIBRARIES}) |
Les présentations étant faites, le principe est donc de fournir à CMake un module FindHELLO.cmake interprétable par les fichiers CMakeLists.txt d’applications voulant utiliser notre bibliothèque.
Ce module doit respecter une liste de directives imposées par les développeurs de CMake et détaillées dans le fichier readme.txt du répertoire ${CMAKE_ROOT}/Modules. La variable CMAKE_ROOT contient le chemin vers le répertoire où les fichiers dont a besoin CMake (notamment les modules) sont installés.
|
Remarque |
|
Si vous ne connaissez pas ce chemin, demandez a CMake de vous l’afficher : # CmakeLists.txt ... message (STATUS "Répertoire CMake : ${CMAKE_ROOT}) ... |
Voici l’indispensable du contenu de ce fichier readme.txt :
● Le module doit se nommer FindXXX.cmake. Il sera appelé par la commande find_package (XXX [QUIET] [REQUIRED]). Lors de son appel, les variables XXX_FIND_QUIETLY et XXX_FIND_REQUIRED sont positionnées si les options QUIET et REQUIRED sont précisées.
● Les noms de toutes les variables définies par le module doivent être préfixés par XXX_, exemple : XXX_FOUND. Ces noms doivent respecter une certaine sémantique en fonction du contenu de la variable.
● Le module doit définir au minimum les variables XXX_FOUND, XXX_INCLUDE_DIRS, XXX_LIBRARIES.
● Le module doit intégrer une description (aide...) et elle doit respecter la forme que nous verrons plus loin. Cette aide sera consultable sur la console par la commande :
~ $ cmake --help-module FindXXX
Il est temps d’écrire notre module... presque temps. Celui qui compile et installe votre bibliothèque sur son système influe directement sur le chemin d’installation de la bibliothèque par l’intermédiaire de la variable CMAKE_INSTALL_PREFIX. Pour que notre module soit efficace, il faut qu’il tienne compte de ceci. Nous allons donc le générer dynamiquement à la configuration de la bibliothèque et créer un fichier modèle FindHELLO.cmake.cmake que nous allons placer dans un sous-répertoire de notre projet :
|
~/Hello-0.0.0 $ mkdir modules ... |
Maintenant, créons ce fichier modèle, modules/FindHELLO.cmake.cmake :
|
01: # - Try to find libhello.so and hello.h 02: # 03: # This module will define : 04: # HELLO_FOUND 05: # HELLO_INCLUDE_DIRS 06: # HELLO_LIBRARIES 07: # 08: 09: find_path (HELLO_INCLUDE_DIRS hello.h PATHS @CMAKE_INSTALL_PREFIX@/include) 10: 11: find_library (HELLO_LIBRARIES hello PATHS @CMAKE_INSTALL_PREFIX@/lib) 12: 13: if (HELLO_INCLUDE_DIRS AND HELLO_LIBRARIES) 14: set (HELLO_FOUND TRUE) 15: if (NOT HELLO_FIND_QUIETLY) 16: message (STATUS "Found libhello.so") 17: endif (NOT HELLO_FIND_QUIETLY) 18: else (HELLO_INCLUDE_DIRS AND HELLO_LIBRARIES) 19: set (HELLO_FOUND FALSE) |
|
20: if (HELLO_FIND_REQUIRED) 21: message (FATAL_ERROR "Can’t find libhello.so") 22: endif (HELLO_FIND_REQUIRED) 23: endif (HELLO_INCLUDE_DIRS AND HELLO_LIBRARIES) 24: 25: mark_as_advanced (HELLO_INCLUDE_DIRS HELLO_LIBRARIES) 26: |
Les lignes de commentaires au début du fichier constituent les lignes d’aide affichées par l’appel à cmake --help-module. La première est le titre. Il faut respecter le format présenté y compris dans le nombre d’espaces.
Le code est assez explicite. À la ligne 9, nous recherchons le header. Un chemin de recherche est proposé par l’option PATHS qui sera correctement renseignée à la création du module. À la ligne 11, nous faisons la même chose pour trouver l’emplacement du fichier libhello.so.
Le bloc qui commence à la ligne 13 permet trois choses : le positionnement de la variable HELLO_FOUND, l’affichage d’un message si le paramètre QUIET n’est pas passé lors de l’appel de la fonction find_package et l’arrêt du processus de configuration si le paramètre REQUIRED est présent.
Créons un fichier CMakeLists.txt dans ce sous-répertoire afin de " compiler " et installer notre module :
|
01: # modules CmakeLists.txt 02: 03: configure_file (${Hello_SOURCE_DIR}/modules/FindHELLO.cmake.cmake ${Hello_BINARY_DIR}/modules/FindHELLO.cmake @ONLY) 04: install (FILES FindHELLO.cmake DESTINATION ${CMAKE_ROOT}/Modules) 05: |
Ainsi, notre module connaîtra les chemins où sont installés le header et la bibliothèque. Il ne reste plus qu’à appeler ce fichier CMakeLists.txt dans le fichier CMakeLists.txt racine...
|
01: # top-level CmakeLists.txt ... 60: add_subdirectory (modules) ... |
Reconstruisons le projet :
|
~/hello_build $ cmake -DCMAKE_INSTALL_PREFIX=../hello_install ../Hello-0.0.0 ... ~/hello_build $ make ... |
Nous avons bien un fichier modules/FindHello.cmake correctement configuré. Lors de l’installation de notre bibliothèque, le module sera lui aussi installé dans le répertoire par défaut de CMake. Le développeur souhaitant intégrer notre bibliothèque pourra le faire de la même manière que celle montrée précédemment pour bzip2.
|
Remarque |
|
Il y a ici un problème que je n’ai pas résolu... Le chemin d’installation du module est absolu, or, conformément à ce que nous avons vu, cela veut dire que notre module ne fera pas partie du paquet binaire généré par CPack. Je ne vois qu’une rustine possible : ajouter le module au paquet à la main. |
|
3 |
Et pkg-config ? |
Tout le monde n’utilise pas CMake... Si, si... Nous devrions donc laisser aux développeurs le choix. Nous allons donc fournir un module pour pkg-config.
Je n’expliquerai pas ici l’utilisation de pkg-config. Ceci a déjà été fait par Yves Mettier dans un article paru dans GLMF [1]. Je vais me contenter de générer le fichier libhello.pc nécessaire pour que pkg-config puisse travailler.
Nous allons reprendre le même principe que pour le module CMake : un fichier libhello.pc.cmake qui sera configuré et renommé.
Nous partons d’un squelette tel que celui proposé par Yves Mettier :
|
01: prefix=@CMAKE_INSTALL_PREFIX@ 02: exec_prefix=@CMAKE_INSTALL_PREFIX@ 03: libdir=@CMAKE_INSTALL_PREFIX@/lib 04: includedir=@CMAKE_INSTALL_PREFIX@/include 05: 06: Name: libhello 07: Description: A Class to say "hello" 08: Version: @Hello_VERSION@ 09: Libs: -L${libdir} -lhello 10: Cflags: -I${includedir} 11: |
Ceci est le contenu du fichier libhello.pc.cmake du sous-répertoire pkgconfig. Dans ce même sous-répertoire, nous créons un fichier CMakeLists.txt :
|
01: # pkgconfig CmakeLists.txt 02: 03: configure_file (${Hello_SOURCE_DIR}/pkgconfig/libhello.pc.cmake ${Hello_BINARY_DIR}/pkgconfig/libhello.pc @ONLY) 04: install (FILES libhello.pc DESTINATION lib/pkgconfig) |
Comme vous pouvez le constater, nous installons le fichier dans le sous-répertoire pkgconfig du répertoire contenant les bibliothèques. Cet emplacement semble être assez standard pour toutes les distributions.
Il ne reste plus qu’à rajouter la ligne suivante dans le CMakeLists.txt racine :
|
01: top-level CmakeLists.txt ... 61: add_subdirectrory (pkgconfig) |
Si vous reconstruisez le projet, vous pouvez constater la présence du fichier pkgconfig/libhello.pc au contenu adapté.
|
4 |
Faciliter la documentation de l’API |
Dans le numéro 102 de GLMF [2], nous avons vu qu’il était facile de documenter une API grâce à Doxygen. Servons-nous de CMake pour créer une cible dédiée à l’appel de Doxygen.
Nous allons créer une cible toute bête pour demander à Doxygen de réaliser une documentation au format html. Commençons par rajouter les commentaires nécessaires dans le fichier hello.cpp :
|
... 14: 15: /*! 16: \class Hello hello.h 17: Just to say hello. 18: */ ... 21: 22: /*! 23: Class constructor 24: */ ... 30: 31: /*! 32: Print hello message to the console. 33: */ ... |
Minimaliste ! Mais suffisant pour nous. Créons un sous-répertoire doc, allons-y et lançons doxywizard :
|
~/Hello-0.0.0 $ mkdir doc ~/Hello-0.0.0 $ cd doc ~/Hello-0.0.0/doc $ doxywizard |
Grâce à doxywizard, nous allons obtenir un fichier de configuration pour Doxygen. Cliquez sur le bouton , puis, dans le champ , mettez Hello et dans le champ mettez 0. Dans le champ , assurez-vous de sélectionner le répertoire racine du projet et cochez la case . Passez à l’onglet . Ne gardez que le HTML, puis, dans l’onglet , cochez . Sortez du en cliquant sur . Sauvez votre fichier de configuration, dans le sous-répertoire doc sous le nom Doxyfile-html.
Nous pouvons d’ores et déjà générer une documentation. Il suffit de lancer la commande doxygen Doxyfile-html. Cependant, tous les chemins indiqués dans le fichier de configuration Doxyfile-html sont des chemins absolus. Ils ne seront plus valables chez l’utilisateur. Il va falloir adapter le fichier de configuration lors de l’exécution de CMake. Renommez le fichier Doxyfile-html en Doxyfile-html.cmake, puis éditez-le. Dans la variable STRIP_FROM_PATH, mettez @Hello_BINARY_DIR@/doc/ puis, dans la variable INPUT, mettez @Hello_SOURCE_DIR@.
La suite ne varie pas beaucoup de ce que nous avons déjà fait... Créez un fichier CMakeLists.txt dans le sous-répertoire doc contenant le code suivant :
|
01: # doc CmakeLists.txt 02: 03: find_package (Doxygen) 04: 05: if (DOXYGEN_FOUND) 06: configure_file (${Hello_SOURCE_DIR}/doc/Doxyfile-html.cmake ${Hello_BINARY_DIR}/doc/Doxyfile-html @ONLY) |
|
07: add_custom_target (html ${DOXYGEN_EXECUTABLE} ${Hello_BINARY_DIR}/doc/Doxyfile-html) 08: endif (DOXYGEN_FOUND) 09: |
Nous commençons par rechercher Doxygen sur le système, puis, si Doxygen a été trouvé, nous générons le fichier de configuration et une cible html qui exécutera Doxygen. Il ne reste plus qu’à faire appel à ce nouveau fichier CMakeLists.txt à partir de celui du répertoire racine du projet :
|
01: # top-level CmakeLists.txt ... 62: add_subdirectory (doc) ... |
Et voilà le travail...
|
~/hello_build $ cmake ../Hello-0.0.0 ... ~/hello_build $ make html ... |
Le répertoire hello_build/doc contient maintenant un répertoire html avec la documentation générée par Doxygen.
Ceci est en fait le minimum que nous puissions faire. Nous pouvons perfectionner... Par exemple, il est très facile de transmettre certaines informations comme le numéro de version. Éditons le fichier Doxyfile-html.cmake. Pour la variable PROJECT_NUMBER, mettez @Hello_VERSION@. Vous pouvez également fournir une page principale au contenu " dynamique " : dans le sous-répertoire doc, créez un fichier mainpage.dox.cmake :
|
01: /*! 02: \mainpage 03: 04: This library contains a C++ class that only print a 05: "Hello World" message to the console. 06: 07: \version @Hello_VERSION@ 08: \date @Hello_RELEASE@ 09: \author Arvernux 10: */ 11: |
Puis, rajoutez la ligne suivante dans le fichier doc/CMakeLists.txt :
|
01: # doc CmakeLists.txt ... 06: configure_file (${Hello_SOURCE_DIR}/doc/mainpage.dox.cmake ${Hello_BINARY_DIR}/doc/mainpage.dox) ... |
N’oubliez pas d’inclure le répertoire de construction parmi les sources que doit parcourir Doxygen. Modifiez votre fichier Doxyfile-html.cmake :
|
... INPUT = @Hello_SOURCE_DIR@ @Hello_BINARY_DIR@ ... |
Si vous exécutez de nouveau CMake, puis si vous relancez make html, vous obtiendrez alors une documentation avec une page principale qui s’adaptera automatiquement aux changements de version et de date de publication, conformément à notre technique de gestion centralisée de ceux-ci avec le fichier version.cmake.
Encore plus fort... Vous êtes développeur francophone... Alors proposez une documentation en français pour les francophones et une documentation en anglais pour les autres. Nous allons pour réaliser cela exploiter les possibilités de Doxygen et de CMake.
Commençons par traduire la documentation. Renommez le fichier mainpage.dox.cmake en mainpage-en.dox.cmake, puis créez un fichier mainpage-fr.dox.cmake contenant la traduction du premier :
|
01: /*! 02: \mainpage 03: 04: Cette bibliothèque fournit une classe C++ dont le seul but 05: est d’afficher un message "Bonjour Monde" sur la console. 06: 07: \version @Hello_VERSION@ 08: \date @Hello_RELEASE@ 09: \author Arvernux 10: */ 11: |
Ensuite, traduisez la documentation contenue dans le fichier src/hello.cpp :
|
01: //hello.cpp ... 15: /*! 16: \class Hello hello.h 17: \~english 18: Just to say hello. 19: \~french 20: Juste pour dire bonjour. 21: */ ... 25: /*! 26: \~english 27: Class constructor. 28: \~french 29: Constructeur de la classe. 30: */ ... 38: /*! 39: \~english 40: Print hello message to the console. 41: \~french 42: Affiche le message bonjour sur la console. 43: */ |
Ensuite, modifiez le fichier de configuration de Doxygen pour pouvoir lui préciser la langue à utiliser. Éditez le fichier doc/Doxyfile-html.cmake :
|
... OUTPUT_LANGUAGE = @Hello_DOC_LANGUAGE@ ... |
Pour finir, utilisons CMake pour configurer correctement la variable Hello_DOC_LANGUAGE et le fichier mainpage.dox :
|
01: # doc/CMakeLists.txt 02: 03: find_package (Doxygen) 04: 05: if (DOXYGEN_FOUND) 06: if ("$ENV{LANG}" MATCHES "^fr_*") 07: set (Hello_DOC_LANGUAGE French) 08: configure_file (${Hello_SOURCE_DIR}/doc/mainpage-fr.dox.cmake ${Hello_BINARY_DIR}/doc/mainpage.dox @ONLY) 09: else ("$ENV{LANG}" MATCHES "^fr_*") 10: set (Hello_DOC_LANGUAGE English) 11: configure_file (${Hello_SOURCE_DIR}/doc/mainpage-en.dox.cmake ${Hello_BINARY_DIR}/doc/mainpage.dox @ONLY) 12: endif ("$ENV{LANG}" MATCHES "^fr_*") 13: configure_file (${Hello_SOURCE_DIR}/doc/Doxyfile-html.cmake ${Hello_BINARY_DIR}/doc/Doxyfile-html @ONLY) 14: add_custom_target (html ${DOXYGEN_EXECUTABLE} ${Hello_BINARY_DIR}/doc/Doxyfile-html) 15: endif (DOXYGEN_FOUND) 16: |
Ce code mérite quelques explications... À la ligne 6, nous faisons un test sur la variable d’environnement LANG. Si celle-ci commence par fr_ (vous avez bien sûr reconnu la syntaxe d’une expression régulière ;-), alors l’utilisateur parle le français. Dans ce cas, la ligne 7 positionne la variable Hello_DOC_LANGUAGE à French et la ligne 8 configure le fichier mainpage-fr.dox.cmake et le renomme en mainpage.dox. Sinon, l’utilisateur ne parle pas le français. Alors la ligne 10 positionne la variable Hello_DOC_LANGUAGE à English et la ligne 11 configure le fichier mainpage-en.dox.cmake et le renomme en mainpage.dox.
Générez la documentation :
|
~/hello_build $ cmake ../Hello-0.0.0 ... ~/hello_build $ make html ... |
Cette fois-ci, la documentation sera en français. Magique !
Vous pouvez continuer en rajoutant des cibles pour la génération de pages de manuel, etc. Vous avez la trame de départ.
|
5 |
Conclusion |
Voilà, nous sommes arrivés au terme de cet article.
J’espère, à travers ces deux articles, vous avoir montré quelques voies pour réaliser vos projets. J’espère également vous avoir convaincu de la puissance de CMake.
Personnellement, autant les autotools étaient une horreur à manipuler pour moi, autant j’apprécie CMake.
Je ne sais pas vous, mais j’attends avec impatience la prochaine version 2.6.
|
Liens et références |
|
● [1] METTIER (Yves), " pkg-config et les autotools ", GNU/Linux Magazine France, numéro 81, page 54. ● [2] BUFFENOIR (Christophe), " Documentation avec Doxygen ", GNU/Linux Magazine France, numéro 102, page 72. ● Documentation de CMake 2.4 : ● Documentation de Doxygen : ● Site de l’auteur : http://www.arvernux.fr |
Retrouvez cet article dans : Linux Magazine 108


