Auteur: R. Koucha
Dernère mise à jour: 03-jan-2017


Retour à la page principale
Retour à la page précédente


Approche empirique de CMAKE










s



Introduction
Projet 1 : Génération d'un exécutable
Projet 2 : Génération d'un exécutable lié à une librairie
Projet 3 : Génération d'un exécutable et d'une librairie partagée avec le même nom
Projet 4 : Génération et installation d'un programme avec ses manuels
Projet 5 : Installation d'un exécutable et d'une API en librairie partagée avec les fichiers d'entête et les manuels
Projet 6 : Packaging d'un projet
Projet 7 : Génération d'un fichier de configuration (config.h)
Projet 8 : Génération d'un programme Linux et de son module kernel
Projets libres utilisant cmake
Ressources
A propos de l'auteur



Introduction

cmake est un nouvel outil de génération pour les projets logiciels. Il est destiné à remplacer les célèbres autotools.

cmake est libre et bien décrit ici. Parmi les nombreuses fonctionnalités offertes, on peut citer les suivantes :

Le but de cet article est d'aider le lecteur à maîtriser les concepts de base de cmake à travers des petits projets logiciels.



Projet 1 : Génération d'un exécutable

Le projet appelé PROJECT1 est composé du sous-arbre suivant (les fichiers sont téléchargeables à partir des hyperliens):

   simple
     /\
    /  \
   /    \
 main.c simple

Le projet génère une cible : un exécutable appelé simple. Tous les fichiers objets sont stockés dans le répertoire racine du projet.

Le sous-arbre suivant montre les fichiers qui ont été ajoutés pour contruire le projet.

         simple
           /    |      \
     /   |    \
    /    |     \
   /     |      \
main.c  simple  CMakeLists.txt

Quand les fichiers cmake sont configurés, lancer cmake pour créer l'environnement de génération :

$ cd simple
$ cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: .../simple

Ainsi, les makefiles sont prêts pour générer le projet :

$ make
Scanning dependencies of target simple
[100%] Building C object CMakeFiles/simple.dir/main.o
Linking C executable simple
[100%] Built target simple

Ensuite, il est possible de faire le ménage en effaçant tous les fichiers générés par l'environnement cmake avec la commande suivante :

$ make clean

CMakeLists.txt dans project1

simple est le répertoire racine du projet. C'est l'endroit où on lance la commande cmake.

C'est le CMakeLists.txt racine du projet.
On identifie le projet et le langage de programmation avec la directive PROJECT.
On définit les flags qui vont être passés en argument au compilateur via la directive ADD_DEFINITIONS. C'est facultatif de faire cela mais les flags par défaut peuvent se révéler insuffisamment sévères pour détecter les warnings et les erreurs.
On définit la cible simple avec la directive ADD_EXECUTABLE.


PROJECT(project1 C)

ADD_DEFINITIONS(-g -O2 -fsigned-char -freg-struct-return -Wall -W -Wshadow -Wstrict-prototypes -Wpointer-arith -Wcast-qual -Winline -Werror)

ADD_EXECUTABLE(simple main.c)



Projet 2 : Génération d'un exécutable lié à une librairie

Le projet appelé PROJECT2 est composé du sous-arbre suivant (les fichiers sont téléchargeables à partir des hyperliens):

   crypto
     /\
    /  \
   /    \
 main.c  crypto

Le projet génère une cible : un exécutable appelé crypto. Tous les fichiers objets sont stockés dans le répertoire racine du projet. L'exécutable a besoin de la librairie partagée libcrypt.so.

Le sous-arbre suivant montre les fichiers qui ont été ajoutés pour contruire le projet.


          crypto
           /    |      \
     /   |    \
    /    |     \
   /     |      \
main.c  crypto  CMakeLists.txt

Quand les fichiers cmake sont configurés, lancer cmake pour créer l'environnement de génération :

$ cd crypto
$ cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: .../crypto

Ainsi, les makefiles sont prêts pour générer le projet :

$ make
[100%] Built target crypto

Ensuite, il est possible de faire le ménage en effaçant tous les fichiers générés par l'environnement cmake avec la commande suivante :

$ make clean

CMakeLists.txt dans project2

crypto est le répertoire racine du projet. C'est l'endroit où on lance la commande cmake.

C'est le CMakeLists.txt racine du projet.
On identifie le projet et le langage de programmation avec la directive PROJECT.
On définit les flags qui vont être passés en argument au compilateur via la directive ADD_DEFINITIONS. C'est facultatif de faire cela mais les flags par défaut peuvent se révéler insuffisamment sévères pour détecter les warnings et les erreurs.
On définit la cible crypto avec la directive ADD_EXECUTABLE.
Comme crypto dépend de la librairie partagée libcrypt.so, on spécifie cela avec la directive TARGET_LINK_LIBRARIES.

PROJECT(project2 C)

ADD_DEFINITIONS(-g -O2 -fsigned-char -freg-struct-return -Wall -W -Wshadow -Wstrict-prototypes -Wpointer-arith -Wcast-qual -Winline -Werror)

ADD_EXECUTABLE(crypto main.c)

TARGET_LINK_LIBRARIES(crypto crypt)


Projet 3 : Génération d'un exécutable et d'une librairie partagée avec la même nom

Le projet appelé ROOF est composé du sous-arbre suivant :

                roof
            /   |  \     \
           /    |   \     \
          /     |    \     \
         /      |     \     \
        /       |      \     \
       /        |       \     \
      /         |        \     \
     /          |         \     \
    lib        client   include  bin
     |          |          |      |
     |          |          |      |
   roof.c      main.c    roof.h   main.o
   roof_p.h                       roof.o
                                  roof
                                  libroof.so

                             
