Wie erstelle ich eine gemeinsam genutzte Bibliothek mit cmake?

141

Ich habe eine Bibliothek geschrieben, die ich mit einem selbstgeschriebenen Makefile kompiliert habe, aber jetzt möchte ich zu cmake wechseln. Der Baum sieht folgendermaßen aus (ich habe alle irrelevanten Dateien entfernt):

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

Ich versuche also, die Quelle in eine gemeinsam genutzte Bibliothek zu kompilieren und sie dann mit den Header-Dateien zu installieren.

Die meisten Beispiele, die ich gefunden habe, kompilieren ausführbare Dateien mit einigen gemeinsam genutzten Bibliotheken, aber niemals nur mit einer einfachen gemeinsam genutzten Bibliothek. Es wäre auch hilfreich, wenn mir jemand eine sehr einfache Bibliothek nennen könnte, die cmake verwendet, damit ich dies als Beispiel verwenden kann.

Florian M.
quelle
verwandte stackoverflow.com/questions/2152077/…
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功
Dieselbe Frage, aber gibt es eine Möglichkeit, meine Quellen gemischt zu halten (.h ans .cpp im selben Quellenverzeichnis), aber Cmake ein Include-Verzeichnis erstellen zu lassen, ein Produkt seiner Arbeit?
Sandburg

Antworten:

203

Geben Sie immer die minimal erforderliche Version von an cmake

cmake_minimum_required(VERSION 3.9)

Sie sollten ein Projekt deklarieren. cmakesagt, dass es obligatorisch ist und bequeme Variablen definiert PROJECT_NAME, PROJECT_VERSIONund PROJECT_DESCRIPTION(diese letztere Variable erfordert cmake 3.9):

project(mylib VERSION 1.0.1 DESCRIPTION "mylib description")

