Come creare una libreria condivisa con cmake?


142

Ho scritto una libreria che ho usato per compilare usando un Makefile auto-scritto, ma ora voglio passare a cmake. L'albero si presenta così (ho rimosso tutti i file irrilevanti):

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

Quindi quello che sto cercando di fare è solo quello di compilare l'origine in una libreria condivisa e quindi installarlo con i file di intestazione.

La maggior parte degli esempi che ho trovato compilano eseguibili con alcune librerie condivise ma mai solo una semplice libreria condivisa. Sarebbe anche utile se qualcuno potesse semplicemente dirmi una libreria molto semplice che usa cmake, quindi posso usare questo come esempio.



Stessa domanda, ma c'è un modo per mantenere le mie fonti miste (.h ans .cpp nella stessa directory dei sorgenti) ma lasciare che Cmake produca una directory include, prodotto del suo lavoro?
Sandburg,

Risposte:


205

Specifica sempre la versione minima richiesta di cmake

cmake_minimum_required(VERSION 3.9)

Dovresti dichiarare un progetto. cmakedice che è obbligatorio e definirà variabili convenienti PROJECT_NAME, PROJECT_VERSIONe PROJECT_DESCRIPTION(quest'ultima variabile richiede cmake 3.9):

project(mylib VERSION 1.0.1 DESCRIPTION "mylib description")

Dichiarare un nuovo target di libreria. Si prega di evitare l'uso di file(GLOB ...). Questa funzione non fornisce una padronanza attenta del processo di compilazione. Se sei pigro, copia e incolla l'output di ls -1 sources/*.cpp:

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

Imposta VERSIONproprietà (facoltativo ma è una buona pratica):

set_target_properties(mylib PROPERTIES VERSION ${PROJECT_VERSION})

Puoi anche impostare SOVERSIONun numero maggiore di VERSION. Quindi libmylib.so.1sarà un link simbolico a libmylib.so.1.0.0.

set_target_properties(mylib PROPERTIES SOVERSION 1)

Dichiara l'API pubblica della tua libreria. Questa API verrà installata per l'applicazione di terze parti. È buona norma isolarlo nell'albero del progetto (come posizionarlo nella include/directory). Si noti che le intestazioni private non devono essere installate e consiglio vivamente di inserirle nei file di origine.

set_target_properties(mylib PROPERTIES PUBLIC_HEADER include/mylib.h)

Se lavori con le sottodirectory, non è molto conveniente includere percorsi relativi come "../include/mylib.h". Quindi, passa una directory principale nelle directory incluse:

target_include_directories(mylib PRIVATE .)

o

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

Crea una regola di installazione per la tua libreria. Suggerisco di usare le variabili CMAKE_INSTALL_*DIRdefinite in GNUInstallDirs:

include(GNUInstallDirs)

E dichiarare i file da installare:

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

Puoi anche esportare un pkg-configfile. Questo file consente a un'applicazione di terze parti di importare facilmente la tua libreria:

Creare un file modello denominato mylib.pc.in(consultare la manpage pc (5) per ulteriori informazioni):

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}

Nel tuo CMakeLists.txt, aggiungi una regola per espandere le @macro ( @ONLYchiedi a cmake di non espandere le variabili del modulo ${VAR}):

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

E infine, installa il file generato:

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

Puoi anche usare la funzione cmakeEXPORT . Tuttavia, questa funzione è compatibile solo con cmakee trovo difficile da usare.

Infine l'intero CMakeLists.txtdovrebbe apparire come:

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
A complemento della fantastica spiegazione di @ Jezz: dopo tutti i passaggi precedenti, il programmatore può creare e installare la libreria mkdir build && cd build/ && cmake .. && sudo make install(o sudo make install/stripper installare la versione della libreria a strisce ).
silvioprog,

1
Hai una tecnica per tramandare le dipendenze delle biblioteche? Ad esempio, se mylib dipendesse da liblog4cxx, quale sarebbe un buon modo per farlo scorrere fino a mylib.pc?
MPR

1
@mpr Se liblog4cxx fornisce un .pcfile, aggiungilo Requires: liblog4cxxal tuo mylib.pc, altrimenti puoi semplicemente aggiungere -llog4cxxa Libs:.
Jérôme Pouiller,

Come userei questa libreria in un altro progetto? Potresti estendere il tuo esempio?
Damir Porobic,

E aggiungi tutte le intestazioni a PUBLIC_HEADER o solo quella che gli altri utenti dovrebbero poter vedere? In caso contrario, cosa fai con tutte le altre intestazioni?
Damir Porobic,

84

Questo CMakeLists.txtfile minimo compila una semplice libreria condivisa:

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)

Tuttavia, non ho esperienza con la copia di file in una destinazione diversa con CMake. Il comando file con la firma COPIA / INSTALLA sembra essere utile.


34
CMAKE_BUILD_TYPE dovrebbe essere omesso, quindi la decisione spetta a chi compila.
ManuelSchneid3r

È utile specificare ${CMAKE_CURRENT_SOURCE_DIR}/in include_directories?
Jérôme Pouiller,

@Jezz Non credo, la stessa directory viene inclusa senza il prefisso. Importa se tu fossi in una sottodirectory, comunque.
Arnav Borborah,

E se volessi mescolare i miei sorgenti e le mie intestazioni in una directory "sorgente" generica? Esiste la possibilità di "post generazione" per creare la directory "header" dalle mie fonti? (installa comandi forse)
Sandburg,

24

Sto cercando di imparare come farlo da solo, e sembra che tu possa installare la libreria in questo modo:

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

Innanzitutto, questo è il layout della directory che sto usando:

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

Dopo un paio di giorni dando un'occhiata a questo, questo è il mio modo preferito di farlo grazie al moderno CMake:

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)

Dopo aver eseguito CMake e aver installato la libreria, non è necessario utilizzare i file Trova ***. Cmake, può essere utilizzato in questo modo:

find_package(MyLib REQUIRED)

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

Questo è tutto, se è stato installato in una directory standard verrà trovato e non è necessario fare altro. Se è stato installato in un percorso non standard, è anche facile, basta dire a CMake dove trovare MyLibConfig.cmake usando:

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

Spero che questo aiuti tutti quanto mi ha aiutato. I vecchi modi di farlo erano piuttosto ingombranti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.