Le projet génère deux cibles : une librarie partagée appelée libroof.so et un exécutable appelé roof. Tous les fichiers objets sont stockés dans le sous-répertoire bin du projet.

Le sous-arbre suivant montre les fichiers qui ont été ajoutés pour contruire le projet.


                             roof
           /         |            \       \         \
          /          |             \       \         \
         /           |              \       \         \
        /            |               \       \         \
       /             |                \       \         \
      /              |                 \       \         \
     /               |                  \       \         \
    /                |                   \       \         \
   lib              client               include    bin   CMakeLists.txt
    |                |                   |        |
    |                |                   |        |
  roof.c            main.c             roof.h     main.o
  roof_p.h         
CMakeLists.txt              roof.o
 
CMakeLists.txt                                roof
                                                  libroof.so


Quand les fichiers cmake sont configurés, lancer cmake pour créer l'environnement de génération :

$ cd roof
$ cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: .../roof

Ainsi, les makefiles sont prêts pour générer le projet :

$ make
[ 50%] Building C object bin/CMakeFiles/roof.dir/roof.o
Linking C shared library libroof.so
[ 50%] Built target roof
[100%] Building C object bin/CMakeFiles/main.dir/main.o
Linking C executable roof
[100%] Built target main


Ensuite, il est possible de faire le ménage en effaçant tous les fichiers générés par l'environnement cmake avec la commande suivante :

$ make clean

CMakeLists.txt dans roof

roof est le répertoire racine du projet. C'est l'endroit où on lance la commande cmake.

C'est le CMakeLists.txt racine du projet.
On identifie le projet ainsi que le langage de programmation avec la directive PROJECT.
On spécifie les répertoires où sont localisés les fichiers d'entête (include et lib) avec la directive INCLUDE_DIRECTORIES.
On définit les flags qui vont être passés en argument au compilateur via la directive ADD_DEFINITIONS. C'est facultatif de faire cela mais les flags par défaut peuvent se révéler insuffisamment sévères pour détecter les warnings et les erreurs.
On spécifie les répertoires où sont localisés les fichiers source à compiler (lib et client) ainsi que répertoire de destination des fichiers objet (bin) avec la directive ADD_SUBDIRECTORY.

PROJECT(roof C)

INCLUDE_DIRECTORIES(include lib)

ADD_DEFINITIONS(-g -O2 -fsigned-char -freg-struct-return -Wall -W -Wshadow -Wstrict-prototypes -Wpointer-arith -Wcast-qual -Winline -Werror)


ADD_SUBDIRECTORY(lib bin)
ADD_SUBDIRECTORY(client bin)


CMakeLists.txt dans lib

Le répertoire lib contient les fichiers pour fabriquer la librairie partagée libroof.so.

On affecte la variable roof_lib_src avec la liste des fichiers source nécessaire à la construction de la librairie partagée. Il n'y a en fait qu'un fichier appelé roof.c. Cela se fait avec la directive SET.
Ensuite, on spécifie la librairie partagée à construire avec la directive ADD_LIBRARY. On passe à cette directive le nom de la librairie (roof) pour générer libroof.so, le mot clé SHARED pour spécifier qu'il s'agit d'une librairie partagée et la liste des fichiers sources de la librairie avec la variable roof_lib_src.

SET(roof_lib_src roof.c)

ADD_LIBRARY(roof SHARED ${roof_lib_src})

CMakeLists.txt dans client

Le répertoire client contient les fichiers pour fabriquer l'exécutable roof.

On définit d'abord l'exécutable cible avec la directive ADD_EXECUTABLE.
On affecte la variable roof_client_src avec la liste des fichiers source utilisés pour construire l'exécutable. Il n'y a en fait qu'un fichier appelé main.c. Cela se fait avec la directive SET.
On spécifie la dépendance de l'exécutable roof avec la librairie libroof.so via la directive TARGET_LINK_LIBRARIES. Il y a un problème ici car on veut générer un exécutable appelé roof et une librairie appelée libroof.so. Normalement, la directive devrait s'écrire :

ADD_EXECUTABLE(roof)
TARGET_LINK_LIBRARIES(roof roof)

Mais cmake génère une erreur :

CMake Error: Attempt to add link target roof of type: EXECUTABLE
to target roof. You can only link to STATIC or SHARED libraries.

En fait, cmake est perturbé car on lui donne l'impression de générer roof avec lui même. D'où l'utilisation du nom intermédiaire main dans les directives TARGET_LINK_LIBRARIES et ADD_EXECUTABLE. Ensuite, on indique à cmake de renommer l'exécutable main en roof grâce à la directive SET_TARGET_PROPERTIES.

SET(roof_client_src main.c)

ADD_EXECUTABLE(main ${roof_client_src})

TARGET_LINK_LIBRARIES(main roof)
SET_TARGET_PROPERTIES(main
                      PROPERTIES OUTPUT_NAME roof)


Projet 4 : Génération et installation d'un programme avec ses manuels

Le projet appelé PDIP construit un exécutable appelé pdip et un ensemble de manuels Linux compressés avec l'outil gzip.
Le projet est dans le sous-arbre suivant (les fichiers sont téléchargeables à partir des hyperliens):

  pdip
   |
   |
 pdip.c
 pdip_en.1 
 pdip_fr.1



pdip.c est le source du programme et pdip_en.1 ainsi que pdip_fr.1 sont respectivement les manuels anglais et français.
Le sous-arbre suivant montre les fichiers qui ont été ajoutés pour contruire le projet.

   pdip
    |
    |
  pdip.c
  pdip_en.1
  pdip_fr.1
  FindGZIP.cmake
  CMakeLists.txt
  pdip_chown.cmake

   

Quand les fichiers cmake sont configurés, lancer cmake pour créer l'environnement de génération :

$ cd pdip
-- Configuring done
-- Generating done
-- Build files have been written to: .../pdip 


