À quoi sert find_package () si vous devez quand même spécifier CMAKE_MODULE_PATH?

167

J'essaie de faire fonctionner un système de construction multi-plateforme en utilisant CMake. Maintenant, le logiciel a quelques dépendances. Je les ai compilés moi-même et les ai installés sur mon système.

Quelques exemples de fichiers installés:

-- Installing: /usr/local/share/SomeLib/SomeDir/somefile
-- Installing: /usr/local/share/SomeLib/SomeDir/someotherfile
-- Installing: /usr/local/lib/SomeLib/somesharedlibrary
-- Installing: /usr/local/lib/SomeLib/cmake/FindSomeLib.cmake
-- Installing: /usr/local/lib/SomeLib/cmake/HelperFile.cmake

Maintenant, CMake a un find_package()qui ouvre un Find*.cmakefichier et recherche après la bibliothèque sur le système et définit certaines variables comme SomeLib_FOUNDetc.

My CMakeLists.txt contient quelque chose comme ceci:

set(CMAKE_MODULE_PATH "/usr/local/lib/SomeLib/cmake/;${CMAKE_MODULE_PATH}")
find_package(SomeLib REQUIRED)

La première commande définit où CMake recherche après le Find*.cmakeet j'ai ajouté le répertoire SomeLiboù le FindSomeLib.cmakepeut être trouvé, donc find_package()fonctionne comme prévu.

Mais c'est un peu étrange car l'une des raisons pour lesquelles il find_package()existe est de s'éloigner des chemins codés en dur non inter-plates-formes.

Comment cela se fait-il habituellement? Dois-je copier le cmake/répertoire de SomeLibdans mon projet et définir le CMAKE_MODULE_PATHrelatif?

MarcDefiant
la source
Ce schéma me semble très étrange. Les bibliothèques utilisant CMake ne sont pas censées exposer leur module «find» de cette façon. Comment avez-vous trouvé une telle manière de trouver ce "SomeLib"? Et quelle lib est-ce?
SirDarius
2
Quelque chose de similaire est fait dans cmake.org/Wiki/… . Et c'est OGRE.
MarcDefiant
2
La section vers laquelle vous créez un lien mentionne ceci: "Puisque CMake (actuellement) ne l'expédie pas, vous devrez l'expédier dans votre projet." C'est ce que j'ai fait dans flvmeta pour trouver LibYAML (voir github.com/noirotm/flvmeta/tree/master/cmake/modules ). Le chemin du module pointe vers ce répertoire, dans mon projet.
SirDarius
3
Je copie généralement les modules FindXXX dans mon projet et définissez CMAKE_MODULE_PATH (si ces modules ne sont pas présents dans CMake bien sûr), j'ai également vu ce modèle plusieurs fois dans d'autres projets
szx

Réponses:

214

La commande find_packagea deux modes: Modulemode et Configmode. Vous essayez d'utiliser le Modulemode alors que vous en avez réellement besoin Config.

Mode module

Find<package>.cmakefichier situé dans votre projet. Quelque chose comme ça:

CMakeLists.txt
cmake/FindFoo.cmake
cmake/FindBoo.cmake

CMakeLists.txt contenu:

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
find_package(Foo REQUIRED) # FOO_INCLUDE_DIR, FOO_LIBRARIES
find_package(Boo REQUIRED) # BOO_INCLUDE_DIR, BOO_LIBRARIES

include_directories("${FOO_INCLUDE_DIR}")
include_directories("${BOO_INCLUDE_DIR}")
add_executable(Bar Bar.hpp Bar.cpp)
target_link_libraries(Bar ${FOO_LIBRARIES} ${BOO_LIBRARIES})

Notez que cela CMAKE_MODULE_PATHa une priorité élevée et peut être utile lorsque vous devez réécrire un Find<package>.cmakefichier standard .

Mode de configuration (installer)

<package>Config.cmakefichier situé à l' extérieur et produit par install commande d'un autre projet ( Foopar exemple).

