Skip to content

Latest commit

 

History

History
454 lines (320 loc) · 17.9 KB

File metadata and controls

454 lines (320 loc) · 17.9 KB

🔝 Retour au Sommaire

26.3.1 find_package

Objectif : Maîtriser find_package() — le mécanisme principal de CMake pour localiser des bibliothèques pré-installées sur le système — en comprenant ses deux modes de fonctionnement (Config et Module), les cibles importées qu'il produit, la gestion des versions et des composants, et les techniques de diagnostic quand une bibliothèque n'est pas trouvée.


Principe de fonctionnement

find_package() répond à une question simple : « Cette bibliothèque est-elle présente sur le système, et si oui, où sont ses headers, ses binaires et ses dépendances ? »

Quand l'appel réussit, CMake crée des cibles importées (imported targets) qui encapsulent toutes ces informations. Vous consommez ensuite ces cibles via target_link_libraries(), exactement comme n'importe quelle autre cible du projet :

find_package(OpenSSL REQUIRED)  
target_link_libraries(my_lib PRIVATE OpenSSL::SSL OpenSSL::Crypto)  

En coulisse, OpenSSL::SSL porte les include directories d'OpenSSL, les flags de compilation nécessaires, et le chemin vers libssl.so ou libssl.a. Le consommateur n'a besoin de connaître aucun de ces détails — la cible importée s'en charge.


Les deux modes de find_package

CMake utilise deux stratégies distinctes pour localiser une bibliothèque. Comprendre cette distinction est essentiel pour diagnostiquer les problèmes de détection.

Mode Config (recommandé)

Dans ce mode, CMake cherche un fichier de configuration fourni par la bibliothèque elle-même. Ce fichier s'appelle <Package>Config.cmake ou <package>-config.cmake et est installé avec la bibliothèque (typiquement dans lib/cmake/<Package>/).

/usr/local/lib/cmake/spdlog/
├── spdlogConfig.cmake          ← Fichier de configuration principal
├── spdlogConfigVersion.cmake   ← Vérification de version
└── spdlogTargets.cmake         ← Définition des cibles importées

Le fichier Config est le mécanisme le plus fiable car il est écrit par les développeurs de la bibliothèque, qui connaissent exactement la structure de leur installation. Les bibliothèques C++ modernes fournissent presque toujours des fichiers Config quand elles utilisent CMake comme build system.

Comment CMake trouve le fichier Config :

