Làm thế nào để tạo một thư viện chia sẻ với cmake?


141

Tôi đã viết một thư viện mà tôi đã sử dụng để biên dịch bằng Makefile tự viết, nhưng bây giờ tôi muốn chuyển sang cmake. Cây trông như thế này (tôi đã xóa tất cả các tệp không liên quan):

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

Vì vậy, những gì tôi đang cố gắng chỉ là biên dịch nguồn vào một thư viện chia sẻ và sau đó cài đặt nó với các tệp tiêu đề.

Hầu hết các ví dụ mà tôi đã tìm thấy các tệp thực thi biên dịch với một số thư viện dùng chung nhưng không bao giờ chỉ là một thư viện dùng chung. Nó cũng sẽ hữu ích nếu ai đó có thể chỉ cho tôi một thư viện rất đơn giản sử dụng cmake, vì vậy tôi có thể sử dụng nó làm ví dụ.



Cùng một câu hỏi, nhưng có cách nào để duy trì các nguồn của tôi hỗn hợp (.h ans .cpp trong cùng thư mục nguồn) nhưng để Cmake tạo ra một thư mục bao gồm, sản phẩm của công việc của nó?
Sandburg

Câu trả lời:


204

Luôn chỉ định phiên bản bắt buộc tối thiểu của cmake

cmake_minimum_required(VERSION 3.9)

Bạn nên khai báo một dự án. cmakenói nó là bắt buộc và nó sẽ xác định các biến thuận tiện PROJECT_NAME, PROJECT_VERSIONPROJECT_DESCRIPTION(biến đòi hỏi này sau cmake 3.9):

project(mylib VERSION 1.0.1 DESCRIPTION "mylib description")

