Jak stworzyć współdzieloną bibliotekę za pomocą cmake?


141

Napisałem bibliotekę, którą skompilowałem przy użyciu samodzielnie napisanego pliku Makefile, ale teraz chcę przełączyć się na cmake. Drzewo wygląda tak (usunąłem wszystkie nieistotne pliki):

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

Więc to, co próbuję zrobić, to po prostu skompilować źródło do biblioteki współdzielonej, a następnie zainstalować je z plikami nagłówkowymi.

Większość przykładów, które znalazłem, kompiluje pliki wykonywalne z niektórymi bibliotekami współdzielonymi, ale nigdy nie jest to zwykła biblioteka współdzielona. Byłoby również pomocne, gdyby ktoś mógł mi po prostu powiedzieć bardzo prostą bibliotekę używającą cmake, więc mogę użyć tego jako przykładu.



To samo pytanie, ale czy istnieje sposób na utrzymanie mieszanych źródeł (.h i .cpp w tym samym katalogu źródeł), ale pozwolenie Cmake na utworzenie katalogu włączającego, produktu jego pracy?
Sandburg

Odpowiedzi:


204

Zawsze określaj minimalną wymaganą wersję cmake

cmake_minimum_required(VERSION 3.9)

Powinieneś zgłosić projekt. cmakemówi, że jest to obowiązkowe i zdefiniuje wygodne zmienne PROJECT_NAME, PROJECT_VERSIONi PROJECT_DESCRIPTION(ta ostatnia zmienna wymaga cmake 3.9):

project(mylib VERSION 1.0.1 DESCRIPTION "mylib description")

Zadeklaruj nowy cel biblioteki. Prosimy unikać używania file(GLOB ...). Ta funkcja nie zapewnia biegłego opanowania procesu kompilacji. Jeśli jesteś leniwy, skopiuj i wklej wyjście ls -1 sources/*.cpp:

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

Ustaw VERSIONwłaściwość (opcjonalna, ale jest to dobra praktyka):

set_target_properties(mylib PROPERTIES VERSION ${PROJECT_VERSION})

Możesz również ustawić SOVERSIONwiększą liczbę plików VERSION. Więc libmylib.so.1będzie dowiązanie symboliczne do libmylib.so.1.0.0.

set_target_properties(mylib PROPERTIES SOVERSION 1)

Zadeklaruj publiczny interfejs API swojej biblioteki. Ten interfejs API zostanie zainstalowany dla aplikacji innej firmy. Dobrą praktyką jest odizolowanie go w drzewie projektu (np. Umieszczenie go w include/katalogu). Zauważ, że nagłówki prywatne nie powinny być instalowane i zdecydowanie sugeruję umieszczenie ich razem z plikami źródłowymi.

set_target_properties(mylib PROPERTIES PUBLIC_HEADER include/mylib.h)

Jeśli pracujesz z podkatalogami, dołączanie ścieżek względnych, takich jak "../include/mylib.h". Więc przekaż główny katalog w dołączonych katalogach:

target_include_directories(mylib PRIVATE .)

lub

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

Utwórz regułę instalacji dla swojej biblioteki. Proponuję używać zmiennych CMAKE_INSTALL_*DIRzdefiniowanych w GNUInstallDirs:

include(GNUInstallDirs)

I zadeklaruj pliki do zainstalowania:

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

Możesz także wyeksportować pkg-configplik. Ten plik umożliwia aplikacji innej firmy łatwe importowanie Twojej biblioteki:

Utwórz plik szablonu o nazwie mylib.pc.in(zobacz stronę podręcznika pc (5), aby uzyskać więcej informacji):

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}

W swoim CMakeLists.txtdodaj regułę rozwijania @makr ( @ONLYpoproś, aby cmake nie rozwijał zmiennych formularza ${VAR}):

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

Na koniec zainstaluj wygenerowany plik:

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

Możesz także skorzystać z funkcji cmakeEXPORT . Jednak ta funkcja jest kompatybilna tylko z cmakei jest trudna w użyciu.

Wreszcie całość CMakeLists.txtpowinna wyglądać następująco:

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
Tylko uzupełniając niesamowite wyjaśnienie @ Jezz: po wszystkich powyższych krokach programista może zbudować i zainstalować bibliotekę przez mkdir build && cd build/ && cmake .. && sudo make install(lub sudo make install/stripzainstalować wersję biblioteki w paski ).
silvioprog

1
Czy masz technikę przekazywania zależności bibliotek? Na przykład, gdyby mylib zależało od liblog4cxx, jaki byłby dobry sposób, aby przepuścić to przez całą drogę do mylib.pc?
mpr

1
@mpr Jeśli liblog4cxx dostarcza .pcplik, dodaj Requires: liblog4cxxdo swojego mylib.pc, w przeciwnym razie możesz po prostu dodać -llog4cxxdo Libs:.
Jérôme Pouiller

Jak użyłbym tej biblioteki w innym projekcie? Czy mógłbyś przedłużyć swój przykład?
Damir Porobic,

Czy dodajesz wszystkie nagłówki do PUBLIC_HEADER, czy tylko ten, który inni użytkownicy powinni widzieć? Jeśli nie, co robisz ze wszystkimi innymi nagłówkami?
Damir Porobic,

84

Ten minimalny CMakeLists.txtplik kompiluje prostą bibliotekę współdzieloną:

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)

Jednak nie mam doświadczenia w kopiowaniu plików do innego miejsca docelowego za pomocą CMake. Polecenie file z podpisem COPY / INSTALL wygląda na przydatne.


34
CMAKE_BUILD_TYPE należy pominąć, więc decyzja należy do tego, kto kompiluje.
ManuelSchneid3r

Czy określanie ${CMAKE_CURRENT_SOURCE_DIR}/w include_directoriesjest przydatne?
Jérôme Pouiller

@Jezz Nie sądzę, ten sam katalog zostaje dołączony bez prefiksu. Miałoby to jednak znaczenie, gdybyś był w podkatalogu.
Arnav Borborah

A co, jeśli chcę wymieszać moje źródła i nagłówki w ogólnym katalogu „źródłowym”? Czy istnieje możliwość „generowania postów”, aby utworzyć katalog „nagłówek” z moich źródeł? (być może polecenia instalacji)
Sandburg

24

Próbuję się nauczyć, jak to zrobić, i wygląda na to, że możesz zainstalować bibliotekę w następujący sposób:

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

Po pierwsze, to jest układ katalogu, którego używam:

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

Po kilku dniach przyjrzenia się temu, oto mój ulubiony sposób robienia tego dzięki nowoczesnemu 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)

Po uruchomieniu CMake i zainstalowaniu biblioteki nie ma potrzeby korzystania z plików Find ***. Cmake, można go używać w następujący sposób:

find_package(MyLib REQUIRED)

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

To wszystko, jeśli został zainstalowany w standardowym katalogu, zostanie znaleziony i nie ma potrzeby robić nic więcej. Jeśli został zainstalowany w niestandardowej ścieżce, jest to również łatwe, po prostu powiedz CMake, gdzie znaleźć MyLibConfig.cmake, używając:

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

Mam nadzieję, że pomogło to wszystkim tak samo, jak pomogło mi. Stare sposoby na to były dość uciążliwe.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.