Outils pour utilisateurs

Outils du site


outils:cmake

CMake

Syntaxe général

Comment rendre inactive une portion de code ?

Les commentaires multilignes n'existent pas. Donc à moins d'avoir un éditeur avancé qui sache gérer la mise (et retrait) en commentaire ligne par ligne (comme kate ou kwrite - Ctrl + d/Ctrl + shift + d), il suffit simplement d'entourer la partie à désactiver d'une condition toujours fausse :

if(0)
    # mon bloc de code à désactiver temporairement
endif()

Pour réactiver temporairement cette portion de code, changer la valeur du if de 0 en 1. Et, bien sûr, retirer le if/endif quand il n'est plus nécessaire.

CMake 3.0 a, depuis, introduit les blocs de commentaire. Exemple :

#[[
    # mon bloc de code à désactiver temporairement
]]

Les variables

Les variables sont-elles sensibles à la casse ?

Oui.

set(x 1)
set(X 2)
 
message("x = ${x} ; X = ${X}")

Résultat :

x = 1 ; X = 2

Comment déterminer si une variable existe ?

Avec if(DEFINED nom_variable) :

if(DEFINED x)
    message("x est définie")
else(DEFINED x)
    message("x n'est pas définie")
endif(DEFINED x)

L'opérateur NOT pourrait être envisageable mais considère également le contenu : outre l’inexistence, toute valeur fausse (0, vide, FALSE, OFF, etc) rendrait la condition vraie.

Comment créer un hachage (table de hachage) ?

Une telle structure de données n'existe pas en CMake. À la place, on recourt généralement aux variables dynamiques de façon à créer une variable telle que <préfixe>_<clé> = <valeur> à l'usuel <nom>[<clé>] = <valeur>. Exemple :

set(cle "abc")
set(hash_${cle} "def")
 
message("hash_${cle} = ${hash_${cle}}")

Il existe cependant une exception : ENV.

Comment s'assurer qu'une variable ne contient pas de doublons ?

En utilisant une liste, puis à la fin des opérations d'ajout utiliser l'option REMOVE_DUPLICATES de list. Exemple :

set(LISTE )
list(APPEND LISTE "a")
list(APPEND LISTE "b")
list(APPEND LISTE "a")
 
message("[AVANT] LISTE = ${LISTE}")
 
list(REMOVE_DUPLICATES LISTE)
 
message("[APRES] LISTE = ${LISTE}")

Résultat :

[AVANT] LISTE = a;b;a
[APRES] LISTE = a;b

Comment déterminer si une valeur appartient à une liste prédéterminée ?

La réponse est en partie dans la question : il faut passer par une liste. Pour la seconde partie, on passera par la fonction list et sa "commande" FIND qui renvoie l'index de l'élément sinon -1 :

set(CHOICES "poire" "pomme" "banane")
set(VALUE "cerise")
 
# Chercher si la valeur $VALUE est dans la liste CHOICES
list(FIND CHOICES "${VALUE}" IN_CHOICES)
if(IN_CHOICES EQUAL -1)
    # valeur interdite (elle n'est pas dans CHOICES)
else(IN_CHOICES EQUAL -1)
    # valeur autorisée (elle est dans CHOICES)
endif(IN_CHOICES EQUAL -1)

Comment obtenir la liste des variables "en cours" ?

La liste des variables est une propriété de répertoire dans la mesure où une variable est associée à un répertoire donné. Sachant cela, on récupère cette liste par la commande get_directory_property et la propriété prédéterminée VARIABLES. Par défaut, le répertoire concerné est le répertoire de travail courant de CMake mais il peut être modifié par le paramètre DIRECTORY. Afficher ces variables et leur valeur respective :

get_directory_property(CURRENT_VARIABLES VARIABLES)
foreach(CURRENT_VARNAME CURRENT_VARIABLES})
    message("${CURRENT_VARNAME} = ${${CURRENT_VARNAME}}")
endforeach(CURRENT_VARNAME)

Fichiers

Comment travailler sur le chemin d'un fichier ?

Les différentes composantes du chemin d'un fichier peuvent être obtenues par la commande get_filename_component. Exemple :