Khai báo một mục tiêu thư viện mới. Xin vui lòng tránh sử dụng file(GLOB ...). Tính năng này không cung cấp quyền làm chủ của quá trình biên dịch. Nếu bạn lười biếng, sao chép-dán đầu ra của ls -1 sources/*.cpp:

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

Đặt thuộc VERSIONtính (tùy chọn nhưng đó là một thực tiễn tốt):

set_target_properties(mylib PROPERTIES VERSION ${PROJECT_VERSION})

Bạn cũng có thể thiết lập SOVERSIONmột số lượng lớn VERSION. Vì vậy, libmylib.so.1sẽ là một liên kết đến libmylib.so.1.0.0.

set_target_properties(mylib PROPERTIES SOVERSION 1)

Khai báo API công khai của thư viện của bạn. API này sẽ được cài đặt cho ứng dụng của bên thứ ba. Đó là một thực hành tốt để cô lập nó trong cây dự án của bạn (như đặt include/thư mục của nó ). Lưu ý rằng, không nên cài đặt các tiêu đề riêng tư và tôi thực sự khuyên bạn nên đặt chúng với các tệp nguồn.

set_target_properties(mylib PROPERTIES PUBLIC_HEADER include/mylib.h)

Nếu bạn làm việc với các thư mục con, sẽ không thuận tiện khi bao gồm các đường dẫn tương đối như thế nào "../include/mylib.h". Vì vậy, vượt qua một thư mục hàng đầu trong các thư mục bao gồm:

target_include_directories(mylib PRIVATE .)

hoặc là

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

Tạo một quy tắc cài đặt cho thư viện của bạn. Tôi đề nghị sử dụng các biến CMAKE_INSTALL_*DIRđược định nghĩa trong GNUInstallDirs:

include(GNUInstallDirs)

Và khai báo các tập tin để cài đặt:

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

Bạn cũng có thể xuất một pkg-configtập tin. Tệp này cho phép ứng dụng của bên thứ ba dễ dàng nhập thư viện của bạn:

Tạo một tệp mẫu có tên mylib.pc.in(xem pc (5) manpage để biết thêm thông tin):

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}

Trong của bạn CMakeLists.txt, thêm quy tắc để mở rộng @macro ( @ONLYyêu cầu cmake để không mở rộng các biến của biểu mẫu ${VAR}):

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

Và cuối cùng, cài đặt tệp được tạo:

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

Bạn cũng có thể sử dụng tính năng cmakeEXPORT . Tuy nhiên, tính năng này chỉ tương thích cmakevà tôi cảm thấy khó sử dụng.

Cuối cùng, toàn bộ CMakeLists.txtsẽ trông như:

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
Chỉ cần bổ sung cho lời giải thích tuyệt vời của @ Jezz: sau tất cả các bước trên, lập trình viên có thể xây dựng và cài đặt thư viện bằng cách mkdir build && cd build/ && cmake .. && sudo make install(hoặc sudo make install/stripđể cài đặt phiên bản thư viện sọc ).
silvioprog

1
Bạn có một kỹ thuật để chuyển xuống phụ thuộc thư viện? Ví dụ: nếu mylib phụ thuộc vào liblog4cxx, điều gì sẽ là một cách tốt để chuyển tất cả thông qua mylib.pc?
mpr

1
@mpr Nếu liblog4cxx cung cấp một .pctệp, hãy thêm Requires: liblog4cxxvào mylib.pc, nếu không, bạn chỉ có thể thêm -llog4cxxvào Libs:.
Jérôme Pouiller

Làm thế nào tôi có thể sử dụng thư viện này trong một dự án khác? Bạn có thể mở rộng ví dụ của bạn?
Damir porobic

Và bạn có thêm tất cả các tiêu đề vào PUBLIC_HEADER hoặc chỉ một tiêu đề mà người dùng khác có thể nhìn thấy? Nếu không, bạn sẽ làm gì với tất cả các tiêu đề khác?
Damir porobic

84

CMakeLists.txtTệp tối thiểu này biên dịch một thư viện chia sẻ đơn giản:

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)

Tuy nhiên, tôi không có kinh nghiệm sao chép các tệp đến một đích khác với CMake. Lệnh tập tin có chữ ký COPY / INSTALL có vẻ hữu ích.


34
CMAKE_BUILD_TYPE nên được bỏ qua, vì vậy quyết định tùy thuộc vào người biên dịch.
ManuelSchneid3r

Liệu chỉ định ${CMAKE_CURRENT_SOURCE_DIR}/trong include_directorieslà hữu ích?
Jérôme Pouiller

@Jezz Tôi không nghĩ vậy, cùng một thư mục được bao gồm mà không có tiền tố. Tuy nhiên, sẽ rất có vấn đề nếu bạn ở trong thư mục con.
Arnav Borborah 16/03/18

Và nếu tôi muốn trộn các nguồn của mình và các tiêu đề của mình trong một thư mục "nguồn" chung thì sao? Có khả năng "tạo thế hệ" để tạo thư mục "tiêu đề" từ các nguồn của tôi không? (có thể cài đặt các lệnh)
Sandburg

24

Tôi đang cố gắng tự học cách làm điều này và dường như bạn có thể cài đặt thư viện như thế này:

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

Đầu tiên, đây là cách bố trí thư mục mà tôi đang sử dụng:

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

Sau một vài ngày xem xét điều này, đây là cách làm yêu thích của tôi nhờ vào CMake hiện đại:

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)

Sau khi chạy CMake và cài đặt thư viện, không cần sử dụng tệp Find ***. Cmake, nó có thể được sử dụng như thế này:

find_package(MyLib REQUIRED)

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

Đó là nó, nếu nó đã được cài đặt trong một thư mục tiêu chuẩn, nó sẽ được tìm thấy và không cần phải làm gì khác. Nếu nó đã được cài đặt trong một đường dẫn không chuẩn, điều đó cũng dễ dàng, chỉ cần cho CMake biết nơi tìm MyLibConfig.cmake bằng cách sử dụng:

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

Tôi hy vọng điều này sẽ giúp mọi người nhiều như nó đã giúp tôi. Những cách làm cũ này khá cồng kềnh.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.