web-dev-qa-db-fra.com

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

J'ai écrit une bibliothèque que j'avais l'habitude de compiler en utilisant un Makefile auto-écrit, mais je souhaite maintenant 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 est simplement de compiler le 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 avec une simple bibliothèque partagée. Il serait également utile que quelqu'un me dise simplement une bibliothèque très simple qui utilise cmake afin que je puisse utiliser ceci à titre d'exemple.

110
Florian M

Toujours spécifier la version minimale requise de cmake

_cmake_minimum_required(VERSION 3.9)
_

Vous devriez déclarer un projet. cmake indique qu'il est obligatoire et qu'il définira des variables pratiques _PROJECT_NAME_, _PROJECT_VERSION_ et _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 d'utiliser file(GLOB ...). Cette fonctionnalité ne permet pas de maîtriser le processus de compilation. Si vous êtes paresseux, copiez-coller la sortie de _ls -1 sources/*.cpp_:

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

Définissez la propriété VERSION (facultatif mais c'est une bonne pratique):

_set_target_properties(mylib PROPERTIES VERSION ${PROJECT_VERSION})
_

Vous pouvez également définir SOVERSION sur un nombre majeur de VERSION. Donc _libmylib.so.1_ sera 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 une application tierce. Il est recommandé de l'isoler dans votre arborescence de projet (par exemple, en plaçant le répertoire _include/_). 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 un chemin relatif tel que _"../include/mylib.h"_. Donc, passez le répertoire principal dans les répertoires inclus:

_target_include_directories(mylib PRIVATE .)
_

ou

_target_include_directories(mylib PRIVATE include)
target_include_directories(mylib PRIVATE src)
_

Créer une règle d'installation pour votre bibliothèque. Je suggère d'utiliser les variables _CMAKE_INSTALL_*DIR_ dé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 fichier _pkg-config_. Ce fichier permet à une application tierce d'importer facilement votre bibliothèque:

Créez un fichier modèle nommé _mylib.pc.in_ (consultez la page de manuel pc (5) ):

_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 (_@ONLY_ demander à cmake de ne pas développer les variables de la forme _${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 aussi utiliser cmake EXPORT feature . Cependant, cette fonctionnalité est uniquement compatible avec cmake et je trouve cela difficile à utiliser.

Enfin, l'intégralité de _CMakeLists.txt_ devrait ressembler à ceci:

_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)
_
145
Jezz

Ce fichier CMakeLists.txt 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 la copie de fichiers vers une autre destination avec CMake. La commande de fichier avec la signature COPY/INSTALL semble utile.

71
Robert Franke

J'essaie d'apprendre à le faire moi-même, et il semble que vous puissiez 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})
21
gromit190

Premièrement, voici la structure de répertoire que j'utilise:

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

Après quelques jours passés à regarder cela, 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)

C'est ça, s'il a été installé dans un répertoire standard, il sera trouvé et il n'y a pas besoin de faire autre chose. S'il a été installé dans un chemin non standard, rien de plus simple, il suffit d'indiquer à CMake où trouver MyLibConfig.cmake à l'aide de:

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

J'espère que cela aide tout le monde autant que cela m'a aidé. Les vieilles façons de faire étaient assez lourdes.

8
Luis