CMake parcourt une liste de chemins de recherche dans un ordre défini. Les principaux, sous Linux :

  1. Les chemins listés dans CMAKE_PREFIX_PATH (variable CMake ou variable d'environnement)
  2. Les chemins listés dans <Package>_DIR (variable CMake)
  3. Les chemins systèmes standards : /usr, /usr/local, /opt
  4. Les répertoires enregistrés dans le registre système (principalement Windows)

Pour chaque préfixe, CMake cherche dans plusieurs sous-répertoires :

<prefix>/lib/cmake/<Package>/
<prefix>/share/cmake/<Package>/
<prefix>/lib/<arch>/cmake/<Package>/
<prefix>/cmake/

Mode Module (fallback)

Si aucun fichier Config n'est trouvé, CMake bascule sur le mode Module. Il cherche alors un fichier Find<Package>.cmake — un script de recherche qui localise la bibliothèque par lui-même, en cherchant des headers et des bibliothèques à des emplacements connus.

CMake embarque une collection de modules de recherche pour les bibliothèques courantes (OpenSSL, ZLIB, Threads, Curses, etc.). Ces fichiers se trouvent dans le répertoire d'installation de CMake :

ls /usr/share/cmake-3.31/Modules/Find*.cmake | head
# FindOpenSSL.cmake
# FindZLIB.cmake
# FindThreads.cmake
# FindBoost.cmake
# ...

Vous pouvez aussi écrire vos propres modules Find dans le répertoire cmake/ de votre projet (ajouté à CMAKE_MODULE_PATH — voir section 26.1).

Le mode Module est un mécanisme historique, moins précis que le mode Config. Les modules Find doivent deviner la structure d'installation de la bibliothèque, ce qui peut échouer sur des installations non standard. Quand une bibliothèque propose un fichier Config, il est toujours préférable de l'utiliser.

Forcer un mode spécifique

# Forcer le mode Config uniquement (échoue s'il n'y a pas de fichier Config)
find_package(spdlog CONFIG REQUIRED)

# Forcer le mode Module uniquement (échoue s'il n'y a pas de Find*.cmake)
find_package(OpenSSL MODULE REQUIRED)

Sans mot-clé, CMake essaie d'abord Config, puis Module. C'est le comportement par défaut et le plus courant.


Syntaxe complète

find_package(<Package> [version] [EXACT] [QUIET] [REQUIRED]
    [COMPONENTS comp1 comp2 ...]
    [OPTIONAL_COMPONENTS comp3 ...]
    [CONFIG|MODULE]
    [PATHS path1 path2 ...]
    [HINTS hint1 hint2 ...]
)

Détaillons les paramètres les plus importants.

REQUIRED vs optionnel

# Obligatoire — la configuration échoue si OpenSSL n'est pas trouvé
find_package(OpenSSL REQUIRED)

# Optionnel — la configuration continue, on vérifie manuellement
find_package(ZLIB QUIET)  
if(ZLIB_FOUND)  
    message(STATUS "ZLIB trouvé — compression activée")
    target_link_libraries(my_lib PRIVATE ZLIB::ZLIB)
    target_compile_definitions(my_lib PRIVATE HAS_ZLIB=1)
else()
    message(STATUS "ZLIB absent — compression désactivée")
endif()

REQUIRED transforme l'absence de la bibliothèque en erreur fatale. Sans REQUIRED, CMake positionne la variable <Package>_FOUND à TRUE ou FALSE et continue. QUIET supprime les messages d'information (utile pour les dépendances optionnelles où un message « not found » serait trompeur).

Version

# Version minimale : 1.15 ou supérieure
find_package(spdlog 1.15 CONFIG REQUIRED)

# Version exacte : uniquement 3.4.0
find_package(protobuf 3.4.0 EXACT CONFIG REQUIRED)

La vérification de version repose sur le fichier <Package>ConfigVersion.cmake (mode Config) ou sur la logique du module Find (mode Module). Si la version installée ne satisfait pas la contrainte, find_package échoue comme si la bibliothèque n'avait pas été trouvée.

Composants

Certaines bibliothèques sont organisées en composants — des sous-ensembles indépendants qu'on peut demander individuellement.

# Boost : ne demander que filesystem et system
find_package(Boost 1.84 REQUIRED COMPONENTS filesystem system)  
target_link_libraries(my_lib PRIVATE Boost::filesystem Boost::system)  

# Qt6 : demander Widgets et Network
find_package(Qt6 REQUIRED COMPONENTS Widgets Network)  
target_link_libraries(my_app PRIVATE Qt6::Widgets Qt6::Network)  

Chaque composant produit sa propre cible importée. Demander uniquement les composants nécessaires réduit les dépendances transitives et accélère la configuration.

OPTIONAL_COMPONENTS permet de demander des composants sans rendre leur absence fatale :

find_package(Boost 1.84 REQUIRED
    COMPONENTS filesystem system
    OPTIONAL_COMPONENTS json
)
# Boost::filesystem et Boost::system sont garantis
# Boost::json peut être absent — vérifier avant utilisation

HINTS et PATHS

Quand une bibliothèque est installée dans un emplacement non standard, vous pouvez guider CMake :

find_package(CustomLib REQUIRED
    HINTS ${CMAKE_SOURCE_DIR}/vendor/customlib
    PATHS /opt/customlib
)

HINTS est consulté avant les chemins système — c'est une suggestion prioritaire. PATHS est consulté après — c'est un fallback. En pratique, HINTS est plus couramment utilisé.

L'approche recommandée pour les installations non standard est toutefois de positionner CMAKE_PREFIX_PATH au moment de la configuration, plutôt que d'encoder des chemins dans les CMakeLists.txt :

cmake -B build -G Ninja -DCMAKE_PREFIX_PATH="/opt/customlib;/opt/another_lib"

Cela garde les CMakeLists.txt portables et indépendants des chemins locaux.


Cibles importées : ce que find_package produit

Le résultat le plus important d'un find_package réussi est la création de cibles importées. La convention de nommage est <Package>::<composant> :

Package Cibles importées Description
OpenSSL OpenSSL::SSL, OpenSSL::Crypto Bibliothèques SSL et crypto
Threads Threads::Threads Support threading portable
ZLIB ZLIB::ZLIB Compression zlib
GTest GTest::gtest, GTest::gtest_main, GTest::gmock Google Test et Mock
Protobuf protobuf::libprotobuf, protobuf::protoc Protocol Buffers
spdlog spdlog::spdlog Logging structuré
fmt fmt::fmt Formatage (pré-C++23)
nlohmann_json nlohmann_json::nlohmann_json JSON header-only

Ces cibles portent toutes les propriétés nécessaires : include directories (marqués SYSTEM automatiquement), bibliothèques à lier, flags de compilation, et dépendances transitives. C'est pourquoi un simple target_link_libraries() suffit — tout le reste est automatique.

Variables héritées (mode Module principalement)

Certains modules Find positionnent aussi des variables en plus des cibles importées, pour la compatibilité avec l'ancien style CMake :

find_package(OpenSSL REQUIRED)

# Cibles importées (modern CMake — à utiliser) :
#   OpenSSL::SSL
#   OpenSSL::Crypto

# Variables (ancien style — à éviter si possible) :
#   OPENSSL_FOUND       → TRUE
#   OPENSSL_INCLUDE_DIR → /usr/include/openssl
#   OPENSSL_LIBRARIES   → /usr/lib/x86_64-linux-gnu/libssl.so;...
#   OPENSSL_VERSION     → 3.2.1

Les variables restent utiles pour le diagnostic ou pour des cas où la cible importée n'existe pas (vieux modules Find). Mais pour la consommation, utilisez toujours les cibles importées, jamais les variables :

# ❌ Ancien style — pas de propagation, pas de détection de typo
include_directories(${OPENSSL_INCLUDE_DIR})  
target_link_libraries(my_lib ${OPENSSL_LIBRARIES})  

# ✅ Modern CMake — propagation complète, typo détectée
target_link_libraries(my_lib PRIVATE OpenSSL::SSL)

Bibliothèques courantes sous Ubuntu et leurs find_package

Voici les dépendances les plus fréquemment rencontrées dans les projets C++ sous Ubuntu, avec la commande d'installation système et l'appel CMake correspondant :

# Installation des bibliothèques système courantes
sudo apt install \
    libssl-dev \        # OpenSSL
    zlib1g-dev \        # ZLIB
    libboost-all-dev \  # Boost
    libprotobuf-dev \   # Protobuf
    libcurl4-openssl-dev  # CURL
# Dans CMakeLists.txt
find_package(OpenSSL REQUIRED)       # → OpenSSL::SSL, OpenSSL::Crypto  
find_package(Threads REQUIRED)       # → Threads::Threads (pas de -dev à installer)  
find_package(ZLIB REQUIRED)          # → ZLIB::ZLIB  
find_package(Boost 1.84 REQUIRED     # → Boost::filesystem, etc.  
    COMPONENTS filesystem system)
find_package(Protobuf REQUIRED)      # → protobuf::libprotobuf  
find_package(CURL REQUIRED)          # → CURL::libcurl  

Le paquet -dev (ex: libssl-dev) est essentiel : il contient les headers et les fichiers CMake/pkg-config nécessaires à la compilation. Le paquet sans -dev (ex: libssl3) ne contient que les .so de runtime, insuffisants pour find_package.


Diagnostic : quand find_package échoue

Un find_package qui ne trouve pas la bibliothèque est l'un des problèmes les plus courants en CMake. Voici une méthode de diagnostic systématique.

Lire le message d'erreur

CMake fournit généralement un message explicite :

CMake Error at CMakeLists.txt:12 (find_package):
  Could not find a package configuration file provided by "spdlog"
  (requested version 1.15) with any of the following names:

    spdlogConfig.cmake
    spdlog-config.cmake

  Add the installation prefix of "spdlog" to CMAKE_PREFIX_PATH or set
  "spdlog_DIR" to a directory containing one of the above files.

Ce message indique trois choses : CMake cherchait un fichier Config (mode Config), il ne l'a pas trouvé, et il vous dit exactement quoi faire pour l'aider.

Vérifier que le paquet -dev est installé

C'est la cause n°1 sous Ubuntu. Les fichiers CMake sont dans le paquet -dev :

# Vérifier si le paquet de développement est installé
dpkg -l | grep libssl-dev

# Chercher quel paquet fournit un fichier CMake spécifique
apt-file search spdlogConfig.cmake

Activer le mode debug de find_package

# Avant l'appel find_package
set(CMAKE_FIND_DEBUG_MODE TRUE)  
find_package(spdlog REQUIRED)  
set(CMAKE_FIND_DEBUG_MODE FALSE)  

Ou depuis la ligne de commande, pour tout le projet :

cmake -B build -G Ninja --debug-find

CMake affichera alors chaque répertoire parcouru, chaque fichier cherché, et la raison de l'échec. La sortie est verbeuse mais exhaustive — elle montre exactement où CMake a cherché et ce qu'il a trouvé (ou pas).

Pointer CMake vers l'installation

Si la bibliothèque est installée dans un emplacement non standard :

# Via CMAKE_PREFIX_PATH (recommandé)
cmake -B build -G Ninja -DCMAKE_PREFIX_PATH="/opt/spdlog;/opt/fmt"

# Via la variable spécifique au package
cmake -B build -G Ninja -Dspdlog_DIR=/opt/spdlog/lib/cmake/spdlog

CMAKE_PREFIX_PATH est la méthode préférable car elle s'applique à tous les find_package() du projet. <Package>_DIR est utile pour cibler un seul package spécifique.

Localiser manuellement les fichiers Config

# Trouver où le fichier Config est installé
find / -name "spdlogConfig.cmake" 2>/dev/null

# Ou plus ciblé
find /usr /opt -name "*Config.cmake" -path "*/spdlog/*" 2>/dev/null

Écrire un fichier Find<Package>.cmake personnalisé

Certaines bibliothèques C (ou anciennes bibliothèques C++) ne fournissent ni fichier Config CMake, ni module Find dans CMake. Dans ce cas, vous pouvez écrire votre propre module de recherche.

Le fichier se place dans le répertoire cmake/ de votre projet (ajouté à CMAKE_MODULE_PATH — voir section 26.1) et suit la convention Find<Package>.cmake :

# cmake/FindLibUV.cmake
# Module de recherche pour libuv (https://libuv.org)

find_path(LIBUV_INCLUDE_DIR
    NAMES uv.h
    PATHS /usr/include /usr/local/include
)

find_library(LIBUV_LIBRARY
    NAMES uv uv1
    PATHS /usr/lib /usr/local/lib
)

include(FindPackageHandleStandardArgs)  
find_package_handle_standard_args(LibUV  
    REQUIRED_VARS LIBUV_LIBRARY LIBUV_INCLUDE_DIR
)

# Créer la cible importée (modern CMake)
if(LibUV_FOUND AND NOT TARGET LibUV::LibUV)
    add_library(LibUV::LibUV UNKNOWN IMPORTED)
    set_target_properties(LibUV::LibUV PROPERTIES
        IMPORTED_LOCATION "${LIBUV_LIBRARY}"
        INTERFACE_INCLUDE_DIRECTORIES "${LIBUV_INCLUDE_DIR}"
    )
endif()

Les points clés de ce module :

  • find_path() cherche un header caractéristique de la bibliothèque dans les chemins courants ;
  • find_library() cherche le fichier bibliothèque (.so ou .a) ;
  • find_package_handle_standard_args() est un helper CMake qui gère la logique REQUIRED/QUIET/version et positionne <Package>_FOUND ;
  • la création d'une cible importée LibUV::LibUV est essentielle pour que les consommateurs puissent utiliser la bibliothèque via target_link_libraries() de manière moderne.

Ce module s'utilise ensuite de manière transparente :

find_package(LibUV REQUIRED)  
target_link_libraries(my_lib PRIVATE LibUV::LibUV)  

💡 Conseil : avant d'écrire votre propre module Find, vérifiez si la bibliothèque supporte pkg-config. Si c'est le cas, le module FindPkgConfig de CMake peut la localiser automatiquement :

find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBUV REQUIRED IMPORTED_TARGET libuv)
target_link_libraries(my_lib PRIVATE PkgConfig::LIBUV)