Pour déclencher la génération :

$ make
[ 33%] Building pdip_fr.1.gz
[ 66%] Building pdip_en.1.gz
Scanning dependencies of target pdip
[100%] Building C object CMakeFiles/pdip.dir/pdip.o
Linking C executable pdip
[100%] Built target pdip


Pour déclencher l'installation du logiciel :

$ make install

Ensuite, il est possible de faire le ménage en effaçant tous les fichiers générés par l'environnement cmake avec la commande suivante :

$ make clean

FindGZIP.cmake dans pdip

Comme le projet a besoin d'installer les manuels dans le format compressé, on utilise la directive FIND_PROGRAM pour contrôler la présence de l'outil gzip (spécifié après le mot clé NAMES). La directive reçoit en paramètres la liste des répertoires où effectuer la recherche (/bin, /usr/bin and /usr/local/bin). Le résultat de la commande est stocké dans la variable GZIP_TOOL qui est passé comme premier paramètre à la directive. Si gzip est trouvé, son chemin est stocké dans GZIP_TOOL. S'il n'est pas trouvé, GZIP_TOOL se voit assigner GZIP_TOOL-NOTFOUND.

Le résultat de la recherche est testé avec la directive IF qui retourne vrai si la valeur de la variable n'est pas vide, 0,  N, NO, OFF, FALSE, NOTFOUND ou <variable>-NOTFOUND. Si le test est faux, un message est affiché via la directive MESSAGE avec un poids positionné à FATAL_ERROR pour arrêter tout processing.

FIND_PROGRAM(GZIP_TOOL
             NAMES gzip
             PATHS /bin
                   /usr/bin
                   /usr/local/bin)


IF(NOT GZIP_TOOL)
  MESSAGE(FATAL_ERROR "Unable to find 'gzip' program")
ENDIF(NOT GZIP_TOOL)


CMakeLists.txt dans pdip

Ce projet ne contient qu'un fichier CMakeLists.txt.
Le nom du projet pdip ainsi que le langage de programmation utilisé (C) sont déclarés avec la directive PROJECT.
Ensuite, on inclut le fichier FindGZIP.cmake pour déclencher la recherche de l'outil gzip (directive INCLUDE).
Si l'outil gzip est trouvé, son chemin est stocké dans la variable GZIP_TOOL.
Trois variables sont positionnées avec la directive SET :
Dans la boucle (directive FOREACH), on définit les cibles des manuels et la manière de les construire avec la directive ADD_CUSTOM_COMMAND. Cette directive liste aussi les dépendances (DEPENDS) de la cible pour ne déclencher la génération qu'à la condition où les sources des manuels ont été modifiés. L'étiquette COMMENT est utile pour faire en sorte que cmake affiche la chaîne de caractères "Building pdip_....1.gz" quand il exécute la commande.

On définit les flags qui vont être passés en argument au compilateur via la directive ADD_DEFINITIONS. C'est facultatif de faire cela mais les flags par défaut peuvent se révéler insuffisamment sévères pour détecter les warnings et les erreurs.

Le point d'entrée de la génration est la directive ADD_EXECUTABLE qui indique que le programme cible pdip dépend du source pdip.c et des manuels compressés pdip_en.1.gz et pdip_fr.1.gz.

LEs directives suivantes décrivent l'installation du logiciel. L'installation est faite à partir du répertoire spécifié dans la variable CMAKE_INSTALL_PREFIX. Pa défaut, c'est /usr/local:
cmake ne fournit pas de facilités pour changer les identifiants de propriétaire et de groupe. On doit indiquer un script à lancer à la fin de l'installation avec la directive INSTALL(SCRIPT...). Le script appelé pdip_chown.cmake est écrit en langage cmake et ne fait que changer les propriétaires et groupes à la valeur "root" en appelant les commandes shell chown and chgrp avec la directive EXECUTE_PROCESS.

PROJECT(pdip C)

# Search for gzip program
INCLUDE (FindGZIP.cmake)

SET(pdip_src pdip.c)
SET(pdip_exe pdip)
SET(pdip_man_src pdip_en.1 pdip_fr.1)
SET(pdip_man_gz pdip_en.1.gz pdip_fr.1.gz)

# Compression of the manuals
FOREACH(man ${pdip_man_src})
  ADD_CUSTOM_COMMAND(OUTPUT ${man}.gz
                     COMMAND ${GZIP_TOOL} -c ${man} > ${man}.gz
                     DEPENDS ${man}
                     COMMENT "Building ${man}.gz")
ENDFOREACH(man)

# Compilation options passed to the compiler
ADD_DEFINITIONS(-g -O2 -fsigned-char -freg-struct-return -Wall -W -Wshadow -Wstrict-prototypes -Wpointer-arith -Wcast-qual -Winline -Werror)

# Build of the program
ADD_EXECUTABLE(${pdip_exe} ${pdip_src} ${pdip_man_gz})

# Installation of the program
INSTALL(TARGETS pdip
        DESTINATION "bin"
        PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)

# Installation of the manuals
INSTALL(FILES pdip_fr.1.gz
        DESTINATION "share/man/fr/man1"
        PERMISSIONS OWNER_READ GROUP_READ WORLD_READ RENAME pdip.1.gz)
INSTALL(FILES pdip_en.1.gz
        DESTINATION "share/man/man1"
        PERMISSIONS OWNER_READ GROUP_READ WORLD_READ RENAME pdip.1.gz)

# Script to be executed at installation time (kind of post-intallation script) to
# change the right accesses on the installed files
INSTALL(SCRIPT pdip_chown.cmake)

pdip_chown.cmake dans pdip


