• 如何用 CMake 生成 C++ 库(支持 find_package 机制)


    参考链接:
    bottle/cpp_with_cmake/cmake_static_library

    googletest

    CMake官方文档:CMakePackageConfigHelpers

    CMake要生成一个C++静态库,其实非常简单,只需要如下即可

    1. # CMakeLists.txt
    2. cmake_minimum_required(VERSION 3.16.6)
    3. project(lib)
    4. ##################################################################
    5. # 目标配置
    6. ##################################################################
    7. set(TARGET_NAME lib)
    8. ##################################################################
    9. # 源文件
    10. ##################################################################
    11. set(TARGET_SOURCES
    12. lib.cpp
    13. )
    14. set(TARGET_PUBLIC_HEADERS
    15. lib.h
    16. )
    17. ##################################################################
    18. # 生成库属性
    19. ##################################################################
    20. add_library(${TARGET_NAME} STATIC ${TARGET_PUBLIC_HEADERS} ${TARGET_SOURCES})

    我们的源文件内容如下

    1. // lib.cpp
    2. #include "lib.h"
    3. int add(int a, int b) {
    4. return a + b;
    5. }
    6. // lib.h
    7. #pragma once
    8. int add(int a, int b);

     通过以下命令就可以完成构建

     构建结果如下

    但是我们需要通过 install 命令安装,需要增加 isntall 到 CMakeLists.txt

    1. # CMakeLists.txt
    2. cmake_minimum_required(VERSION 3.16.6)
    3. project(lib)
    4. ##################################################################
    5. # 目标配置
    6. ##################################################################
    7. set(TARGET_NAME lib)
    8. set(TARGET_NAMESPACE Library)
    9. set(TARGET_RUNTIME_INSTALL_DIR bin)
    10. set(TARGET_ARCHIVE_INSTALL_DIR lib)
    11. set(TARGET_LIBRARY_INSTALL_DIR lib)
    12. set(TARGET_PUBLIC_HEADER_INSTALL_DIR include)
    13. ##################################################################
    14. # 源文件
    15. ##################################################################
    16. set(TARGET_SOURCES
    17. lib.cpp
    18. )
    19. set(TARGET_PUBLIC_HEADERS
    20. lib.h
    21. )
    22. ##################################################################
    23. # 生成库属性
    24. ##################################################################
    25. add_library(${TARGET_NAME} STATIC ${TARGET_PUBLIC_HEADERS} ${TARGET_SOURCES})
    26. # 配置头文件,install命令执行时将复制这些配置的头文件
    27. set_target_properties(${TARGET_NAME} PROPERTIES
    28. PUBLIC_HEADER "${TARGET_PUBLIC_HEADERS}"
    29. )
    30. # 将头文件包含目录配置到 INTERFACE,便于生成的库在被引用时可以自动添加包含目录
    31. string(REPLACE ";" "$" _include_dirs "${TARGET_INCLUDE_DIRS}")
    32. target_include_directories(${TARGET_NAME} SYSTEM INTERFACE
    33. "$${_include_dirs}>"
    34. "$/${TARGET_PUBLIC_HEADER_INSTALL_DIR}>"
    35. )
    36. ##################################################################
    37. # 安装目标
    38. ##################################################################
    39. # 配置安装路径
    40. install(
    41. TARGETS ${TARGET_NAME}
    42. EXPORT ${TARGET_NAME}
    43. RUNTIME DESTINATION ${TARGET_RUNTIME_INSTALL_DIR}
    44. ARCHIVE DESTINATION ${TARGET_ARCHIVE_INSTALL_DIR}
    45. LIBRARY DESTINATION ${TARGET_LIBRARY_INSTALL_DIR}
    46. PUBLIC_HEADER DESTINATION ${TARGET_PUBLIC_HEADER_INSTALL_DIR}
    47. PRIVATE_HEADER DESTINATION ${TARGET_PRIVATE_HEADER_INSTALL_DIR}
    48. )
    49. # 生成 Targets*.cmake 文件
    50. install(
    51. EXPORT ${TARGET_NAME}
    52. FILE ${TARGET_NAME}Targets.cmake
    53. NAMESPACE ${TARGET_NAMESPACE}:: # Defines namespace in XXXTargets.cmake
    54. DESTINATION lib/cmake/${TARGET_NAME}
    55. )

    那么我们重新进行生成,指定 CMAKE_INSTALL_PREFIX,安装到指定临时文件夹

     生成的结果为

    将被安装到

    我们看看通过 install 命令生成的 Targets*.cmake 内容

    1. # libTargets.cmake
    2. # Generated by CMake
    3. if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.8)
    4. message(FATAL_ERROR "CMake >= 2.8.0 required")
    5. endif()
    6. if(CMAKE_VERSION VERSION_LESS "2.8.3")
    7. message(FATAL_ERROR "CMake >= 2.8.3 required")
    8. endif()
    9. cmake_policy(PUSH)
    10. cmake_policy(VERSION 2.8.3...3.22)
    11. #----------------------------------------------------------------
    12. # Generated CMake target import file.
    13. #----------------------------------------------------------------
    14. # Commands may need to know the format version.
    15. set(CMAKE_IMPORT_FILE_VERSION 1)
    16. # Protect against multiple inclusion, which would fail when already imported targets are added once more.
    17. set(_cmake_targets_defined "")
    18. set(_cmake_targets_not_defined "")
    19. set(_cmake_expected_targets "")
    20. foreach(_cmake_expected_target IN ITEMS Library::lib)
    21. list(APPEND _cmake_expected_targets "${_cmake_expected_target}")
    22. if(TARGET "${_cmake_expected_target}")
    23. list(APPEND _cmake_targets_defined "${_cmake_expected_target}")
    24. else()
    25. list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}")
    26. endif()
    27. endforeach()
    28. unset(_cmake_expected_target)
    29. if(_cmake_targets_defined STREQUAL _cmake_expected_targets)
    30. unset(_cmake_targets_defined)
    31. unset(_cmake_targets_not_defined)
    32. unset(_cmake_expected_targets)
    33. unset(CMAKE_IMPORT_FILE_VERSION)
    34. cmake_policy(POP)
    35. return()
    36. endif()
    37. if(NOT _cmake_targets_defined STREQUAL "")
    38. string(REPLACE ";" ", " _cmake_targets_defined_text "${_cmake_targets_defined}")
    39. string(REPLACE ";" ", " _cmake_targets_not_defined_text "${_cmake_targets_not_defined}")
    40. message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_cmake_targets_defined_text}\nTargets not yet defined: ${_cmake_targets_not_defined_text}\n")
    41. endif()
    42. unset(_cmake_targets_defined)
    43. unset(_cmake_targets_not_defined)
    44. unset(_cmake_expected_targets)
    45. # Compute the installation prefix relative to this file.
    46. get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
    47. get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
    48. get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
    49. get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
    50. if(_IMPORT_PREFIX STREQUAL "/")
    51. set(_IMPORT_PREFIX "")
    52. endif()
    53. # Create imported target Library::lib
    54. add_library(Library::lib STATIC IMPORTED)
    55. set_target_properties(Library::lib PROPERTIES
    56. INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
    57. INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
    58. )
    59. # Load information for each installed configuration.
    60. file(GLOB _cmake_config_files "${CMAKE_CURRENT_LIST_DIR}/libTargets-*.cmake")
    61. foreach(_cmake_config_file IN LISTS _cmake_config_files)
    62. include("${_cmake_config_file}")
    63. endforeach()
    64. unset(_cmake_config_file)
    65. unset(_cmake_config_files)
    66. # Cleanup temporary variables.
    67. set(_IMPORT_PREFIX)
    68. # Loop over all imported files and verify that they actually exist
    69. foreach(_cmake_target IN LISTS _cmake_import_check_targets)
    70. foreach(_cmake_file IN LISTS "_cmake_import_check_files_for_${_cmake_target}")
    71. if(NOT EXISTS "${_cmake_file}")
    72. message(FATAL_ERROR "The imported target \"${_cmake_target}\" references the file
    73. \"${_cmake_file}\"
    74. but this file does not exist. Possible reasons include:
    75. * The file was deleted, renamed, or moved to another location.
    76. * An install or uninstall procedure did not complete successfully.
    77. * The installation package was faulty and contained
    78. \"${CMAKE_CURRENT_LIST_FILE}\"
    79. but not all the files it references.
    80. ")
    81. endif()
    82. endforeach()
    83. unset(_cmake_file)
    84. unset("_cmake_import_check_files_for_${_cmake_target}")
    85. endforeach()
    86. unset(_cmake_target)
    87. unset(_cmake_import_check_targets)
    88. # This file does not depend on other imported targets which have
    89. # been exported from the same project but in a separate export set.
    90. # Commands beyond this point should not need to know the version.
    91. set(CMAKE_IMPORT_FILE_VERSION)
    92. cmake_policy(POP)
    1. # libTargets-debug.cmake
    2. #----------------------------------------------------------------
    3. # Generated CMake target import file for configuration "Debug".
    4. #----------------------------------------------------------------
    5. # Commands may need to know the format version.
    6. set(CMAKE_IMPORT_FILE_VERSION 1)
    7. # Import target "Library::lib" for configuration "Debug"
    8. set_property(TARGET Library::lib APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
    9. set_target_properties(Library::lib PROPERTIES
    10. IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "CXX"
    11. IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/lib.lib"
    12. )
    13. list(APPEND _cmake_import_check_targets Library::lib )
    14. list(APPEND _cmake_import_check_files_for_Library::lib "${_IMPORT_PREFIX}/lib/lib.lib" )
    15. # Commands beyond this point should not need to know the version.
    16. set(CMAKE_IMPORT_FILE_VERSION)

    第一个文件 libTargets.cmake,主要就是配置了版本号,并通过 add_library 添加了一个 IMPORTED 类型的静态库,并添加了包含路径

    1. # CMakeLists.txt
    2. string(REPLACE ";" "$" _include_dirs "${TARGET_INCLUDE_DIRS}")
    3. target_include_directories(${TARGET_NAME} SYSTEM INTERFACE
    4. "$${_include_dirs}>"
    5. "$/${TARGET_PUBLIC_HEADER_INSTALL_DIR}>"
    6. )
    7. # libTargets.cmake 中这一段指定的 INCLUDE 路径,就是通过上面 CMakeLists.txt 的 target_include_directories (INTERFACE) 指定的
    8. set_target_properties(Library::lib PROPERTIES
    9. INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
    10. INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
    11. )

     这里如果我们想通过 find_package 去引用此库,会发生以下现象

    虽然库是有的,但是 find-package 并不能找到。

    那么需要通过 CMakePackageConfigHelpers 来完成配置。在 CMakeLists.txt 末尾增加如下内容

    1. # CMakeLists.txt
    2. ##################################################################
    3. # Config.cmake and ConfigVersion.cmake
    4. ##################################################################
    5. # CMakePackageConfigHelpers provides
    6. # write_basic_package_version_file
    7. # configure_package_config_file
    8. include(CMakePackageConfigHelpers)
    9. # 生成 ConfigVersion.cmake 文件
    10. write_basic_package_version_file(
    11. ${TARGET_NAME}ConfigVersion.cmake
    12. VERSION 1.0.0
    13. COMPATIBILITY SameMajorVersion # AnyNewerVersion|SameMajorVersion|SameMinorVersion|ExactVersion
    14. )
    15. # 生成 Config.cmake 文件 -- 非常重要,find_package 主要就是搜索该文件
    16. configure_package_config_file(
    17. PackageConfig.cmake.in ${TARGET_NAME}Config.cmake
    18. INSTALL_DESTINATION lib/cmake/${TARGET_NAME}
    19. )
    20. # 安装生成的 Config.cmake 和 ConfigVersion.cmake 文件
    21. install(
    22. FILES
    23. ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake
    24. ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}ConfigVersion.cmake
    25. DESTINATION lib/cmake/${TARGET_NAME}
    26. )

     这里用到了一个输入文件 PackageConfig.cmake.in,其内容如下,主要就是自动生成 libConfig.cmake 的模板,CMake会将 @PACKAGE_INIT@ 部分用默认的模板填充,后面的部分是我们根据自己库的需要指定的。这里我们只增加了一个组件校验,用作示例(其实这个是可以不需要的)。

    1. @PACKAGE_INIT@
    2. include(CMakeFindDependencyMacro)
    3. include("${CMAKE_CURRENT_LIST_DIR}/@TARGET_NAME@Targets.cmake")
    4. check_required_components("@TARGET_NAME@")

    好了,那么接下来我们再次生成,得到了如下结果。

    这个时候,再用 find_package 来搜索,就可以搜索到 lib 库了。

    引用库的 CMakeLists.txt 也非常简单,这里简单贴一下

    1. # 引用库的 CMakeLists.txt
    2. cmake_minimum_required(VERSION 3.16.6)
    3. project(lib_ref_demo)
    4. # 只需要再 CMAKE_PREFIX_PATH 中添加了安装目录即可
    5. # CMake 会按照 .install/lib/cmake//Config.cmake 这个路径去搜索目标
    6. list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_LIST_DIR}/library/.install)
    7. # 通过 find_package 命令搜索我们构建的库
    8. find_package(lib CONFIG REQUIRED)
    9. # 添加 exe 示例程序
    10. add_executable(lib_ref_demo
    11. ref.cpp
    12. )
    13. # 链接刚刚搜索到的 lib 库
    14. target_link_libraries(lib_ref_demo PUBLIC Library::lib)

     具体的工程内容详见 bottle,当然bottle里面还有动态库的生成。补充说明一下的是,bottle 库中的示例要比这里复杂一丢丢,主要验证了库的后缀指定,include多种结构,public/private 头文件等等细节问题。

  • 相关阅读:
    C++ 入门14:STL 容器之向量(vector)
    回忆我的电大
    微信公众号开发与本地调试详细教程
    Paper - Neural Discrete Representation Learning (VQ-VAE) 论文简读
    干货技巧分享:怎么把图片转文字?
    跨链桥已成行业最大安全隐患,为什么和怎么办
    【ZYNQ-嵌入式】zynq学习笔记(三)——GPIO中断软件配置
    jdk8 | Function<T,R>实践应用
    Linux中的DNS服务搭建与管理
    【动态规划之完全背包问题】如何将完全背包运用到实际问题,强化完全背包以及一维优化的推导
  • 原文地址:https://blog.csdn.net/davied9/article/details/126950517