¿Cómo crear una biblioteca compartida con cmake?


142

He escrito una biblioteca que solía compilar usando un Makefile auto escrito, pero ahora quiero cambiar a cmake. El árbol se ve así (eliminé todos los archivos irrelevantes):

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

Entonces, lo que intento hacer es compilar la fuente en una biblioteca compartida y luego instalarla con los archivos de encabezado.

La mayoría de los ejemplos que he encontrado compilan ejecutables con algunas bibliotecas compartidas, pero nunca solo una biblioteca compartida simple. También sería útil si alguien pudiera decirme una biblioteca muy simple que usa cmake, para que yo pueda usar esto como un ejemplo.



La misma pregunta, pero ¿hay alguna manera de mantener mis fuentes mezcladas (.h ans .cpp en el mismo directorio de fuentes) pero dejando que Cmake produzca un directorio de inclusión, producto de su trabajo?
Sandburg

Respuestas:


205

Especifique siempre la versión mínima requerida de cmake

cmake_minimum_required(VERSION 3.9)

Deberías declarar un proyecto. cmakedice que es obligatorio y definirá variables convenientes PROJECT_NAME, PROJECT_VERSIONy PROJECT_DESCRIPTION(esta última variable necesita cmake 3.9):

project(mylib VERSION 1.0.1 DESCRIPTION "mylib description")

Declarar un nuevo objetivo de biblioteca. Por favor, evite el uso de file(GLOB ...). Esta característica no proporciona dominio asistido del proceso de compilación. Si eres flojo, copia y pega la salida de ls -1 sources/*.cpp:

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

Establecer VERSIONpropiedad (opcional pero es una buena práctica):

set_target_properties(mylib PROPERTIES VERSION ${PROJECT_VERSION})

También puede establecer SOVERSIONun número mayor de VERSION. Entonces libmylib.so.1será un enlace simbólico a libmylib.so.1.0.0.

set_target_properties(mylib PROPERTIES SOVERSION 1)

Declara la API pública de tu biblioteca. Esta API se instalará para la aplicación de terceros. Es una buena práctica aislarlo en su árbol de proyecto (como colocarlo en el include/directorio). Tenga en cuenta que no se deben instalar encabezados privados y le sugiero que los coloque con los archivos de origen.

set_target_properties(mylib PROPERTIES PUBLIC_HEADER include/mylib.h)

Si trabaja con subdirectorios, no es muy conveniente incluir rutas relativas como "../include/mylib.h". Entonces, pase un directorio superior en los directorios incluidos:

target_include_directories(mylib PRIVATE .)

o

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

Cree una regla de instalación para su biblioteca. Sugiero usar variables CMAKE_INSTALL_*DIRdefinidas en GNUInstallDirs:

include(GNUInstallDirs)

Y declarar archivos para instalar:

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

También puede exportar un pkg-configarchivo. Este archivo permite que una aplicación de terceros importe fácilmente su biblioteca:

Crear un archivo de plantilla llamada mylib.pc.in(ver PC (5) página de manual para más información):

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}

En su CMakeLists.txt, agregue una regla para expandir las @macros ( @ONLYsolicite cmake para no expandir las variables del formulario ${VAR}):

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

Y finalmente, instale el archivo generado:

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

También puede usar la función cmakeEXPORT . Sin embargo, esta función solo es compatible cmakey me resulta difícil de usar.

Finalmente todo CMakeLists.txtdebería verse así:

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)

10
Simplemente complementando la increíble explicación de @ Jezz: después de todos los pasos anteriores, el programador puede compilar e instalar la biblioteca mkdir build && cd build/ && cmake .. && sudo make install(o sudo make install/strippara instalar la versión de biblioteca rayada ).
silvioprog

1
¿Tiene una técnica para pasar dependencias de la biblioteca? Por ejemplo, si mylib dependiera de liblog4cxx, ¿cuál sería una buena forma de fluir hasta mylib.pc?
mpr

1
Si @mpr liblog4cxx proporcionan un .pcarchivo, agregue Requires: liblog4cxxa su mylib.pc, de lo contrario, sólo puede añadir -llog4cxxa Libs:.
Jérôme Pouiller

¿Cómo usaría esta biblioteca en otro proyecto? ¿Podrías extender tu ejemplo?
Damir Porobic

¿Y agrega todos los encabezados a PUBLIC_HEADER o solo el que otros usuarios deberían poder ver? Si no, ¿qué haces con todos los otros encabezados?
Damir Porobic

84

Este CMakeLists.txtarchivo mínimo compila una biblioteca compartida 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)

Sin embargo, no tengo experiencia copiando archivos a un destino diferente con CMake. Parece que el comando de archivo con la firma COPY / INSTALL podría ser útil.


34
CMAKE_BUILD_TYPE se debe omitir, por lo que la decisión depende de quien compila.
ManuelSchneid3r

¿Especificar ${CMAKE_CURRENT_SOURCE_DIR}/en include_directorieses útil?
Jérôme Pouiller

@Jezz No lo creo, el mismo directorio se incluye sin el prefijo. Sin embargo, sería importante si estuvieras en un subdirectorio.
Arnav Borborah

¿Y qué pasa si quiero mezclar mis fuentes y mis encabezados en un directorio genérico de "fuente"? ¿Existe la posibilidad de "generación posterior" para crear el directorio "encabezado" a partir de mis fuentes? (instalar comandos tal vez)
Sandburg

24

Estoy tratando de aprender cómo hacerlo yo mismo, y parece que puedes instalar la biblioteca de esta manera:

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})

12

Primero, este es el diseño del directorio que estoy usando:

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

Después de un par de días analizando esto, esta es mi forma favorita de hacerlo gracias a CMake moderno:

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)

Después de ejecutar CMake e instalar la biblioteca, no es necesario usar los archivos Find ***. Cmake, se puede usar así:

find_package(MyLib REQUIRED)

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

Eso es todo, si se ha instalado en un directorio estándar, se encontrará y no hay necesidad de hacer nada más. Si se ha instalado en una ruta no estándar, también es fácil, solo dígale a CMake dónde encontrar MyLibConfig.cmake usando:

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

Espero que esto ayude a todos tanto como me ha ayudado a mí. Las viejas formas de hacer esto eran bastante engorrosas.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.