Rendre une bibliothèque "consommable"

L'utilisation de la bibliothèque par un projet indépendant (sans que le projet bibliothèque soit englobé dans l'autre projet) nécessite une préparation. Il faut créer un fichier <packageName>Config.cmake :

  • <packageName> est le nom sous lequel la bibliothèque sera cherchée par le projet consommateur. En général, on choisit le nom du projet bibliothèque.
  • Le fichier contient les propriétés d'interface de la bibliothèque.

Création du fichier config, cas sans dépendances, sans installation

Dans le cas simple où la bibliothèque ne dépend pas elle-même d'autres bibliothèques, et où on ne veut pas installer la bibliothèque (voir plus loin pour la notion d'installation), le fichier peut être créé par la commande suivante, à ajouter dans CMakeLists.txt :

export(TARGETS <cible bibliothèque> FILE ${PROJECT_NAME}Config.cmake)

Création du fichier config, cas avec dépendances, sans installation

Si la bibliothèque A dépend elle-même d'autres bibliothèques, le fichier AConfig.cmake doit vérifier la disponibilité de ces bibliothèques.

Ajouter la commande dans CMakeLists.txt :

export(TARGETS A FILE ${PROJECT_NAME}Targets.cmake)

Noter la différence avec :

FILE ${PROJECT_NAME}Config.cmake

qui crée directement le fichier ${PROJECT_NAME}Config.cmake dans le cas sans dépendances. Ici, nous allons inclure ${PROJECT_NAME}Targets.cmake dans ${PROJECT_NAME}Config.cmake.

Créer manuellement un fichier AConfig.cmake.in avec le contenu :

include(CMakeFindDependencyMacro)
find_dependency(B)
find_dependency(C)
...
include(${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake)

Ajouter dans CMakeLists.txt la commande :

configure_file(${PROJECT_NAME}Config.cmake.in ${PROJECT_NAME}Config.cmake @ONLY)

qui copie le fichier dans le répertoire de compilation en remplaçant les chaînes de caractères entre @.

Utilisation d'une bibliothèque extérieure au projet

Ajouter dans CMakeLists.txt du projet utilisateur :

find_package(<PackageName> CONFIG REQUIRED)

À l'exécution de cmake ou cmake-gui, si la bibliothèque utilisée n'est pas trouvée automatiquement, donner le chemin du répertoire parent de son répertoire de compilation avec la variable de cache CMAKE_PREFIX_PATH. Nota bene :

  • Lui donner le type PATH.
  • CMAKE_PREFIX_PATH est interprété comme une liste : vous pouvez donner plusieurs chemins (en général pour plusieurs bibliothèques) séparés par des points-virgules.

  • Donner des chemins absolus.

Manipulation 25

  • Compiler NR_util et Jumble avec deux projets indépendants. À la configuration de Jumble, utilisez NR_util que vous venez de compiler et non la version installée dans ~/.local.
  • Préparer aussi la consommation de Jumble même si vous ne l'utilisez pas tout de suite.

Namespace

Conseil : ajouter une option NAMESPACE à la commande export :

export(TARGETS <cible> NAMESPACE <namespace> FILE ${PROJECT_NAME}....cmake)

L'effet est de préfixer le nom de la cible avec namespace dans ${PROJECT_NAME}....cmake. Les projets consommateurs devront faire référence à la cible préfixée avec namespace.

Terminer namespace par ::. Typiquement, on choisit pour namespace : ${PROJECT_NAME}::.

Namespace : exemple

Dans le CMakeLists.txt de NR_util :

export(TARGETS nr_util NAMESPACE ${PROJECT_NAME}:: FILE ${PROJECT_NAME}Config.cmake)

et dans le CMakeLists.txt d'un projet consommateur de NR_util :

find_package(NR_util CONFIG REQUIRED)
target_link_libraries(Jumble PRIVATE NR_util::nr_util)

Intérêts de namespace

Intérêt principal : si un nom contient :: alors CMake le traite comme le nom d'une cible importée (typiquement avec find_package) ou d'un alias (cf. plus loin). Si find_package a été oublié ou s'il y a une faute de frappe dans le nom, une erreur est signalée à la génération. Si le nom ne contient pas ::, CMake admet la possibilité que ce soit le nom d'une bibliothèque à trouver dans les répertoires du système, au moment de la compilation.

Exemple :

target_link_libraries(Jumble PRIVATE nr_util)

et on a oublié find_package. CMake ajoute juste -lnr_util dans les options d'édition de liens et l'erreur n'apparaîtra qu'à la compilation.

Autre exemple, on n'a pas oublié find_package mais on a une faute de frappe dans le nom de la cible consommée :

target_link_libraries(Jumble PRIVATE NR_util)

Là encore, l'erreur n'apparaîtra qu'à la compilation.

Intérêt secondaire de namespace : éviter une collision entre cibles consommées de noms identiques, venant de différents projets.

Laisser le choix entre englober le projet ou non : le problème de find_package

Si les projets A et B sont englobés alors la commande :

find_package(A CONFIG REQUIRED)

depuis B :

  • d'une part est inutile, puisque la cible A est définie dans une partie du projet global ;
  • d'autre part échoue (le fichier AConfig.cmake n'est pas encore créé au moment de la configuration).

Il faut d'une façon ou d'une autre tester l'opportunité de l'appel à find_package.

Première méthode :

if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
    find_package(A CONFIG REQUIRED)
endif()

Inconvénient : suppose que le projet englobant B englobe aussi A. Ou, en d'autres termes, que tout projet englobant B, englobera logiquement aussi A.

Seconde méthode :

if(NOT TARGET A)
    find_package(A CONFIG REQUIRED)
endif()

Plus robuste. Le projet englobant B peut ne pas englober A. Inconvénient : il faut faire attention à l'ordre des appels à add_subdirectory dans le projet englobant, add_subdirectory(A) doit être avant add_subdirectory(B).

Laisser le choix entre englober le projet ou non : le problème du namespace

Supposons que A soit utilisé par B :

target_link(B ... Project_A::A)

et que A et B soient englobés. Le namespace Projet_A:: n'est ajouté que dans le fichier AConfig.cmake, à l'intention seule de find_package. Pour que la commande target_link ci-dessus n'échoue pas, il faut ajouter un alias dans le projet A :

add_library(PROJECT_A::A ALIAS A)

Manipulation 26

Modifier les CMakeLists.txt de NR_util, Jumble, Numer_Rec_95 et Coriolis pour que ces codes puissent être compilés aussi bien indépendamment qu'englobés dans un super-projet. Penser à mettre des namespaces. Tester.