C'est souvent plus simple et plus robuste qu'un module Find manuel.


find_package et Conan / vcpkg

Quand vous utilisez un gestionnaire de paquets, find_package() reste la commande de consommation — mais le gestionnaire prépare le terrain en amont.

Avec Conan 2.0

Conan génère des fichiers toolchain et de configuration CMake. Le workflow typique :

# 1. Conan installe les dépendances et génère les fichiers CMake
conan install . --output-folder=build --build=missing

# 2. CMake utilise le toolchain Conan et trouve les packages
cmake -B build -G Ninja \
    -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \
    -DCMAKE_BUILD_TYPE=Release

Le conan_toolchain.cmake configure CMAKE_PREFIX_PATH pour que vos find_package() trouvent les bibliothèques installées par Conan. Aucune modification de vos CMakeLists.txt n'est nécessaire.

Avec vcpkg

vcpkg fonctionne de manière similaire, via son propre toolchain :

cmake -B build -G Ninja \
    -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake

Là encore, vos find_package() fonctionnent tels quels — vcpkg injecte les bons chemins de recherche.

L'intérêt de cette architecture est que vos CMakeLists.txt restent agnostiques vis-à-vis du gestionnaire de paquets. Un utilisateur peut consommer votre projet avec Conan, vcpkg, des paquets système (apt), ou une installation manuelle — les mêmes find_package() fonctionnent dans tous les cas.


Récapitulatif

Aspect Recommandation
Mode Préférer Config, fallback Module
Consommation Toujours via les cibles importées (Package::Component), jamais via les variables
Dépendances critiques REQUIRED — échec immédiat si absent
Dépendances optionnelles QUIET + test de <Package>_FOUND
Version Toujours spécifier une version minimale pour les dépendances critiques
Emplacement non standard CMAKE_PREFIX_PATH (global) ou <Package>_DIR (ciblé)
Diagnostic --debug-find ou CMAKE_FIND_DEBUG_MODE
Bibliothèque sans Config ni Find Écrire un Find<Package>.cmake ou utiliser pkg-config

À suivre : La sous-section 26.3.2 couvre FetchContent — le mécanisme de CMake pour télécharger et intégrer automatiquement des dépendances au moment de la configuration, sans installation préalable.

⏭️ FetchContent