参考链接:
bottle/cpp_with_cmake/cmake_static_library
CMake官方文档:CMakePackageConfigHelpers
CMake要生成一个C++静态库,其实非常简单,只需要如下即可
- # CMakeLists.txt
-
- cmake_minimum_required(VERSION 3.16.6)
- project(lib)
-
- ##################################################################
- # 目标配置
- ##################################################################
- set(TARGET_NAME lib)
-
- ##################################################################
- # 源文件
- ##################################################################
- set(TARGET_SOURCES
- lib.cpp
- )
- set(TARGET_PUBLIC_HEADERS
- lib.h
- )
-
- ##################################################################
- # 生成库属性
- ##################################################################
- add_library(${TARGET_NAME} STATIC ${TARGET_PUBLIC_HEADERS} ${TARGET_SOURCES})
我们的源文件内容如下
- // lib.cpp
- #include "lib.h"
-
- int add(int a, int b) {
- return a + b;
- }
-
-
- // lib.h
- #pragma once
-
- int add(int a, int b);
通过以下命令就可以完成构建
构建结果如下
但是我们需要通过 install 命令安装,需要增加 isntall 到 CMakeLists.txt
- # CMakeLists.txt
-
- cmake_minimum_required(VERSION 3.16.6)
- project(lib)
-
- ##################################################################
- # 目标配置
- ##################################################################
- set(TARGET_NAME lib)
- set(TARGET_NAMESPACE Library)
-
- set(TARGET_RUNTIME_INSTALL_DIR bin)
- set(TARGET_ARCHIVE_INSTALL_DIR lib)
- set(TARGET_LIBRARY_INSTALL_DIR lib)
- set(TARGET_PUBLIC_HEADER_INSTALL_DIR include)
-
- ##################################################################
- # 源文件
- ##################################################################
- set(TARGET_SOURCES
- lib.cpp
- )
- set(TARGET_PUBLIC_HEADERS
- lib.h
- )
-
- ##################################################################
- # 生成库属性
- ##################################################################
- add_library(${TARGET_NAME} STATIC ${TARGET_PUBLIC_HEADERS} ${TARGET_SOURCES})
-
- # 配置头文件,install命令执行时将复制这些配置的头文件
- set_target_properties(${TARGET_NAME} PROPERTIES
- PUBLIC_HEADER "${TARGET_PUBLIC_HEADERS}"
- )
-
- # 将头文件包含目录配置到 INTERFACE,便于生成的库在被引用时可以自动添加包含目录
- string(REPLACE ";" "$
" _include_dirs "${TARGET_INCLUDE_DIRS}") - target_include_directories(${TARGET_NAME} SYSTEM INTERFACE
- "$
${_include_dirs} >" - "$
/${TARGET_PUBLIC_HEADER_INSTALL_DIR}>" - )
-
- ##################################################################
- # 安装目标
- ##################################################################
- # 配置安装路径
- install(
- TARGETS ${TARGET_NAME}
- EXPORT ${TARGET_NAME}
- RUNTIME DESTINATION ${TARGET_RUNTIME_INSTALL_DIR}
- ARCHIVE DESTINATION ${TARGET_ARCHIVE_INSTALL_DIR}
- LIBRARY DESTINATION ${TARGET_LIBRARY_INSTALL_DIR}
- PUBLIC_HEADER DESTINATION ${TARGET_PUBLIC_HEADER_INSTALL_DIR}
- PRIVATE_HEADER DESTINATION ${TARGET_PRIVATE_HEADER_INSTALL_DIR}
- )
-
- # 生成
Targets*.cmake 文件 - install(
- EXPORT ${TARGET_NAME}
- FILE ${TARGET_NAME}Targets.cmake
- NAMESPACE ${TARGET_NAMESPACE}:: # Defines namespace in XXXTargets.cmake
- DESTINATION lib/cmake/${TARGET_NAME}
- )
那么我们重新进行生成,指定 CMAKE_INSTALL_PREFIX,安装到指定临时文件夹
生成的结果为
、
将被安装到
我们看看通过 install 命令生成的
- # libTargets.cmake
-
- # Generated by CMake
-
- if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.8)
- message(FATAL_ERROR "CMake >= 2.8.0 required")
- endif()
- if(CMAKE_VERSION VERSION_LESS "2.8.3")
- message(FATAL_ERROR "CMake >= 2.8.3 required")
- endif()
- cmake_policy(PUSH)
- cmake_policy(VERSION 2.8.3...3.22)
- #----------------------------------------------------------------
- # Generated CMake target import file.
- #----------------------------------------------------------------
-
- # Commands may need to know the format version.
- set(CMAKE_IMPORT_FILE_VERSION 1)
-
- # Protect against multiple inclusion, which would fail when already imported targets are added once more.
- set(_cmake_targets_defined "")
- set(_cmake_targets_not_defined "")
- set(_cmake_expected_targets "")
- foreach(_cmake_expected_target IN ITEMS Library::lib)
- list(APPEND _cmake_expected_targets "${_cmake_expected_target}")
- if(TARGET "${_cmake_expected_target}")
- list(APPEND _cmake_targets_defined "${_cmake_expected_target}")
- else()
- list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}")
- endif()
- endforeach()
- unset(_cmake_expected_target)
- if(_cmake_targets_defined STREQUAL _cmake_expected_targets)
- unset(_cmake_targets_defined)
- unset(_cmake_targets_not_defined)
- unset(_cmake_expected_targets)
- unset(CMAKE_IMPORT_FILE_VERSION)
- cmake_policy(POP)
- return()
- endif()
- if(NOT _cmake_targets_defined STREQUAL "")
- string(REPLACE ";" ", " _cmake_targets_defined_text "${_cmake_targets_defined}")
- string(REPLACE ";" ", " _cmake_targets_not_defined_text "${_cmake_targets_not_defined}")
- 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")
- endif()
- unset(_cmake_targets_defined)
- unset(_cmake_targets_not_defined)
- unset(_cmake_expected_targets)
-
-
- # Compute the installation prefix relative to this file.
- get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
- get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
- get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
- get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
- if(_IMPORT_PREFIX STREQUAL "/")
- set(_IMPORT_PREFIX "")
- endif()
-
- # Create imported target Library::lib
- add_library(Library::lib STATIC IMPORTED)
-
- set_target_properties(Library::lib PROPERTIES
- INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
- INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
- )
-
- # Load information for each installed configuration.
- file(GLOB _cmake_config_files "${CMAKE_CURRENT_LIST_DIR}/libTargets-*.cmake")
- foreach(_cmake_config_file IN LISTS _cmake_config_files)
- include("${_cmake_config_file}")
- endforeach()
- unset(_cmake_config_file)
- unset(_cmake_config_files)
-
- # Cleanup temporary variables.
- set(_IMPORT_PREFIX)
-
- # Loop over all imported files and verify that they actually exist
- foreach(_cmake_target IN LISTS _cmake_import_check_targets)
- foreach(_cmake_file IN LISTS "_cmake_import_check_files_for_${_cmake_target}")
- if(NOT EXISTS "${_cmake_file}")
- message(FATAL_ERROR "The imported target \"${_cmake_target}\" references the file
- \"${_cmake_file}\"
- but this file does not exist. Possible reasons include:
- * The file was deleted, renamed, or moved to another location.
- * An install or uninstall procedure did not complete successfully.
- * The installation package was faulty and contained
- \"${CMAKE_CURRENT_LIST_FILE}\"
- but not all the files it references.
- ")
- endif()
- endforeach()
- unset(_cmake_file)
- unset("_cmake_import_check_files_for_${_cmake_target}")
- endforeach()
- unset(_cmake_target)
- unset(_cmake_import_check_targets)
-
- # This file does not depend on other imported targets which have
- # been exported from the same project but in a separate export set.
-
- # Commands beyond this point should not need to know the version.
- set(CMAKE_IMPORT_FILE_VERSION)
- cmake_policy(POP)
- # libTargets-debug.cmake
-
- #----------------------------------------------------------------
- # Generated CMake target import file for configuration "Debug".
- #----------------------------------------------------------------
-
- # Commands may need to know the format version.
- set(CMAKE_IMPORT_FILE_VERSION 1)
-
- # Import target "Library::lib" for configuration "Debug"
- set_property(TARGET Library::lib APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
- set_target_properties(Library::lib PROPERTIES
- IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "CXX"
- IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/lib.lib"
- )
-
- list(APPEND _cmake_import_check_targets Library::lib )
- list(APPEND _cmake_import_check_files_for_Library::lib "${_IMPORT_PREFIX}/lib/lib.lib" )
-
- # Commands beyond this point should not need to know the version.
- set(CMAKE_IMPORT_FILE_VERSION)
第一个文件 libTargets.cmake,主要就是配置了版本号,并通过 add_library 添加了一个 IMPORTED 类型的静态库,并添加了包含路径
-
- # CMakeLists.txt
-
- string(REPLACE ";" "$
" _include_dirs "${TARGET_INCLUDE_DIRS}") - target_include_directories(${TARGET_NAME} SYSTEM INTERFACE
- "$
${_include_dirs} >" - "$
/${TARGET_PUBLIC_HEADER_INSTALL_DIR}>" - )
-
- # libTargets.cmake 中这一段指定的 INCLUDE 路径,就是通过上面 CMakeLists.txt 的 target_include_directories (INTERFACE) 指定的
-
- set_target_properties(Library::lib PROPERTIES
- INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
- INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
- )
-
这里如果我们想通过 find_package 去引用此库,会发生以下现象
虽然库是有的,但是 find-package 并不能找到。
那么需要通过 CMakePackageConfigHelpers 来完成配置。在 CMakeLists.txt 末尾增加如下内容
- # CMakeLists.txt
-
- ##################################################################
- #
Config.cmake and ConfigVersion.cmake - ##################################################################
- # CMakePackageConfigHelpers provides
- # write_basic_package_version_file
- # configure_package_config_file
- include(CMakePackageConfigHelpers)
-
- # 生成
ConfigVersion.cmake 文件 - write_basic_package_version_file(
- ${TARGET_NAME}ConfigVersion.cmake
- VERSION 1.0.0
- COMPATIBILITY SameMajorVersion # AnyNewerVersion|SameMajorVersion|SameMinorVersion|ExactVersion
- )
- # 生成
Config.cmake 文件 -- 非常重要,find_package 主要就是搜索该文件 - configure_package_config_file(
- PackageConfig.cmake.in ${TARGET_NAME}Config.cmake
- INSTALL_DESTINATION lib/cmake/${TARGET_NAME}
- )
-
- # 安装生成的
Config.cmake 和 ConfigVersion.cmake 文件 - install(
- FILES
- ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake
- ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}ConfigVersion.cmake
- DESTINATION lib/cmake/${TARGET_NAME}
- )
这里用到了一个输入文件 PackageConfig.cmake.in,其内容如下,主要就是自动生成 libConfig.cmake 的模板,CMake会将 @PACKAGE_INIT@ 部分用默认的模板填充,后面的部分是我们根据自己库的需要指定的。这里我们只增加了一个组件校验,用作示例(其实这个是可以不需要的)。
- @PACKAGE_INIT@
-
- include(CMakeFindDependencyMacro)
-
- include("${CMAKE_CURRENT_LIST_DIR}/@TARGET_NAME@Targets.cmake")
- check_required_components("@TARGET_NAME@")
好了,那么接下来我们再次生成,得到了如下结果。
这个时候,再用 find_package 来搜索,就可以搜索到 lib 库了。
引用库的 CMakeLists.txt 也非常简单,这里简单贴一下
- # 引用库的 CMakeLists.txt
-
- cmake_minimum_required(VERSION 3.16.6)
- project(lib_ref_demo)
-
- # 只需要再 CMAKE_PREFIX_PATH 中添加了安装目录即可
- # CMake 会按照 .install/lib/cmake/
/Config.cmake 这个路径去搜索目标 - list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_LIST_DIR}/library/.install)
-
- # 通过 find_package 命令搜索我们构建的库
- find_package(lib CONFIG REQUIRED)
-
- # 添加 exe 示例程序
- add_executable(lib_ref_demo
- ref.cpp
- )
-
- # 链接刚刚搜索到的 lib 库
- target_link_libraries(lib_ref_demo PUBLIC Library::lib)
具体的工程内容详见 bottle,当然bottle里面还有动态库的生成。补充说明一下的是,bottle 库中的示例要比这里复杂一丢丢,主要验证了库的后缀指定,include多种结构,public/private 头文件等等细节问题。