【问题标题】:How to create a shared library with cmake?如何使用 cmake 创建共享库?
【发布时间】:2013-07-04 21:23:57
【问题描述】:

我写了一个库,以前用自写的 Makefile 编译,但现在我想切换到 cmake。树看起来像这样(我删除了所有不相关的文件):

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

所以我要做的只是将源代码编译成共享库,然后将其与头文件一起安装。

我发现的大多数示例都使用一些共享库编译可执行文件,但绝不只是简单的共享库。 如果有人能告诉我一个使用 cmake 的非常简单的库也会很有帮助,所以我可以以此为例。

【问题讨论】:

  • 同样的问题,但是有没有办法保持我的源混合(.h 和 .cpp 在同一源目录中)但让 Cmake 生成一个包含目录,它的工作产品?

标签: c++ compilation cmake shared-libraries


【解决方案1】:

始终指定cmake 所需的最低版本

cmake_minimum_required(VERSION 3.9)

您应该声明一个项目。 cmake 表示这是强制性的,它将定义方便的变量 PROJECT_NAMEPROJECT_VERSIONPROJECT_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)

为您的库创建安装规则。我建议使用在GNUInstallDirs 中定义的变量CMAKE_INSTALL_*DIR

include(GNUInstallDirs)

并声明要安装的文件:

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

您还可以导出pkg-config 文件。此文件允许第三方应用程序轻松导入您的库:

创建一个名为mylib.pc.in 的模板文件(有关详细信息,请参阅pc(5) manpage):

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)

您也可以使用cmake EXPORT feature。不过这个功能只兼容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)

编辑

如 cmets 中所述,为了符合标准,您应该能够生成静态库以及共享库。这个过程有点复杂,与最初的问题不匹配。不过值得一提的是here大大解释了。

【讨论】:

  • 只是补充@Jezz的精彩解释:经过以上所有步骤,程序员可以通过mkdir build && cd build/ && cmake .. && sudo make install(或sudo make install/strip安装striped库来构建和安装库版本)。
  • 你有传递库依赖的技术吗?例如,如果 mylib 依赖于 liblog4cxx,那么将它一直流到 mylib.pc 的好方法是什么?
  • @mpr 如果 liblog4cxx 提供了一个.pc 文件,请将Requires: liblog4cxx 添加到您的mylib.pc,否则,您可以将-llog4cxx 添加到Libs:
  • 如何在另一个项目中使用这个库?你能扩展你的例子吗?
  • add_library 应该在没有 STATIC/SHARED 的情况下使用,BUILD_SHARED_LIBS 必须使用。 cgold.readthedocs.io/en/latest/tutorials/libraries/…
【解决方案2】:

这个最小的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 将文件复制到其他目的地的经验。带有 COPY/INSTALL 签名的文件命令看起来很有用。

【讨论】:

  • CMAKE_BUILD_TYPE 应该被省略,所以决定权在于编译。
  • include_directories 中指定${CMAKE_CURRENT_SOURCE_DIR}/ 有用吗?
  • @Jezz 我不这么认为,相同的目录被包含在没有前缀的情况下。但是,如果您在子目录中,那就很重要了。
  • 如果我想在一个通用的“源”目录中混合我的源和我的标题怎么办?是否有“后生成”的可能性从我的来源创建“标题”目录? (可能安装命令)
【解决方案3】:

我正在尝试自己学习如何做到这一点,看来您可以像这样安装库:

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})

【讨论】:

    【解决方案4】:

    首先,这是我正在使用的目录布局:

    .
    ├── 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文件,可以这样使用:

    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 ..
    

    我希望这对每个人都有帮助,就像它对我有帮助一样。这样做的旧方法非常麻烦。

    【讨论】:

    • 完美答案,我会从 target_include_directories 中删除 PRIVATE src ,因为您不应该使用 #include &lt;header.h&gt; 将它们“全局化”,而是使用 #include "header.h" 作为相对路径。
    猜你喜欢
    • 2023-03-03
    • 1970-01-01
    • 2021-09-10
    • 1970-01-01
    • 1970-01-01
    • 2016-12-30
    • 2020-01-17
    • 2016-08-20
    • 2018-08-25
    相关资源
    最近更新 更多