foo bibliothèque:

> cat CMakeLists.txt 
cmake_minimum_required(VERSION 2.8)
project(Foo)

add_library(foo Foo.hpp Foo.cpp)
install(FILES Foo.hpp DESTINATION include)
install(TARGETS foo DESTINATION lib)
install(FILES FooConfig.cmake DESTINATION lib/cmake/Foo)

Version simplifiée du fichier de configuration:

> cat FooConfig.cmake 
add_library(foo STATIC IMPORTED)
find_library(FOO_LIBRARY_PATH foo HINTS "${CMAKE_CURRENT_LIST_DIR}/../../")
set_target_properties(foo PROPERTIES IMPORTED_LOCATION "${FOO_LIBRARY_PATH}")

Par défaut, projet installé dans le CMAKE_INSTALL_PREFIXrépertoire:

> cmake -H. -B_builds
> cmake --build _builds --target install
-- Install configuration: ""
-- Installing: /usr/local/include/Foo.hpp
-- Installing: /usr/local/lib/libfoo.a
-- Installing: /usr/local/lib/cmake/Foo/FooConfig.cmake

Mode de configuration (utilisation)

Utilisez find_package(... CONFIG)pour inclure FooConfig.cmakeavec la cible importée foo:

> cat CMakeLists.txt 
cmake_minimum_required(VERSION 2.8)
project(Boo)

# import library target `foo`
find_package(Foo CONFIG REQUIRED)

add_executable(boo Boo.cpp Boo.hpp)
target_link_libraries(boo foo)
> cmake -H. -B_builds -DCMAKE_VERBOSE_MAKEFILE=ON
> cmake --build _builds
Linking CXX executable Boo
/usr/bin/c++ ... -o Boo /usr/local/lib/libfoo.a

Notez que la cible importée est hautement configurable. Voyez ma réponse .

Mettre à jour