# Copy the files to the destination directory
EXECUTE_PROCESS(COMMAND chown root ${CMAKE_INSTALL_PREFIX}/bin/pdip
                COMMAND chgrp root ${CMAKE_INSTALL_PREFIX}/bin/pdip
                COMMAND chown root ${CMAKE_INSTALL_PREFIX}/share/man/fr/man1/pdip.1.gz
                COMMAND chgrp root ${CMAKE_INSTALL_PREFIX}/share/man/fr/man1/pdip.1.gz
                COMMAND chown root ${CMAKE_INSTALL_PREFIX}/share/man/man1/pdip.1.gz
                COMMAND chgrp root ${CMAKE_INSTALL_PREFIX}/share/man/man1/pdip.1.gz)



Project 5 : Installation d'un exécutable et d'une API en librairie partagée avec les fichiers d'entête et manuels

Le projet appelé ROOF est composé du sous-arbre suivant :

              roof
            /   |  \        \
           /    |   \        \
          /     |    \        \
         /      |     \        \
        /       |      \        \
       /        |       \        \
      /         |        \        \
     /          |         \        \
    lib        client   include    man
     |          |          |        |
     |          |          |        |
   roof.c      main.c    roof.h    roof.1
   roof_p.h                        roof.3
                                   ...
                                   roof.7


                             
Le projet génère les cibles suivantes :
Le sous-arbre suivant montre les fichiers qui ont été ajoutés pour contruire le projet.

                             roof
           /         |            \             \         \
          /          |             \             \         \
         /           |              \             \         \
        /            |               \             \         \
       /             |                \             \         \
      /              |                 \             \         \
     /               |                  \             \         \
    /                |                   \             \         \
   lib              client               include          man   CMakeLists.txt
    |                |                   |              |
    |                |                   |              |
  roof.c            main.c             roof.h          roof.1
  roof_p.h         
CMakeLists.txt   CMakeLists.txt  roof.3
 
CMakeLists.txt                                     ...
                                                       roof.7
                                                       CMakeLists.txt
                                                       FindGZIP.cmake



Quand les fichiers cmake sont configurés, lancer cmake pour générer l'environment de génération (avec /tmp comme répertoire d'installation cible au lieu de /usr/local par défaut):

$ cd roof
$ cmake . -DCMAKE_INSTALL_PREFIX=/tmp
-- Configuring done
-- Generating done
-- Build files have been written to: .../roof


Ensuite, les makefiles sont prêts à déclencher la génération avec :

$ make
[ 50%] Building C object bin/CMakeFiles/roof.dir/roof.o
Linking C shared library libroof.so
[ 50%] Built target roof
[100%] Building C object bin/CMakeFiles/main.dir/main.o
Linking C executable roof
[100%] Built target main


Pour installer les fichiers dans /tmp, il suffit de lancer :

$ make install
[  3%] Built target roof
[  7%] Built target main
[100%] Built target man
Install the project...
-- Install configuration: ""
-- Install configuration: ""
-- Installing /tmp/lib/libroof.so.1.0.0
-- Install configuration: ""
-- Installing /tmp/bin/roof
-- Install configuration: ""
-- Installing /tmp/share/man/man1/roof.1.gz
-- Installing /tmp/share/man/man3/roof_cwd.3.gz
-- Installing /tmp/share/man/man3/roof_login.3.gz
...

Ensuite, il est possible de faire le ménage en effaçant tous les fichiers générés par l'environnement cmake avec la commande suivante :

$ make clean

CMakeLists.txt dans roof

Le répertoire roof est la racine du projet. C'est l'endroit où on lance la commande cmake.

C'est le CMakeLists.txt racine du projet.
On identifie le projet ainsi que le langage de programmation avec la directive PROJECT.
On spécifie les répertoires où sont localisés les fichiers d'entête (include et lib) avec la directive INCLUDE_DIRECTORIES.
On définit les flags qui vont être passés en argument au compilateur via la directive ADD_DEFINITIONS. C'est facultatif de faire cela mais les flags par défaut peuvent se révéler insuffisamment sévères pour détecter les warnings et les erreurs.
On spécifie les répertoires où sont localisés les fichiers source à compiler (lib, client, man et include) avec la directive ADD_SUBDIRECTORY. On ne spécifie pas de répertoire pour les binaires (deuxième paramètre de ADD_SUBDIRECTORY) car on a été confronté à des problèmes lors de l'installation des librairies partagées (Bug dans cmake ?).

PROJECT(roof C)

INCLUDE_DIRECTORIES(include lib)

ADD_DEFINITIONS(-g -O2 -fsigned-char -freg-struct-return -Wall -W -Wshadow -Wstrict-prototypes -Wpointer-arith -Wcast-qual -Winline -Werror)

ADD_SUBDIRECTORY(lib)
ADD_SUBDIRECTORY(client)
ADD_SUBDIRECTORY(man)
ADD_SUBDIRECTORY(include)


CMakeLists.txt dans lib

Le répertoire lib contient tous les fichiers pour faire la librairie partagée libroof.so.

On défnit la librairie partagée à construire avec la directive ADD_LIBRARY. Cette directive reçoit en partamètres le nom de la librairie (roof) pour générer libroof.so, le mot clé SHARED pour indiquer qu'il s'agit d'une librairie partagée et la liste des fichiers source composant la librairie : roof.c.
On définit la version de la génération (VERSION) et la version de l'API (SOVERSION) de la librairie via la directive SET_TARGET_PROPERTIES.
L'installation de la librairie est spécifiée avec la directive INSTALL. On indique ici que le répertoire destination dans le sous-arbre d'installation est lib. Le sous-arbre d'installation est spécifié par la variable CMAKE_INSTALL_PREFIX. Par défaut, c'est /usr/local.

# Make shared library libroof.so from 'roof.c'
ADD_LIBRARY(roof SHARED roof.c)

