Gérer ses dépendances avec CMake

17 avr. 2025#  cmake, tutorial, C++, buildsystem

Dans l’article précédent, nous avons vu comment personnaliser votre projet en fonction du contexte et des entrées utilisateur.

Si cela suffit pour écrire une application simple, un logiciel plus complexe dépend souvent d’autres projets, bibliothèques, ou outils. Dans cet article, vous découvrirez plusieurs manières1 d’intégrer ces dépendances avec CMake.

La méthode simple (mais moins fiable)

Comme vu dans le premier article, on peut utiliser add_subdirectory pour inclure un dossier dans son projet CMake. Cela peut également s’appliquer à des projets externes : il suffit d’écrire add_subdirectory(chemin/vers/dependance) pour l’ajouter à votre build.

Si vous souhaitez définir une nouvelle valeur par défaut pour certaines options de votre dépendance, vous pouvez le faire avant l’appel à add_subdirectory.

Prenons l’exemple suivant d’un CMakeLists.txt situé dans votre dossier external :

set(CMAKE_FOLDER external) # Indique à CMake de regrouper les cibles dans le dossier "external" pour les IDEs compatibles.

# Si vous ne souhaitez pas compiler/exécuter les tests des dépendances.
set(BUILD_TESTING_BCKP ${BUILD_TESTING}) # Sauvegarde de la valeur actuelle
set(BUILD_TESTING OFF CACHE BOOL "Force disable of tests for external dependencies" FORCE)

# Ces options sont définies par Tracy, mais comme nous les définissons en amont,
# la valeur fournie sera utilisée comme valeur par défaut.
# Il est conseillé d’utiliser le même commentaire, car c’est aussi celui qui sera affiché.
option(TRACY_ON_DEMAND "On-demand profiling" ON)
option(TRACY_NO_SAMPLING "Disable call stack sampling" ON)
add_subdirectory(tracy) # Essayez-le, c’est un excellent profiler !

# Restauration de la valeur initiale de BUILD_TESTING
set(BUILD_TESTING ${BUILD_TESTING_BCKP} CACHE BOOL "Build tests (default variable for CTest)" FORCE)

Comme vous pouvez le constater, on commence à écrire du code pour gérer des éléments qu’on ne souhaite pas forcément dans notre build. De plus, cette méthode présente plusieurs limitations :

Cela reste acceptable pour des petits projets non destinés à être partagés.
En revanche, pour un projet plus costaud ou une bibliothèque destinée à être partagée, je vous invite à lire la suite.

Les packages : la “bonne” méthode

La plupart des gestionnaires de paquets ou mécanismes d’intégration de dépendances dans CMake s’appuient sur un outil plus adapté : find_package.
Comme son nom l’indique, cette commande permet de rechercher un package (souvent précompilé). C’est la méthode recommandée pour intégrer des dépendances dans un projet.

Au moment d’écrire ces lignes, find_package supporte trois modes : Module, Config et redirection via FetchContent.

Si vous vous contentez de consommer une dépendance, les différences entre ces modes importent peu. Sachez simplement que le mode Module est principalement utilisé pour des bibliothèques système/préinstallées qui ne supportent pas nativement CMake (via des scripts intégrés), tandis que le mode Config est à préfer pour toute dépendance installée manuellement ou via un gestionnaire de paquets moderne : c’est l’approche que vous devriez privilégier.

Rechercher des packages

Voyons les principaux paramètres de cette commande :

find_package(<NomDuPackage> [version] [EXACT] [MODULE|CONFIG] [REQUIRED] [COMPONENTS])

Chaque package peut exposer ses cibles différemment, mais la convention en Modern CMake est de le faire via un namespace.

Exemple avec les bibliothèques fmt et TracyClient :

find_package(fmt CONFIG REQUIRED)
find_package(Tracy CONFIG REQUIRED)

target_link_libraries(MyTarget 
  PRIVATE
    fmt::fmt           # namespace = fmt, cible = fmt
    Tracy::TracyClient # namespace = Tracy, cible = TracyClient
)

Où CMake cherche-t-il les packages ?

“Ça dépend”, comme on dit dang le jargon.

Mais la règle principale est la suivante : CMake recherche un fichier nommé <NomDuPackageEnMinuscules>-config.cmake ou <NomDuPackage>Config.cmake (en mode Config) ou FindXXXX.cmake (en mode Module).

La procédure complète est un peu complexe, mais on peut la résumer ainsi : CMake recherche ces fichiers dans les répertoires pointés par les variables suivantes, dans l’ordre :

  1. CMAKE_FIND_PACKAGE_REDIRECTS_DIR : utilisée par les gestionnaires de paquets pour rediriger vers leurs propres packages.
  2. <NomDuPackage>_ROOT : permet d’indiquer manuellement l’emplacement d’un package.
  3. CMAKE_PREFIX_PATH : si vous installez toutes vos dépendances au même endroit, c’est le moyen le plus simple (peut être une variable CMake ou une variable d’environnement).
  4. La variable d’environnement PATH.
  5. Le registre de packages CMake : si vous installez/exportez un package, il peut être ajouté au registre (à moins que CMAKE_EXPORT_NO_PACKAGE_REGISTRY ne soit activée).

Dans la pratique, vous utiliserez généralement un gestionnaire de paquets ou CMAKE_PREFIX_PATH.

Installation manuelle d’une dépendance

Si votre dépendance peut être construite avec CMake et supporte l’installation, il vous suffit de la configurer (cmake -B builddir), de la compiler (cmake --build builddir) puis de l’installer avec :

cmake --install builddir

Vous souhaiterez probablement ajouter les options suivantes :

Exemple complet :

cmake --install out/build --prefix out/install --config RelWithDebInfo

Nous verrons comment supporter l’installation dans votre projet dans un prochain article.

Common Packge Specification

Alors que j’écrivais ces lignes, Kitware a annoncé un nouvau standard pour la description des packages: la Common Package Specification.

Bien qu’encore au stade experimental, la bonne nouvelle est que la partie qui permet de consommer un package n’a pas changé par rapport à ce qui a été dit ici: vous utiliserez toujours find_package.


C’est tout pour cet article !
Dans le prochain, vous apprendrez à créer vos propres packages et à générer des installeurs avec CMake.

Footnotes

  1. Il existe de nombreuses façons d’intégrer des dépendances dans CMake : modules préinstallés, FetchContent, ExternalProject, add_subdirectory, ou encore des gestionnaires de paquets tiers comme CPM ou VCPKG.

  2. Comme mentionné dans l’article précédent, les variables CMAKE_*_FLAGS sont destinées à être modifiées uniquement par l’utilisateur ou via des fichiers de toolchain !

photoClément GRÉGOIRE

Clément GRÉGOIRE

Expert Performance & Optimisation
# C++PerformanceJeux-VidéosRendering