Comment créer une bibliothèque partagée avec cmake?

142

J'ai écrit une bibliothèque que j'avais l'habitude de compiler en utilisant un Makefile auto-écrit, mais maintenant je veux passer à cmake. L'arbre ressemble à ceci (j'ai supprimé tous les fichiers non pertinents):

.
├── include
   ├── animation.h
   ├── buffers.h
   ├── ...
   ├── vertex.h
   └── world.h
└── src
    ├── animation.cpp
    ├── buffers.cpp
    ├── ...
    ├── vertex.cpp
    └── world.cpp

Donc, ce que j'essaie de faire, c'est simplement de compiler la source dans une bibliothèque partagée, puis de l'installer avec les fichiers d'en-tête.

La plupart des exemples que j'ai trouvés compilent des exécutables avec certaines bibliothèques partagées mais jamais simplement une bibliothèque partagée. Ce serait également utile si quelqu'un pouvait simplement me dire une bibliothèque très simple qui utilise cmake, afin que je puisse l'utiliser comme exemple.

Florian M
la source
associé stackoverflow.com/questions/2152077/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Même question, mais y a-t-il moyen de maintenir mes sources mixtes (.h et .cpp dans le même répertoire sources) mais de laisser Cmake produire un répertoire d'inclusion, produit de son travail?
Sandburg

Réponses:

205

Spécifiez toujours la version minimale requise de cmake

cmake_minimum_required(VERSION 3.9)

Vous devez déclarer un projet. cmakedit que c'est obligatoire et qu'il définira des variables pratiques PROJECT_NAME, PROJECT_VERSIONet PROJECT_DESCRIPTION(cette dernière variable nécessite cmake 3.9):

project(mylib VERSION 1.0.1 DESCRIPTION "mylib description")

Déclarez une nouvelle cible de bibliothèque. Veuillez éviter l'utilisation de file(GLOB ...). Cette fonctionnalité ne permet pas une maîtrise assistée du processus de compilation. Si vous êtes paresseux, copiez-collez la sortie de ls -1 sources/*.cpp:

add_library(mylib SHARED
    sources/animation.cpp
    sources/buffers.cpp
    [...]
)

Définir la VERSIONpropriété (facultatif mais c'est une bonne pratique):

set_target_properties(mylib PROPERTIES VERSION ${PROJECT_VERSION})

Vous pouvez également définir SOVERSIONun nombre majeur de VERSION. Ce libmylib.so.1sera donc un lien symbolique vers libmylib.so.1.0.0.

set_target_properties(mylib PROPERTIES SOVERSION 1)

Déclarez l'API publique de votre bibliothèque. Cette API sera installée pour l'application tierce. Il est recommandé de l'isoler dans l'arborescence de votre projet (comme en le plaçant dans le include/répertoire). Notez que les en-têtes privés ne doivent pas être installés et je suggère fortement de les placer avec les fichiers source.

set_target_properties(mylib PROPERTIES PUBLIC_HEADER include/mylib.h)

Si vous travaillez avec des sous-répertoires, il n'est pas très pratique d'inclure des chemins relatifs comme "../include/mylib.h". Alors, passez un répertoire supérieur dans les répertoires inclus:

target_include_directories(mylib PRIVATE .)

ou

target_include_directories(mylib PRIVATE include)
target_include_directories(mylib PRIVATE src)

Créez une règle d'installation pour votre bibliothèque. Je suggère d'utiliser des variables CMAKE_INSTALL_*DIRdéfinies dans GNUInstallDirs:

include(GNUInstallDirs)

Et déclarez les fichiers à installer:

install(TARGETS mylib
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

Vous pouvez également exporter un pkg-configfichier. Ce fichier permet à une application tierce d'importer facilement votre bibliothèque:

Créez un fichier modèle nommé mylib.pc.in(voir la page de manuel pc (5) pour plus d'informations):

prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@

Name: @PROJECT_NAME@
Description: @PROJECT_DESCRIPTION@
Version: @PROJECT_VERSION@

Requires:
Libs: -L${libdir} -lmylib
Cflags: -I${includedir}

Dans votre CMakeLists.txt, ajoutez une règle pour développer les @macros ( @ONLYdemandez à cmake de ne pas développer les variables du formulaire ${VAR}):

configure_file(mylib.pc.in mylib.pc @ONLY)

Et enfin, installez le fichier généré:

install(FILES ${CMAKE_BINARY_DIR}/mylib.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig)

Vous pouvez également utiliser la fonction cmakeEXPORT . Cependant, cette fonctionnalité n'est compatible qu'avec cmakeet je trouve difficile à utiliser.

Enfin l'ensemble CMakeLists.txtdevrait ressembler à:

cmake_minimum_required(VERSION 3.9)
project(mylib VERSION 1.0.1 DESCRIPTION "mylib description")
include(GNUInstallDirs)
add_library(mylib SHARED src/mylib.c)
set_target_properties(mylib PROPERTIES
    VERSION ${PROJECT_VERSION}
    SOVERSION 1
    PUBLIC_HEADER api/mylib.h)
configure_file(mylib.pc.in mylib.pc @ONLY)
target_include_directories(mylib PRIVATE .)
install(TARGETS mylib
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES ${CMAKE_BINARY_DIR}/mylib.pc
    DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig)
Jérôme Pouiller
la source
10
Juste en complément de l'explication géniale de @ Jezz: après toutes les étapes ci-dessus, le programmeur peut construire et installer la bibliothèque par mkdir build && cd build/ && cmake .. && sudo make install(ou sudo make install/strippour installer la version de la bibliothèque par bandes ).
silvioprog
1
Avez-vous une technique pour transmettre les dépendances de bibliothèque? Par exemple, si mylib dépendait de liblog4cxx, quel serait un bon moyen de transmettre tout cela à mylib.pc?
mpr
1
@mpr Si liblog4cxx fournit un .pcfichier, ajoutez-le Requires: liblog4cxxà votre mylib.pc, sinon, vous pouvez simplement ajouter -llog4cxxà Libs:.
Jérôme Pouiller
Comment utiliser cette bibliothèque dans un autre projet? Pouvez-vous prolonger votre exemple?
Damir Porobic
Et ajoutez-vous tous les en-têtes à PUBLIC_HEADER ou uniquement celui que les autres utilisateurs devraient pouvoir voir? Sinon, que faites-vous de tous les autres en-têtes?
Damir Porobic
84

Ce CMakeLists.txtfichier minimal compile une bibliothèque partagée simple:

cmake_minimum_required(VERSION 2.8)

project (test)
set(CMAKE_BUILD_TYPE Release)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
add_library(test SHARED src/test.cpp)

Cependant, je n'ai aucune expérience de copie de fichiers vers une destination différente avec CMake. La commande de fichier avec la signature COPY / INSTALL semble être utile.

Robert Franke
la source
34
CMAKE_BUILD_TYPE doit être omis, la décision appartient donc à celui qui compile.
ManuelSchneid3r
Est-ce que spécifier ${CMAKE_CURRENT_SOURCE_DIR}/dans include_directoriesest utile?
Jérôme Pouiller
@Jezz Je ne pense pas, le même répertoire est inclus sans le préfixe. Ce serait important si vous étiez dans un sous-répertoire, cependant.
Arnav Borborah
Et si je veux mélanger mes sources et mes en-têtes dans un répertoire "source" générique? Existe-t-il une possibilité "post-génération" de créer le répertoire "header" à partir de mes sources? (installer les commandes peut-être)
Sandburg
24

J'essaie d'apprendre à faire cela moi-même, et il semble que vous pouvez installer la bibliothèque comme ceci:

cmake_minimum_required(VERSION 2.4.0)

project(mycustomlib)

# Find source files
file(GLOB SOURCES src/*.cpp)

# Include header files
include_directories(include)

# Create shared library
add_library(${PROJECT_NAME} SHARED ${SOURCES})

# Install library
install(TARGETS ${PROJECT_NAME} DESTINATION lib/${PROJECT_NAME})

# Install library headers
file(GLOB HEADERS include/*.h)
install(FILES ${HEADERS} DESTINATION include/${PROJECT_NAME})
gromit190
la source
12

Tout d'abord, voici la disposition du répertoire que j'utilise:

.
├── include
   ├── class1.hpp
   ├── ...
   └── class2.hpp
└── src
    ├── class1.cpp
    ├── ...
    └── class2.cpp

Après quelques jours à y jeter un coup d'œil, voici ma façon préférée de le faire grâce à CMake moderne:

cmake_minimum_required(VERSION 3.5)
project(mylib VERSION 1.0.0 LANGUAGES CXX)

set(DEFAULT_BUILD_TYPE "Release")

if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  message(STATUS "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.")
  set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "Choose the type of build." FORCE)
  # Set the possible values of build type for cmake-gui
  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()

include(GNUInstallDirs)

set(SOURCE_FILES src/class1.cpp src/class2.cpp)

add_library(${PROJECT_NAME} ...)

target_include_directories(${PROJECT_NAME} PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
    PRIVATE src)

set_target_properties(${PROJECT_NAME} PROPERTIES
    VERSION ${PROJECT_VERSION}
    SOVERSION 1)

install(TARGETS ${PROJECT_NAME} EXPORT MyLibConfig
    ARCHIVE  DESTINATION ${CMAKE_INSTALL_LIBDIR}
    LIBRARY  DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME  DESTINATION ${CMAKE_INSTALL_BINDIR})
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})

install(EXPORT MyLibConfig DESTINATION share/MyLib/cmake)

export(TARGETS ${PROJECT_NAME} FILE MyLibConfig.cmake)

Après avoir exécuté CMake et installé la bibliothèque, il n'est pas nécessaire d'utiliser les fichiers Find ***. Cmake, il peut être utilisé comme ceci:

find_package(MyLib REQUIRED)

#No need to perform include_directories(...)
target_link_libraries(${TARGET} mylib)

Voilà, s'il a été installé dans un répertoire standard, il sera trouvé et il n'y a rien d'autre à faire. S'il a été installé dans un chemin non standard, c'est également facile, dites simplement à CMake où trouver MyLibConfig.cmake en utilisant:

cmake -DMyLib_DIR=/non/standard/install/path ..

J'espère que cela aide tout le monde autant que cela m'a aidé. Les anciennes méthodes étaient assez lourdes.

Luis
la source