# Set the build version (VERSION) and the API version (SOVERSION)
SET_TARGET_PROPERTIES(roof
                      PROPERTIES
                      VERSION 1.0.0
                      SOVERSION 1)

# Installation of the library
INSTALL(TARGETS roof
        DESTINATION lib
        PERMISSIONS OWNER_READ GROUP_READ WORLD_READ)

CMakeLists.txt dans client

Le répertoire client contient les fichiers pour générer l'exécutable roof.

On définit d'abord l'exécutable cible roof avec la directive ADD_EXECUTABLE.
On définit la dépendance de l'exécutable avec la librairie libroof.so avec la directive TARGET_LINK_LIBRARIES. Il y a un problème ici car on veut générer un exécutable appelé roof et une librairie appelée libroof.so. Normalement, la directive devrait s'écrire :

ADD_EXECUTABLE(roof)
TARGET_LINK_LIBRARIES(roof roof)

Mais cmake génère une erreur :

CMake Error: Attempt to add link target roof of type: EXECUTABLE
to target roof. You can only link to STATIC or SHARED libraries.


En fait, cmake est perturbé car on lui donne l'impression de générer roof avec lui même. D'où l'utilisation du nom intermédiaire main dans les directives TARGET_LINK_LIBRARIES et ADD_EXECUTABLE. Ensuite, on indique à cmake de renommer l'exécutable main en roof grâce à la directive SET_TARGET_PROPERTIES.

L'installation du programme est indiquée par la directive INSTALL. On indique ici que le répertoire destination dans le sous-arbre d'installation est bin. Le sous-arbre d'installation est spécifié par la variable CMAKE_INSTALL_PREFIX. Par défaut, c'est /usr/local.


# 'main' depends on some C source files
ADD_EXECUTABLE(main main.c)

# 'main' depends on libroof.so
TARGET_LINK_LIBRARIES(main roof)

# In the preceding rules, we can't use 'roof' as target name otherwise
# cmake will return in error with TARGET_LINK_LIBRARIES(roof roof):
#
#    CMake Error: Attempt to add link target roof of type: EXECUTABLE
#    to target roof. You can only link to STATIC or SHARED libraries.
#
# Hence the SET_TARGET_PROPERTIES to rename main to roof
#
SET_TARGET_PROPERTIES(main
                      PROPERTIES OUTPUT_NAME roof)

# Installation of the program
INSTALL(TARGETS main
        RUNTIME
        DESTINATION bin
        PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)

CMakeLists.txt dans include

L'installation des fichiers d'entête est spécifiée avec la directive INSTALL. On indique ici que le répertoire destination dans le sous-arbre d'installation est include. Le sous-arbre d'installation est spécifié par la variable CMAKE_INSTALL_PREFIX. Par défaut, c'est /usr/local.

INSTALL(FILES roof.h
        DESTINATION include
        PERMISSIONS OWNER_READ GROUP_READ WORLD_READ)


FindGZIP.cmake dans man

Comme le projet a besoin d'installer les manuels dans le format compressé, on utilise la directive FIND_PROGRAM pour contrôler la présence de l'outil gzip (spécifié après le mot clé NAMES). La directive reçoit en paramètres la liste des répertoires où effectuer la recherche (/bin, /usr/bin and /usr/local/bin). Le résultat de la commande est stocké dans la variable GZIP_TOOL qui est passé comme premier paramètre à la directive. Si gzip est trouvé, son chemin est stocké dans GZIP_TOOL. S'il n'est pas trouvé, GZIP_TOOL se voit assigner GZIP_TOOL-NOTFOUND.

Le résultat de la recherche est testé avec la directive IF qui retourne vrai si la valeur de la variable n'est pas vide, 0,  N, NO, OFF, FALSE, NOTFOUND ou <variable>-NOTFOUND. Si le test est faux, un message est affiché via la directive MESSAGE avec un poids positionné à FATAL_ERROR pour arrêter tout processing.

FIND_PROGRAM(GZIP_TOOL
             NAMES gzip
             PATHS /bin
                   /usr/bin
                   /usr/local/bin)


IF(NOT GZIP_TOOL)
  MESSAGE(FATAL_ERROR "Unable to find 'gzip' program")
ENDIF(NOT GZIP_TOOL)

CMakeLists.txt dans man

On inclut d'abord le script FindGZIP.cmake pour vérifier la présence de l'outil gzip.
On utilise la directive SET pour positionner trois variables avec la liste des sources des manuels.
Grâce à la directive STRING de manipulation des chaînes de caractères, on définit trois autres variables contenant la liste des manuels compressés (suffix .gz).
Pour chaque manuel source, on utilise ADD_CUSTOM_COMMAND pour générer la commande qui compresse les manuels sources.
On définit la cible man à ajouter à la génération par défaut de sorte à ce qu'elle soit générée à chaque fois avec la directive ADD_CUSTOM_TARGET. Mais on la fait dépendre des sources des manuels pour éviter de compresser les manuels à chaque fois qu'on appelle make.
L'installation des manuels compressés est spécifiée avec la directive INSTALL. On indique ici que le répertoire destination dans le sous-arbre d'installation est share/man/manX. Le sous-arbre d'installation est spécifié par la variable CMAKE_INSTALL_PREFIX. Par défaut, c'est /usr/local.

# Search for gzip program
INCLUDE (FindGZIP.cmake)

# Lists of source manuals
SET(roof_man_src_1 roof.1)
SET(roof_man_src_3 roof_cwd.3
                   roof_login.3
                   roof_pwd.3
                   roof_syst.3
                   roof_delete.3
                   roof_mkdir.3
                   roof_retr.3
                   roof_type.3
                   roof.3
                   roof_get_debug_level.3
                   roof_mv.3
                   roof_rm.3
                   roof_get_reply.3
                   roof_new.3
                   roof_rmdir.3
                   roof_cdup.3
                   roof_initialize.3
                   roof_nlst.3
                   roof_set_debug_level.3
                   roof_close_ctrl.3
                   roof_list.3
                   roof_open_ctrl.3
                   roof_stor.3)