Deklarieren Sie ein neues Bibliotheksziel. Bitte vermeiden Sie die Verwendung von file(GLOB ...). Diese Funktion bietet keine kontrollierte Beherrschung des Kompilierungsprozesses. Wenn Sie faul sind, kopieren Sie die Ausgabe von ls -1 sources/*.cpp:

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

VERSIONEigenschaft festlegen (optional, aber eine gute Vorgehensweise):

set_target_properties(mylib PROPERTIES VERSION ${PROJECT_VERSION})

Sie können auch SOVERSIONeine große Anzahl von einstellen VERSION. Also libmylib.so.1wird ein Symlink zu libmylib.so.1.0.0.

set_target_properties(mylib PROPERTIES SOVERSION 1)

Deklarieren Sie die öffentliche API Ihrer Bibliothek. Diese API wird für die Drittanbieteranwendung installiert. Es wird empfohlen, es in Ihrem Projektbaum zu isolieren (z. B. im include/Verzeichnis zu platzieren). Beachten Sie, dass private Header nicht installiert werden sollten, und ich empfehle dringend, sie zusammen mit den Quelldateien zu platzieren.

set_target_properties(mylib PROPERTIES PUBLIC_HEADER include/mylib.h)

Wenn Sie mit Unterverzeichnissen arbeiten, ist es nicht sehr praktisch, relative Pfade wie einzuschließen "../include/mylib.h". Übergeben Sie also ein Top-Verzeichnis in den enthaltenen Verzeichnissen:

target_include_directories(mylib PRIVATE .)

oder

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

Erstellen Sie eine Installationsregel für Ihre Bibliothek. Ich schlage vor, Variablen zu verwenden, die CMAKE_INSTALL_*DIRdefiniert sind in GNUInstallDirs:

include(GNUInstallDirs)

Und deklarieren Sie die zu installierenden Dateien:

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

Sie können auch eine pkg-configDatei exportieren . Mit dieser Datei kann eine Drittanbieteranwendung Ihre Bibliothek problemlos importieren:

Erstellen Sie eine Vorlagendatei mit dem Namen mylib.pc.in( weitere Informationen finden Sie in der Manpage zu 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}

Fügen Sie in Ihrem CMakeLists.txteine Regel hinzu, um @Makros zu erweitern ( @ONLYfragen Sie cmake, um Variablen des Formulars nicht zu erweitern ${VAR}):

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

Und schließlich installieren Sie die generierte Datei:

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

Sie können auch die cmake- EXPORTFunktion verwenden . Diese Funktion ist jedoch nur mit kompatibel cmakeund ich finde es schwierig, sie zu verwenden.

Schließlich CMakeLists.txtsollte das Ganze so aussehen:

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
quelle
10
Ergänzt nur die großartige Erklärung von @ Jezz: Nach allen oben genannten Schritten kann der Programmierer die Bibliothek erstellen und installieren mkdir build && cd build/ && cmake .. && sudo make install(oder sudo make install/stripdie gestreifte Bibliotheksversion installieren ).
Silvioprog
1
Haben Sie eine Technik zum Weitergeben von Bibliotheksabhängigkeiten? Wenn zum Beispiel mylib von liblog4cxx abhängt, was wäre eine gute Möglichkeit, dies bis zu mylib.pc durchzulassen?
mpr
1
@mpr Wenn liblog4cxx eine bereitstellen .pcDatei, fügen Sie Requires: liblog4cxxzu Ihrem mylib.pc, sonst können Sie fügen Sie einfach -llog4cxxauf Libs:.
Jérôme Pouiller
Wie würde ich diese Bibliothek in einem anderen Projekt verwenden? Könnten Sie Ihr Beispiel erweitern?
Damir Porobic
Und fügen Sie PUBLIC_HEADER alle Header hinzu oder nur den, den andere Benutzer sehen sollten? Wenn nicht, was machen Sie mit all den anderen Headern?
Damir Porobic
84

Diese minimale CMakeLists.txtDatei kompiliert eine einfache gemeinsam genutzte Bibliothek:

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)

Ich habe jedoch keine Erfahrung damit, Dateien mit CMake an ein anderes Ziel zu kopieren. Der Befehl file mit der Signatur COPY / INSTALL scheint nützlich zu sein.

Robert Franke
quelle
34
CMAKE_BUILD_TYPE sollte weggelassen werden, daher liegt die Entscheidung bei demjenigen, der kompiliert.
ManuelSchneid3r
Ist die Angabe ${CMAKE_CURRENT_SOURCE_DIR}/in include_directoriesnützlich?
Jérôme Pouiller
@Jezz Ich glaube nicht, das gleiche Verzeichnis wird ohne das Präfix aufgenommen. Es wäre jedoch wichtig, wenn Sie sich in einem Unterverzeichnis befinden würden.
Arnav Borborah
Und was ist, wenn ich meine Quellen und meine Header in einem generischen "Quell" -Verzeichnis mischen möchte? Gibt es eine Möglichkeit nach der Generierung, das Verzeichnis "Header" aus meinen Quellen zu erstellen? (Installationsbefehle vielleicht)
Sandburg
24

Ich versuche selbst zu lernen, wie das geht, und anscheinend können Sie die Bibliothek folgendermaßen installieren:

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
quelle
12

Erstens ist dies das Verzeichnislayout, das ich verwende:

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

Nach ein paar Tagen, in denen ich mir das anschaue, ist dies dank des modernen CMake meine Lieblingsmethode:

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)

Nachdem Sie CMake ausgeführt und die Bibliothek installiert haben, müssen Sie keine Find ***. Cmake-Dateien verwenden. Sie können folgendermaßen verwendet werden:

find_package(MyLib REQUIRED)

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

Das war's, wenn es in einem Standardverzeichnis installiert wurde, wird es gefunden und es besteht keine Notwendigkeit, etwas anderes zu tun. Wenn es in einem nicht standardmäßigen Pfad installiert wurde, ist es auch einfach. Teilen Sie CMake einfach mit, wo sich MyLibConfig.cmake befindet, indem Sie:

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

Ich hoffe, das hilft allen so sehr, wie es mir geholfen hat. Alte Methoden waren ziemlich umständlich.

Luis
quelle