get_filename_component(OUTPUT_DIRNAME "src/foo.c" PATH) # la partie répertoire du chemin original
get_filename_component(OUTPUT_BASENAME "src/foo.c" NAME) # le nom du fichier, sans la partie répertoire
get_filename_component(OUTPUT_EXTENSION "src/foo.c" EXT) # uniquement l'extension du fichier (point compris)
get_filename_component(OUTPUT_NAME_WE "src/foo.c" NAME_WE) # le nom du fichier, sans celui du répertoire ni son extension
get_filename_component(OUTPUT_ABSOLUTE "src/foo.c" ABSOLUTE) # le chemin complet/absolu du fichier
 
message("OUTPUT_DIRNAME = ${OUTPUT_DIRNAME}")
message("OUTPUT_BASENAME= ${OUTPUT_BASENAME}")
message("OUTPUT_EXTENSION = ${OUTPUT_EXTENSION}")
message("OUTPUT_NAME_WE = ${OUTPUT_NAME_WE}")
message("OUTPUT_ABSOLUTE = ${OUTPUT_ABSOLUTE}")

Résultat :

OUTPUT_DIRNAME = src
OUTPUT_BASENAME= foo.c
OUTPUT_EXTENSION = .c
OUTPUT_NAME_WE = foo
OUTPUT_ABSOLUTE = C:/prog/cmake_tests/src/foo.c

Fonctions et macros

Les noms des macros/fonctions sont-ils sensibles à la casse ?

Non. Démonstration :

function(foo)
    message("foo")
endfunction(foo)
 
function(FOO)
    message("FOO")
endfunction(FOO)
 
macro(bar)
    message("bar")
endmacro(bar)
 
macro(BAR)
    message("BAR")
endmacro(BAR)
 
foo()
bar()

Résultat :

FOO
BAR

Remarquer au passage que la dernière macro ou fonction déclarée remplace silencieusement la précédente.

Quelle différence entre une macro et une fonction ?

Les variables créées au sein d'une fonction possèdent leur propre portée. Ainsi elles n'existent pas en dehors de la fonction. Une macro, similaire dans le principe à des langages comme le C (remplacement à la volée), récupère de fait la portée où elle figure (le scope global en somme). Exemple :

function(A)
    set(y 3)
endfunction(A)
 
macro(B)
    set(z 3)
endmacro(B)
 
A()
B()
 
message("y = ${y}")
message("z = ${z}")

Résultat (en ajoutant l'option –warn-uninitialized à la commande cmake) :

CMake Warning (dev) at CMakeLists.txt:12: uninitialized variable 'y'
This warning is for project developers.  Use -Wno-dev to suppress it.

y =
z = 3

Notes :

  • prendre garde au cache qui peut faire persister certaines variables (commandes find_* notamment)
  • un set avec l'option PARENT_SCOPE permet d'outrepasser cette "restriction"

Comment parser les arguments d'une fonction (ou d'une macro) ?

CMake comprend un module standard appelé CMakeParseArguments qui fournit la fonction CMAKE_PARSE_ARGUMENTS. Son prototype est le suivant :

include(CMakeParseArguments)
 
CMAKE_PARSE_ARGUMENTS(<prefix> <options> <one_value_keywords> <multi_value_keywords> args...)

Ses paramètres sont :

  • prefix : le préfixe des variables créées par cette fonction qui vont recevoir les différentes valeurs après traitement des arguments
  • options : une liste d'options (booléens) attendus par votre fonction. Par défaut, tous ceux qui ne sont pas fournis prendront la valeur FALSE (ceux présents seront à valeur TRUE)
  • one_value_keywords : une liste d'options qui n'attendent qu'une seule valeur (les paramètres absents auront pour valeur une chaîne vide)
  • multi_value_keywords : une liste d'options qui peuvent avoir plusieurs valeurs
  • args : les arguments que la fonction doit traiter. Typiquement, il s'agit de parser les arguments reçus par votre fonction/macro, donc vous devriez avoir la variable ${ARGN} pour valeur

Notes :

  • si vous n'avez pas un certain type d'options, fournissez une chaîne vide
  • tous les arguments en trop, au sens d'associer à aucune option, seront regroupés dans la variable de suffixe _UNPARSED_ARGUMENTS