SET(roof_man_src_7 roof.7)

# Lists of compressed manuals
STRING(REGEX REPLACE ".1" ".1.gz" roof_man_gz_1 "${roof_man_src_1}")
STRING(REGEX REPLACE ".3" ".3.gz" roof_man_gz_3 "${roof_man_src_3}")
STRING(REGEX REPLACE ".7" ".7.gz" roof_man_gz_7 "${roof_man_src_7}")


# Compression of the manuals
FOREACH(man ${roof_man_src_1} ${roof_man_src_3} ${roof_man_src_7})
  ADD_CUSTOM_COMMAND(OUTPUT ${man}.gz
                     COMMAND ${GZIP_TOOL} -c ${man} > ${man}.gz
                     DEPENDS ${man}
                     COMMENT "Building ${man}.gz")
ENDFOREACH(man)

# Add the manual generation in the global rules
ADD_CUSTOM_TARGET(man ALL
                  DEPENDS ${roof_man_gz_1} ${roof_man_gz_3} ${roof_man_gz_7})

# Installation of the manuals
INSTALL(FILES ${roof_man_gz_1}
        DESTINATION "share/man/man1"
        PERMISSIONS OWNER_READ GROUP_READ WORLD_READ)
INSTALL(FILES ${roof_man_gz_3}
        DESTINATION "share/man/man3"
        PERMISSIONS OWNER_READ GROUP_READ WORLD_READ)
INSTALL(FILES ${roof_man_gz_7}
        DESTINATION "share/man/man7"
        PERMISSIONS OWNER_READ GROUP_READ WORLD_READ)


Projet 6 : Packaging d'un projet

Sur ce lien, il est expliqué comment "packager" un projet. Grosso modo, cmake est associé à l'outil CPack pour générer les packages. Pour le moment, les packages DEB et RPM ne sont pas supportés. Seuls les formats suivants sont supportés :

Dans ce projet, on génère un package pour le projet 5. Il suffit d'ajouter la directive INCLUDE(CPack) dans le fichier CMakeLists.txt racine. De manière optionnelle, il est possible de positionner des variables de configuration pour CPack telles que CPACK_PACKAGE_VERSION_MAJOR, CPACK_PACKAGE_VERSION_MINOR, CPACK_PACKAGE_VERSION_PATCH...
Cela génère une nouvelle cible appelée package dans le système de build. Quand cette cible est générée, CPack est sollicité pour générer tous les packages. En interne, CPack utilise les directives d'installation de CMake pour construire le package.

PROJECT(roof C)

INCLUDE_DIRECTORIES(include lib)

ADD_DEFINITIONS(-g -O2 -fsigned-char -freg-struct-return -Wall -W -Wshadow -Wstrict-prototypes -Wpointer-arith -Wcast-qual -Winline -Werror)

ADD_SUBDIRECTORY(lib)
ADD_SUBDIRECTORY(client)
ADD_SUBDIRECTORY(man)
ADD_SUBDIRECTORY(include)

SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Remote Operations On Files")
SET(CPACK_PACKAGE_VENDOR "Rachid Koucha")
#SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/ReadMe.txt")
SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING")
SET(CPACK_PACKAGE_VERSION_MAJOR "1")
SET(CPACK_PACKAGE_VERSION_MINOR "0")
SET(CPACK_PACKAGE_VERSION_PATCH "0")
SET(CPACK_PACKAGE_INSTALL_DIRECTORY "CMake ${CMake_VERSION_MAJOR}.${CMake_VERSION_MINOR}")
#SET(CPACK_STRIP_FILES "bin/MyExecutable")
#SET(CPACK_SOURCE_STRIP_FILES "")
SET(CPACK_PACKAGE_EXECUTABLES "roof" "FTP client")
INCLUDE(CPack)



Appeler la commande suivante pour générer le package :

$ make package
[  3%] Built target roof
[  7%] Built target main
[100%] Built target man
Linking C executable CMakeFiles/CMakeRelink.dir/roof
Run CPack packaging tool...
CPack: Create package using STGZ
CPack: Install projects
CPack: - Run preinstall target for: roof
CPack: - Install project: roof
CPack: Compress package
CPack: Finalize package
CPack: Package .../roof/roof-1.0.0-Linux.sh generated.
CPack: Create package using TGZ
CPack: Install projects
CPack: - Run preinstall target for: roof
CPack: - Install project: roof
CPack: Compress package
CPack: Finalize package
CPack: Package .../roof/roof-1.0.0-Linux.tar.gz generated.
CPack: Create package using TZ
CPack: Install projects
CPack: - Run preinstall target for: roof
CPack: - Install project: roof
CPack: Compress package
CPack: Finalize package
CPack: Package .../roof/roof-1.0.0-Linux.tar.Z generated.


Comme on peut le voir, cela génère les packages STGZ, TGZ et TZ. Les packages ne contiennent pas les sources mais seulement les binaires.

Installation à partir du package STGZ dans le répertoire /tmp :

$ ./roof-1.0.0-Linux.sh --help
Usage: ./roof-1.0.0-Linux.sh [options]
Options: [defaults in brackets after descriptions]
  --help            print this message
  --prefix=dir      directory in which to install
  --include-subdir  include the roof-1.0.0-Linux subdirectory
  --exclude-subdir  exclude the roof-1.0.0-Linux subdirectory

$ ./roof-1.0.0-Linux.sh --prefix=/tmp --exclude-subdir
roof Installer Version: 1.0.0, Copyright (c) Rachid Koucha
This is a self-extracting archive.
The archive will be extracted to: /tmp

