Compilation d'une bibliothèque
La commande est :
add_library(<name> [<source>...])
En général, il est préférable de laisser le choix entre bibliothèque statique et dynamique à l'utilisateur. CMake crée par défaut une bibliothèque statique.
Le nom est un nom de cible, comme pour la commande add_executable
(voir plus haut). Le nom de la cible est utilisé pour former le nom de
fichier contenant la bibliothèque compilée, selon les conventions du
système d'exploitation. Ne pas mettre le préfixe lib dans le nom de la
cible, ne pas mettre de suffixe .a
ou .so
.
La liste de fichiers sources doit contenir les fichiers visés par les
lignes include
.
Propriétés d'une cible
-
La cible n'est pas seulement un identificateur utilisé pour former un nom de fichier (bibliothèque ou exécutable). Une cible a des propriétés.
-
Propriétés d'interface ou de "non interface". On peut aussi appeler "privées" les propriétés de "non interface".
- Les propriétés privées sont utilisées pour compiler la cible elle-même.
- Les propriétés d'interface d'une cible A sont destinées aux cibles "consommatrices" de A : les cibles qui vont utiliser A.
-
Propriétés importantes (mais il en existe d'autres) :
INTERFACE_COMPILE_DEFINITIONS
etCOMPILE_DEFINITIONS
: les macros pour le préprocesseur-
INTERFACE_INCLUDE_DIRECTORIES
etINCLUDE_DIRECTORIES
: les répertoires dans lesquels chercher les "en-têtes", c'est-à-dire les fichiers inclus et les fichiers.mod
-
INTERFACE_LINK_LIBRARIES
etLINK_LIBRARIES
: les dépendances, c'est-à-dire typiquement des cibles qui désignent des bibliothèques
-
Celles qui n'ont pas le préfixe
INTERFACE_
sont les propriétés privées.
Définition des propriétés
Trois commandes :
target_compile_definitions
pourINTERFACE_COMPILE_DEFINITIONS
etCOMPILE_DEFINITIONS
target_include_directories
pourINTERFACE_INCLUDE_DIRECTORIES
etINCLUDE_DIRECTORIES
target_link_libraries
pourINTERFACE_LINK_LIBRARIES
etLINK_LIBRARIES
Ces commandes prennent un argument PRIVATE, INTERFACE ou PUBLIC.
PUBLIC : la commande définit deux propriétés d'un coup, la propriété privée et la propriété interface. PUBLIC = PRIVATE + INTERFACE
Définition d'une macro du préprocesseur
target_compile_definitions(<nom de cible> <INTERFACE|PUBLIC|PRIVATE> <macro>)
ou
target_compile_definitions(<nom de cible> <INTERFACE|PUBLIC|PRIVATE> <macro>=<valeur>)
Pas de -D
Ajout d'un chemin d'en-tête
Ajout d'un répertoire à la liste des répertoires dans lesquels
chercher un fichier inclus ou un fichier .mod
:
target_include_directories(<nom de cible> <INTERFACE|PUBLIC|PRIVATE> répertoire)
Ajout d'une dépendance à une bibliothèque
target_link_libraries(<nom de cible A> <INTERFACE|PUBLIC|PRIVATE> <nom de cible B>)
Nota bene : cette commande est différente des deux précédentes en ce qu'elle met en relation deux cibles.
Elle indique en particulier que B est nécessaire à l'édition de liens de A.
Transitivité des contraintes
-
Les propriétés d'interface d'une cible C sont prises en compte non seulement pour la compilation de toute cible B consommant C mais aussi de toute cible A consommant B, si C est dans l'interface de B. La documentation de CMake parle de "propagation le long de la chaîne de consommation", ou encore de "transitivité des contraintes".
-
Si CMake crée des bibliothèques statiques et non dynamiques, toutes les bibliothèques dans la chaîne de dépendance d'une cible A sont bien prises en compte à l'édition de liens de A, même si elles ne sont pas en interface.
- À l'édition de liens, CMake met automatiquement les bibliothèques dans le bon ordre en tenant compte des dépendances entre cibles.
Exemple de suivi des dépendances
Compilation de A :
-
def privées A + def interface B + def interface C
-
inc privés A + inc interface B + inc interface C
-
édition de liens :
- bibliothèques dynamiques : lib B + lib C
- bibliothèques statiques : lib B + lib C + lib D
Compilation de B :
- def privées B + def interface C + def interface D
- inc privés B + inc interface C + inc interface D
- édition de liens, bibliothèques statiques ou dynamiques : lib C + lib D
target_link_libraries
avec PUBLIC
Le choix de PUBLIC dans :
target_link_libraries(A PUBLIC B)
est le bon si par exemple les procédures de A prennent toujours au moins un argument qui est d'un type dérivé défini dans B. Alors toute cible consommatrice de A doit faire directement référence à B.
Manipulation 19
- Écrire le fichier
CMakeLists.txt
pourNR_util
. Ne pas oublier les cibles pour les programmes de test. - Tester.
nagfor et les fichiers inclus
Le compilateur NAG cherche automatiquement les fichiers visés par les
lignes include
dans le répertoire courant mais pas dans le répertoire
contenant le fichier incluant, si le fichier incluant n'est pas dans
le répertoire courant. Donc, si le code (bibliothèque ou exécutable)
contient des fichiers inclus :
if(CMAKE_Fortran_COMPILER_ID MATCHES NAG)
target_include_directories(<cible> PRIVATE ${CMAKE_CURRENT_LIST_DIR})
endif()
Variable normale ou de cache
Deux types de variables dans CMake : normales et de cache.
Variables normales : définies à l'intérieur de CMakeLists.txt
, le
temps de l'éxécution de cmake (ou cmake-gui), puis perdues. Syntaxe :
set(varName value)
Variables de cache : peuvent être modifiées à l'extérieur de
CMakeLists.txt
, au moment de la configuration, valeur conservée
d'une exécution de cmake à l'autre, dans CMakeCache.txt
.
L'idée de variable de cache est de donner une option à l'utilisateur
sans qu'il ait besoin de modifier CMakeLists.txt
.
Affectation à une variable de cache
On définit habituellement une variable de cache dans CMakeLists.txt
,
avec la syntaxe :
set(varName <valeur> CACHE <type> "docstring")
La valeur doit être comprise comme une valeur par défaut. Le type peut être : BOOL, FILEPATH, PATH ou STRING. Conseil : quand on définit ainsi une variable de cache, préfixer son nom avec le nom du projet. Idée d'éviter un conflit si le projet est inclus dans un autre projet.
La valeur peut être modifiée sur la ligne de commande avec l'option -D
de cmake. Cette option définit toujours une variable de cache.
Si la variable est aussi définie dans CMakeLists.txt
ou prédéfinie
par CMake alors la donnée de la valeur suffit :
-D<var>=<value>
Par exemple : CMAKE_BUILD_TYPE
est une variable de cache prédéfinie de
CMake, donc on peut juste écrire :
-DCMAKE_BUILD_TYPE=Debug
Sinon, il est très conseillé de donner un type avec la syntaxe :
-D<var>:<type>=<value>
La valeur peut être modifiée dans cmake-gui, avec le bouton "Add Entry". La variable ainsi définie est toujours une variable de cache.
Manipulation 20
- Pour la bibliothèque
NR_util
, ajouter la prise en compte du compilateur NAG. - Donner l'option à l'utilisateur de choisir la valeur de la macro
CPP_WP
à la configuration (sans qu'il ait besoin de modifierCMakeLists.txt
). - Tester en changeant la valeur par défaut (par exemple à
kind(0d0)
).
Bibliothèque dynamique
Pour créer une bibliothèque dynamique :
cmake -DBUILD_SHARED_LIBS:BOOL=True ...
(Ou définir BUILD_SHARED_LIBS
dans cmake-gui.)
Messages
Pour afficher des messages à la configuration :
message(<mode> <un message>)
où mode peut être, dans l'ordre du plus "haut niveau" au plus "bas
niveau" : FATAL_ERROR
, WARNING
, STATUS
, VERBOSE
. FATAL_ERROR
arrête l'exécution.
Choix par l'utilisateur du niveau de message à afficher :
- avec l'option
--log-level
de cmake, seulement pour cette exécution de cmake (pas de création de variable de cache) - ou en définissant la variable de cache
CMAKE_MESSAGE_LOG_LEVEL
. Nota bene : cette variable n'est pas prédéfinie par CMake donc si vous la définissez sur la ligne de commande de cmake, donnez-lui son type, STRING. Exemple :
-DCMAKE_MESSAGE_LOG_LEVEL:STRING=VERBOSE
Les messages affichés sont ceux du niveau spécifié et ceux de niveau supérieur.
Par défaut, le niveau de log est STATUS.
Manipulation 21
À la configuration de la bibliothèque NR_util
, ajouter un message
annonçant la précision (valeur de CPP_WP
) utilisée.
Modification des options de compilation
- CMake ajoute tout seul des options correspondant aux choix :
CMAKE_BUILD_TYPE
=Release ou Debug -
Par exemple avec gfortran :
-O3
pour Release et-g
pour Debug. -
Les variables de cache prédéfinies
CMAKE_Fortran_FLAGS
,CMAKE_Fortran_FLAGS_DEBUG
etCMAKE_Fortran_FLAGS_RELEASE
contiennent les options de base et les options supplémentaires pour Debug et Release. On peut les modifier à la configuration. Nota bene : ces variables ne sont pas censées contenir d'option-I
(recherche des fichiers inclus et des modules) ni-D
(définition de macros). -
Dans cmake-gui, cocher "Advanced" pour voir
CMAKE_Fortran_FLAGS
,CMAKE_Fortran_FLAGS_DEBUG
etCMAKE_Fortran_FLAGS_RELEASE
.
Toolchain
Pour ajouter d'un seul coup de nombreuses options (notamment pour le
débogage), il est pratique de collecter ces options dans un fichier,
destiné à être lu directement par CMake. Mais c'est un choix qui doit
être laissé à l'utilisateur et non à l'auteur du fichier
CMakeLists.txt
. Ce n'est donc pas une bonne idée d'inclure le
fichier d'options de compilation dans CMakeLists.txt
. Il faut un
autre mécanisme qui puisse prendre en compte un tel fichier à la
configuration.
On utilise la variable de cache CMAKE_TOOLCHAIN_FILE
, qui donne le
chemin du fichier. Le chemin peut être absolu ou relatif à la racine
du répertoire source ou du répertoire de compilation.
Définir CMAKE_TOOLCHAIN_FILE
à la première exécution de cmake ou
cmake-gui. Ne pas le modifier ensuite. Si vous utilisez cmake-gui,
cocher l'option "Specify toolchain file for cross-compiling".
Les variables à renseigner (avec la syntaxe de CMake) dans ce fichier
sont CMAKE_Fortran_FLAGS_INIT
, CMAKE_Fortran_FLAGS_DEBUG_INIT
ou
CMAKE_Fortran_FLAGS_RELEASE_INIT
. Ces variables sont utilisées pour
initialiser CMAKE_Fortran_FLAGS
, CMAKE_Fortran_FLAGS_DEBUG
ou
CMAKE_Fortran_FLAGS_RELEASE
à la première exécution de cmake ou
cmake-gui seulement.
On peut éventuellement encore modifier ensuite CMAKE_Fortran_FLAGS
,
CMAKE_Fortran_FLAGS_DEBUG
ou CMAKE_Fortran_FLAGS_RELEASE
à la
configuration. Le plus pratique est alors de le faire via cmake-gui.
Nota bene : l'esprit du fichier toolchain est d'être spécifique à la machine mais indépendant des projets. Conseils :
- ne pas mettre de logique spécifique à un projet dedans ;
- ne pas le placer dans le répertoire source ;
- ne pas le placer dans le répertoire de compilation.
Manipulation 22
- Configurer
NR_util
pour une compilation de type Debug, en utilisant le fichiergfortran_7.cmake
, qui contient (entre autres) des options de débogage. - Re-configurer en supprimant la sous-option invalid de
-ffpe-trap
.
Projet à plusieurs répertoires
Dans le répertoire racine, créer la cible (exécutable ou bibliothèque) en référençant seulement les fichiers sources du répertoire racine.
Pour chaque sous-répertoire :
add_subdirectory(source_dir)
Chaque sous-répertoire doit contenir un fichier CMakeLists.txt
. La
lecture du CMakeLists.txt
parent est suspendue pour lire le
CMakeLists.txt
du sous-répertoire.
Dans chaque sous-répertoire :
target_sources(<target> PRIVATE liste de fichiers sources)
avec les fichiers qui sont à ce niveau de l'arborescence uniquement.