Communauté
la source
1
Votre réponse est excellente. Cependant, l'exemple de github est plus complexe qu'il peut être IMO. Dans le cas courant où un sous-répertoire (module) exporte un seul artefact, disons une bibliothèque avec les en-têtes, vous n'avez pas besoin de générer un * Config.cmake personnalisé. En conséquence, la configuration peut être considérablement réduite. Je pense que je vais faire moi-même un exemple similaire.
Dimitris
2
@Dimitris Oui, cela peut être un peu simplifié. J'ai mis à jour l'exemple de github donc maintenant il ne l'utilise pas configure_package_config_file. Au fait, si vous avez d'autres suggestions, vous pouvez m'envoyer une pull request.
1
@rusio Voici mon exemple . Il prend en charge la construction monolithique (tous les modules du dossier racine) ou les versions autonomes (chaque module séparément, nécessite une installation).
Dimitris
1
@Dimitris Ok, maintenant je vois. Habituellement, le fichier que vous «optimisez» sert à charger des éléments supplémentaires comme find_dependency . Je pense que c'est un bon modèle pour commencer, donc je le garderai même s'il n'est pas utilisé en fait. Le reste du code semble plus simple car il vous manque des fonctionnalités telles que la version, l'exportation pour dll, la mise en page avec bin/lib(essayez d'installer l'exécutable et de l'exécuter sous Windows). Et les espaces de noms sont très jolis, donc je les garderai aussi :) J'ai également ajouté monolithicbuild.
1
Chacun de vos exemples m'a été très utile. Merci à vous deux!
zmb
2

Si vous exécutez cmakepour SomeLibvous générer (par exemple dans le cadre d'un superbuild), envisagez d'utiliser le registre de packages utilisateur . Cela ne nécessite aucun chemin codé en dur et est multiplateforme. Sous Windows (y compris mingw64), cela fonctionne via le registre. Si vous examinez comment la liste des préfixes d'installation est construite par le CONFIGmode de la commande find_packages () , vous verrez que le registre de packages utilisateur est l'un des éléments.

Bref comment faire

Associez les cibles SomeLibdont vous avez besoin en dehors de ce projet externe en les ajoutant à un jeu d'export dans les CMakeLists.txtfichiers où elles sont créées:

add_library(thingInSomeLib ...)
install(TARGETS thingInSomeLib Export SomeLib-export DESTINATION lib)

Créez un XXXConfig.cmakefichier pour SomeLibdans son ${CMAKE_CURRENT_BUILD_DIR}et stockez cet emplacement dans le registre de packages utilisateur en ajoutant deux appels à export () au CMakeLists.txtassocié à SomeLib:

export(EXPORT SomeLib-export NAMESPACE SomeLib:: FILE SomeLibConfig.cmake) # Create SomeLibConfig.cmake
export(PACKAGE SomeLib)                                                    # Store location of SomeLibConfig.cmake

find_package(SomeLib REQUIRED)Émettez votre commande dans le CMakeLists.txtfichier du projet qui dépend de SomeLibsans que les "chemins codés en dur non multiplateformes" bricolent avec le CMAKE_MODULE_PATH.

Quand cela pourrait être la bonne approche

Cette approche est probablement la mieux adaptée aux situations où vous n'utiliserez jamais votre logiciel en aval du répertoire de construction (par exemple, vous faites une compilation croisée et n'installez jamais rien sur votre machine, ou vous construisez le logiciel juste pour exécuter des tests dans le répertoire build), car il crée un lien vers un fichier .cmake dans votre sortie "build", qui peut être temporaire.

Mais si vous n'installez jamais réellement SomeLibdans votre flux de travail, l'appel EXPORT(PACKAGE <name>)vous permet d'éviter le chemin codé en dur. Et, bien sûr, si vous installez SomeLib, vous connaissez probablement votre plate-forme CMAKE_MODULE_PATH, etc., donc l'excellente réponse de @ user2288008 vous couvrira.

Ryan Feeley
la source
1

Vous n'avez pas besoin de spécifier le chemin du module en soi. CMake est livré avec son propre ensemble de scripts find_package intégrés, et leur emplacement est dans le CMAKE_MODULE_PATH par défaut.

Le cas d'utilisation plus normal pour les projets dépendants qui ont été CMakeified serait d'utiliser la commande external_project de CMake et d'inclure ensuite le fichier Use [Project] .cmake du sous-projet. Si vous avez juste besoin du script Find [Project] .cmake, copiez-le hors du sous-projet et dans le code source de votre propre projet, et vous n'aurez pas besoin d'augmenter le CMAKE_MODULE_PATH pour trouver le sous-projet au niveau du système.

zjm555
la source
12
their location is in the default CMAKE_MODULE_PATHpar défaut CMAKE_MODULE_PATHest vide
Peut confirmer le commentaire de @ user2288008 en 2018. CMAKE_MODULE_PATHest vide sous Windows.
Jeroen
Il s'agit d'une variable spécifique au projet, pour les modules livrés avec votre projet. "Par défaut, il est vide, il est destiné à être défini par le projet." cmake.org/cmake/help/latest/variable/CMAKE_MODULE_PATH.html
Farway
1

Comment cela se fait-il habituellement? Dois-je copier le cmake/répertoire de SomeLib dans mon projet et définir le CMAKE_MODULE_PATH relativement?

Si vous ne faites pas confiance à CMake pour avoir ce module, alors - oui, faites cela - en quelque sorte: Copiez le find_SomeLib.cmakeet ses dépendances dans votre cmake/répertoire. C'est ce que je fais comme solution de secours. C'est une solution moche cependant.

Notez que les FindFoo.cmakemodules sont chacun une sorte de pont entre la dépendance de la plate-forme et l'indépendance de la plate-forme - ils recherchent à divers endroits spécifiques à la plate-forme pour obtenir des chemins dans des variables dont les noms sont indépendants de la plate-forme.

einpoklum
la source