Using target directory: /tmp
Extracting, please wait...

Unpacking finished successfully
$




Projet 7 : Génération d'un fichier de configuration (config.h)

Le projet appelé PROJET7 est composé du sous-arbre suivant (les fichiers sont téléchargeables à partir des hyperliens):


   simple
     /\
    /  \
   /    \
 main.c simple

Le projet génère une cible : un exécutable appelé simple. Tous les fichiers objets sont stockés dans le répertoire racine du projet.
Le fichier main.c a besoin de macros de configuration localisées dans le fichier d'entête config.h. Ce dernier sera construit automatiquement par cmake car il est fait à partir de variables cmake.

Once the cmake files are setup, launch cmake to generate the build environment:

                    simple
      /       /         |       \        \
     /       /          |        \        \
    /       /           |         \        \
   /       /            |          \        \
main.c  config.h  config.h.cmake  simple  CMakeLists.txt

Quand les fichiers cmake sont configurés, lancer cmake pour créer l'environnement de génération :

$ cd simple
$ cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: .../simple

Ainsi, les makefiles sont prêts pour générer le projet :

$ make
Scanning dependencies of target simple
[100%] Building C object CMakeFiles/simple.dir/main.o
Linking C executable simple
[100%] Built target simple

Ensuite, il est possible de faire le ménage en effaçant tous les fichiers générés par l'environnement cmake avec la commande suivante :

$ make clean

CMakeLists.txt dans projet7

project7 est le répertoire racine du projet. C'est l'endroit où on lance la commande cmake.

C'est le CMakeLists.txt racine du projet.
On définit la version minimum de cmake qu'on supporte pour ce projet via la directive CMAKE_MINIMUM_REQUIRED.
On identifie le projet et le langage de programmation avec la directive PROJECT.
Grâce à la directive SET, on définit quelques variables (PROJECT_NAME, SIMPLE_VERSION) qui vont être utilisée pour faire le fichier config.h.
On construit le fichier d'entête config.h via la directive CONFIGURE_FILE. On lui passe un fichier squelette (config.h.cmake) qui sera converti en config.h par des substitutions.
On définit les flags qui vont être passés en argument au compilateur via la directive ADD_DEFINITIONS. C'est facultatif de faire cela mais les flags par défaut peuvent se révéler insuffisamment sévères pour détecter les warnings et les erreurs.
On définit la cible simple avec la directive ADD_EXECUTABLE.

CMAKE_MINIMUM_REQUIRED(VERSION 2.6)

PROJECT(project7 C)

SET(PROJECT_NAME SIMPLE)

SET(SIMPLE_MAJOR 1)
SET(SIMPLE_MINOR 8)
SET(SIMPLE_PATCH 6)
SET(SIMPLE_VERSION ${SIMPLE_MAJOR}.${SIMPLE_MINOR}.${SIMPLE_PATCH})


CONFIGURE_FILE(config.h.cmake config.h)

ADD_DEFINITIONS(-g -O2 -fsigned-char -freg-struct-return -Wall -W -Wshadow -Wstrict-prototypes -Wpointer-arith -Wcast-qual -Winline -Werror)

ADD_EXECUTABLE(simple main.c)

config.h.cmake dans projet7

Ce fichier est le squelette de config.h : toutes les chaîne encadrées par le caractère @ (e.g. @PROJECT_NAME@) seront substituées par les variables cmake de même nom (e.g. ${PROJECT_NAME}).

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// File        : config.h
// Description : Configuration of @PROJECT_NAME@
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

#ifndef CONFIG_H
#define CONFIG_H

//---------------------------------------------------------------------------
// Name : SIMPLE_VERSION
// Usage: Version of
@PROJECT_NAME@
//----------------------------------------------------------------------------
#define SIMPLE_VERSION "@SIMPLE_VERSION@"

#endif // CONFIG_H


config.h dans projet7

Le fichier config.h est le résultat des substitutions faites par la directive CONFIGURE_FILE dans le fichier config.h.cmake.

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// File        : config.h
// Description : Configuration of SIMPLE
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

#ifndef CONFIG_H
#define CONFIG_H

//---------------------------------------------------------------------------
// Name : SIMPLE_VERSION
// Usage: Version of SIMPLE
//----------------------------------------------------------------------------
#define SIMPLE_VERSION "1.8.6"

#endif // CONFIG_H


Projet 8 : Génération d'un programme Linux et de son module kernel

Le projet appelé PROJECT8 est composé du sous-arbre suivant (les fichiers sont téléchargeables à partir des hyperliens):

                    project8
             /            /   \      \     \
            /            /     \      \     \
           /            /       \      \     \
          /            /         \      \     \
udp_console_u.c udpc_console_k.c udpc Kbuild udpc.ko

Le projet génère une cible : un exécutable en espace utilisateur appelé udpc et un module kernel udpc.ko. Tous les objets sont stockés dans le même répertoire. Le fichier udpc_console_u.c a besoin de macros de configuration localisés dans le fichier d'entête config.h. Ce dernier est construit automatiquement par cmake car fabriqué grâce à quelques variables cmake.

Le sous-arbre suivant montre les fichiers ajoutés pour construire le projet.

                                    project8
      /                  /              |          \         \          \          \
     /                  /               |           \         \          \          \
    /                  /                |            \         \          \          \
   /                  /                 |             \         \          \          \
udpc_console_u.c   udpc_console_k.c  config.h.cmake   udpc     Kbuild    udpc.ko  CMakeLists.txt


Quand les fichiers cmake sont configurés, lancer cmake pour créer l'environnement de génération :

$ cd project8
$ cmake .
-- Building UDPC version 1.0.0
-- Configuring done
-- Generating done
-- Build files have been written to: .../project8

