Como criar uma biblioteca compartilhada com o cmake?


141

Eu escrevi uma biblioteca que costumava compilar usando um Makefile auto-escrito, mas agora quero mudar para cmake. A árvore fica assim (removi todos os arquivos irrelevantes):

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

Então, o que estou tentando fazer é apenas compilar a fonte em uma biblioteca compartilhada e instalá-la com os arquivos de cabeçalho.

Muitos exemplos que encontrei executáveis ​​de compilação com algumas bibliotecas compartilhadas, mas nunca apenas uma biblioteca compartilhada simples. Também seria útil se alguém pudesse me dizer uma biblioteca muito simples que usa cmake, para que eu possa usar isso como um exemplo.



A mesma pergunta, mas existe uma maneira de manter minhas fontes mistas (.h ans .cpp no ​​mesmo diretório de fontes), mas permitir que o Cmake produza um diretório de inclusão, produto de seu trabalho?
Sandburg

Respostas:


204

Sempre especifique a versão mínima exigida do cmake

cmake_minimum_required(VERSION 3.9)

Você deve declarar um projeto. cmakediz que é obrigatória e vai definir variáveis convenientes PROJECT_NAME, PROJECT_VERSIONe PROJECT_DESCRIPTION(esta última necessitate variável cmake 3.9):

project(mylib VERSION 1.0.1 DESCRIPTION "mylib description")

Declare um novo destino de biblioteca. Por favor, evite o uso de file(GLOB ...). Esse recurso não fornece o domínio do processo de compilação. Se você é preguiçoso, copie e cole a saída de ls -1 sources/*.cpp:

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

Defina a VERSIONpropriedade (opcional, mas é uma boa prática):

set_target_properties(mylib PROPERTIES VERSION ${PROJECT_VERSION})

Você também pode definir SOVERSIONum número principal de VERSION. Então libmylib.so.1será um link simbólico para libmylib.so.1.0.0.

set_target_properties(mylib PROPERTIES SOVERSION 1)

Declare a API pública da sua biblioteca. Essa API será instalada para o aplicativo de terceiros. É uma boa prática isolá-lo na árvore de projetos (como colocá-lo no include/diretório). Observe que cabeçalhos privados não devem ser instalados e eu sugiro colocá-los com os arquivos de origem.

set_target_properties(mylib PROPERTIES PUBLIC_HEADER include/mylib.h)

Se você trabalha com subdiretórios, não é muito conveniente incluir caminhos relativos como "../include/mylib.h". Portanto, passe um diretório superior nos diretórios incluídos:

target_include_directories(mylib PRIVATE .)

ou

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

Crie uma regra de instalação para sua biblioteca. Sugiro usar variáveis CMAKE_INSTALL_*DIRdefinidas em GNUInstallDirs:

include(GNUInstallDirs)

E declare os arquivos para instalar:

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

Você também pode exportar um pkg-configarquivo. Este arquivo permite que um aplicativo de terceiros importe facilmente sua biblioteca:

Crie um arquivo de modelo chamado mylib.pc.in(consulte a página de manual do pc (5) para obter mais informações):

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}

No seu CMakeLists.txt, adicione uma regra para expandir @macros ( @ONLYpeça ao cmake para não expandir variáveis ​​do formulário ${VAR}):

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

E, finalmente, instale o arquivo gerado:

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

Você também pode usar o recurso cmakeEXPORT . No entanto, esse recurso é compatível apenas cmakee acho difícil de usar.

Finalmente, o conjunto CMakeLists.txtdeve se parecer com:

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
Apenas complementando a incrível explicação do @ Jezz: após todas as etapas acima, o programador pode construir e instalar a biblioteca mkdir build && cd build/ && cmake .. && sudo make install(ou sudo make install/strippara instalar a versão distribuída da biblioteca).
silvioprog

1
Você tem uma técnica para transmitir dependências da biblioteca? Por exemplo, se mylib dependesse de liblog4cxx, qual seria uma boa maneira de passar todo esse caminho para mylib.pc?
Mpr3

1
@mpr Se o liblog4cxx fornecer um .pcarquivo, adicione Requires: liblog4cxxao seu mylib.pc, caso contrário, você pode simplesmente adicionar -llog4cxxao Libs:.
Jérôme Pouiller

Como eu usaria essa biblioteca em outro projeto? Você poderia estender seu exemplo?
Damir Porobic

E você adiciona todos os cabeçalhos a PUBLIC_HEADER ou apenas o que outros usuários devem poder ver? Caso contrário, o que você faz com todos os outros cabeçalhos?
Damir Porobic

84

Esse CMakeLists.txtarquivo mínimo compila uma biblioteca compartilhada simples:

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)

No entanto, não tenho experiência em copiar arquivos para um destino diferente com o CMake. O comando file com a assinatura COPY / INSTALL parece ser útil.


34
CMAKE_BUILD_TYPE deve ser omitido, portanto a decisão depende de quem compila.
precisa saber é o seguinte

Não especificando ${CMAKE_CURRENT_SOURCE_DIR}/em include_directoriesé útil?
Jérôme Pouiller

@ Jezz Acho que não, o mesmo diretório é incluído sem o prefixo. No entanto, seria importante se você estivesse em um subdiretório.
Arnav Borborah

E se eu quiser misturar minhas fontes e meus cabeçalhos em um diretório "fonte" genérico? Existe a possibilidade de "pós-geração" de criar o diretório "cabeçalho" a partir das minhas fontes? (comandos de instalação talvez)
Sandburg

24

Estou tentando aprender como fazer isso sozinho, e parece que você pode instalar a biblioteca assim:

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

Primeiro, este é o layout do diretório que estou usando:

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

Depois de alguns dias analisando isso, esta é a minha maneira favorita de fazer isso, graças ao 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)

Após executar o CMake e instalar a biblioteca, não há necessidade de usar os arquivos Find ***. Cmake, eles podem ser usados ​​assim:

find_package(MyLib REQUIRED)

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

É isso, se tiver sido instalado em um diretório padrão, ele será encontrado e não será necessário fazer mais nada. Se ele foi instalado em um caminho não padrão, também é fácil, basta dizer ao CMake onde encontrar o MyLibConfig.cmake usando:

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

Espero que isso ajude a todos, tanto quanto me ajudou. As velhas maneiras de fazer isso eram bastante complicadas.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.