Как создать общую библиотеку с помощью cmake?

142

Я написал библиотеку, которую использовал для компиляции, используя самописный Makefile, но теперь я хочу перейти на cmake. Дерево выглядит следующим образом (я удалил все ненужные файлы):

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

Поэтому я пытаюсь скомпилировать исходный код в общую библиотеку, а затем установить его с заголовочными файлами.

Большинство примеров, которые я нашел, компилировали исполняемые файлы с некоторыми общими библиотеками, но никогда не были простыми общими библиотеками. Также было бы полезно, если бы кто-то мог просто сказать мне очень простую библиотеку, которая использует cmake, поэтому я могу использовать это в качестве примера.

Флориан М
источник
Связанный stackoverflow.com/questions/2152077/…
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
Тот же вопрос, но есть ли способ поддерживать мои источники смешанными (.h ans .cpp в том же каталоге источников), но позволить Cmake создать каталог include, продукт его работы?
Сандбург

Ответы:

205

Всегда указывайте минимально необходимую версию cmake

cmake_minimum_required(VERSION 3.9)

Вы должны объявить проект. cmakeговорит, что это обязательно, и это определит удобные переменные PROJECT_NAME, PROJECT_VERSIONи PROJECT_DESCRIPTION(эта последняя переменная требует cmake 3.9):

project(mylib VERSION 1.0.1 DESCRIPTION "mylib description")

Объявите новую библиотечную цель. Пожалуйста, избегайте использования file(GLOB ...). Эта функция не обеспечивает мастерства в процессе компиляции. Если вы ленивы, скопируйте и вставьте вывод ls -1 sources/*.cpp:

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

Установите VERSIONсвойство (необязательно, но это хорошая практика):

set_target_properties(mylib PROPERTIES VERSION ${PROJECT_VERSION})

Вы также можете установить SOVERSIONна большое количество VERSION. Так libmylib.so.1будет символическая ссылка на libmylib.so.1.0.0.

set_target_properties(mylib PROPERTIES SOVERSION 1)

Объявите публичный API вашей библиотеки. Этот API будет установлен для стороннего приложения. Рекомендуется изолировать его в дереве проекта (например, поместить его в include/каталог). Обратите внимание, что частные заголовки не должны быть установлены, и я настоятельно рекомендую размещать их вместе с исходными файлами.

set_target_properties(mylib PROPERTIES PUBLIC_HEADER include/mylib.h)

Если вы работаете с подкаталогами, не очень удобно включать относительные пути, такие как "../include/mylib.h". Итак, передайте верхний каталог во включенные каталоги:

target_include_directories(mylib PRIVATE .)

или

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

Создайте правило установки для вашей библиотеки. Я предлагаю использовать переменные, CMAKE_INSTALL_*DIRопределенные в GNUInstallDirs:

include(GNUInstallDirs)

И объявить файлы для установки:

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

Вы также можете экспортировать pkg-configфайл. Этот файл позволяет стороннему приложению легко импортировать вашу библиотеку:

Создайте файл шаблона с именем mylib.pc.in( для получения дополнительной информации см. Справочную страницу 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}

В вашем CMakeLists.txt, добавьте правило для расширения @макросов ( @ONLYпопросите cmake не раскрывать переменные формы ${VAR}):

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

И наконец, установите сгенерированный файл:

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

Вы также можете использовать функцию cmakeEXPORT . Однако эта функция совместима только с, cmakeи мне трудно ее использовать.

Наконец, все CMakeLists.txtдолжно выглядеть так:

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
Просто дополняя потрясающее объяснение @ Jezz: после всех вышеперечисленных шагов программист может собрать и установить библиотеку mkdir build && cd build/ && cmake .. && sudo make install(или sudo make install/stripустановить версию библиотеки с чередованием ).
silvioprog
1
У вас есть методика передачи библиотечных зависимостей? Например, если mylib зависит от liblog4cxx, что будет хорошим способом прохождения через mylib.pc?
Мпр
1
@mpr Если liblog4cxx предоставит .pcфайл, добавьте Requires: liblog4cxxв свой mylib.pc, иначе вы можете просто добавить -llog4cxxв Libs:.
Жером Пуйлер
Как бы я использовал эту библиотеку в другом проекте? Не могли бы вы расширить свой пример?
Дамир Поробич
И вы добавляете все заголовки в PUBLIC_HEADER или только тот, который должны видеть другие пользователи? Если нет, что вы делаете со всеми остальными заголовками?
Дамир Поробич
84

Этот минимальный CMakeLists.txtфайл компилирует простую общую библиотеку:

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)

Тем не менее, у меня нет опыта копирования файлов в другое место с помощью CMake. Команда file с подписью COPY / INSTALL выглядит так, как будто она может быть полезна.

Роберт Франке
источник
34
CMAKE_BUILD_TYPE должен быть опущен, поэтому решение остается за тем, кто компилирует.
ManuelSchneid3r
Полезно ли указывать ${CMAKE_CURRENT_SOURCE_DIR}/в include_directories?
Жером Пуйлер
@ Jezz Я так не думаю, тот же каталог включается без префикса. Однако было бы важно, если бы вы были в подкаталоге.
Арнав Борбора
А что если я захочу смешать мои источники и мои заголовки в общей папке «source»? Есть ли возможность "пост-генерации" создать каталог "header" из моих источников? (установите команды возможно)
Сандбург
24

Я пытаюсь научиться делать это сам, и кажется, что вы можете установить библиотеку следующим образом:

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
источник
12

Во-первых, это макет каталога, который я использую:

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

После нескольких дней изучения этого, это мой любимый способ сделать это благодаря современному 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)

После запуска CMake и установки библиотеки нет необходимости использовать Find ***. Cmake files, его можно использовать так:

find_package(MyLib REQUIRED)

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

Вот и все, если он был установлен в стандартном каталоге, он будет найден, и больше ничего не нужно будет делать. Если он был установлен по нестандартному пути, это также легко, просто сообщите CMake, где найти MyLibConfig.cmake, используя:

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

Надеюсь, это поможет всем так же, как и мне. Старые способы сделать это были довольно громоздкими.

Луис
источник