Ainsi, les makefiles sont prêts pour générer le projet :

$ make
Scanning dependencies of target kudpc
[  0%] Building udpc.ko
[ 50%] Built target kudpc
Scanning dependencies of target udpc
[100%] Building C object CMakeFiles/udpc.dir/udp_console_u.o
Linking C executable udpc
[100%] Built target udpc

Ensuite, il est possible de faire le ménage en effaçant tous les fichiers générés par l'environnement cmake avec la commande suivante :

$ make clean

CMakeLists.txt dans project8

project8 est le répertoire racine du projet. C'est l'endroit où on lance la commande cmake.

C'est le CMakeLists.txt racine du projet.
On définit la version minimum de cmake qu'on supporte pour ce projet via la directive CMAKE_MINIMUM_REQUIRED.
On identifie le projet et le langage de programmation avec la directive PROJECT.
Grâce à la directive SET, on définit quelques variables (PROJECT_NAME, UDPC_VERSION) qui vont être utilisée pour faire le fichier config.h.
On construit le fichier d'entête config.h via la directive CONFIGURE_FILE. On lui passe un fichier squelette (config.h.cmake) qui sera converti en config.h par des substitutions.
On définit les flags qui vont être passés en argument au compilateur via la directive ADD_DEFINITIONS. C'est facultatif de faire cela mais les flags par défaut peuvent se révéler insuffisamment sévères pour détecter les warnings et les erreurs.
On définit la cible udpc avec la directive ADD_EXECUTABLE.
Pour construire le module kernel:
Ensuite le module kernel et l'exécutable sont installés par la directive INSTALL.

cmake_minimum_required(VERSION 2.4)

PROJECT(udpc C)

SET(PROJECT_NAME UDPC)

# Version number
SET(UDPC_MAJOR 1)
SET(UDPC_MINOR 0)
SET(UDPC_PATCH 0)
SET(UDPC_VERSION ${UDPC_MAJOR}.${UDPC_MINOR}.${UDPC_PATCH})


MESSAGE(STATUS "Building UDPC version ${UDPC_VERSION}")

CONFIGURE_FILE(config.h.cmake config.h)


SET(udpc_src udp_console_u.c)
SET(udpc_exe udpc)

ADD_DEFINITIONS(-g -O2 -fsigned-char -freg-struct-return -Wall -W -Wshadow -Wstrict-prototypes -Wpointer-arith -Wcast-qual -Winline -Werror)

# Build of the program
ADD_EXECUTABLE(${udpc_exe} ${udpc_src})

# Build the module
EXECUTE_PROCESS(COMMAND uname -r
                OUTPUT_VARIABLE os_release
                OUTPUT_STRIP_TRAILING_WHITESPACE)
SET(module_path /lib/modules/${os_release})
SET(module_build_path ${module_path}/build)

ADD_CUSTOM_COMMAND(OUTPUT udpc.ko
                   COMMAND make -C ${module_build_path} M=`pwd`
                   DEPENDS udp_console_k.c Kbuild
                   COMMENT "Building udpc.ko"
                  )

ADD_CUSTOM_TARGET(kudpc ALL DEPENDS udpc.ko)


# Installation of the module
SET(module_install_path ${module_path}/kernel)
INSTALL(FILES udpc.ko
        DESTINATION ${module_install_path}
        PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)

# Installation of the program
INSTALL(TARGETS udpc
        DESTINATION "bin"
        PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)

config.h.cmake dans project8

Ce fichier est le squelette de config.h : Toutes les chaînes encadrées par le caractère @ (e.g. @PROJECT_NAME@) sont substituées par les variable cmake du même nom (e.g. ${PROJECT_NAME}).

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// File        : config.h
// Description : Configuration of @PROJECT_NAME@
// License     :
//
//  Copyright (C) 2011 Rachid Koucha <rachid dot koucha at free dot fr>
//
//
//  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 3 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, see <http://www.gnu.org/licenses/>.
//
//
// Evolutions  :
//
//     25-Jan-2011  R. Koucha           - Creation
//
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

#ifndef CONFIG_H
#define CONFIG_H

//---------------------------------------------------------------------------
// Name : @PROJECT_NAME@_VERSION
// Usage: Version of @PROJECT_NAME@
//----------------------------------------------------------------------------
#define @PROJECT_NAME@_VERSION "@UDPC_VERSION@"

#endif // CONFIG_H

config.h dans project8

The file config.h is the result of the substitutions done by the directive CONFIGURE_FILE into the file config.h.cmake.

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// File        : config.h
// Description : Configuration of UDPC
// License     :
//
//  Copyright (C) 2011 Rachid Koucha <rachid dot koucha at free dot fr>
//
//
//  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 3 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, see <http://www.gnu.org/licenses/>.
//
//
// Evolutions  :
//
//     25-Jan-2011  R. Koucha           - Creation
//
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

#ifndef CONFIG_H
#define CONFIG_H

//---------------------------------------------------------------------------
// Name : UDPC_VERSION
// Usage: Version of UDPC
//----------------------------------------------------------------------------
#define UDPC_VERSION "1.0.0"

#endif // CONFIG_H

Kbuild dans project8

Le fichier Kbuild comme expliqué dans la documentation du système Linux (.../Documentation/kbuild/modules.txt et .../Documentation/kbuild/kbuild.txt) sert à construire le module kernel Linux :

# Driver for the UDP based Linux console
obj-m += udpc.o
udpc-objs := udp_console_k.o



Projets libres utilisant cmake

Voici des liens sur des logiciels utilisant cmake:

Ressources

A propos de l'auteur

L'auteur est un ingénieur en informatique basé en France. Il peut être contacté ici ou vous pouvez consulter son site WEB.


Retour à la page principale
Retour à la page précédente