Exemple, une fonction visant à packager des sources et éventuellement les installer :

  • aucune option de type booléen
  • deux options à une valeur (PACKAGE : le nom de l'archive ; DESTINATION : le chemin où l'installer)
  • pas d'options multivaluées : toutes les sources seront regroupées comme arguments supplémentaires
include(CMakeParseArguments)
 
function(create_jar)
    cmake_parse_arguments(PARSED_ARGS "" "PACKAGE;DESTINATION" "" ${ARGN})
 
    if(PARSED_ARGS_PACKAGE)
        message("Nom de l'archive : ${PARSED_ARGS_PACKAGE}")
    #else(PARSED_ARGS_PACKAGE)
        # en définir un par défaut ?
    endif(PARSED_ARGS_PACKAGE)
    if(PARSED_ARGS_DESTINATION)
        message("Installation dans : ${PARSED_ARGS_DESTINATION}")
    #else(PARSED_ARGS_DESTINATION)
        # en définir un par défaut ?
    endif(PARSED_ARGS_DESTINATION)
 
    message("Sources devant composer cette archive :")
    foreach(SOURCE ${PARSED_ARGS_UNPARSED_ARGUMENTS})
        message("- ${SOURCE}")
    endforeach(SOURCE)
endfunction(create_jar)
 
# Exemple d'utilisation
create_jar(PACKAGE monAPI.jar DESTINATION share/monAPI classeA.class classeB.class classeC.class)

Très pratique. Ne pas hésiter à user et abuser de cette fonction que CMake met à notre disposition. Inutile de réinventer la roue !

Interactions avec les sources de l'application

Comment faire passer des variables de CMake à un fichier de "configuration"

Pour l'illustration nous supposerons qu'il s'agit d'un programme écrit en C (en pratique, ça pourrait être un peu n'importe quoi). Ainsi, nous définissons un modèle de fichier d'entête, nommé config.h.in ici, qui contient du code C/préprocesseur valide et des variables de la forme @VARIABLE@. Ces dernières seulement, seront remplacées par la valeur de la variable CMake éponyme lors de la création du fichier d'entête final, à utiliser dans notre code source, config.h. Voici un exemple sans grand intérêt de ce fichier config.h.in :

#define MON_APP_MAJOR_VERSION @MON_APP_MAJOR@
#define MON_APP_MINOR_VERSION @MON_APP_MINOR@
#define MON_APP_PATH_VERSION @MON_APP_PATH@

Du côté de CMake, c'est la commande configure_file qui, pour mon exemple, va créer config.h à partir de config.h.in après avoir substitué les variables (ne pas oublier de définir vos variables avant l'appel de configure_file) :

set(MON_APP_MAJOR 1)
set(MON_APP_MINOR 2)
set(MON_APP_PATH 3)
 
configure_file(
    "config.h.in"
    "config.h"
    @ONLY
)

Ne pas oublier d'inclure config.h (#include "config.h") dans les sources qui nécessite ces informations.

Langage C : comment s'adapter à la présence/absence d'un fichier d'entête ?

Deux étapes :

  1. Tout d'abord, faire chercher le fichier d'entête voulu à CMake par la commande check_include_file, qui fait partie du module CheckIncludeFile (à préalablement inclure). Les paramètres de cette commande sont le nom/chemin de l'entête à chercher puis le nom de la variable qui accueillera le résultat :
    include(CheckIncludeFile)
    check_include_file("bidule.h" HAVE_BIDULE_H)
  2. Deux possibilités pour la suite
    • soit on en fait un paramètre (-D ou /D) de la ligne de commande de compilation. Ce que l'on peut faire de manière globale par la commande add_definitions ou individuellement ou jouant sur la propriété COMPILE_DEFINITIONS. Par exemple, en global, on pourrait aboutir à :
      if(HAVE_BIDULE_H)
          add_definitions(-DHAVE_BIDULE_H=1)
      endif(HAVE_BIDULE_H)
    • soit on reprend la recette précédente, d'autant plus commode quand on a d'autres tests à prendre en compte :
      1. un fichier config.h.in qui contiendrait :
        #cmakedefine HAVE_BIDULE_H
      2. et la suite du fichier CMakeLists.txt contiendrait :
        configure_file(
            "config.h.in"
            "config.h"
            @ONLY
        )

La commande #cmakedefine VARIABLE (ne pas mettre d'arobases ici) sera remplacée par #define VARIABLE si du côté de CMake VARIABLE a une valeur vraie. Elle ne sera pas définie (#ifdef VARIABLE ne serait pas satisfait) si elle a une valeur fausse ou est inexistante. Pour que VARIABLE, en C, soit toujours définie et prenne une valeur 0 ou 1, remplacer #cmakedefine par #cmakedefine01.

Autres commandes utiles du même style :

  • check_symbol_exists(<nom symbole> <entêtes> <variable>) (module à inclure : CheckSymbolExists) : chercher un symbole (macro ou fonction - dans le dernier cas, il faudrait aussi vérifier si elle peut être liée) parmi les fichiers d'entête indiqués
  • check_struct_has_member(<struct> <membre> <entêtes> <variable>) (module CheckStructHasMember) : chercher si la structure (type) possède un membre donné parmi ses champs à partir des fichiers d'entête indiqué. Exemple pour stat :
    check_struct_has_member("struct stat" st_blocks "sys/stat.h" HAVE_ST_BLOCKS)
  • check_prototype_definition(<nom fonction> <prototype> <valeur retournée> <entêtes nécessaires> <variable>) (module CheckPrototypeDefinition)
  • check_library_exists(<nom librairie> <nom symbole> <emplacement> <variable>) (module : CheckLibraryExists)
  • check_function_exists(<nom fonction> <variable>) (module CheckFunctionExists) : même chose que check_symbol_exists mais pour les fonctions système
  • check_type_size(<nom du type> <variable>) (module CheckTypeSize)
    include(CheckTypeSize)
    Check_Type_Size(long LONG_SIZE)
    message("LONG_SIZE = ${LONG_SIZE}")

La compilation

Comment ne compiler qu'une fois une source commune à plusieurs cibles ?

Avant de commencer, il est bon de savoir que ce n'est possible que depuis CMake 2.8.8 (compris). En temps normal, si vous avez une source commune à plusieurs cibles, comme ci-dessous, celle-ci va être compilée autant de fois qu'elle est associée à de cibles différentes :

add_executable(exe1 common.c util.c exe1.c)
add_executable(exe2 common.c util.c exe2.c)

Ici common.c serait compilée deux fois. Sur un projet plus important en cibles et/ou en sources partagées, on peut gagner un temps précieux en regroupant les sources communes sous la forme d'une bibliothèque dite objet (c'est ainsi que CMake le gère). On réinjecte ensuite cette "bibliothèque" dans les add_executable via une variable spéciale $<TARGET_OBJECTS:nom_bibliothèque_objet>. Mon exemple deviendrait alors :

add_library(common_util OBJECT EXCLUDE_FROM_ALL common.c util.c)
add_executable(exe1 exe1.c $<TARGET_OBJECTS:common_util>)
add_executable(exe1 exe1.c $<TARGET_OBJECTS:common_util>)

On peut utiliser autant de ces "bibliothèques objet" que l'on souhaite, ce qui permet de faire des petits groupes pour toujours s'assurer que tout n'est compilé qu'une seule fois. Et, bien sûr, ce système n'est pas limité à la création d'exécutables, il est tout aussi valable pour des bibliothèques (add_library). (voir le CMakeLists.txt de mon projet ugrep sur github - branche master - pour un exemple plus concret).

Autres

NMake : comment compiler en 64 bits ? (MSVC 11)

  1. Ouvrir "Invite de commandes développeur pour VS2012"
  2. Exécuter :
    VC\bin\x86_amd64\vcvarsx86_amd64.bat
  3. Maintenant seulement, vous déplacer vers votre répertoire de travail (cd) pour exécuter cmake puis nmake

Notes :

  • pour vérifier si l'exécutable est compilé en 32 ou en 64 bits, lancer la commande suivante pour y chercher, dans les premières lignes : machine (x??)
    dumpbin /headers <fichier.exe>
  • dans cette "configuration" CMake définit la variable CMAKE_CL_64 à valeur vraie
outils/cmake.txt · Dernière modification: 07/01/2016 17:34 de julp